]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/x509: add MarshalPKCS8PrivateKey
authorJames Hartig <fastest963@gmail.com>
Fri, 14 Apr 2017 04:53:09 +0000 (00:53 -0400)
committerAdam Langley <agl@golang.org>
Tue, 15 Aug 2017 18:43:32 +0000 (18:43 +0000)
asn1.NullRawValue was used according to RFC 3279 2.2.1. Without this tag,
the output didn't match openssl.

Fixes #19972

Change-Id: Ia52ddb810888837f913dbd65c4e1328f6c8084bb
Reviewed-on: https://go-review.googlesource.com/40730
Reviewed-by: Adam Langley <agl@golang.org>
Run-TryBot: Adam Langley <agl@golang.org>
Run-TryBot: Emmanuel Odeke <emm.odeke@gmail.com>
Run-TryBot: Ian Lance Taylor <iant@golang.org>

src/crypto/x509/pkcs8.go
src/crypto/x509/pkcs8_test.go
src/crypto/x509/sec1.go

index b304a3f63c93dc2ff3d351d28321cdffcc28b8ee..eb051b6e114f4792c22b2bedc6a6499fc74850c8 100644 (file)
@@ -5,6 +5,8 @@
 package x509
 
 import (
+       "crypto/ecdsa"
+       "crypto/rsa"
        "crypto/x509/pkix"
        "encoding/asn1"
        "errors"
@@ -52,3 +54,48 @@ func ParsePKCS8PrivateKey(der []byte) (key interface{}, err error) {
                return nil, fmt.Errorf("x509: PKCS#8 wrapping contained private key with unknown algorithm: %v", privKey.Algo.Algorithm)
        }
 }
+
+// MarshalPKCS8PrivateKey converts a private key to PKCS#8 encoded form.
+// The following key types are supported: *rsa.PrivateKey, *ecdsa.PublicKey.
+// Unsupported key types result in an error.
+//
+// See RFC 5208.
+func MarshalPKCS8PrivateKey(key interface{}) ([]byte, error) {
+       var privKey pkcs8
+
+       switch k := key.(type) {
+       case *rsa.PrivateKey:
+               privKey.Algo = pkix.AlgorithmIdentifier{
+                       Algorithm:  oidPublicKeyRSA,
+                       Parameters: asn1.NullRawValue,
+               }
+               privKey.PrivateKey = MarshalPKCS1PrivateKey(k)
+
+       case *ecdsa.PrivateKey:
+               oid, ok := oidFromNamedCurve(k.Curve)
+               if !ok {
+                       return nil, errors.New("x509: unknown curve while marshalling to PKCS#8")
+               }
+
+               oidBytes, err := asn1.Marshal(oid)
+               if err != nil {
+                       return nil, errors.New("x509: failed to marshal curve OID: " + err.Error())
+               }
+
+               privKey.Algo = pkix.AlgorithmIdentifier{
+                       Algorithm: oidPublicKeyECDSA,
+                       Parameters: asn1.RawValue{
+                               FullBytes: oidBytes,
+                       },
+               }
+
+               if privKey.PrivateKey, err = marshalECPrivateKeyWithOID(k, nil); err != nil {
+                       return nil, errors.New("x509: failed to marshal EC private key while building PKCS#8: " + err.Error())
+               }
+
+       default:
+               return nil, fmt.Errorf("x509: unknown key type while marshalling PKCS#8: %T", key)
+       }
+
+       return asn1.Marshal(privKey)
+}
index 4114efd0e0d1ad24c5d3aa736a37b92c3009d18c..c8f11e64d12ebf809eceee259850fdb9d5ec923b 100644 (file)
 package x509
 
 import (
+       "bytes"
+       "crypto/ecdsa"
+       "crypto/elliptic"
+       "crypto/rsa"
        "encoding/hex"
+       "reflect"
        "testing"
 )
 
