mirror of
https://github.com/golang/go
synced 2024-10-04 06:11:21 -06:00
crypto/x509: support ECDSA keys when generating certificates.
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
This commit is contained in:
parent
89d40b911c
commit
bbb5f1bffb
@ -28,7 +28,7 @@ func ParsePKCS8PrivateKey(der []byte) (key interface{}, err error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case privKey.Algo.Algorithm.Equal(oidRSA):
|
case privKey.Algo.Algorithm.Equal(oidPublicKeyRSA):
|
||||||
key, err = ParsePKCS1PrivateKey(privKey.PrivateKey)
|
key, err = ParsePKCS1PrivateKey(privKey.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("crypto/x509: failed to parse RSA private key embedded in PKCS#8: " + err.Error())
|
return nil, errors.New("crypto/x509: failed to parse RSA private key embedded in PKCS#8: " + err.Error())
|
||||||
|
@ -262,18 +262,18 @@ func getSignatureAlgorithmFromOID(oid asn1.ObjectIdentifier) SignatureAlgorithm
|
|||||||
// id-ecPublicKey OBJECT IDENTIFIER ::= {
|
// id-ecPublicKey OBJECT IDENTIFIER ::= {
|
||||||
// iso(1) member-body(2) us(840) ansi-X9-62(10045) keyType(2) 1 }
|
// iso(1) member-body(2) us(840) ansi-X9-62(10045) keyType(2) 1 }
|
||||||
var (
|
var (
|
||||||
oidPublicKeyRsa = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}
|
oidPublicKeyRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}
|
||||||
oidPublicKeyDsa = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 1}
|
oidPublicKeyDSA = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 1}
|
||||||
oidPublicKeyEcdsa = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}
|
oidPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}
|
||||||
)
|
)
|
||||||
|
|
||||||
func getPublicKeyAlgorithmFromOID(oid asn1.ObjectIdentifier) PublicKeyAlgorithm {
|
func getPublicKeyAlgorithmFromOID(oid asn1.ObjectIdentifier) PublicKeyAlgorithm {
|
||||||
switch {
|
switch {
|
||||||
case oid.Equal(oidPublicKeyRsa):
|
case oid.Equal(oidPublicKeyRSA):
|
||||||
return RSA
|
return RSA
|
||||||
case oid.Equal(oidPublicKeyDsa):
|
case oid.Equal(oidPublicKeyDSA):
|
||||||
return DSA
|
return DSA
|
||||||
case oid.Equal(oidPublicKeyEcdsa):
|
case oid.Equal(oidPublicKeyECDSA):
|
||||||
return ECDSA
|
return ECDSA
|
||||||
}
|
}
|
||||||
return UnknownPublicKeyAlgorithm
|
return UnknownPublicKeyAlgorithm
|
||||||
@ -302,7 +302,7 @@ var (
|
|||||||
oidNamedCurveP521 = asn1.ObjectIdentifier{1, 3, 132, 0, 35}
|
oidNamedCurveP521 = asn1.ObjectIdentifier{1, 3, 132, 0, 35}
|
||||||
)
|
)
|
||||||
|
|
||||||
func getNamedCurveFromOID(oid asn1.ObjectIdentifier) elliptic.Curve {
|
func namedCurveFromOID(oid asn1.ObjectIdentifier) elliptic.Curve {
|
||||||
switch {
|
switch {
|
||||||
case oid.Equal(oidNamedCurveP224):
|
case oid.Equal(oidNamedCurveP224):
|
||||||
return elliptic.P224()
|
return elliptic.P224()
|
||||||
@ -316,6 +316,21 @@ func getNamedCurveFromOID(oid asn1.ObjectIdentifier) elliptic.Curve {
|
|||||||
return nil
|
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
|
// KeyUsage represents the set of actions that are valid for a given key. It's
|
||||||
// a bitmap of the KeyUsage* constants.
|
// a bitmap of the KeyUsage* constants.
|
||||||
type KeyUsage int
|
type KeyUsage int
|
||||||
@ -648,7 +663,7 @@ func parsePublicKey(algo PublicKeyAlgorithm, keyData *publicKeyInfo) (interface{
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
namedCurve := getNamedCurveFromOID(*namedCurveOID)
|
namedCurve := namedCurveFromOID(*namedCurveOID)
|
||||||
if namedCurve == nil {
|
if namedCurve == nil {
|
||||||
return nil, errors.New("crypto/x509: unsupported elliptic curve")
|
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
|
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) {
|
func subjectBytes(cert *Certificate) ([]byte, error) {
|
||||||
if len(cert.RawSubject) > 0 {
|
if len(cert.RawSubject) > 0 {
|
||||||
return cert.RawSubject, nil
|
return cert.RawSubject, nil
|
||||||
@ -1093,23 +1103,61 @@ func subjectBytes(cert *Certificate) ([]byte, error) {
|
|||||||
//
|
//
|
||||||
// The returned slice is the certificate in DER encoding.
|
// The returned slice is the certificate in DER encoding.
|
||||||
//
|
//
|
||||||
// The only supported key type is RSA (*rsa.PublicKey for pub, *rsa.PrivateKey
|
// The only supported key types are RSA and ECDSA (*rsa.PublicKey or
|
||||||
// for priv).
|
// *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) {
|
func CreateCertificate(rand io.Reader, template, parent *Certificate, pub interface{}, priv interface{}) (cert []byte, err error) {
|
||||||
rsaPub, ok := pub.(*rsa.PublicKey)
|
var publicKeyBytes []byte
|
||||||
if !ok {
|
var publicKeyAlgorithm pkix.AlgorithmIdentifier
|
||||||
return nil, errors.New("x509: non-RSA public keys not supported")
|
|
||||||
|
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)
|
var signatureAlgorithm pkix.AlgorithmIdentifier
|
||||||
if !ok {
|
var hashFunc crypto.Hash
|
||||||
return nil, errors.New("x509: non-RSA private keys not supported")
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1133,15 +1181,15 @@ func CreateCertificate(rand io.Reader, template, parent *Certificate, pub interf
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
encodedPublicKey := asn1.BitString{BitLength: len(asn1PublicKey) * 8, Bytes: asn1PublicKey}
|
encodedPublicKey := asn1.BitString{BitLength: len(publicKeyBytes) * 8, Bytes: publicKeyBytes}
|
||||||
c := tbsCertificate{
|
c := tbsCertificate{
|
||||||
Version: 2,
|
Version: 2,
|
||||||
SerialNumber: template.SerialNumber,
|
SerialNumber: template.SerialNumber,
|
||||||
SignatureAlgorithm: pkix.AlgorithmIdentifier{Algorithm: oidSHA1WithRSA},
|
SignatureAlgorithm: signatureAlgorithm,
|
||||||
Issuer: asn1.RawValue{FullBytes: asn1Issuer},
|
Issuer: asn1.RawValue{FullBytes: asn1Issuer},
|
||||||
Validity: validity{template.NotBefore, template.NotAfter},
|
Validity: validity{template.NotBefore, template.NotAfter},
|
||||||
Subject: asn1.RawValue{FullBytes: asn1Subject},
|
Subject: asn1.RawValue{FullBytes: asn1Subject},
|
||||||
PublicKey: publicKeyInfo{nil, pkix.AlgorithmIdentifier{Algorithm: oidRSA}, encodedPublicKey},
|
PublicKey: publicKeyInfo{nil, publicKeyAlgorithm, encodedPublicKey},
|
||||||
Extensions: extensions,
|
Extensions: extensions,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1152,11 +1200,24 @@ func CreateCertificate(rand io.Reader, template, parent *Certificate, pub interf
|
|||||||
|
|
||||||
c.Raw = tbsCertContents
|
c.Raw = tbsCertContents
|
||||||
|
|
||||||
h := sha1.New()
|
h := hashFunc.New()
|
||||||
h.Write(tbsCertContents)
|
h.Write(tbsCertContents)
|
||||||
digest := h.Sum(nil)
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1164,7 +1225,7 @@ func CreateCertificate(rand io.Reader, template, parent *Certificate, pub interf
|
|||||||
cert, err = asn1.Marshal(certificate{
|
cert, err = asn1.Marshal(certificate{
|
||||||
nil,
|
nil,
|
||||||
c,
|
c,
|
||||||
pkix.AlgorithmIdentifier{Algorithm: oidSHA1WithRSA},
|
signatureAlgorithm,
|
||||||
asn1.BitString{Bytes: signature, BitLength: len(signature) * 8},
|
asn1.BitString{Bytes: signature, BitLength: len(signature) * 8},
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/dsa"
|
"crypto/dsa"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
_ "crypto/sha256"
|
_ "crypto/sha256"
|
||||||
@ -240,65 +241,83 @@ func TestCreateSelfSignedCertificate(t *testing.T) {
|
|||||||
random := rand.Reader
|
random := rand.Reader
|
||||||
|
|
||||||
block, _ := pem.Decode([]byte(pemPrivateKey))
|
block, _ := pem.Decode([]byte(pemPrivateKey))
|
||||||
priv, err := ParsePKCS1PrivateKey(block.Bytes)
|
rsaPriv, err := ParsePKCS1PrivateKey(block.Bytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Failed to parse private key: %s", err)
|
t.Fatalf("Failed to parse private key: %s", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
commonName := "test.example.com"
|
ecdsaPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
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"},
|
|
||||||
}
|
|
||||||
|
|
||||||
derBytes, err := CreateCertificate(random, &template, &template, &priv.PublicKey, priv)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Failed to create certificate: %s", err)
|
t.Fatalf("Failed to generate ECDSA key: %s", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cert, err := ParseCertificate(derBytes)
|
tests := []struct {
|
||||||
if err != nil {
|
name string
|
||||||
t.Errorf("Failed to parse certificate: %s", err)
|
pub, priv interface{}
|
||||||
return
|
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},
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cert.PolicyIdentifiers) != 1 || !cert.PolicyIdentifiers[0].Equal(template.PolicyIdentifiers[0]) {
|
for _, test := range tests {
|
||||||
t.Errorf("Failed to parse policy identifiers: got:%#v want:%#v", cert.PolicyIdentifiers, template.PolicyIdentifiers)
|
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),
|
||||||
|
|
||||||
if len(cert.PermittedDNSDomains) != 2 || cert.PermittedDNSDomains[0] != ".example.com" || cert.PermittedDNSDomains[1] != "example.com" {
|
SubjectKeyId: []byte{1, 2, 3, 4},
|
||||||
t.Errorf("Failed to parse name constraints: %#v", cert.PermittedDNSDomains)
|
KeyUsage: KeyUsageCertSign,
|
||||||
}
|
|
||||||
|
|
||||||
if cert.Subject.CommonName != commonName {
|
BasicConstraintsValid: true,
|
||||||
t.Errorf("Subject wasn't correctly copied from the template. Got %s, want %s", cert.Subject.CommonName, commonName)
|
IsCA: true,
|
||||||
}
|
DNSNames: []string{"test.example.com"},
|
||||||
|
|
||||||
if cert.Issuer.CommonName != commonName {
|
PolicyIdentifiers: []asn1.ObjectIdentifier{[]int{1, 2, 3}},
|
||||||
t.Errorf("Issuer wasn't correctly copied from the template. Got %s, want %s", cert.Issuer.CommonName, commonName)
|
PermittedDNSDomains: []string{".example.com", "example.com"},
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cert.CheckSignatureFrom(cert)
|
derBytes, err := CreateCertificate(random, &template, &template, test.pub, test.priv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Signature verification failed: %s", err)
|
t.Errorf("%s: failed to create certificate: %s", test.name, err)
|
||||||
return
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := ParseCertificate(derBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: failed to parse certificate: %s", test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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.Subject.CommonName != commonName {
|
||||||
|
t.Errorf("%s: subject wasn't correctly copied from the template. Got %s, want %s", test.name, cert.Subject.CommonName, commonName)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user