diff --git a/ecdh/ecdh.go b/ecdh/ecdh.go index d2c115e..acc4bbe 100644 --- a/ecdh/ecdh.go +++ b/ecdh/ecdh.go @@ -66,6 +66,10 @@ type Curve interface { } // PublicKey is an ECDH public key, usually a peer's ECDH share sent over the wire. +// +// These keys can be parsed with [smx509.ParsePKIXPublicKey] and encoded +// with [smx509.MarshalPKIXPublicKey]. For SM2 curve, it then needs to +// be converted with [sm2.PublicKeyToECDH] after parsing. type PublicKey struct { curve Curve publicKey []byte @@ -129,6 +133,10 @@ func (uv *PublicKey) SM2SharedKey(isResponder bool, kenLen int, sPub, sRemote *P } // PrivateKey is an ECDH private key, usually kept secret. +// +// These keys can be parsed with [smx509.ParsePKCS8PrivateKey] and encoded +// with [smx509.MarshalPKCS8PrivateKey]. For SM2 curve, it then needs to +// be converted with [sm2.PrivateKey.ECDH] after parsing. type PrivateKey struct { curve Curve privateKey []byte diff --git a/sm2/sm2.go b/sm2/sm2.go index daeafd5..68382ac 100644 --- a/sm2/sm2.go +++ b/sm2/sm2.go @@ -22,6 +22,7 @@ import ( "math/big" "strings" + "github.com/emmansun/gmsm/ecdh" "github.com/emmansun/gmsm/internal/randutil" "github.com/emmansun/gmsm/internal/subtle" "github.com/emmansun/gmsm/kdf" @@ -869,3 +870,41 @@ func IsSM2PublicKey(publicKey interface{}) bool { func P256() elliptic.Curve { return sm2ec.P256() } + +// PublicKeyToECDH returns k as a [ecdh.PublicKey]. It returns an error if the key is +// invalid according to the definition of [ecdh.Curve.NewPublicKey], or if the +// Curve is not supported by ecdh. +func PublicKeyToECDH(k *ecdsa.PublicKey) (*ecdh.PublicKey, error) { + c := curveToECDH(k.Curve) + if c == nil { + return nil, errors.New("sm2: unsupported curve by ecdh") + } + if !k.Curve.IsOnCurve(k.X, k.Y) { + return nil, errors.New("sm2: invalid public key") + } + return c.NewPublicKey(elliptic.Marshal(k.Curve, k.X, k.Y)) +} + +// ECDH returns k as a [ecdh.PrivateKey]. It returns an error if the key is +// invalid according to the definition of [ecdh.Curve.NewPrivateKey], or if the +// Curve is not supported by ecdh. +func (k *PrivateKey) ECDH() (*ecdh.PrivateKey, error) { + c := curveToECDH(k.Curve) + if c == nil { + return nil, errors.New("sm2: unsupported curve by ecdh") + } + size := (k.Curve.Params().N.BitLen() + 7) / 8 + if k.D.BitLen() > size*8 { + return nil, errors.New("sm2: invalid private key") + } + return c.NewPrivateKey(k.D.FillBytes(make([]byte, size))) +} + +func curveToECDH(c elliptic.Curve) ecdh.Curve { + switch c { + case sm2ec.P256(): + return ecdh.P256() + default: + return nil + } +} diff --git a/smx509/pkcs8.go b/smx509/pkcs8.go index cda03f4..0ce6bd6 100644 --- a/smx509/pkcs8.go +++ b/smx509/pkcs8.go @@ -7,6 +7,7 @@ import ( "encoding/asn1" "errors" + "github.com/emmansun/gmsm/ecdh" "github.com/emmansun/gmsm/sm2" "github.com/emmansun/gmsm/sm9" ) @@ -29,7 +30,7 @@ type pkcs8 struct { // ParsePKCS8PrivateKey parses an unencrypted private key in PKCS #8, ASN.1 DER form. // -// It returns a *rsa.PrivateKey, a *ecdsa.PrivateKey, a *sm2.PrivateKey, a *sm9.SignMasterPrivateKey, +// It returns a *rsa.PrivateKey, a *ecdsa.PrivateKey, a *sm2.PrivateKey, a *sm9.SignMasterPrivateKey, // a *sm9.SignPrivateKey, a *sm9.EncryptMasterPrivateKey, a *sm9.EncryptPrivateKey or a ed25519.PrivateKey. // More types might be supported in the future. // @@ -117,8 +118,8 @@ func parseSM9PrivateKey(privKey pkcs8) (key interface{}, err error) { // MarshalPKCS8PrivateKey converts a private key to PKCS #8, ASN.1 DER form. // -// The following key types are currently supported: *rsa.PrivateKey, *ecdsa.PrivateKey, a *sm2.PrivateKey, -// a *sm9.SignMasterPrivateKey, a *sm9.SignPrivateKey, a *sm9.EncryptMasterPrivateKey, a *sm9.EncryptPrivateKey +// The following key types are currently supported: *rsa.PrivateKey, *ecdsa.PrivateKey, a *sm2.PrivateKey, a *ecdh.PrivateKey +// a *sm9.SignMasterPrivateKey, a *sm9.SignPrivateKey, a *sm9.EncryptMasterPrivateKey, a *sm9.EncryptPrivateKey // and ed25519.PrivateKey. Unsupported key types result in an error. // // This kind of key is commonly encoded in PEM blocks of type "PRIVATE KEY". @@ -126,6 +127,8 @@ func MarshalPKCS8PrivateKey(key interface{}) ([]byte, error) { switch k := key.(type) { case *sm2.PrivateKey: return marshalPKCS8ECPrivateKey(&k.PrivateKey) + case *ecdh.PrivateKey: + return marshalPKCS8ECDHPrivateKey(k) case *sm9.SignPrivateKey: return marshalPKCS8SM9SignPrivateKey(k) case *sm9.EncryptPrivateKey: @@ -280,3 +283,25 @@ func marshalPKCS8ECPrivateKey(k *ecdsa.PrivateKey) ([]byte, error) { } return asn1.Marshal(privKey) } + +func marshalPKCS8ECDHPrivateKey(k *ecdh.PrivateKey) ([]byte, error) { + var privKey pkcs8 + oid, ok := oidFromECDHCurve(k.Curve()) + if !ok { + return nil, errors.New("x509: unknown curve while marshaling 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 = marshalECDHPrivateKey(k); err != nil { + return nil, errors.New("x509: failed to marshal EC private key while building PKCS#8: " + err.Error()) + } + return asn1.Marshal(privKey) +} diff --git a/smx509/pkcs8_test.go b/smx509/pkcs8_test.go index e297a6c..84da81b 100644 --- a/smx509/pkcs8_test.go +++ b/smx509/pkcs8_test.go @@ -124,6 +124,22 @@ func TestPKCS8(t *testing.T) { t.Errorf("%s: marshaled PKCS#8 didn't match original: got %x, want %x", test.name, reserialised, derBytes) continue } + if ecKey, isEC := privKey.(*sm2.PrivateKey); isEC { + ecdhKey, err := ecKey.ECDH() + if err != nil { + t.Errorf("%s: failed to convert to ecdh: %s", test.name, err) + continue + } + reserialised, err := MarshalPKCS8PrivateKey(ecdhKey) + 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: marshaled PKCS#8 didn't match original: got %x, want %x", test.name, reserialised, derBytes) + continue + } + } } } diff --git a/smx509/sec1.go b/smx509/sec1.go index 5e75e46..503388d 100644 --- a/smx509/sec1.go +++ b/smx509/sec1.go @@ -8,6 +8,7 @@ import ( "fmt" "math/big" + "github.com/emmansun/gmsm/ecdh" "github.com/emmansun/gmsm/sm2" ) @@ -133,3 +134,13 @@ func parseECPrivateKey(namedCurveOID *asn1.ObjectIdentifier, der []byte) (key *e return priv, nil } + +// marshalECDHPrivateKey marshals an EC private key into ASN.1, DER format +// suitable for SM2 curve. +func marshalECDHPrivateKey(key *ecdh.PrivateKey) ([]byte, error) { + return asn1.Marshal(ecPrivateKey{ + Version: 1, + PrivateKey: key.Bytes(), + PublicKey: asn1.BitString{Bytes: key.PublicKey().Bytes()}, + }) +} diff --git a/smx509/verify.go b/smx509/verify.go index 4abfecb..7d582c0 100644 --- a/smx509/verify.go +++ b/smx509/verify.go @@ -637,7 +637,7 @@ func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err e for i := 0; i < opts.Intermediates.len(); i++ { c, err := opts.Intermediates.cert(i) if err != nil { - return nil, fmt.Errorf("crypto/x509: error fetching intermediate: %w", err) + return nil, fmt.Errorf("x509: error fetching intermediate: %w", err) } if len(c.Raw) == 0 { return nil, errNotParsed diff --git a/smx509/x509.go b/smx509/x509.go index 115889e..77ef460 100644 --- a/smx509/x509.go +++ b/smx509/x509.go @@ -49,6 +49,7 @@ import ( "golang.org/x/crypto/cryptobyte" cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1" + "github.com/emmansun/gmsm/ecdh" "github.com/emmansun/gmsm/internal/godebug" "github.com/emmansun/gmsm/sm2" ) @@ -114,6 +115,20 @@ func marshalPublicKey(pub interface{}) (publicKeyBytes []byte, publicKeyAlgorith case ed25519.PublicKey: publicKeyBytes = pub publicKeyAlgorithm.Algorithm = oidPublicKeyEd25519 + case *ecdh.PublicKey: //TODO:will add SDK ECDH public key support from golang 1.19 later. + publicKeyBytes = pub.Bytes() + + oid, ok := oidFromECDHCurve(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 default: return nil, pkix.AlgorithmIdentifier{}, fmt.Errorf("x509: unsupported public key type: %T", pub) } @@ -519,6 +534,15 @@ func oidFromNamedCurve(curve elliptic.Curve) (asn1.ObjectIdentifier, bool) { return nil, false } +func oidFromECDHCurve(curve ecdh.Curve) (asn1.ObjectIdentifier, bool) { + switch curve { + case ecdh.P256(): + return oidNamedCurveP256SM2, 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