+// Generated using:
+//   openssl genrsa 1024 | openssl pkcs8 -topk8 -nocrypt
 var pkcs8RSAPrivateKeyHex = `30820278020100300d06092a864886f70d0101010500048202623082025e02010002818100cfb1b5bf9685ffa97b4f99df4ff122b70e59ac9b992f3bc2b3dde17d53c1a34928719b02e8fd17839499bfbd515bd6ef99c7a1c47a239718fe36bfd824c0d96060084b5f67f0273443007a24dfaf5634f7772c9346e10eb294c2306671a5a5e719ae24b4de467291bc571014b0e02dec04534d66a9bb171d644b66b091780e8d020301000102818100b595778383c4afdbab95d2bfed12b3f93bb0a73a7ad952f44d7185fd9ec6c34de8f03a48770f2009c8580bcd275e9632714e9a5e3f32f29dc55474b2329ff0ebc08b3ffcb35bc96e6516b483df80a4a59cceb71918cbabf91564e64a39d7e35dce21cb3031824fdbc845dba6458852ec16af5dddf51a8397a8797ae0337b1439024100ea0eb1b914158c70db39031dd8904d6f18f408c85fbbc592d7d20dee7986969efbda081fdf8bc40e1b1336d6b638110c836bfdc3f314560d2e49cd4fbde1e20b024100e32a4e793b574c9c4a94c8803db5152141e72d03de64e54ef2c8ed104988ca780cd11397bc359630d01b97ebd87067c5451ba777cf045ca23f5912f1031308c702406dfcdbbd5a57c9f85abc4edf9e9e29153507b07ce0a7ef6f52e60dcfebe1b8341babd8b789a837485da6c8d55b29bbb142ace3c24a1f5b54b454d01b51e2ad03024100bd6a2b60dee01e1b3bfcef6a2f09ed027c273cdbbaf6ba55a80f6dcc64e4509ee560f84b4f3e076bd03b11e42fe71a3fdd2dffe7e0902c8584f8cad877cdc945024100aa512fa4ada69881f1d8bb8ad6614f192b83200aef5edf4811313d5ef30a86cbd0a90f7b025c71ea06ec6b34db6306c86b1040670fd8654ad7291d066d06d031`
 
+// Generated using:
+//   openssl ecparam -genkey -name secp224r1 | openssl pkcs8 -topk8 -nocrypt
+var pkcs8P224PrivateKeyHex = `3078020100301006072a8648ce3d020106052b810400210461305f020101041cca3d72b3e88fed2684576dad9b80a9180363a5424986900e3abcab3fa13c033a0004f8f2a6372872a4e61263ed893afb919576a4cacfecd6c081a2cbc76873cf4ba8530703c6042b3a00e2205087e87d2435d2e339e25702fae1`
+
+// Generated using:
+//   openssl ecparam -genkey -name secp256r1 | openssl pkcs8 -topk8 -nocrypt
+var pkcs8P256PrivateKeyHex = `308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b0201010420dad6b2f49ca774c36d8ae9517e935226f667c929498f0343d2424d0b9b591b43a14403420004b9c9b90095476afe7b860d8bd43568cab7bcb2eed7b8bf2fa0ce1762dd20b04193f859d2d782b1e4cbfd48492f1f533113a6804903f292258513837f07fda735`
+
+// Generated using:
+//   openssl ecparam -genkey -name secp384r1 | openssl pkcs8 -topk8 -nocrypt
+var pkcs8P384PrivateKeyHex = `3081b6020100301006072a8648ce3d020106052b8104002204819e30819b02010104309bf832f6aaaeacb78ce47ffb15e6fd0fd48683ae79df6eca39bfb8e33829ac94aa29d08911568684c2264a08a4ceb679a164036200049070ad4ed993c7770d700e9f6dc2baa83f63dd165b5507f98e8ff29b5d2e78ccbe05c8ddc955dbf0f7497e8222cfa49314fe4e269459f8e880147f70d785e530f2939e4bf9f838325bb1a80ad4cf59272ae0e5efe9a9dc33d874492596304bd3`
+
 // Generated using:
 //   openssl ecparam -genkey -name secp521r1 | openssl pkcs8 -topk8 -nocrypt
