]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/x509: support ECDSA keys when generating certificates.
authorAdam Langley <agl@golang.org>
Fri, 3 Aug 2012 14:37:30 +0000 (10:37 -0400)
committerAdam Langley <agl@golang.org>
Fri, 3 Aug 2012 14:37:30 +0000 (10:37 -0400)
We already support reading ECDSA certificates and this change adds
write support.

R=golang-dev, bradfitz, rsc
CC=golang-dev
https://golang.org/cl/6422046

src/pkg/crypto/x509/pkcs8.go
src/pkg/crypto/x509/x509.go
src/pkg/crypto/x509/x509_test.go

index 4d8e0518e02bcc9dc3ba64b3360fbb29b2e681f0..8c3b65f80784d2d2f85cee957efeb7bc005211b5 100644 (file)
@@ -28,7 +28,7 @@ func ParsePKCS8PrivateKey(der []byte) (key interface{}, err error) {
                return nil, err
        }
        switch {
-       case privKey.Algo.Algorithm.Equal(oidRSA):
+       case privKey.Algo.Algorithm.Equal(oidPublicKeyRSA):
                key, err = ParsePKCS1PrivateKey(privKey.PrivateKey)
                if err != nil {
                        return nil, errors.New("crypto/x509: failed to parse RSA private key embedded in PKCS#8: " + err.Error())
index d7ed8bdbd5824e617c97021dce9f8680ad3eebc6..b9e4dc0f6a1fdc36823a8bca9b7d0d341ad5b2ba 100644 (file)
@@ -262,18 +262,18 @@ func getSignatureAlgorithmFromOID(oid asn1.ObjectIdentifier) SignatureAlgorithm
 // id-ecPublicKey OBJECT IDENTIFIER ::= {
 //       iso(1) member-body(2) us(840) ansi-X9-62(10045) keyType(2) 1 }
 var (
-       oidPublicKeyRsa   = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}
-       oidPublicKeyDsa   = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 1}
-       oidPublicKeyEcdsa = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}
+       oidPublicKeyRSA   = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}
+       oidPublicKeyDSA   = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 1}
+       oidPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}
 )
 
 func getPublicKeyAlgorithmFromOID(oid asn1.ObjectIdentifier) PublicKeyAlgorithm {
        switch {
-       case oid.Equal(oidPublicKeyRsa):
+       case oid.Equal(oidPublicKeyRSA):
                return RSA
-       case oid.Equal(oidPublicKeyDsa):
+       case oid.Equal(oidPublicKeyDSA):
                return DSA
-       case oid.Equal(oidPublicKeyEcdsa):
+       case oid.Equal(oidPublicKeyECDSA):
                return ECDSA
        }
        return UnknownPublicKeyAlgorithm
@@ -302,7 +302,7 @@ var (
        oidNamedCurveP521 = asn1.ObjectIdentifier{1, 3, 132, 0, 35}
 )
 
-func getNamedCurveFromOID(oid asn1.ObjectIdentifier) elliptic.Curve {
+func namedCurveFromOID(oid asn1.ObjectIdentifier) elliptic.Curve {
        switch {
        case oid.Equal(oidNamedCurveP224):
                return elliptic.P224()
@@ -316,6 +316,21 @@ func getNamedCurveFromOID(oid asn1.ObjectIdentifier) elliptic.Curve {
        return nil
 }
 
+func oidFromNamedCurve(curve elliptic.Curve) (asn1.ObjectIdentifier, bool) {
+       switch curve {
+       case elliptic.P224():
+               return oidNamedCurveP224, true
+       case elliptic.P256():
+               return oidNamedCurveP256, true
+       case elliptic.P384():
+               return oidNamedCurveP384, true
+       case elliptic.P521():
+               return oidNamedCurveP521, true
+       }
+
+       return nil, false
+}
+
 // KeyUsage represents the set of actions that are valid for a given key. It's
 // a bitmap of the KeyUsage* constants.
 type KeyUsage int
@@ -648,7 +663,7 @@ func parsePublicKey(algo PublicKeyAlgorithm, keyData *publicKeyInfo) (interface{
                if err != nil {
                        return nil, err
                }
-               namedCurve := getNamedCurveFromOID(*namedCurveOID)
+               namedCurve := namedCurveFromOID(*namedCurveOID)
                if namedCurve == nil {
                        return nil, errors.New("crypto/x509: unsupported elliptic curve")
                }
@@ -1069,11 +1084,6 @@ func buildExtensions(template *Certificate) (ret []pkix.Extension, err error) {
        return ret[0:n], nil
 }
 
-var (
-       oidSHA1WithRSA = []int{1, 2, 840, 113549, 1, 1, 5}
-       oidRSA         = []int{1, 2, 840, 113549, 1, 1, 1}
-)
-
 func subjectBytes(cert *Certificate) ([]byte, error) {
        if len(cert.RawSubject) > 0 {
                return cert.RawSubject, nil
@@ -1093,23 +1103,61 @@ func subjectBytes(cert *Certificate) ([]byte, error) {
 //
 // The returned slice is the certificate in DER encoding.
 //
-// The only supported key type is RSA (*rsa.PublicKey for pub, *rsa.PrivateKey
-// for priv).
+// The only supported key types are RSA and ECDSA (*rsa.PublicKey or
+// *ecdsa.PublicKey for pub, *rsa.PrivateKey or *ecdsa.PublicKey for priv).
 func CreateCertificate(rand io.Reader, template, parent *Certificate, pub interface{}, priv interface{}) (cert []byte, err error) {
-       rsaPub, ok := pub.(*rsa.PublicKey)
-       if !ok {
-               return nil, errors.New("x509: non-RSA public keys not supported")
+       var publicKeyBytes []byte
+       var publicKeyAlgorithm pkix.AlgorithmIdentifier
+
+       switch pub := pub.(type) {
+       case *rsa.PublicKey:
+               publicKeyBytes, err = asn1.Marshal(rsaPublicKey{
+                       N: pub.N,
+                       E: pub.E,
+               })
+               publicKeyAlgorithm.Algorithm = oidPublicKeyRSA
+       case *ecdsa.PublicKey:
+               oid, ok := oidFromNamedCurve(pub.Curve)
+               if !ok {
+                       return nil, errors.New("x509: unknown elliptic curve")
+               }
+               publicKeyAlgorithm.Algorithm = oidPublicKeyECDSA
+               var paramBytes []byte
+               paramBytes, err = asn1.Marshal(oid)
+               if err != nil {
+                       return
+               }
+               publicKeyAlgorithm.Parameters.FullBytes = paramBytes
+               publicKeyBytes = elliptic.Marshal(pub.Curve, pub.X, pub.Y)
+       default:
+               return nil, errors.New("x509: only RSA and ECDSA public keys supported")
        }
 
-       rsaPriv, ok := priv.(*rsa.PrivateKey)
-       if !ok {
-               return nil, errors.New("x509: non-RSA private keys not supported")
+       var signatureAlgorithm pkix.AlgorithmIdentifier
+       var hashFunc crypto.Hash
+
+       switch priv := priv.(type) {
+       case *rsa.PrivateKey:
+               signatureAlgorithm.Algorithm = oidSignatureSHA1WithRSA
+               hashFunc = crypto.SHA1
+       case *ecdsa.PrivateKey:
+               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")
        }
 
-       asn1PublicKey, err := asn1.Marshal(rsaPublicKey{
-               N: rsaPub.N,
-               E: rsaPub.E,
-       })
        if err != nil {
                return
        }
@@ -1133,15 +1181,15 @@ func CreateCertificate(rand io.Reader, template, parent *Certificate, pub interf
                return
        }
 
-       encodedPublicKey := asn1.BitString{BitLength: len(asn1PublicKey) * 8, Bytes: asn1PublicKey}
+       encodedPublicKey := asn1.BitString{BitLength: len(publicKeyBytes) * 8, Bytes: publicKeyBytes}
        c := tbsCertificate{
                Version:            2,
                SerialNumber:       template.SerialNumber,
-               SignatureAlgorithm: pkix.AlgorithmIdentifier{Algorithm: oidSHA1WithRSA},
+               SignatureAlgorithm: signatureAlgorithm,
                Issuer:             asn1.RawValue{FullBytes: asn1Issuer},
                Validity:           validity{template.NotBefore, template.NotAfter},
                Subject:            asn1.RawValue{FullBytes: asn1Subject},
-               PublicKey:          publicKeyInfo{nil, pkix.AlgorithmIdentifier{Algorithm: oidRSA}, encodedPublicKey},
+               PublicKey:          publicKeyInfo{nil, publicKeyAlgorithm, encodedPublicKey},
                Extensions:         extensions,
        }
 
@@ -1152,11 +1200,24 @@ func CreateCertificate(rand io.Reader, template, parent *Certificate, pub interf
 
        c.Raw = tbsCertContents
 
-       h := sha1.New()
+       h := hashFunc.New()
        h.Write(tbsCertContents)
        digest := h.Sum(nil)
 
-       signature, err := rsa.SignPKCS1v15(rand, rsaPriv, crypto.SHA1, digest)
+       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
        }
@@ -1164,7 +1225,7 @@ func CreateCertificate(rand io.Reader, template, parent *Certificate, pub interf
        cert, err = asn1.Marshal(certificate{
                nil,
                c,
-               pkix.AlgorithmIdentifier{Algorithm: oidSHA1WithRSA},
+               signatureAlgorithm,
                asn1.BitString{Bytes: signature, BitLength: len(signature) * 8},
        })
        return
index 813a96409b32b8f8dda5fda47caeafc64422180c..9e2e387316eb0a02032c97c171c188c82ae762bb 100644 (file)
@@ -8,6 +8,7 @@ import (
        "bytes"
        "crypto/dsa"
        "crypto/ecdsa"
+       "crypto/elliptic"
        "crypto/rand"
        "crypto/rsa"
        _ "crypto/sha256"
@@ -240,65 +241,83 @@ func TestCreateSelfSignedCertificate(t *testing.T) {
        random := rand.Reader
 
        block, _ := pem.Decode([]byte(pemPrivateKey))
-       priv, err := ParsePKCS1PrivateKey(block.Bytes)
+       rsaPriv, err := ParsePKCS1PrivateKey(block.Bytes)
        if err != nil {
-               t.Errorf("Failed to parse private key: %s", err)
-               return
-       }
-
-       commonName := "test.example.com"
-       template := Certificate{
-               SerialNumber: big.NewInt(1),
-               Subject: pkix.Name{
-                       CommonName:   commonName,
-                       Organization: []string{"Σ Acme Co"},
-               },
-               NotBefore: time.Unix(1000, 0),
-               NotAfter:  time.Unix(100000, 0),
-
-               SubjectKeyId: []byte{1, 2, 3, 4},
-               KeyUsage:     KeyUsageCertSign,
-
-               BasicConstraintsValid: true,
-               IsCA:                  true,
-               DNSNames:              []string{"test.example.com"},
-
-               PolicyIdentifiers:   []asn1.ObjectIdentifier{[]int{1, 2, 3}},
-               PermittedDNSDomains: []string{".example.com", "example.com"},
+               t.Fatalf("Failed to parse private key: %s", err)
        }
 
-       derBytes, err := CreateCertificate(random, &template, &template, &priv.PublicKey, priv)
+       ecdsaPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
        if err != nil {
-               t.Errorf("Failed to create certificate: %s", err)
-               return
-       }
+               t.Fatalf("Failed to generate ECDSA key: %s", err)
+       }
+
+       tests := []struct {
+               name      string
+               pub, priv interface{}
+               checkSig  bool
+       }{
+               {"RSA/RSA", &rsaPriv.PublicKey, rsaPriv, true},
+               {"RSA/ECDSA", &rsaPriv.PublicKey, ecdsaPriv, false},
+               {"ECDSA/RSA", &ecdsaPriv.PublicKey, rsaPriv, false},
+               {"ECDSA/ECDSA", &ecdsaPriv.PublicKey, ecdsaPriv, true},
+       }
+
+       for _, test := range tests {
+               commonName := "test.example.com"
+               template := Certificate{
+                       SerialNumber: big.NewInt(1),
+                       Subject: pkix.Name{
+                               CommonName:   commonName,
+                               Organization: []string{"Σ Acme Co"},
+                       },
+                       NotBefore: time.Unix(1000, 0),
+                       NotAfter:  time.Unix(100000, 0),
+
+                       SubjectKeyId: []byte{1, 2, 3, 4},
+                       KeyUsage:     KeyUsageCertSign,
+
+                       BasicConstraintsValid: true,
+                       IsCA:                  true,
+                       DNSNames:              []string{"test.example.com"},
+
+                       PolicyIdentifiers:   []asn1.ObjectIdentifier{[]int{1, 2, 3}},
+                       PermittedDNSDomains: []string{".example.com", "example.com"},
+               }
 
-       cert, err := ParseCertificate(derBytes)
-       if err != nil {
-               t.Errorf("Failed to parse certificate: %s", err)
-               return
-       }
+               derBytes, err := CreateCertificate(random, &template, &template, test.pub, test.priv)
+               if err != nil {
+                       t.Errorf("%s: failed to create certificate: %s", test.name, err)
+                       continue
+               }
 
-       if len(cert.PolicyIdentifiers) != 1 || !cert.PolicyIdentifiers[0].Equal(template.PolicyIdentifiers[0]) {
-               t.Errorf("Failed to parse policy identifiers: got:%#v want:%#v", cert.PolicyIdentifiers, template.PolicyIdentifiers)
-       }
+               cert, err := ParseCertificate(derBytes)
+               if err != nil {
+                       t.Errorf("%s: failed to parse certificate: %s", test.name, err)
+                       continue
+               }
 
-       if len(cert.PermittedDNSDomains) != 2 || cert.PermittedDNSDomains[0] != ".example.com" || cert.PermittedDNSDomains[1] != "example.com" {
-               t.Errorf("Failed to parse name constraints: %#v", cert.PermittedDNSDomains)
-       }
+               if len(cert.PolicyIdentifiers) != 1 || !cert.PolicyIdentifiers[0].Equal(template.PolicyIdentifiers[0]) {
+                       t.Errorf("%s: failed to parse policy identifiers: got:%#v want:%#v", test.name, cert.PolicyIdentifiers, template.PolicyIdentifiers)
+               }
 
-       if cert.Subject.CommonName != commonName {
-               t.Errorf("Subject wasn't correctly copied from the template. Got %s, want %s", cert.Subject.CommonName, commonName)
-       }
+               if len(cert.PermittedDNSDomains) != 2 || cert.PermittedDNSDomains[0] != ".example.com" || cert.PermittedDNSDomains[1] != "example.com" {
+                       t.Errorf("%s: failed to parse name constraints: %#v", test.name, cert.PermittedDNSDomains)
+               }
 
-       if cert.Issuer.CommonName != commonName {
-               t.Errorf("Issuer wasn't correctly copied from the template. Got %s, want %s", cert.Issuer.CommonName, commonName)
-       }
+               if cert.Subject.CommonName != commonName {
+                       t.Errorf("%s: subject wasn't correctly copied from the template. Got %s, want %s", test.name, cert.Subject.CommonName, commonName)
+               }
 
-       err = cert.CheckSignatureFrom(cert)
-       if err != nil {
-               t.Errorf("Signature verification failed: %s", err)
-               return
+               if cert.Issuer.CommonName != commonName {
+                       t.Errorf("%s: issuer wasn't correctly copied from the template. Got %s, want %s", test.name, cert.Issuer.CommonName, commonName)
+               }
+
+               if test.checkSig {
+                       err = cert.CheckSignatureFrom(cert)
+                       if err != nil {
+                               t.Errorf("%s: signature verification failed: %s", test.name, err)
+                       }
+               }
        }
 }