Skip to content

Commit

Permalink
add x509util package
Browse files Browse the repository at this point in the history
x509util.CreateCertificateRequest wraps x509.CreateCertificateRequest and adds
support for challengePassword attribute.

Adding a new attribute means re-signing the whole CSR, which means importing
private methods and types from the x509 package.  I hope to eventually submit a CL to the stdlib.

Go Issue: golang/go#15995

For #44.
For #22.
  • Loading branch information
groob committed Jul 9, 2017
1 parent 3e36a3c commit 56f38fa
Show file tree
Hide file tree
Showing 3 changed files with 400 additions and 0 deletions.
2 changes: 2 additions & 0 deletions crypto/x509util/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// package x509 provides utilities for working with x509 types.
package x509util
348 changes: 348 additions & 0 deletions crypto/x509util/x509util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,348 @@
/*
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package x509util

import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
_ "crypto/sha1"
_ "crypto/sha256"
_ "crypto/sha512"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"io"
)

type CertificateRequest struct {
x509.CertificateRequest

ChallengePassword string
}

// CreateCertificateRequest creates a new certificate request based on a template.
// The resulting CSR is similar to x509 but optionally supports the
// challengePassword attribute.
//
// See https://github.com/golang/go/issues/15995
func CreateCertificateRequest(rand io.Reader, template *CertificateRequest, priv interface{}) (csr []byte, err error) {
if template.ChallengePassword == "" {
// if no challenge password, return a stdlib CSR.
return x509.CreateCertificateRequest(rand, &template.CertificateRequest, priv)
}
derBytes, err := x509.CreateCertificateRequest(rand, &template.CertificateRequest, priv)
if err != nil {
return nil, err
}
// add the challenge attribute to the CSR, then re-sign the raw csr.
// not checking the crypto.Signer assertion because x509.CreateCertificateRequest already did that.
return addChallenge(
template.CertificateRequest.SignatureAlgorithm,
rand,
derBytes,
template.ChallengePassword,
priv.(crypto.Signer),
)
}

type passwordChallengeAttribute struct {
Type asn1.ObjectIdentifier
Value []string `asn1:"set"`
}

// The structures below are copied from the Go standard library x509 package.

type publicKeyInfo struct {
Raw asn1.RawContent
Algorithm pkix.AlgorithmIdentifier
PublicKey asn1.BitString
}

type tbsCertificateRequest struct {
Raw asn1.RawContent
Version int
Subject asn1.RawValue
PublicKey publicKeyInfo
RawAttributes []asn1.RawValue `asn1:"tag:0"`
}

type certificateRequest struct {
Raw asn1.RawContent
TBSCSR tbsCertificateRequest
SignatureAlgorithm pkix.AlgorithmIdentifier
SignatureValue asn1.BitString
}

// ParseChallengePassword extracts the challengePassword attribute from a
// DER encoded Certificate Signing Request.
func ParseChallengePassword(asn1Data []byte) (string, error) {
type attribute struct {
ID asn1.ObjectIdentifier
Value asn1.RawValue `asn1:"set"`
}
var csr certificateRequest
rest, err := asn1.Unmarshal(asn1Data, &csr)
if err != nil {
return "", err
} else if len(rest) != 0 {
err = asn1.SyntaxError{Msg: "trailing data"}
return "", err
}

var password string
for _, rawAttr := range csr.TBSCSR.RawAttributes {
var attr attribute
_, err := asn1.Unmarshal(rawAttr.FullBytes, &attr)
if err != nil {
return "", err
}
if attr.ID.Equal(oidChallengePassword) {
_, err := asn1.Unmarshal(attr.Value.Bytes, &password)
if err != nil {
return "", err
}
}
}

return password, nil
}

// addChallenge takes a raw CSR created by x509.CreateCertificateRequest,
// adds a passwordChallengeAttribute and re-signs the raw CSR bytes.
func addChallenge(
templateSigAlgo x509.SignatureAlgorithm,
reader io.Reader,
derBytes []byte,
challenge string,
key crypto.Signer,
) (csr []byte, err error) {
var hashFunc crypto.Hash
var sigAlgo pkix.AlgorithmIdentifier
hashFunc, sigAlgo, err = signingParamsForPublicKey(key.Public(), templateSigAlgo)
if err != nil {
return nil, err
}

var req certificateRequest
rest, err := asn1.Unmarshal(derBytes, &req)
if err != nil {
return nil, err
} else if len(rest) != 0 {
err = asn1.SyntaxError{Msg: "trailing data"}
return nil, err
}

passwordAttribute := passwordChallengeAttribute{
Type: oidChallengePassword,
Value: []string{challenge},
}
b, err := asn1.Marshal(passwordAttribute)
if err != nil {
return nil, err
}

var rawAttribute asn1.RawValue
rest, err = asn1.Unmarshal(b, &rawAttribute)
if err != nil {
return nil, err
} else if len(rest) != 0 {
err = asn1.SyntaxError{Msg: "trailing data"}
return nil, err
}

// append attribute
req.TBSCSR.RawAttributes = append(req.TBSCSR.RawAttributes, rawAttribute)

// recreate request
tbsCSR := tbsCertificateRequest{
Version: 0,
Subject: req.TBSCSR.Subject,
PublicKey: req.TBSCSR.PublicKey,
RawAttributes: req.TBSCSR.RawAttributes,
}

tbsCSRContents, err := asn1.Marshal(tbsCSR)
if err != nil {
return nil, err
}
tbsCSR.Raw = tbsCSRContents

h := hashFunc.New()
if _, err := h.Write(tbsCSRContents); err != nil {
return nil, err
}

var signature []byte
signature, err = key.Sign(reader, h.Sum(nil), hashFunc)
if err != nil {
return nil, err
}

return asn1.Marshal(certificateRequest{
TBSCSR: tbsCSR,
SignatureAlgorithm: sigAlgo,
SignatureValue: asn1.BitString{
Bytes: signature,
BitLength: len(signature) * 8,
},
})
}

// signingParamsForPublicKey returns the parameters to use for signing with
// priv. If requestedSigAlgo is not zero then it overrides the default
// signature algorithm.
func signingParamsForPublicKey(pub interface{}, requestedSigAlgo x509.SignatureAlgorithm) (hashFunc crypto.Hash, sigAlgo pkix.AlgorithmIdentifier, err error) {
var pubType x509.PublicKeyAlgorithm

switch pub := pub.(type) {
case *rsa.PublicKey:
pubType = x509.RSA
hashFunc = crypto.SHA256
sigAlgo.Algorithm = oidSignatureSHA256WithRSA
sigAlgo.Parameters = asn1NullRawValue

case *ecdsa.PublicKey:
pubType = x509.ECDSA

switch pub.Curve {
case elliptic.P224(), elliptic.P256():
hashFunc = crypto.SHA256
sigAlgo.Algorithm = oidSignatureECDSAWithSHA256
case elliptic.P384():
hashFunc = crypto.SHA384
sigAlgo.Algorithm = oidSignatureECDSAWithSHA384
case elliptic.P521():
hashFunc = crypto.SHA512
sigAlgo.Algorithm = oidSignatureECDSAWithSHA512
default:
err = errors.New("x509: unknown elliptic curve")
}

default:
err = errors.New("x509: only RSA and ECDSA keys supported")
}

if err != nil {
return
}

if requestedSigAlgo == 0 {
return
}

found := false
for _, details := range signatureAlgorithmDetails {
if details.algo == requestedSigAlgo {
if details.pubKeyAlgo != pubType {
err = errors.New("x509: requested SignatureAlgorithm does not match private key type")
return
}
sigAlgo.Algorithm, hashFunc = details.oid, details.hash
if hashFunc == 0 {
err = errors.New("x509: cannot sign with hash function requested")
return
}
// TODO add this?
// if requestedSigAlgo.isRSAPSS() {
// sigAlgo.Parameters = rsaPSSParameters(hashFunc)
// }
found = true
break
}
}

if !found {
err = errors.New("x509: unknown SignatureAlgorithm")
}

return
}

var signatureAlgorithmDetails = []struct {
algo x509.SignatureAlgorithm
oid asn1.ObjectIdentifier
pubKeyAlgo x509.PublicKeyAlgorithm
hash crypto.Hash
}{
{x509.MD2WithRSA, oidSignatureMD2WithRSA, x509.RSA, crypto.Hash(0) /* no value for MD2 */},
{x509.MD5WithRSA, oidSignatureMD5WithRSA, x509.RSA, crypto.MD5},
{x509.SHA1WithRSA, oidSignatureSHA1WithRSA, x509.RSA, crypto.SHA1},
{x509.SHA1WithRSA, oidISOSignatureSHA1WithRSA, x509.RSA, crypto.SHA1},
{x509.SHA256WithRSA, oidSignatureSHA256WithRSA, x509.RSA, crypto.SHA256},
{x509.SHA384WithRSA, oidSignatureSHA384WithRSA, x509.RSA, crypto.SHA384},
{x509.SHA512WithRSA, oidSignatureSHA512WithRSA, x509.RSA, crypto.SHA512},
{x509.SHA256WithRSAPSS, oidSignatureRSAPSS, x509.RSA, crypto.SHA256},
{x509.SHA384WithRSAPSS, oidSignatureRSAPSS, x509.RSA, crypto.SHA384},
{x509.SHA512WithRSAPSS, oidSignatureRSAPSS, x509.RSA, crypto.SHA512},
{x509.DSAWithSHA1, oidSignatureDSAWithSHA1, x509.DSA, crypto.SHA1},
{x509.DSAWithSHA256, oidSignatureDSAWithSHA256, x509.DSA, crypto.SHA256},
{x509.ECDSAWithSHA1, oidSignatureECDSAWithSHA1, x509.ECDSA, crypto.SHA1},
{x509.ECDSAWithSHA256, oidSignatureECDSAWithSHA256, x509.ECDSA, crypto.SHA256},
{x509.ECDSAWithSHA384, oidSignatureECDSAWithSHA384, x509.ECDSA, crypto.SHA384},
{x509.ECDSAWithSHA512, oidSignatureECDSAWithSHA512, x509.ECDSA, crypto.SHA512},
}

var (
oidSignatureMD2WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 2}
oidSignatureMD5WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 4}
oidSignatureSHA1WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5}
oidSignatureSHA256WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11}
oidSignatureSHA384WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12}
oidSignatureSHA512WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13}
oidSignatureRSAPSS = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10}
oidSignatureDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 3}
oidSignatureDSAWithSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 3, 2}
oidSignatureECDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 1}
oidSignatureECDSAWithSHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2}
oidSignatureECDSAWithSHA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3}
oidSignatureECDSAWithSHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4}

oidSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1}
oidSHA384 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2}
oidSHA512 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 3}

oidMGF1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 8}

// oidISOSignatureSHA1WithRSA means the same as oidSignatureSHA1WithRSA
// but it's specified by ISO. Microsoft's makecert.exe has been known
// to produce certificates with this OID.
oidISOSignatureSHA1WithRSA = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 29}

oidChallengePassword = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 7}
)

// added to Go in 1.9
var asn1NullRawValue = asn1.RawValue{
Tag: 5, /* ASN.1 NULL */
}
Loading

0 comments on commit 56f38fa

Please sign in to comment.