mirror of
https://github.com/golang/go
synced 2024-11-12 07:00:21 -07:00
crypto/x509: Add certificate signature request (CSR) support.
This change adds support for parsing and serialisation of PKCS #10, certificate signature requests. LGTM=agl R=golang-codereviews, agl CC=agl, golang-codereviews, nick https://golang.org/cl/49830048
This commit is contained in:
parent
eea28f6701
commit
fc8e77ca65
@ -30,6 +30,13 @@ type AttributeTypeAndValue struct {
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// AttributeTypeAndValueSET represents a set of ASN.1 sequences of
|
||||
// AttributeTypeAndValue sequences from RFC 2986 (PKCS #10).
|
||||
type AttributeTypeAndValueSET struct {
|
||||
Type asn1.ObjectIdentifier
|
||||
Value [][]AttributeTypeAndValue `asn1:"set"`
|
||||
}
|
||||
|
||||
// Extension represents the ASN.1 structure of the same name. See RFC
|
||||
// 5280, section 4.2.
|
||||
type Extension struct {
|
||||
|
@ -790,6 +790,58 @@ func parsePublicKey(algo PublicKeyAlgorithm, keyData *publicKeyInfo) (interface{
|
||||
}
|
||||
}
|
||||
|
||||
func parseSANExtension(value []byte) (dnsNames, emailAddresses []string, ipAddresses []net.IP, err error) {
|
||||
// RFC 5280, 4.2.1.6
|
||||
|
||||
// SubjectAltName ::= GeneralNames
|
||||
//
|
||||
// GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
|
||||
//
|
||||
// GeneralName ::= CHOICE {
|
||||
// otherName [0] OtherName,
|
||||
// rfc822Name [1] IA5String,
|
||||
// dNSName [2] IA5String,
|
||||
// x400Address [3] ORAddress,
|
||||
// directoryName [4] Name,
|
||||
// ediPartyName [5] EDIPartyName,
|
||||
// uniformResourceIdentifier [6] IA5String,
|
||||
// iPAddress [7] OCTET STRING,
|
||||
// registeredID [8] OBJECT IDENTIFIER }
|
||||
var seq asn1.RawValue
|
||||
if _, err = asn1.Unmarshal(value, &seq); err != nil {
|
||||
return
|
||||
}
|
||||
if !seq.IsCompound || seq.Tag != 16 || seq.Class != 0 {
|
||||
err = asn1.StructuralError{Msg: "bad SAN sequence"}
|
||||
return
|
||||
}
|
||||
|
||||
rest := seq.Bytes
|
||||
for len(rest) > 0 {
|
||||
var v asn1.RawValue
|
||||
rest, err = asn1.Unmarshal(rest, &v)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch v.Tag {
|
||||
case 1:
|
||||
emailAddresses = append(emailAddresses, string(v.Bytes))
|
||||
case 2:
|
||||
dnsNames = append(dnsNames, string(v.Bytes))
|
||||
case 7:
|
||||
switch len(v.Bytes) {
|
||||
case net.IPv4len, net.IPv6len:
|
||||
ipAddresses = append(ipAddresses, v.Bytes)
|
||||
default:
|
||||
err = errors.New("x509: certificate contained IP address of length " + strconv.Itoa(len(v.Bytes)))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func parseCertificate(in *certificate) (*Certificate, error) {
|
||||
out := new(Certificate)
|
||||
out.Raw = in.Raw
|
||||
@ -863,58 +915,12 @@ func parseCertificate(in *certificate) (*Certificate, error) {
|
||||
continue
|
||||
}
|
||||
case 17:
|
||||
// RFC 5280, 4.2.1.6
|
||||
|
||||
// SubjectAltName ::= GeneralNames
|
||||
//
|
||||
// GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
|
||||
//
|
||||
// GeneralName ::= CHOICE {
|
||||
// otherName [0] OtherName,
|
||||
// rfc822Name [1] IA5String,
|
||||
// dNSName [2] IA5String,
|
||||
// x400Address [3] ORAddress,
|
||||
// directoryName [4] Name,
|
||||
// ediPartyName [5] EDIPartyName,
|
||||
// uniformResourceIdentifier [6] IA5String,
|
||||
// iPAddress [7] OCTET STRING,
|
||||
// registeredID [8] OBJECT IDENTIFIER }
|
||||
var seq asn1.RawValue
|
||||
_, err := asn1.Unmarshal(e.Value, &seq)
|
||||
out.DNSNames, out.EmailAddresses, out.IPAddresses, err = parseSANExtension(e.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !seq.IsCompound || seq.Tag != 16 || seq.Class != 0 {
|
||||
return nil, asn1.StructuralError{Msg: "bad SAN sequence"}
|
||||
}
|
||||
|
||||
parsedName := false
|
||||
|
||||
rest := seq.Bytes
|
||||
for len(rest) > 0 {
|
||||
var v asn1.RawValue
|
||||
rest, err = asn1.Unmarshal(rest, &v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch v.Tag {
|
||||
case 1:
|
||||
out.EmailAddresses = append(out.EmailAddresses, string(v.Bytes))
|
||||
parsedName = true
|
||||
case 2:
|
||||
out.DNSNames = append(out.DNSNames, string(v.Bytes))
|
||||
parsedName = true
|
||||
case 7:
|
||||
switch len(v.Bytes) {
|
||||
case net.IPv4len, net.IPv6len:
|
||||
out.IPAddresses = append(out.IPAddresses, v.Bytes)
|
||||
default:
|
||||
return nil, errors.New("x509: certificate contained IP address of length " + strconv.Itoa(len(v.Bytes)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if parsedName {
|
||||
if len(out.DNSNames) > 0 || len(out.EmailAddresses) > 0 || len(out.IPAddresses) > 0 {
|
||||
continue
|
||||
}
|
||||
// If we didn't parse any of the names then we
|
||||
@ -1151,6 +1157,27 @@ func oidInExtensions(oid asn1.ObjectIdentifier, extensions []pkix.Extension) boo
|
||||
return false
|
||||
}
|
||||
|
||||
// marshalSANs marshals a list of addresses into a the contents of an X.509
|
||||
// SubjectAlternativeName extension.
|
||||
func marshalSANs(dnsNames, emailAddresses []string, ipAddresses []net.IP) (derBytes []byte, err error) {
|
||||
var rawValues []asn1.RawValue
|
||||
for _, name := range dnsNames {
|
||||
rawValues = append(rawValues, asn1.RawValue{Tag: 2, Class: 2, Bytes: []byte(name)})
|
||||
}
|
||||
for _, email := range emailAddresses {
|
||||
rawValues = append(rawValues, asn1.RawValue{Tag: 1, Class: 2, Bytes: []byte(email)})
|
||||
}
|
||||
for _, rawIP := range ipAddresses {
|
||||
// If possible, we always want to encode IPv4 addresses in 4 bytes.
|
||||
ip := rawIP.To4()
|
||||
if ip == nil {
|
||||
ip = rawIP
|
||||
}
|
||||
rawValues = append(rawValues, asn1.RawValue{Tag: 7, Class: 2, Bytes: ip})
|
||||
}
|
||||
return asn1.Marshal(rawValues)
|
||||
}
|
||||
|
||||
func buildExtensions(template *Certificate) (ret []pkix.Extension, err error) {
|
||||
ret = make([]pkix.Extension, 10 /* maximum number of elements. */)
|
||||
n := 0
|
||||
@ -1252,22 +1279,7 @@ func buildExtensions(template *Certificate) (ret []pkix.Extension, err error) {
|
||||
if (len(template.DNSNames) > 0 || len(template.EmailAddresses) > 0 || len(template.IPAddresses) > 0) &&
|
||||
!oidInExtensions(oidExtensionSubjectAltName, template.ExtraExtensions) {
|
||||
ret[n].Id = oidExtensionSubjectAltName
|
||||
var rawValues []asn1.RawValue
|
||||
for _, name := range template.DNSNames {
|
||||
rawValues = append(rawValues, asn1.RawValue{Tag: 2, Class: 2, Bytes: []byte(name)})
|
||||
}
|
||||
for _, email := range template.EmailAddresses {
|
||||
rawValues = append(rawValues, asn1.RawValue{Tag: 1, Class: 2, Bytes: []byte(email)})
|
||||
}
|
||||
for _, rawIP := range template.IPAddresses {
|
||||
// If possible, we always want to encode IPv4 addresses in 4 bytes.
|
||||
ip := rawIP.To4()
|
||||
if ip == nil {
|
||||
ip = rawIP
|
||||
}
|
||||
rawValues = append(rawValues, asn1.RawValue{Tag: 7, Class: 2, Bytes: ip})
|
||||
}
|
||||
ret[n].Value, err = asn1.Marshal(rawValues)
|
||||
ret[n].Value, err = marshalSANs(template.DNSNames, template.EmailAddresses, template.IPAddresses)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -1342,6 +1354,71 @@ func subjectBytes(cert *Certificate) ([]byte, error) {
|
||||
return asn1.Marshal(cert.Subject.ToRDNSequence())
|
||||
}
|
||||
|
||||
// signingParamsForPrivateKey returns the parameters to use for signing with
|
||||
// priv. If requestedSigAlgo is not zero then it overrides the default
|
||||
// signature algorithm.
|
||||
func signingParamsForPrivateKey(priv interface{}, requestedSigAlgo SignatureAlgorithm) (hashFunc crypto.Hash, sigAlgo pkix.AlgorithmIdentifier, err error) {
|
||||
var pubType PublicKeyAlgorithm
|
||||
|
||||
switch priv := priv.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
pubType = RSA
|
||||
sigAlgo.Algorithm = oidSignatureSHA256WithRSA
|
||||
hashFunc = crypto.SHA256
|
||||
|
||||
case *ecdsa.PrivateKey:
|
||||
pubType = ECDSA
|
||||
|
||||
switch priv.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 private 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
|
||||
}
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
err = errors.New("x509: unknown SignatureAlgorithm")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CreateCertificate creates a new certificate based on a template. The
|
||||
// following members of template are used: SerialNumber, Subject, NotBefore,
|
||||
// NotAfter, KeyUsage, ExtKeyUsage, UnknownExtKeyUsage, BasicConstraintsValid,
|
||||
@ -1357,60 +1434,14 @@ func subjectBytes(cert *Certificate) ([]byte, error) {
|
||||
// The only supported key types are RSA and ECDSA (*rsa.PublicKey or
|
||||
// *ecdsa.PublicKey for pub, *rsa.PrivateKey or *ecdsa.PrivateKey for priv).
|
||||
func CreateCertificate(rand io.Reader, template, parent *Certificate, pub interface{}, priv interface{}) (cert []byte, err error) {
|
||||
var publicKeyBytes []byte
|
||||
var publicKeyAlgorithm pkix.AlgorithmIdentifier
|
||||
|
||||
if publicKeyBytes, publicKeyAlgorithm, err = marshalPublicKey(pub); err != nil {
|
||||
hashFunc, signatureAlgorithm, err := signingParamsForPrivateKey(priv, template.SignatureAlgorithm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var signatureAlgorithm pkix.AlgorithmIdentifier
|
||||
var hashFunc crypto.Hash
|
||||
var privType PublicKeyAlgorithm
|
||||
|
||||
switch priv := priv.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
privType = RSA
|
||||
signatureAlgorithm.Algorithm = oidSignatureSHA256WithRSA
|
||||
hashFunc = crypto.SHA256
|
||||
case *ecdsa.PrivateKey:
|
||||
privType = ECDSA
|
||||
|
||||
switch priv.Curve {
|
||||
case elliptic.P224(), elliptic.P256():
|
||||
hashFunc = crypto.SHA256
|
||||
signatureAlgorithm.Algorithm = oidSignatureECDSAWithSHA256
|
||||
case elliptic.P384():
|
||||
hashFunc = crypto.SHA384
|
||||
signatureAlgorithm.Algorithm = oidSignatureECDSAWithSHA384
|
||||
case elliptic.P521():
|
||||
hashFunc = crypto.SHA512
|
||||
signatureAlgorithm.Algorithm = oidSignatureECDSAWithSHA512
|
||||
default:
|
||||
return nil, errors.New("x509: unknown elliptic curve")
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("x509: only RSA and ECDSA private keys supported")
|
||||
}
|
||||
|
||||
if template.SignatureAlgorithm != 0 {
|
||||
found := false
|
||||
for _, details := range signatureAlgorithmDetails {
|
||||
if details.algo == template.SignatureAlgorithm {
|
||||
if details.pubKeyAlgo != privType {
|
||||
return nil, errors.New("x509: requested SignatureAlgorithm does not match private key type")
|
||||
}
|
||||
signatureAlgorithm.Algorithm, hashFunc = details.oid, details.hash
|
||||
if hashFunc == 0 {
|
||||
return nil, errors.New("x509: cannot sign with hash function requested")
|
||||
}
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, errors.New("x509: unknown SignatureAlgorithm")
|
||||
}
|
||||
publicKeyBytes, publicKeyAlgorithm, err := marshalPublicKey(pub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@ -1559,3 +1590,313 @@ func (c *Certificate) CreateCRL(rand io.Reader, priv interface{}, revokedCerts [
|
||||
SignatureValue: asn1.BitString{Bytes: signature, BitLength: len(signature) * 8},
|
||||
})
|
||||
}
|
||||
|
||||
// CertificateRequest represents a PKCS #10, certificate signature request.
|
||||
type CertificateRequest struct {
|
||||
Raw []byte // Complete ASN.1 DER content (CSR, signature algorithm and signature).
|
||||
RawTBSCertificateRequest []byte // Certificate request info part of raw ASN.1 DER content.
|
||||
RawSubjectPublicKeyInfo []byte // DER encoded SubjectPublicKeyInfo.
|
||||
RawSubject []byte // DER encoded Subject.
|
||||
|
||||
Version int
|
||||
Signature []byte
|
||||
SignatureAlgorithm SignatureAlgorithm
|
||||
|
||||
PublicKeyAlgorithm PublicKeyAlgorithm
|
||||
PublicKey interface{}
|
||||
|
||||
Subject pkix.Name
|
||||
|
||||
// Attributes is a collection of attributes providing
|
||||
// additional information about the subject of the certificate.
|
||||
// See RFC 2986 section 4.1.
|
||||
Attributes []pkix.AttributeTypeAndValueSET
|
||||
|
||||
// Extensions contains raw X.509 extensions. When parsing CSRs, this
|
||||
// can be used to extract extensions that are not parsed by this
|
||||
// package.
|
||||
Extensions []pkix.Extension
|
||||
|
||||
// ExtraExtensions contains extensions to be copied, raw, into any
|
||||
// marshaled CSR. Values override any extensions that would otherwise
|
||||
// be produced based on the other fields but are overridden by any
|
||||
// extensions specified in Attributes.
|
||||
//
|
||||
// The ExtraExtensions field is not populated when parsing CSRs, see
|
||||
// Extensions.
|
||||
ExtraExtensions []pkix.Extension
|
||||
|
||||
// Subject Alternate Name values.
|
||||
DNSNames []string
|
||||
EmailAddresses []string
|
||||
IPAddresses []net.IP
|
||||
}
|
||||
|
||||
// These structures reflect the ASN.1 structure of X.509 certificate
|
||||
// signature requests (see RFC 2986):
|
||||
|
||||
type tbsCertificateRequest struct {
|
||||
Raw asn1.RawContent
|
||||
Version int
|
||||
Subject asn1.RawValue
|
||||
PublicKey publicKeyInfo
|
||||
Attributes []pkix.AttributeTypeAndValueSET `asn1:"tag:0"`
|
||||
}
|
||||
|
||||
type certificateRequest struct {
|
||||
Raw asn1.RawContent
|
||||
TBSCSR tbsCertificateRequest
|
||||
SignatureAlgorithm pkix.AlgorithmIdentifier
|
||||
SignatureValue asn1.BitString
|
||||
}
|
||||
|
||||
// oidExtensionRequest is a PKCS#9 OBJECT IDENTIFIER that indicates requested
|
||||
// extensions in a CSR.
|
||||
var oidExtensionRequest = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 14}
|
||||
|
||||
// CreateCertificateRequest creates a new certificate based on a template. The
|
||||
// following members of template are used: Subject, Attributes,
|
||||
// SignatureAlgorithm, Extension, DNSNames, EmailAddresses, and IPAddresses.
|
||||
// The private key is the private key of the signer.
|
||||
//
|
||||
// The returned slice is the certificate request in DER encoding.
|
||||
//
|
||||
// The only supported key types are RSA (*rsa.PrivateKey) and ECDSA
|
||||
// (*ecdsa.PrivateKey).
|
||||
func CreateCertificateRequest(rand io.Reader, template *CertificateRequest, priv interface{}) (csr []byte, err error) {
|
||||
hashFunc, sigAlgo, err := signingParamsForPrivateKey(priv, template.SignatureAlgorithm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var publicKeyBytes []byte
|
||||
var publicKeyAlgorithm pkix.AlgorithmIdentifier
|
||||
|
||||
switch priv := priv.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
publicKeyBytes, publicKeyAlgorithm, err = marshalPublicKey(&priv.PublicKey)
|
||||
case *ecdsa.PrivateKey:
|
||||
publicKeyBytes, publicKeyAlgorithm, err = marshalPublicKey(&priv.PublicKey)
|
||||
default:
|
||||
panic("internal error")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var extensions []pkix.Extension
|
||||
|
||||
if (len(template.DNSNames) > 0 || len(template.EmailAddresses) > 0 || len(template.IPAddresses) > 0) &&
|
||||
!oidInExtensions(oidExtensionSubjectAltName, template.ExtraExtensions) {
|
||||
sanBytes, err := marshalSANs(template.DNSNames, template.EmailAddresses, template.IPAddresses)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
extensions = append(extensions, pkix.Extension{
|
||||
Id: oidExtensionSubjectAltName,
|
||||
Value: sanBytes,
|
||||
})
|
||||
}
|
||||
|
||||
extensions = append(extensions, template.ExtraExtensions...)
|
||||
|
||||
var attributes []pkix.AttributeTypeAndValueSET
|
||||
attributes = append(attributes, template.Attributes...)
|
||||
|
||||
if len(extensions) > 0 {
|
||||
// specifiedExtensions contains all the extensions that we
|
||||
// found specified via template.Attributes.
|
||||
specifiedExtensions := make(map[string]bool)
|
||||
|
||||
for _, atvSet := range template.Attributes {
|
||||
if !atvSet.Type.Equal(oidExtensionRequest) {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, atvs := range atvSet.Value {
|
||||
for _, atv := range atvs {
|
||||
specifiedExtensions[atv.Type.String()] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
atvs := make([]pkix.AttributeTypeAndValue, 0, len(extensions))
|
||||
for _, e := range extensions {
|
||||
if specifiedExtensions[e.Id.String()] {
|
||||
// Attributes already contained a value for
|
||||
// this extension and it takes priority.
|
||||
continue
|
||||
}
|
||||
|
||||
atvs = append(atvs, pkix.AttributeTypeAndValue{
|
||||
// There is no place for the critical flag in a CSR.
|
||||
Type: e.Id,
|
||||
Value: e.Value,
|
||||
})
|
||||
}
|
||||
|
||||
// Append the extensions to an existing attribute if possible.
|
||||
appended := false
|
||||
for _, atvSet := range attributes {
|
||||
if !atvSet.Type.Equal(oidExtensionRequest) || len(atvSet.Value) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
atvSet.Value[0] = append(atvSet.Value[0], atvs...)
|
||||
appended = true
|
||||
break
|
||||
}
|
||||
|
||||
// Otherwise, add a new attribute for the extensions.
|
||||
if !appended {
|
||||
attributes = append(attributes, pkix.AttributeTypeAndValueSET{
|
||||
Type: oidExtensionRequest,
|
||||
Value: [][]pkix.AttributeTypeAndValue{
|
||||
atvs,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
asn1Subject := template.RawSubject
|
||||
if len(asn1Subject) == 0 {
|
||||
asn1Subject, err = asn1.Marshal(template.Subject.ToRDNSequence())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
tbsCSR := tbsCertificateRequest{
|
||||
Version: 0, // PKCS #10, RFC 2986
|
||||
Subject: asn1.RawValue{FullBytes: asn1Subject},
|
||||
PublicKey: publicKeyInfo{
|
||||
Algorithm: publicKeyAlgorithm,
|
||||
PublicKey: asn1.BitString{
|
||||
Bytes: publicKeyBytes,
|
||||
BitLength: len(publicKeyBytes) * 8,
|
||||
},
|
||||
},
|
||||
Attributes: attributes,
|
||||
}
|
||||
|
||||
tbsCSRContents, err := asn1.Marshal(tbsCSR)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tbsCSR.Raw = tbsCSRContents
|
||||
|
||||
h := hashFunc.New()
|
||||
h.Write(tbsCSRContents)
|
||||
digest := h.Sum(nil)
|
||||
|
||||
var signature []byte
|
||||
switch priv := priv.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
signature, err = rsa.SignPKCS1v15(rand, priv, hashFunc, digest)
|
||||
case *ecdsa.PrivateKey:
|
||||
var r, s *big.Int
|
||||
if r, s, err = ecdsa.Sign(rand, priv, digest); err == nil {
|
||||
signature, err = asn1.Marshal(ecdsaSignature{r, s})
|
||||
}
|
||||
default:
|
||||
panic("internal error")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return asn1.Marshal(certificateRequest{
|
||||
TBSCSR: tbsCSR,
|
||||
SignatureAlgorithm: sigAlgo,
|
||||
SignatureValue: asn1.BitString{
|
||||
Bytes: signature,
|
||||
BitLength: len(signature) * 8,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ParseCertificateRequest parses a single certificate request from the
|
||||
// given ASN.1 DER data.
|
||||
func ParseCertificateRequest(asn1Data []byte) (*CertificateRequest, error) {
|
||||
var csr certificateRequest
|
||||
|
||||
rest, err := asn1.Unmarshal(asn1Data, &csr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(rest) != 0 {
|
||||
return nil, asn1.SyntaxError{Msg: "trailing data"}
|
||||
}
|
||||
|
||||
return parseCertificateRequest(&csr)
|
||||
}
|
||||
|
||||
func parseCertificateRequest(in *certificateRequest) (*CertificateRequest, error) {
|
||||
out := &CertificateRequest{
|
||||
Raw: in.Raw,
|
||||
RawTBSCertificateRequest: in.TBSCSR.Raw,
|
||||
RawSubjectPublicKeyInfo: in.TBSCSR.PublicKey.Raw,
|
||||
RawSubject: in.TBSCSR.Subject.FullBytes,
|
||||
|
||||
Signature: in.SignatureValue.RightAlign(),
|
||||
SignatureAlgorithm: getSignatureAlgorithmFromOID(in.SignatureAlgorithm.Algorithm),
|
||||
|
||||
PublicKeyAlgorithm: getPublicKeyAlgorithmFromOID(in.TBSCSR.PublicKey.Algorithm.Algorithm),
|
||||
|
||||
Version: in.TBSCSR.Version,
|
||||
Attributes: in.TBSCSR.Attributes,
|
||||
}
|
||||
|
||||
var err error
|
||||
out.PublicKey, err = parsePublicKey(out.PublicKeyAlgorithm, &in.TBSCSR.PublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var subject pkix.RDNSequence
|
||||
if _, err := asn1.Unmarshal(in.TBSCSR.Subject.FullBytes, &subject); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out.Subject.FillFromRDNSequence(&subject)
|
||||
|
||||
var extensions []pkix.AttributeTypeAndValue
|
||||
|
||||
for _, atvSet := range in.TBSCSR.Attributes {
|
||||
if !atvSet.Type.Equal(oidExtensionRequest) {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, atvs := range atvSet.Value {
|
||||
extensions = append(extensions, atvs...)
|
||||
}
|
||||
}
|
||||
|
||||
out.Extensions = make([]pkix.Extension, 0, len(extensions))
|
||||
|
||||
for _, e := range extensions {
|
||||
value, ok := e.Value.([]byte)
|
||||
if !ok {
|
||||
return nil, errors.New("x509: extension attribute contained non-OCTET STRING data")
|
||||
}
|
||||
|
||||
out.Extensions = append(out.Extensions, pkix.Extension{
|
||||
Id: e.Type,
|
||||
Value: value,
|
||||
})
|
||||
|
||||
if len(e.Type) == 4 && e.Type[0] == 2 && e.Type[1] == 5 && e.Type[2] == 29 {
|
||||
switch e.Type[3] {
|
||||
case 17:
|
||||
out.DNSNames, out.EmailAddresses, out.IPAddresses, err = parseSANExtension(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
@ -679,11 +679,11 @@ func TestCRLCreation(t *testing.T) {
|
||||
|
||||
func fromBase64(in string) []byte {
|
||||
out := make([]byte, base64.StdEncoding.DecodedLen(len(in)))
|
||||
_, err := base64.StdEncoding.Decode(out, []byte(in))
|
||||
n, err := base64.StdEncoding.Decode(out, []byte(in))
|
||||
if err != nil {
|
||||
panic("failed to base64 decode")
|
||||
}
|
||||
return out
|
||||
return out[:n]
|
||||
}
|
||||
|
||||
func TestParseDERCRL(t *testing.T) {
|
||||
@ -735,3 +735,213 @@ func TestImports(t *testing.T) {
|
||||
const derCRLBase64 = "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"
|
||||
|
||||
const pemCRLBase64 = "LS0tLS1CRUdJTiBYNTA5IENSTC0tLS0tDQpNSUlCOWpDQ0FWOENBUUV3RFFZSktvWklodmNOQVFFRkJRQXdiREVhTUJnR0ExVUVDaE1SVWxOQklGTmxZM1Z5DQphWFI1SUVsdVl5NHhIakFjQmdOVkJBTVRGVkpUUVNCUWRXSnNhV01nVW05dmRDQkRRU0IyTVRFdU1Dd0dDU3FHDQpTSWIzRFFFSkFSWWZjbk5oYTJWdmJuSnZiM1J6YVdkdVFISnpZWE5sWTNWeWFYUjVMbU52YlJjTk1URXdNakl6DQpNVGt5T0RNd1doY05NVEV3T0RJeU1Ua3lPRE13V2pDQmpEQktBaEVBckRxb2g5RkhKSFhUN09QZ3V1bjQrQmNODQpNRGt4TVRBeU1UUXlOekE1V2pBbU1Bb0dBMVVkRlFRRENnRUpNQmdHQTFVZEdBUVJHQTh5TURBNU1URXdNakUwDQpNalExTlZvd1BnSVJBTEd6blowOTVQQjVhQU9MUGc1N2ZNTVhEVEF5TVRBeU16RTBOVEF4TkZvd0dqQVlCZ05WDQpIUmdFRVJnUE1qQXdNakV3TWpNeE5EVXdNVFJhb0RBd0xqQWZCZ05WSFNNRUdEQVdnQlQxVERGNlVRTS9MTmVMDQpsNWx2cUhHUXEzZzltekFMQmdOVkhSUUVCQUlDQUlRd0RRWUpLb1pJaHZjTkFRRUZCUUFEZ1lFQUZVNUFzNk16DQpxNVBSc2lmYW9iUVBHaDFhSkx5QytNczVBZ2MwYld5QTNHQWR4dXI1U3BQWmVSV0NCamlQL01FSEJXSkNsQkhQDQpHUmNxNXlJZDNFakRrYUV5eFJhK2k2N0x6dmhJNmMyOUVlNks5cFNZd2ppLzdSVWhtbW5Qclh0VHhsTDBsckxyDQptUVFKNnhoRFJhNUczUUE0Q21VZHNITnZicnpnbUNZcHZWRT0NCi0tLS0tRU5EIFg1MDkgQ1JMLS0tLS0NCg0K"
|
||||
|
||||
func TestCreateCertificateRequest(t *testing.T) {
|
||||
random := rand.Reader
|
||||
|
||||
block, _ := pem.Decode([]byte(pemPrivateKey))
|
||||
rsaPriv, err := ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse private key: %s", err)
|
||||
}
|
||||
|
||||
ecdsa256Priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate ECDSA key: %s", err)
|
||||
}
|
||||
|
||||
ecdsa384Priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate ECDSA key: %s", err)
|
||||
}
|
||||
|
||||
ecdsa521Priv, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate ECDSA key: %s", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
priv interface{}
|
||||
sigAlgo SignatureAlgorithm
|
||||
}{
|
||||
{"RSA", rsaPriv, SHA1WithRSA},
|
||||
{"ECDSA-256", ecdsa256Priv, ECDSAWithSHA1},
|
||||
{"ECDSA-384", ecdsa384Priv, ECDSAWithSHA1},
|
||||
{"ECDSA-521", ecdsa521Priv, ECDSAWithSHA1},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
template := CertificateRequest{
|
||||
Subject: pkix.Name{
|
||||
CommonName: "test.example.com",
|
||||
Organization: []string{"Σ Acme Co"},
|
||||
},
|
||||
SignatureAlgorithm: test.sigAlgo,
|
||||
DNSNames: []string{"test.example.com"},
|
||||
EmailAddresses: []string{"gopher@golang.org"},
|
||||
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1).To4(), net.ParseIP("2001:4860:0:2001::68")},
|
||||
}
|
||||
|
||||
derBytes, err := CreateCertificateRequest(random, &template, test.priv)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create certificate request: %s", test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
out, err := ParseCertificateRequest(derBytes)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create certificate request: %s", test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if out.Subject.CommonName != template.Subject.CommonName {
|
||||
t.Errorf("%s: output subject common name and template subject common name don't match", test.name)
|
||||
} else if len(out.Subject.Organization) != len(template.Subject.Organization) {
|
||||
t.Errorf("%s: output subject organisation and template subject organisation don't match", test.name)
|
||||
} else if len(out.DNSNames) != len(template.DNSNames) {
|
||||
t.Errorf("%s: output DNS names and template DNS names don't match", test.name)
|
||||
} else if len(out.EmailAddresses) != len(template.EmailAddresses) {
|
||||
t.Errorf("%s: output email addresses and template email addresses don't match", test.name)
|
||||
} else if len(out.IPAddresses) != len(template.IPAddresses) {
|
||||
t.Errorf("%s: output IP addresses and template IP addresses names don't match", test.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func marshalAndParseCSR(t *testing.T, template *CertificateRequest) *CertificateRequest {
|
||||
block, _ := pem.Decode([]byte(pemPrivateKey))
|
||||
rsaPriv, err := ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
derBytes, err := CreateCertificateRequest(rand.Reader, template, rsaPriv)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
csr, err := ParseCertificateRequest(derBytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return csr
|
||||
}
|
||||
|
||||
func TestCertificateRequestOverrides(t *testing.T) {
|
||||
sanContents, err := marshalSANs([]string{"foo.example.com"}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
template := CertificateRequest{
|
||||
Subject: pkix.Name{
|
||||
CommonName: "test.example.com",
|
||||
Organization: []string{"Σ Acme Co"},
|
||||
},
|
||||
DNSNames: []string{"test.example.com"},
|
||||
|
||||
// An explicit extension should override the DNSNames from the
|
||||
// template.
|
||||
ExtraExtensions: []pkix.Extension{
|
||||
pkix.Extension{
|
||||
Id: oidExtensionSubjectAltName,
|
||||
Value: sanContents,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
csr := marshalAndParseCSR(t, &template)
|
||||
|
||||
if len(csr.DNSNames) != 1 || csr.DNSNames[0] != "foo.example.com" {
|
||||
t.Errorf("Extension did not override template. Got %v\n", csr.DNSNames)
|
||||
}
|
||||
|
||||
// If there is already an attribute with X.509 extensions then the
|
||||
// extra extensions should be added to it rather than creating a CSR
|
||||
// with two extension attributes.
|
||||
|
||||
template.Attributes = []pkix.AttributeTypeAndValueSET{
|
||||
pkix.AttributeTypeAndValueSET{
|
||||
Type: oidExtensionRequest,
|
||||
Value: [][]pkix.AttributeTypeAndValue{
|
||||
[]pkix.AttributeTypeAndValue{
|
||||
pkix.AttributeTypeAndValue{
|
||||
Type: oidExtensionAuthorityInfoAccess,
|
||||
Value: []byte("foo"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
csr = marshalAndParseCSR(t, &template)
|
||||
if l := len(csr.Attributes); l != 1 {
|
||||
t.Errorf("incorrect number of attributes: %d\n", l)
|
||||
}
|
||||
|
||||
if !csr.Attributes[0].Type.Equal(oidExtensionRequest) ||
|
||||
len(csr.Attributes[0].Value) != 1 ||
|
||||
len(csr.Attributes[0].Value[0]) != 2 {
|
||||
t.Errorf("bad attributes: %#v\n", csr.Attributes)
|
||||
}
|
||||
|
||||
sanContents2, err := marshalSANs([]string{"foo2.example.com"}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Extensions in Attributes should override those in ExtraExtensions.
|
||||
template.Attributes[0].Value[0] = append(template.Attributes[0].Value[0], pkix.AttributeTypeAndValue{
|
||||
Type: oidExtensionSubjectAltName,
|
||||
Value: sanContents2,
|
||||
})
|
||||
|
||||
csr = marshalAndParseCSR(t, &template)
|
||||
|
||||
if len(csr.DNSNames) != 1 || csr.DNSNames[0] != "foo2.example.com" {
|
||||
t.Errorf("Attributes did not override ExtraExtensions. Got %v\n", csr.DNSNames)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseCertificateRequest(t *testing.T) {
|
||||
csrBytes := fromBase64(csrBase64)
|
||||
csr, err := ParseCertificateRequest(csrBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse CSR: %s", err)
|
||||
}
|
||||
|
||||
if len(csr.EmailAddresses) != 1 || csr.EmailAddresses[0] != "gopher@golang.org" {
|
||||
t.Errorf("incorrect email addresses found: %v", csr.EmailAddresses)
|
||||
}
|
||||
|
||||
if len(csr.DNSNames) != 1 || csr.DNSNames[0] != "test.example.com" {
|
||||
t.Errorf("incorrect DNS names found: %v", csr.DNSNames)
|
||||
}
|
||||
|
||||
if len(csr.Subject.Country) != 1 || csr.Subject.Country[0] != "AU" {
|
||||
t.Errorf("incorrect Subject name: %v", csr.Subject)
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, e := range csr.Extensions {
|
||||
if e.Id.Equal(oidExtensionBasicConstraints) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("basic constraints extension not found in CSR")
|
||||
}
|
||||
}
|
||||
|
||||
// This CSR was generated with OpenSSL:
|
||||
// openssl req -out CSR.csr -new -newkey rsa:2048 -nodes -keyout privateKey.key -config openssl.cnf
|
||||
//
|
||||
// The openssl.cnf needs to include this section:
|
||||
// [ v3_req ]
|
||||
// basicConstraints = CA:FALSE
|
||||
// keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
// subjectAltName = email:gopher@golang.org,DNS:test.example.com
|
||||
const csrBase64 = "MIIC4zCCAcsCAQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOY+MVedRg2JEnyeLcSzcsMv2VcsTfkB5+Etd6hihAh6MrGezNyASMMKuQN6YhCX1icQDiQtGsDLTtheNnSXK06tAhHjAP/hGlszRJp+5+rP2M58fDBAkUBEhskbCUWwpY14jFtVuGNJ8vF8h8IeczdolvQhX9lVai9G0EUXJMliMKdjA899H0mRs9PzHyidyrXFNiZlQXfD8Kg7gETn2Ny965iyI6ujAIYSCvam6TnxRHYH2MBKyVGvsYGbPYUQJCsgdgyajEg6ekihvQY3SzO1HSAlZAd7d1QYO4VeWJ2mY6Wu3Jpmh+AmG19S9CcHqGjd0bhuAX9cpPOKgnEmqn0CAwEAAaBZMFcGCSqGSIb3DQEJDjFKMEgwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwLgYDVR0RBCcwJYERZ29waGVyQGdvbGFuZy5vcmeCEHRlc3QuZXhhbXBsZS5jb20wDQYJKoZIhvcNAQEFBQADggEBAC9+QpKfdabxwCWwf4IEe1cKjdXLS1ScSuw27a3kZzQiPV78WJMa6dB8dqhdH5BRwGZ/qsgLrO6ZHlNeIv2Ib41Ccq71ecHW/nXc94A1BzJ/bVdI9LZcmTUvR1/m1jCpN7UqQ0ml1u9VihK7Pe762hEYxuWDQzYEU0l15S/bXmqeq3eF1A59XT/2jwe5+NV0Wwf4UQlkTXsAQMsJ+KzrQafd8Qv2A49o048uRvmjeJDrXLawGVianZ7D5A6Fpd1rZh6XcjqBpmgLw41DRQWENOdzhy+HyphKRv1MlY8OLkNqpGMhu8DdgJVGoT16DGiickoEa7Z3UCPVNgdTkT9jq7U="
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -197,6 +198,19 @@ func (oi ObjectIdentifier) Equal(other ObjectIdentifier) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (oi ObjectIdentifier) String() string {
|
||||
var s string
|
||||
|
||||
for i, v := range oi {
|
||||
if i > 0 {
|
||||
s += "."
|
||||
}
|
||||
s += strconv.Itoa(v)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// parseObjectIdentifier parses an OBJECT IDENTIFIER from the given bytes and
|
||||
// returns it. An object identifier is a sequence of variable length integers
|
||||
// that are assigned in a hierarchy.
|
||||
|
@ -232,6 +232,10 @@ func TestObjectIdentifier(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if s := ObjectIdentifier([]int{1, 2, 3, 4}).String(); s != "1.2.3.4" {
|
||||
t.Errorf("bad ObjectIdentifier.String(). Got %s, want 1.2.3.4", s)
|
||||
}
|
||||
}
|
||||
|
||||
type timeTest struct {
|
||||
|
Loading…
Reference in New Issue
Block a user