-var pkcs8ECPrivateKeyHex = `3081ed020100301006072a8648ce3d020106052b810400230481d53081d20201010441850d81618c5da1aec74c2eed608ba816038506975e6427237c2def150c96a3b13efbfa1f89f1be15cdf4d0ac26422e680e65a0ddd4ad3541ad76165fbf54d6e34ba18189038186000400da97bcedba1eb6d30aeb93c9f9a1454598fa47278df27d6f60ea73eb672d8dc528a9b67885b5b5dcef93c9824f7449ab512ee6a27e76142f56b94b474cfd697e810046c8ca70419365245c1d7d44d0db82c334073835d002232714548abbae6e5700f5ef315ee08b929d8581383dcf2d1c98c2f8a9fccbf79c9579f7b2fd8a90115ac2`
+//
+// Note that OpenSSL will truncate the private key if it can (i.e. it emits it
+// like an integer, even though it's an OCTET STRING field). Thus if you
+// regenerate this you may, randomly, find that it's a byte shorter than
+// expected and the Go test will fail to recreate it exactly.
+var pkcs8P521PrivateKeyHex = `3081ee020100301006072a8648ce3d020106052b810400230481d63081d3020101044200cfe0b87113a205cf291bb9a8cd1a74ac6c7b2ebb8199aaa9a5010d8b8012276fa3c22ac913369fa61beec2a3b8b4516bc049bde4fb3b745ac11b56ab23ac52e361a1818903818600040138f75acdd03fbafa4f047a8e4b272ba9d555c667962b76f6f232911a5786a0964e5edea6bd21a6f8725720958de049c6e3e6661c1c91b227cebee916c0319ed6ca003db0a3206d372229baf9dd25d868bf81140a518114803ce40c1855074d68c4e9dab9e65efba7064c703b400f1767f217dac82715ac1f6d88c74baf47a7971de4ea`
 
 func TestPKCS8(t *testing.T) {
-       derBytes, _ := hex.DecodeString(pkcs8RSAPrivateKeyHex)
-       if _, err := ParsePKCS8PrivateKey(derBytes); err != nil {
-               t.Errorf("failed to decode PKCS8 with RSA private key: %s", err)
+       tests := []struct {
+               name    string
+               keyHex  string
+               keyType reflect.Type
+               curve   elliptic.Curve
+       }{
+               {
+                       name:    "RSA private key",
+                       keyHex:  pkcs8RSAPrivateKeyHex,
+                       keyType: reflect.TypeOf(&rsa.PrivateKey{}),
+               },
+               {
+                       name:    "P-224 private key",
+                       keyHex:  pkcs8P224PrivateKeyHex,
+                       keyType: reflect.TypeOf(&ecdsa.PrivateKey{}),
+                       curve:   elliptic.P224(),
+               },
+               {
+                       name:    "P-256 private key",
+                       keyHex:  pkcs8P256PrivateKeyHex,
+                       keyType: reflect.TypeOf(&ecdsa.PrivateKey{}),
+                       curve:   elliptic.P256(),
+               },
+               {
+                       name:    "P-384 private key",
+                       keyHex:  pkcs8P384PrivateKeyHex,
+                       keyType: reflect.TypeOf(&ecdsa.PrivateKey{}),
+                       curve:   elliptic.P384(),
+               },
+               {
+                       name:    "P-521 private key",
+                       keyHex:  pkcs8P521PrivateKeyHex,
+                       keyType: reflect.TypeOf(&ecdsa.PrivateKey{}),
+                       curve:   elliptic.P521(),
+               },
        }
 
-       derBytes, _ = hex.DecodeString(pkcs8ECPrivateKeyHex)
-       if _, err := ParsePKCS8PrivateKey(derBytes); err != nil {
-               t.Errorf("failed to decode PKCS8 with EC private key: %s", err)
+       for _, test := range tests {
+               derBytes, err := hex.DecodeString(test.keyHex)
+               if err != nil {
+                       t.Errorf("%s: failed to decode hex: %s", test.name, err)
+                       continue
+               }
+               privKey, err := ParsePKCS8PrivateKey(derBytes)
+               if err != nil {
+                       t.Errorf("%s: failed to decode PKCS#8: %s", test.name, err)
+                       continue
+               }
+               if reflect.TypeOf(privKey) != test.keyType {
+                       t.Errorf("%s: decoded PKCS#8 returned unexpected key type: %T", test.name, privKey)
+                       continue
+               }
+               if ecKey, isEC := privKey.(*ecdsa.PrivateKey); isEC && ecKey.Curve != test.curve {
+                       t.Errorf("%s: decoded PKCS#8 returned unexpected curve %#v", test.name, ecKey.Curve)
+                       continue
+               }
+               reserialised, err := MarshalPKCS8PrivateKey(privKey)
+               if err != nil {
+                       t.Errorf("%s: failed to marshal into PKCS#8: %s", test.name, err)
+                       continue
+               }
+               if !bytes.Equal(derBytes, reserialised) {
+                       t.Errorf("%s: marshalled PKCS#8 didn't match original: got %x, want %x", test.name, reserialised, derBytes)
+                       continue
+               }
        }
 }
index 33f376c072cf577d59b69b46a2782a176c939e25..3008d0df773fcc34814d7913846acac798c6632b 100644 (file)
@@ -40,6 +40,12 @@ func MarshalECPrivateKey(key *ecdsa.PrivateKey) ([]byte, error) {
                return nil, errors.New("x509: unknown elliptic curve")
        }
 
+       return marshalECPrivateKeyWithOID(key, oid)
+}
+
+// marshalECPrivateKey marshals an EC private key into ASN.1, DER format and
+// sets the curve ID to the given OID, or omits it if OID is nil.
+func marshalECPrivateKeyWithOID(key *ecdsa.PrivateKey, oid asn1.ObjectIdentifier) ([]byte, error) {
        privateKeyBytes := key.D.Bytes()
        paddedPrivateKey := make([]byte, (key.Curve.Params().N.BitLen()+7)/8)
        copy(paddedPrivateKey[len(paddedPrivateKey)-len(privateKeyBytes):], privateKeyBytes)