diff --git a/smx509/parser.go b/smx509/parser.go index 0e9e9f0..c412d57 100644 --- a/smx509/parser.go +++ b/smx509/parser.go @@ -3,6 +3,7 @@ package smx509 import ( "bytes" "crypto/dsa" + sdkecdh "crypto/ecdh" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" @@ -338,14 +339,13 @@ func parsePublicKey(keyData *publicKeyInfo) (any, error) { return nil, errors.New("x509: wrong Ed25519 public key size") } return ed25519.PublicKey(der), nil - //TODO: will enable it since golang 1.19.x - //case oid.Equal(oidPublicKeyX25519): - // RFC 8410, Section 3 - // > For all of the OIDs, the parameters MUST be absent. - // if len(params.FullBytes) != 0 { - // return nil, errors.New("x509: X25519 key encoded with illegal parameters") - // } - // return ecdh.X25519().NewPublicKey(der) + case oid.Equal(oidPublicKeyX25519): + // RFC 8410, Section 3 + // > For all of the OIDs, the parameters MUST be absent. + if len(params.FullBytes) != 0 { + return nil, errors.New("x509: X25519 key encoded with illegal parameters") + } + return sdkecdh.X25519().NewPublicKey(der) case oid.Equal(oidPublicKeyDSA): y := new(big.Int) if !der.ReadASN1Integer(y) { diff --git a/smx509/pkcs8.go b/smx509/pkcs8.go index b2a121c..062cb73 100644 --- a/smx509/pkcs8.go +++ b/smx509/pkcs8.go @@ -61,8 +61,10 @@ func ParsePKCS8PrivateKey(der []byte) (key any, err error) { return nil, errors.New("x509: unsupported SM2 curve") } return new(sm2.PrivateKey).FromECPrivateKey(ecKey) + case privKey.Algo.Algorithm.Equal(oidSM9), privKey.Algo.Algorithm.Equal(oidSM9Sign), privKey.Algo.Algorithm.Equal(oidSM9Enc): return parseSM9PrivateKey(privKey) + case privKey.Algo.Algorithm.Equal(oidPublicKeyECDSA): bytes := privKey.Algo.Parameters.FullBytes namedCurveOID := new(asn1.ObjectIdentifier) @@ -78,6 +80,7 @@ func ParsePKCS8PrivateKey(der []byte) (key any, err error) { return new(sm2.PrivateKey).FromECPrivateKey(ecKey) } return ecKey, err + default: // fallback to golang sdk return x509.ParsePKCS8PrivateKey(der) diff --git a/smx509/pkcs8_test.go b/smx509/pkcs8_test.go index 37e9611..d143072 100644 --- a/smx509/pkcs8_test.go +++ b/smx509/pkcs8_test.go @@ -2,6 +2,7 @@ package smx509 import ( "bytes" + sdkecdh "crypto/ecdh" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" @@ -55,6 +56,11 @@ var pkcs8P521PrivateKeyHex = `3081ee020100301006072a8648ce3d020106052b8104002304 // From RFC 8410, Section 7. var pkcs8Ed25519PrivateKeyHex = `302e020100300506032b657004220420d4ee72dbf913584ad5b6d8f1f769f8ad3afe7c28cbf1d4fbe097a88f44755842` +// Generated using: +// +// openssl genpkey -algorithm x25519 +var pkcs8X25519PrivateKeyHex = `302e020100300506032b656e0422042068ff93a73c5adefd6d498b24e588fd4daa10924d992afed01b43ca5725025a6b` + func TestPKCS8(t *testing.T) { tests := []struct { name string @@ -108,6 +114,11 @@ func TestPKCS8(t *testing.T) { keyHex: pkcs8Ed25519PrivateKeyHex, keyType: reflect.TypeOf(ed25519.PrivateKey{}), }, + { + name: "X25519 private key", + keyHex: pkcs8X25519PrivateKeyHex, + keyType: reflect.TypeOf(&sdkecdh.PrivateKey{}), + }, } for _, test := range tests { diff --git a/smx509/x509.go b/smx509/x509.go index cc09bbe..f905df0 100644 --- a/smx509/x509.go +++ b/smx509/x509.go @@ -22,6 +22,7 @@ package smx509 import ( "bytes" "crypto" + sdkecdh "crypto/ecdh" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" @@ -66,8 +67,9 @@ type pkixPublicKey struct { // The encoded public key is a SubjectPublicKeyInfo structure // (see RFC 5280, Section 4.1). // -// It returns a *rsa.PublicKey, *dsa.PublicKey, *ecdsa.PublicKey, or -// ed25519.PublicKey. More types might be supported in the future. +// It returns a *[rsa.PublicKey], *[dsa.PublicKey], *[ecdsa.PublicKey], +// [ed25519.PublicKey] (not a pointer), or *[ecdh.PublicKey] (for X25519). +// More types might be supported in the future. // // This kind of key is commonly encoded in PEM blocks of type "PUBLIC KEY". func ParsePKIXPublicKey(derBytes []byte) (any, error) { @@ -116,9 +118,25 @@ func marshalPublicKey(pub any) (publicKeyBytes []byte, publicKeyAlgorithm pkix.A case ed25519.PublicKey: publicKeyBytes = pub publicKeyAlgorithm.Algorithm = oidPublicKeyEd25519 - case *ecdh.PublicKey: //TODO:will add SDK ECDH public key support from golang 1.19 later. + case *sdkecdh.PublicKey: + publicKeyBytes = pub.Bytes() + if pub.Curve() == sdkecdh.X25519() { + publicKeyAlgorithm.Algorithm = oidPublicKeyX25519 + } else { + oid, ok := oidFromSDKECDHCurve(pub.Curve()) + if !ok { + return nil, pkix.AlgorithmIdentifier{}, errors.New("x509: unsupported elliptic curve") + } + publicKeyAlgorithm.Algorithm = oidPublicKeyECDSA + var paramBytes []byte + paramBytes, err = asn1.Marshal(oid) + if err != nil { + return + } + publicKeyAlgorithm.Parameters.FullBytes = paramBytes + } + case *ecdh.PublicKey: publicKeyBytes = pub.Bytes() - oid, ok := oidFromECDHCurve(pub.Curve()) if !ok { return nil, pkix.AlgorithmIdentifier{}, errors.New("x509: unsupported elliptic curve") @@ -562,6 +580,21 @@ func oidFromECDHCurve(curve ecdh.Curve) (asn1.ObjectIdentifier, bool) { return nil, false } +func oidFromSDKECDHCurve(curve sdkecdh.Curve) (asn1.ObjectIdentifier, bool) { + switch curve { + case sdkecdh.X25519(): + return oidPublicKeyX25519, true + case sdkecdh.P256(): + return oidNamedCurveP256, true + case sdkecdh.P384(): + return oidNamedCurveP384, true + case sdkecdh.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 = x509.KeyUsage diff --git a/smx509/x509_test.go b/smx509/x509_test.go index db12552..c2a2287 100644 --- a/smx509/x509_test.go +++ b/smx509/x509_test.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto" "crypto/dsa" + sdkecdh "crypto/ecdh" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" @@ -75,6 +76,13 @@ func TestParsePKIXPublicKey(t *testing.T) { t.Errorf("Value returned from ParsePKIXPublicKey was not an Ed25519 public key") } }) + t.Run("X25519", func(t *testing.T) { + pub := testParsePKIXPublicKey(t, pemX25519Key) + k, ok := pub.(*sdkecdh.PublicKey) + if !ok || k.Curve() != sdkecdh.X25519() { + t.Errorf("Value returned from ParsePKIXPublicKey was not an X25519 public key") + } + }) } var pemPublicKey = `-----BEGIN PUBLIC KEY----- @@ -113,6 +121,13 @@ MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE= -----END PUBLIC KEY----- ` +// pemX25519Key was generated from pemX25519Key with "openssl pkey -pubout". +var pemX25519Key = ` +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VuAyEA5yGXrH/6OzxuWEhEWS01/f4OP+Of3Yrddy6/J1kDTVM= +-----END PUBLIC KEY----- +` + func TestPKIXMismatchPublicKeyFormat(t *testing.T) { const pkcs1PublicKey = "308201080282010100817cfed98bcaa2e2a57087451c7674e0c675686dc33ff1268b0c2a6ee0202dec710858ee1c31bdf5e7783582e8ca800be45f3275c6576adc35d98e26e95bb88ca5beb186f853b8745d88bc9102c5f38753bcda519fb05948d5c77ac429255ff8aaf27d9f45d1586e95e2e9ba8a7cb771b8a09dd8c8fed3f933fd9b439bc9f30c475953418ef25f71a2b6496f53d94d39ce850aa0cc75d445b5f5b4f4ee4db78ab197a9a8d8a852f44529a007ac0ac23d895928d60ba538b16b0b087a7f903ed29770e215019b77eaecc360f35f7ab11b6d735978795b2c4a74e5bdea4dc6594cd67ed752a108e666729a753ab36d6c4f606f8760f507e1765be8cd744007e629020103" @@ -2464,7 +2479,7 @@ func TestCreateRevocationList(t *testing.T) { ThisUpdate: time.Time{}.Add(time.Hour * 24), NextUpdate: time.Time{}.Add(time.Hour * 48), }, - }, + }, { name: "valid, Ed25519 key", key: ed25519Priv, @@ -2676,7 +2691,7 @@ func TestCreateRevocationList(t *testing.T) { t.Fatalf("Unexpected second extension: got %v, want %v", parsedCRL.Extensions[1], crlExt) } - // With Go 1.19's updated RevocationList, we can now directly compare + // With Go 1.19's updated RevocationList, we can now directly compare // the RawSubject of the certificate to RawIssuer on the parsed CRL. // However, this doesn't work with our hacked issuers above (that // aren't parsed from a proper DER bundle but are instead manually