From ebf55ebb37b299650a6ca0f287e192308933eb8c Mon Sep 17 00:00:00 2001 From: Dmitriy Kinoshenko Date: Tue, 31 Mar 2020 11:01:05 +0300 Subject: [PATCH] refactor: Support JwsVerificationKey2020 for DID public key closes #1527, #1513 Signed-off-by: Dmitriy Kinoshenko --- pkg/doc/did/doc.go | 80 ++++-- pkg/doc/jose/jwk.go | 252 +++++++++++++----- pkg/doc/jose/jwk_test.go | 136 ++++++++-- .../public_key_verifier.go | 42 ++- .../public_key_verifier_test.go | 106 ++++++-- pkg/doc/signature/verifier/verifier.go | 7 +- pkg/doc/verifiable/credential_ldp_test.go | 13 +- 7 files changed, 500 insertions(+), 136 deletions(-) diff --git a/pkg/doc/did/doc.go b/pkg/doc/did/doc.go index 7786fac314..0af6a5ac8a 100644 --- a/pkg/doc/did/doc.go +++ b/pkg/doc/did/doc.go @@ -112,7 +112,12 @@ type PublicKey struct { ID string Type string Controller string - Value []byte + + Value []byte + + Algorithm string `json:"-"` + Curve string `json:"-"` + KeyType string `json:"-"` } // Service DID doc service @@ -347,57 +352,83 @@ func populatePublicKeys(context string, rawPKs []map[string]interface{}) ([]Publ var publicKeys []PublicKey for _, rawPK := range rawPKs { - decodeValue, err := decodePK(rawPK) - if err != nil { - return nil, err - } - controllerKey := jsonldController if context == contextV011 { controllerKey = jsonldOwner } - publicKeys = append(publicKeys, PublicKey{ID: stringEntry(rawPK[jsonldID]), Type: stringEntry(rawPK[jsonldType]), - Controller: stringEntry(rawPK[controllerKey]), Value: decodeValue}) + publicKey := PublicKey{ID: stringEntry(rawPK[jsonldID]), Type: stringEntry(rawPK[jsonldType]), + Controller: stringEntry(rawPK[controllerKey])} + + err := decodePK(&publicKey, rawPK) + if err != nil { + return nil, err + } + + publicKeys = append(publicKeys, publicKey) } return publicKeys, nil } -func decodePK(rawPK map[string]interface{}) ([]byte, error) { +func decodePK(publicKey *PublicKey, rawPK map[string]interface{}) error { if stringEntry(rawPK[jsonldPublicKeyBase58]) != "" { - return base58.Decode(stringEntry(rawPK[jsonldPublicKeyBase58])), nil + publicKey.Value = base58.Decode(stringEntry(rawPK[jsonldPublicKeyBase58])) + return nil } if stringEntry(rawPK[jsonldPublicKeyHex]) != "" { value, err := hex.DecodeString(stringEntry(rawPK[jsonldPublicKeyHex])) if err != nil { - return nil, fmt.Errorf("decode public key hex failed: %w", err) + return fmt.Errorf("decode public key hex failed: %w", err) } - return value, nil + publicKey.Value = value + + return nil } if stringEntry(rawPK[jsonldPublicKeyPem]) != "" { block, _ := pem.Decode([]byte(stringEntry(rawPK[jsonldPublicKeyPem]))) if block == nil { - return nil, errors.New("failed to decode PEM block containing public key") + return errors.New("failed to decode PEM block containing public key") } - return block.Bytes, nil + publicKey.Value = block.Bytes + + return nil } if jwkMap := mapEntry(rawPK[jsonldPublicKeyjwk]); jwkMap != nil { + jwkMap := mapEntry(rawPK[jsonldPublicKeyjwk]) + jwkBytes, err := json.Marshal(jwkMap) if err != nil { - return nil, fmt.Errorf("failed to marshal '%s', cause: %w ", jsonldPublicKeyjwk, err) + return fmt.Errorf("failed to marshal '%s', cause: %w ", jsonldPublicKeyjwk, err) } - return jose.DecodePublicKey(jwkBytes) + var jwk jose.JWK + + err = json.Unmarshal(jwkBytes, &jwk) + if err != nil { + return fmt.Errorf("unmarshal JWK: %w", err) + } + + pkBytes, err := jwk.PublicKeyBytes() + if err != nil { + return fmt.Errorf("failed to decode public key from JWK: %w", err) + } + + publicKey.Value = pkBytes + publicKey.Algorithm = jwk.Algorithm + publicKey.KeyType = jwk.Kty + publicKey.Curve = jwk.Crv + + return nil } - return nil, errors.New("public key encoding not supported") + return errors.New("public key encoding not supported") } func (r *rawDoc) ParseContext() []string { @@ -583,8 +614,11 @@ func (r *didKeyResolver) Resolve(id string) (*verifier.PublicKey, error) { for _, key := range r.PubKeys { if key.ID == id { return &verifier.PublicKey{ - Type: key.Type, - Value: key.Value, + Type: key.Type, + Value: key.Value, + Curve: key.Curve, + Alg: key.Algorithm, + KeyType: key.KeyType, }, nil } } @@ -620,14 +654,14 @@ func populateRawServices(services []Service) []map[string]interface{} { func populateRawPublicKeys(context string, pks []PublicKey) []map[string]interface{} { var rawPKs []map[string]interface{} - for _, pk := range pks { - rawPKs = append(rawPKs, populateRawPublicKey(context, pk)) + for i := range pks { + rawPKs = append(rawPKs, populateRawPublicKey(context, &pks[i])) } return rawPKs } -func populateRawPublicKey(context string, pk PublicKey) map[string]interface{} { +func populateRawPublicKey(context string, pk *PublicKey) map[string]interface{} { rawPK := make(map[string]interface{}) rawPK[jsonldID] = pk.ID rawPK[jsonldType] = pk.Type @@ -649,7 +683,7 @@ func populateRawAuthentications(context string, vms []VerificationMethod) []inte var rawAuthentications []interface{} for _, vm := range vms { - rawAuthentications = append(rawAuthentications, populateRawPublicKey(context, vm.PublicKey)) + rawAuthentications = append(rawAuthentications, populateRawPublicKey(context, &vm.PublicKey)) } return rawAuthentications diff --git a/pkg/doc/jose/jwk.go b/pkg/doc/jose/jwk.go index eb2dae8a76..ff3a9a0c16 100644 --- a/pkg/doc/jose/jwk.go +++ b/pkg/doc/jose/jwk.go @@ -24,101 +24,211 @@ import ( ) const ( - secp256k1Crv = "secp256k1" - secp256k1Kty = "EC" - bitsPerByte = 8 + secp256k1Alg = "ES256K" + secp256k1Crv = "secp256k1" + secp256k1Kty = "EC" + secp256k1Size = 32 + bitsPerByte = 8 ) // JWK (JSON Web Key) is a JSON data structure that represents a cryptographic key. -type JWK jose.JSONWebKey +type JWK struct { + jose.JSONWebKey -// JWK gets JWK from JOSE headers. -func (h Headers) JWK() (*JWK, bool) { - jwkRaw, ok := h[HeaderJSONWebKey] - if !ok { - return nil, false - } - - var jwk JWK + x *big.Int + y *big.Int + Kty string + Crv string +} - err := convertMapToValue(jwkRaw, &jwk) - if err != nil { - return nil, false +// PublicKeyBytes converts a public key to bytes. +func (j *JWK) PublicKeyBytes() ([]byte, error) { + if isSecp256k1(j.Algorithm, j.Kty, j.Crv) { + return secp256k1.NewPublicKey(j.x, j.y).Serialize(), nil } - return &jwk, true -} + switch pubKey := j.Public().Key.(type) { + case *ecdsa.PublicKey, *rsa.PublicKey, ed25519.PublicKey: + pubKBytes, err := x509.MarshalPKIXPublicKey(pubKey) + if err != nil { + return nil, fmt.Errorf("failed to read get public key: %w", err) + } -// jsonWebKey contains subset of json web key json properties -type jsonWebKey struct { - Kty string `json:"kty,omitempty"` - Kid string `json:"kid,omitempty"` - Crv string `json:"crv,omitempty"` - X *byteBuffer `json:"x,omitempty"` - Y *byteBuffer `json:"y,omitempty"` + return pubKBytes, nil + default: + return nil, fmt.Errorf("unsupported public key type in kid '%s'", j.KeyID) + } } -// DecodePublicKey reads a public key from its JSON Web Key representation. -// TODO : to be part of jose.JWK [Issue#1513] -func DecodePublicKey(jwkBytes []byte) ([]byte, error) { - key := &jsonWebKey{} +// UnmarshalJSON reads a key from its JSON representation. +func (j *JWK) UnmarshalJSON(jwkBytes []byte) error { + var key jsonWebKey - err := json.Unmarshal(jwkBytes, key) - if err != nil { - return nil, fmt.Errorf("unable to read JWK, %w", err) + marshalErr := json.Unmarshal(jwkBytes, &key) + if marshalErr != nil { + return fmt.Errorf("unable to read JWK: %w", marshalErr) } - if strings.EqualFold(key.Kty, secp256k1Kty) && strings.EqualFold(key.Crv, secp256k1Crv) { - // if kty="EC" and Crv="secp256k1", then handle differently - return btcPublicKey(key.X, key.Y) + if isSecp256k1(key.Alg, key.Kty, key.Crv) { + jwk, err := unmarshalSecp256k1(&key) + if err != nil { + return fmt.Errorf("unable to read JWK: %w", err) + } + + *j = *jwk + } else { + var joseJWK jose.JSONWebKey + + err := json.Unmarshal(jwkBytes, &joseJWK) + if err != nil { + return fmt.Errorf("unable to read jose JWK, %w", err) + } + + j.JSONWebKey = joseJWK } - jwk := jose.JSONWebKey{} + if key.X != nil { + j.x = key.X.bigInt() + } - // read key from its JSON representation. - err = jwk.UnmarshalJSON(jwkBytes) - if err != nil { - return nil, fmt.Errorf("failed to read: %w", err) + if key.Y != nil { + j.y = key.Y.bigInt() } - // get public key bytes from jwk - switch pubKey := jwk.Public().Key.(type) { - case *ecdsa.PublicKey, *rsa.PublicKey, ed25519.PublicKey: - pubKBytes, err := x509.MarshalPKIXPublicKey(pubKey) - if err != nil { - return nil, fmt.Errorf("failed to read public key: %w", err) - } + j.Kty = key.Kty + j.Crv = key.Crv - return pubKBytes, nil - default: - return nil, fmt.Errorf("unsupported public key type in kid '%s'", key.Kid) + return nil +} + +// MarshalJSON serializes the given key to its JSON representation. +func (j *JWK) MarshalJSON() ([]byte, error) { + if isSecp256k1(j.Algorithm, j.Kty, j.Crv) { + return marshalSecp256k1(j) } + + return (&j.JSONWebKey).MarshalJSON() +} + +func isSecp256k1(alg, kty, crv string) bool { + return strings.EqualFold(alg, secp256k1Alg) || + (strings.EqualFold(kty, secp256k1Kty) && strings.EqualFold(crv, secp256k1Crv)) } -// return public key bytes using given(x,y) using curve=secp256k1 -func btcPublicKey(xBuffer, yBuffer *byteBuffer) ([]byte, error) { +func unmarshalSecp256k1(jwk *jsonWebKey) (*JWK, error) { + if jwk.X == nil { + return nil, fmt.Errorf("missed x value in EC key") + } + + if jwk.Y == nil { + return nil, fmt.Errorf("missed y value in EC key") + } + curve := secp256k1.S256() - if xBuffer == nil || yBuffer == nil { - return nil, errors.New("invalid EC key, missing x/y values") + if curveSize(curve) != len(jwk.X.data) { + return nil, fmt.Errorf("invalid x length of EC key") } - if curveSize(curve) != len(xBuffer.data) { - return nil, fmt.Errorf("invalid EC public key, wrong length for x") + if curveSize(curve) != len(jwk.Y.data) { + return nil, fmt.Errorf("invalid y length of EC key") } - if curveSize(curve) != len(yBuffer.data) { - return nil, fmt.Errorf("invalid EC public key, wrong length for y") + if jwk.D != nil && dSize(curve) != len(jwk.D.data) { + return nil, fmt.Errorf("invalid d length of EC key") } - x := xBuffer.bigInt() - y := yBuffer.bigInt() + x := jwk.X.bigInt() + y := jwk.Y.bigInt() if !curve.IsOnCurve(x, y) { - return nil, errors.New("invalid EC key, X/Y are not on declared curve") + return nil, errors.New("invalid EC key, x/y are not located on curve") + } + + var key interface{} + + if jwk.D != nil { + key = &ecdsa.PrivateKey{ + PublicKey: ecdsa.PublicKey{ + Curve: curve, + X: x, + Y: y, + }, + D: jwk.D.bigInt(), + } + } else { + key = &ecdsa.PublicKey{ + Curve: curve, + X: x, + Y: y, + } + } + + return &JWK{ + JSONWebKey: jose.JSONWebKey{ + Key: key, KeyID: jwk.Kid, Algorithm: jwk.Alg, Use: jwk.Use, + }, + }, nil +} + +func marshalSecp256k1(jwk *JWK) ([]byte, error) { + var raw jsonWebKey + + switch ecdsaKey := jwk.Key.(type) { + case *ecdsa.PublicKey: + raw = jsonWebKey{ + Kty: secp256k1Kty, + Crv: secp256k1Crv, + X: newFixedSizeBuffer(ecdsaKey.X.Bytes(), secp256k1Size), + Y: newFixedSizeBuffer(ecdsaKey.Y.Bytes(), secp256k1Size), + } + + case *ecdsa.PrivateKey: + raw = jsonWebKey{ + Kty: secp256k1Kty, + Crv: secp256k1Crv, + X: newFixedSizeBuffer(ecdsaKey.X.Bytes(), secp256k1Size), + Y: newFixedSizeBuffer(ecdsaKey.Y.Bytes(), secp256k1Size), + D: newFixedSizeBuffer(ecdsaKey.D.Bytes(), dSize(ecdsaKey.Curve)), + } } - return secp256k1.NewPublicKey(x, y).Serialize(), nil + raw.Kid = jwk.KeyID + raw.Alg = jwk.Algorithm + raw.Use = jwk.Use + + return json.Marshal(raw) +} + +// JWK gets JWK from JOSE headers. +func (h Headers) JWK() (*JWK, bool) { + jwkRaw, ok := h[HeaderJSONWebKey] + if !ok { + return nil, false + } + + var jwk JWK + + err := convertMapToValue(jwkRaw, &jwk) + if err != nil { + return nil, false + } + + return &jwk, true +} + +// jsonWebKey contains subset of json web key json properties +type jsonWebKey struct { + Use string `json:"use,omitempty"` + Kty string `json:"kty,omitempty"` + Kid string `json:"kid,omitempty"` + Crv string `json:"crv,omitempty"` + Alg string `json:"alg,omitempty"` + + X *byteBuffer `json:"x,omitempty"` + Y *byteBuffer `json:"y,omitempty"` + + D *byteBuffer `json:"d,omitempty"` } // Get size of curve in bytes @@ -135,6 +245,18 @@ func curveSize(crv elliptic.Curve) int { return div + 1 } +func dSize(curve elliptic.Curve) int { + order := curve.Params().P + bitLen := order.BitLen() + size := bitLen / bitsPerByte + + if bitLen%bitsPerByte != 0 { + size++ + } + + return size +} + // byteBuffer represents a slice of bytes that can be serialized to url-safe base64. type byteBuffer struct { data []byte @@ -167,3 +289,11 @@ func (b *byteBuffer) UnmarshalJSON(data []byte) error { func (b byteBuffer) bigInt() *big.Int { return new(big.Int).SetBytes(b.data) } + +func newFixedSizeBuffer(data []byte, length int) *byteBuffer { + paddedData := make([]byte, length-len(data)) + + return &byteBuffer{ + data: append(paddedData, data...), + } +} diff --git a/pkg/doc/jose/jwk_test.go b/pkg/doc/jose/jwk_test.go index 871e10ec1c..d12022ca21 100644 --- a/pkg/doc/jose/jwk_test.go +++ b/pkg/doc/jose/jwk_test.go @@ -7,11 +7,14 @@ SPDX-License-Identifier: Apache-2.0 package jose import ( + "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rand" "testing" + "github.com/square/go-jose/v3" + "github.com/decred/dcrd/dcrec/secp256k1/v2" "github.com/square/go-jose/v3/json" "github.com/stretchr/testify/require" @@ -24,12 +27,14 @@ func TestHeaders_GetJWK(t *testing.T) { require.NoError(t, err) jwk := JWK{ - Key: pubKey, - KeyID: "kid", - Algorithm: "EdDSA", + JSONWebKey: jose.JSONWebKey{ + Key: pubKey, + KeyID: "kid", + Algorithm: "EdDSA", + }, } - jwkBytes, err := json.Marshal(jwk) + jwkBytes, err := json.Marshal(&jwk) require.NoError(t, err) var jwkMap map[string]interface{} @@ -108,7 +113,20 @@ func TestDecodePublicKey(t *testing.T) { "kid": "sample@sample.id", "x": "YRrvJocKf39GpdTnd-zBFE0msGDqawR-Cmtc6yKoFsM", "y": "kE-dMH9S3mxnTXo0JFEhraCU_tVYFDfpu9tpP1LfVKQ", - "alg": "ES256" + "alg": "ES256K" + }`, + }, + { + name: "get private key bytes EC SECP256K1 JWK", + jwkJSON: `{ + "kty": "EC", + "d": "Lg5xrN8Usd_T-MfqBIs3bUWQCNsXY8hGU-Ru3Joom8E", + "use": "sig", + "crv": "secp256k1", + "kid": "sample@sample.id", + "x": "dv6X5DheBaFWR2H_yv9pUI2dcmL2XX8m7zgFc9Coaqg", + "y": "AUVSmytVWP350kV1RHhQ6AcCWaJj8AFt4aNLlDws7C4", + "alg": "ES256K" }`, }, } @@ -118,9 +136,18 @@ func TestDecodePublicKey(t *testing.T) { for _, test := range tests { tc := test t.Run(tc.name, func(t *testing.T) { - pk, err := DecodePublicKey([]byte(tc.jwkJSON)) + var jwk JWK + + err := json.Unmarshal([]byte(tc.jwkJSON), &jwk) require.NoError(t, err) - require.NotEmpty(t, pk) + + pkBytes, err := jwk.PublicKeyBytes() + require.NoError(t, err) + require.NotEmpty(t, pkBytes) + + jwkBytes, err := json.Marshal(&jwk) + require.NoError(t, err) + require.NotEmpty(t, jwkBytes) }) } }) @@ -134,7 +161,7 @@ func TestDecodePublicKey(t *testing.T) { { name: "attempt public key bytes from invalid JSON bytes", jwkJSON: `}`, - err: "unable to read JWK", + err: "invalid character", }, { name: "attempt public key bytes from invalid curve", @@ -160,7 +187,7 @@ func TestDecodePublicKey(t *testing.T) { "y": "", "alg": "ES256" }`, - err: "invalid EC public key, wrong length for x", + err: "unable to read JWK: invalid x length of EC key", }, { name: "attempt public key bytes from invalid JSON bytes", @@ -173,7 +200,7 @@ func TestDecodePublicKey(t *testing.T) { "y": "", "alg": "ES256" }`, - err: "invalid EC public key, wrong length for y", + err: "unable to read JWK: invalid y length of EC key", }, { name: "attempt public key bytes from invalid JSON bytes", @@ -188,6 +215,57 @@ func TestDecodePublicKey(t *testing.T) { }`, err: "unable to read JWK", }, + { + name: "X is not defined", + jwkJSON: `{ + "kty": "EC", + "use": "enc", + "crv": "secp256k1", + "kid": "sample@sample.id", + "y": "rIJO8RmkExUecJ5i15L9OC7rl7pwmYFR8QQgdM1ERWI", + "alg": "ES256" + }`, + err: "missed x value in EC key", + }, + { + name: "Y is not defined", + jwkJSON: `{ + "kty": "EC", + "use": "enc", + "crv": "secp256k1", + "kid": "sample@sample.id", + "x": "wQehEGTVCu32yp8IwTaBCqPUIYslyd-WoFRsfDKE9II", + "alg": "ES256" + }`, + err: "missed y value in EC key", + }, + { + name: "Y is not defined", + jwkJSON: `{ + "kty": "EC", + "use": "enc", + "crv": "secp256k1", + "kid": "sample@sample.id", + "x": "wQehEGTVCu32yp8IwTaBCqPUIYslyd-WoFRsfDKE9II", + "y": "rIJO8RmkExUecJ5i15L9OC7rl7pwmYFR8QQgdM1ERWI", + "d": "", + "alg": "ES256" + }`, + err: "invalid d length of EC key", + }, + { + name: "Y is not defined", + jwkJSON: `{ + "kty": "EC", + "use": "enc", + "crv": "secp256k1", + "kid": "sample@sample.id", + "x": "wQehEGTVCu32yp8IwTaBCqPUIYslyd-WoFRsfDKE9II", + "y": "rIJO8RmkExUecJ5i15L9OC7rl7pwmYFR8QQgdM1ERWO", + "alg": "ES256" + }`, + err: "invalid EC key, x/y are not located on curve", + }, { name: "attempt public key bytes from invalid JSON bytes", jwkJSON: `{ @@ -208,8 +286,8 @@ func TestDecodePublicKey(t *testing.T) { for _, test := range tests { tc := test t.Run(tc.name, func(t *testing.T) { - pk, err := DecodePublicKey([]byte(tc.jwkJSON)) - require.Empty(t, pk) + var jwk JWK + err := json.Unmarshal([]byte(tc.jwkJSON), &jwk) require.Error(t, err) require.Contains(t, err.Error(), tc.err) }) @@ -223,12 +301,6 @@ func TestByteBufferUnmarshalFailure(t *testing.T) { require.Error(t, err) } -func TestECPublicKeyValidation(t *testing.T) { - k, err := btcPublicKey(nil, nil) - require.Empty(t, k) - require.Error(t, err) -} - func TestCurveSize(t *testing.T) { require.Equal(t, 32, curveSize(secp256k1.S256())) require.Equal(t, 32, curveSize(elliptic.P256())) @@ -236,3 +308,31 @@ func TestCurveSize(t *testing.T) { require.Equal(t, 48, curveSize(elliptic.P384())) require.Equal(t, 66, curveSize(elliptic.P521())) } + +func TestJWK_PublicKeyBytesValidation(t *testing.T) { + // invalid public key + privKey, err := ecdsa.GenerateKey(secp256k1.S256(), rand.Reader) + require.NoError(t, err) + + jwk := &JWK{ + JSONWebKey: jose.JSONWebKey{ + Key: &privKey.PublicKey, + Algorithm: "ES256", + KeyID: "pubkey#123", + }, + Crv: "P-256", + Kty: "EC", + } + + pkBytes, err := jwk.PublicKeyBytes() + require.Error(t, err) + require.Contains(t, err.Error(), "failed to read get public key") + require.Empty(t, pkBytes) + + // unsupported public key type + jwk.Key = "key of invalid type" + pkBytes, err = jwk.PublicKeyBytes() + require.Error(t, err) + require.Contains(t, err.Error(), "unsupported public key type in kid 'pubkey#123'") + require.Empty(t, pkBytes) +} diff --git a/pkg/doc/signature/suite/jsonwebsignature2020/public_key_verifier.go b/pkg/doc/signature/suite/jsonwebsignature2020/public_key_verifier.go index 8b9ea3ecef..e37db52b17 100644 --- a/pkg/doc/signature/suite/jsonwebsignature2020/public_key_verifier.go +++ b/pkg/doc/signature/suite/jsonwebsignature2020/public_key_verifier.go @@ -11,24 +11,45 @@ import ( "crypto/ecdsa" "crypto/elliptic" "errors" + "fmt" "math/big" + "github.com/decred/dcrd/dcrec/secp256k1/v2" + sigverifier "github.com/hyperledger/aries-framework-go/pkg/doc/signature/verifier" ) -// PublicKeyVerifierP256 verifies a ECDSA signature taking public key bytes as input. +// PublicKeyVerifierEC verifies a ECDSA signature taking public key bytes as input. // NOTE: this verifier is present for backward compatibility reasons and can be removed soon. // Please use CryptoVerifier or your own verifier. -type PublicKeyVerifierP256 struct { +type PublicKeyVerifierEC struct { } // Verify will verify a signature. -func (v *PublicKeyVerifierP256) Verify(pubKey *sigverifier.PublicKey, doc, signature []byte) error { - pubKeyBytes := pubKey.Value +func (v *PublicKeyVerifierEC) Verify(pubKey *sigverifier.PublicKey, doc, signature []byte) error { + var ( + curve elliptic.Curve + keySize int + ) + + switch pubKey.Curve { + case "P-256": + curve = elliptic.P256() + keySize = 32 + case "P-384": + curve = elliptic.P384() + keySize = 48 + case "P-521": + curve = elliptic.P521() + keySize = 66 + case "secp256k1": + curve = secp256k1.S256() + keySize = 32 + default: + return fmt.Errorf("ecdsa: unsupported elliptic curve '%s'", pubKey.Curve) + } - // TODO Read curve parameter from PublicKey, support P384 and P521 curves - // (https://github.com/hyperledger/aries-framework-go/issues/1527) - curve := elliptic.P256() + pubKeyBytes := pubKey.Value x, y := elliptic.Unmarshal(curve, pubKeyBytes) if x == nil { @@ -41,8 +62,7 @@ func (v *PublicKeyVerifierP256) Verify(pubKey *sigverifier.PublicKey, doc, signa Y: y, } - p256KeySize := 32 - if len(signature) != 2*p256KeySize { + if len(signature) != 2*keySize { return errors.New("ecdsa: invalid signature size") } @@ -55,8 +75,8 @@ func (v *PublicKeyVerifierP256) Verify(pubKey *sigverifier.PublicKey, doc, signa hash := hasher.Sum(nil) - r := big.NewInt(0).SetBytes(signature[:p256KeySize]) - s := big.NewInt(0).SetBytes(signature[p256KeySize:]) + r := big.NewInt(0).SetBytes(signature[:keySize]) + s := big.NewInt(0).SetBytes(signature[keySize:]) verified := ecdsa.Verify(ecdsaPubKey, hash, r, s) if !verified { diff --git a/pkg/doc/signature/suite/jsonwebsignature2020/public_key_verifier_test.go b/pkg/doc/signature/suite/jsonwebsignature2020/public_key_verifier_test.go index 3bf57c782c..e460c2c2cf 100644 --- a/pkg/doc/signature/suite/jsonwebsignature2020/public_key_verifier_test.go +++ b/pkg/doc/signature/suite/jsonwebsignature2020/public_key_verifier_test.go @@ -13,13 +13,14 @@ import ( "crypto/rand" "testing" + "github.com/decred/dcrd/dcrec/secp256k1/v2" "github.com/stretchr/testify/require" sigverifier "github.com/hyperledger/aries-framework-go/pkg/doc/signature/verifier" "github.com/hyperledger/aries-framework-go/pkg/kms" ) -func TestPublicKeyVerifier_Verify(t *testing.T) { +func TestPublicKeyVerifierP256_Verify(t *testing.T) { curve := elliptic.P256() privKey, err := ecdsa.GenerateKey(curve, rand.Reader) require.NoError(t, err) @@ -28,11 +29,14 @@ func TestPublicKeyVerifier_Verify(t *testing.T) { pubKeyBytes := elliptic.Marshal(curve, privKey.X, privKey.Y) pubKey := &sigverifier.PublicKey{ - Type: kms.ED25519, - Value: pubKeyBytes, + Type: kms.ED25519, + Value: pubKeyBytes, + Curve: "P-256", + Alg: "ES256", + KeyType: "EC", } - v := &PublicKeyVerifierP256{} + v := &PublicKeyVerifierEC{} signature := getSignature(privKey, msg) t.Run("happy path", func(t *testing.T) { @@ -40,10 +44,25 @@ func TestPublicKeyVerifier_Verify(t *testing.T) { require.NoError(t, verifyError) }) + t.Run("unsupported curve", func(t *testing.T) { + verifyError := v.Verify(&sigverifier.PublicKey{ + Type: kms.ED25519, + Value: pubKeyBytes, + Curve: "unsupported", + Alg: "ES256", + KeyType: "EC", + }, msg, signature) + require.Error(t, verifyError) + require.EqualError(t, verifyError, "ecdsa: unsupported elliptic curve 'unsupported'") + }) + t.Run("invalid public key", func(t *testing.T) { verifyError := v.Verify(&sigverifier.PublicKey{ - Type: kms.ED25519, - Value: []byte("invalid public key"), + Type: kms.ED25519, + Value: []byte("invalid public key"), + Curve: "P-256", + Alg: "ES256", + KeyType: "EC", }, msg, signature) require.Error(t, verifyError) require.EqualError(t, verifyError, "ecdsa: invalid public key") @@ -61,10 +80,65 @@ func TestPublicKeyVerifier_Verify(t *testing.T) { }) } +func TestPublicKeyVerifier_Verify(t *testing.T) { + tests := []struct { + curve elliptic.Curve + curveName string + algorithm string + }{ + { + curve: elliptic.P256(), + curveName: "P-256", + algorithm: "ES256", + }, + { + curve: elliptic.P384(), + curveName: "P-384", + algorithm: "ES384", + }, + { + curve: elliptic.P521(), + curveName: "P-521", + algorithm: "ES521", + }, + { + curve: secp256k1.S256(), + curveName: "secp256k1", + algorithm: "ES256K", + }, + } + + t.Parallel() + + for _, test := range tests { + tc := test + t.Run(tc.curveName, func(t *testing.T) { + privKey, err := ecdsa.GenerateKey(tc.curve, rand.Reader) + require.NoError(t, err) + + msg := []byte("test message") + + pubKeyBytes := elliptic.Marshal(tc.curve, privKey.X, privKey.Y) + pubKey := &sigverifier.PublicKey{ + Type: kms.ED25519, + Value: pubKeyBytes, + Curve: tc.curveName, + Alg: tc.algorithm, + KeyType: "EC", + } + + v := &PublicKeyVerifierEC{} + signature := getSignature(privKey, msg) + + err = v.Verify(pubKey, msg, signature) + require.NoError(t, err) + }) + } +} + func getSignature(privKey *ecdsa.PrivateKey, payload []byte) []byte { hasher := crypto.SHA256.New() - // According to documentation, Write() on hash never fails _, err := hasher.Write(payload) if err != nil { panic(err) @@ -84,18 +158,12 @@ func getSignature(privKey *ecdsa.PrivateKey, payload []byte) []byte { keyBytes++ } - // We serialize the outputs (r and s) into big-endian byte arrays and pad - // them with zeros on the left to make sure the sizes work out. Both arrays - // must be keyBytes long, and the output must be 2*keyBytes long. - rBytes := r.Bytes() - rBytesPadded := make([]byte, keyBytes) - copy(rBytesPadded[keyBytes-len(rBytes):], rBytes) - - sBytes := s.Bytes() - sBytesPadded := make([]byte, keyBytes) - copy(sBytesPadded[keyBytes-len(sBytes):], sBytes) + return append(copyPadded(r.Bytes(), keyBytes), copyPadded(s.Bytes(), keyBytes)...) +} - out := append(rBytesPadded, sBytesPadded...) +func copyPadded(source []byte, size int) []byte { + dest := make([]byte, size) + copy(dest[size-len(source):], source) - return out + return dest } diff --git a/pkg/doc/signature/verifier/verifier.go b/pkg/doc/signature/verifier/verifier.go index 93f295968b..edd92de060 100644 --- a/pkg/doc/signature/verifier/verifier.go +++ b/pkg/doc/signature/verifier/verifier.go @@ -33,8 +33,11 @@ type signatureSuite interface { // PublicKey contains a result of public key resolution. type PublicKey struct { - Type string - Value []byte + Type string + Value []byte + Curve string + Alg string + KeyType string } // keyResolver encapsulates key resolution diff --git a/pkg/doc/verifiable/credential_ldp_test.go b/pkg/doc/verifiable/credential_ldp_test.go index 198b5269c4..2ccbad4eb9 100644 --- a/pkg/doc/verifiable/credential_ldp_test.go +++ b/pkg/doc/verifiable/credential_ldp_test.go @@ -182,12 +182,21 @@ func TestNewCredentialFromLinkedDataProof_JsonWebSignature2020_ecdsaP256(t *test sigSuite := jsonwebsignature2020.New( // TODO use suite.NewCryptoVerifier(createLocalCrypto()) verifier (as it's done in Ed25519 test above) // (https://github.com/hyperledger/aries-framework-go/issues/1534) - suite.WithVerifier(&jsonwebsignature2020.PublicKeyVerifierP256{}), + suite.WithVerifier(&jsonwebsignature2020.PublicKeyVerifierEC{}), suite.WithCompactProof()) vcWithLdp, _, err := NewCredential([]byte(vcFromTransmute), WithEmbeddedSignatureSuites(sigSuite), - WithPublicKeyFetcher(SingleKey(pubKeyBytes, kms.ECDSAP256))) + WithPublicKeyFetcher( + func(issuerID, keyID string) (*sigverifier.PublicKey, error) { + return &sigverifier.PublicKey{ + Type: kms.ECDSAP256, + Value: pubKeyBytes, + Curve: "P-256", + Alg: "ES256", + KeyType: "EC", + }, nil + })) r.NoError(err) r.NotNil(t, vcWithLdp) }