diff --git a/crypto-ext/jwksupport/create.go b/crypto-ext/jwksupport/create.go new file mode 100644 index 0000000..f27deb7 --- /dev/null +++ b/crypto-ext/jwksupport/create.go @@ -0,0 +1,142 @@ +package jwksupport + +import ( + "crypto/ed25519" + "crypto/elliptic" + "errors" + "fmt" + "math/big" + + "github.com/btcsuite/btcutil/base58" + "github.com/trustbloc/bbs-signature-go/bbs12381g2pub" + "github.com/trustbloc/kms-go/util/cryptoutil" + + "github.com/trustbloc/did-go/doc/jose/jwk" +) + +const ( + ecKty = "EC" + okpKty = "OKP" + x25519Crv = "X25519" + ed25519Crv = "Ed25519" + bls12381G2Crv = "BLS12381_G2" + bls12381G2Size = 96 +) + +func FromEdPublicKey(pub ed25519.PublicKey) *jwk.JWK { + return &jwk.JWK{ + Kty: "OKP", + Crv: ed25519Crv, + X: jwk.NewBuffer(pub), + } +} + +func FromEdPrivateKey(ed ed25519.PrivateKey) *jwk.JWK { + raw := FromEdPublicKey(ed25519.PublicKey(ed[32:])) + + raw.D = jwk.NewBuffer(ed[0:32]) + return raw +} + +func JWKFromX25519Key(pubKey []byte) (*jwk.JWK, error) { + if len(pubKey) != cryptoutil.Curve25519KeySize { + return nil, errors.New("JWKFromX25519Key: invalid key") + } + + return &jwk.JWK{ + Crv: x25519Crv, + Kty: okpKty, + X: jwk.NewFixedSizeBuffer(pubKey, cryptoutil.Curve25519KeySize), + }, nil +} + +func FromEcdsaPubKeyBytes(curve elliptic.Curve, pubKeyBytes []byte) (*jwk.JWK, error) { + println(base58.Encode(pubKeyBytes)) + x, y := elliptic.UnmarshalCompressed(curve, pubKeyBytes) + if x == nil { + return nil, fmt.Errorf("error unmarshalling key bytes") + } + + return FromEcdsaContent(EcdsaContent{ + Curve: curve, + X: x, + Y: y, + }) +} + +func FromBLS12381G2(key *bbs12381g2pub.PublicKey) (*jwk.JWK, error) { + var raw *jwk.JWK + + mKey, err := key.Marshal() + if err != nil { + return nil, err + } + + raw = &jwk.JWK{ + Kty: ecKty, + Crv: bls12381G2Crv, + X: jwk.NewFixedSizeBuffer(mKey, bls12381G2Size), + } + + return raw, nil +} + +type EcdsaContent struct { + Curve elliptic.Curve + + X *big.Int + Y *big.Int +} + +func FromEcdsaContent(content EcdsaContent) (*jwk.JWK, error) { + name, err := curveName(content.Curve) + if err != nil { + return nil, err + } + + size := curveSize(content.Curve) + + xBytes := content.X.Bytes() + yBytes := content.Y.Bytes() + + if len(xBytes) > size || len(yBytes) > size { + return nil, fmt.Errorf("go-jose/go-jose: invalid EC key (X/Y too large)") + } + + key := &jwk.JWK{ + Kty: "EC", + Crv: name, + X: jwk.NewFixedSizeBuffer(xBytes, size), + Y: jwk.NewFixedSizeBuffer(yBytes, size), + } + + return key, nil +} + +// Get JOSE name of curve +func curveName(crv elliptic.Curve) (string, error) { + switch crv { + case elliptic.P256(): + return "P-256", nil + case elliptic.P384(): + return "P-384", nil + case elliptic.P521(): + return "P-521", nil + default: + return "", fmt.Errorf("unsupported/unknown elliptic curve") + } +} + +// Get size of curve in bytes +func curveSize(crv elliptic.Curve) int { + bits := crv.Params().BitSize + + div := bits / 8 + mod := bits % 8 + + if mod == 0 { + return div + } + + return div + 1 +} diff --git a/crypto-ext/jwksupport/fingerprint.go b/crypto-ext/jwksupport/fingerprint.go new file mode 100644 index 0000000..cb3da6b --- /dev/null +++ b/crypto-ext/jwksupport/fingerprint.go @@ -0,0 +1,96 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package jwksupport + +import ( + "crypto/elliptic" + "fmt" + + "github.com/trustbloc/kms-go/util/cryptoutil" + + "github.com/trustbloc/did-go/doc/jose/jwk" + + "github.com/trustbloc/did-go/doc/fingerprint" +) + +// CreateDIDKeyByJwk creates a did:key ID using the multicodec key fingerprint as per the did:key format spec found at: +// https://w3c-ccg.github.io/did-method-key/#format. +func CreateDIDKeyByJwk(jsonWebKey *jwk.JWK) (string, string, error) { + if jsonWebKey == nil { + return "", "", fmt.Errorf("jsonWebKey is required") + } + + switch jsonWebKey.Kty { + case "EC": + code, curve, err := ecCodeAndCurve(jsonWebKey.Crv) + if err != nil { + return "", "", err + } + + bytes := elliptic.MarshalCompressed(curve, jsonWebKey.X.BigInt(), jsonWebKey.Y.BigInt()) + didKey, keyID := fingerprint.CreateDIDKeyByCode(code, bytes) + + return didKey, keyID, nil + + case "OKP": + var code uint64 + + switch jsonWebKey.Crv { + case "X25519": + var keyData = jsonWebKey.X.Bytes() + + if len(keyData) != cryptoutil.Curve25519KeySize { + return "", "", jwk.ErrInvalidKey + } + + code = fingerprint.X25519PubKeyMultiCodec + didKey, keyID := fingerprint.CreateDIDKeyByCode(code, keyData) + + return didKey, keyID, nil + case "Ed25519": + keyData, err := ToED25519PublicKeyBytes(jsonWebKey) + if err != nil { + return "", "", err + } + + code = fingerprint.ED25519PubKeyMultiCodec + didKey, keyID := fingerprint.CreateED25519DIDKey(keyData) + + return didKey, keyID, nil + + default: + return "", "", fmt.Errorf( + "unsupported kty %q and crv %q combination", jsonWebKey.Kty, jsonWebKey.Crv) + } + + default: + return "", "", fmt.Errorf("unsupported kty %q", jsonWebKey.Kty) + } +} + +func ecCodeAndCurve(ecCurve string) (uint64, elliptic.Curve, error) { + var ( + curve elliptic.Curve + code uint64 + ) + + switch ecCurve { + case elliptic.P256().Params().Name, "NIST_P256": + curve = elliptic.P256() + code = fingerprint.P256PubKeyMultiCodec + case elliptic.P384().Params().Name, "NIST_P384": + curve = elliptic.P384() + code = fingerprint.P384PubKeyMultiCodec + case elliptic.P521().Params().Name, "NIST_P521": + curve = elliptic.P521() + code = fingerprint.P521PubKeyMultiCodec + default: + return 0, nil, fmt.Errorf("unsupported crv %s", ecCurve) + } + + return code, curve, nil +} diff --git a/crypto-ext/jwksupport/jwksupport_test.go b/crypto-ext/jwksupport/jwksupport_test.go new file mode 100644 index 0000000..19ad0a6 --- /dev/null +++ b/crypto-ext/jwksupport/jwksupport_test.go @@ -0,0 +1,134 @@ +package jwksupport_test + +import ( + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "encoding/base64" + "testing" + + "github.com/btcsuite/btcutil/base58" + "github.com/stretchr/testify/require" + "github.com/trustbloc/bbs-signature-go/bbs12381g2pub" + + "github.com/trustbloc/did-go/crypto-ext/jwksupport" + "github.com/trustbloc/did-go/doc/jose/jwk" +) + +const ( + ecP256PubKeyBase58 = "23youFZZdHMVdpv28DRSWP2zJbTJ8KHBeSKUX3qVqqnmp" + ecP384PubKeyBase58 = "ad1jjx1hRrEkMWSsFsXpLULAbmUq67ii1jRsBNtYEKhCwLXTo4wcjY7C2K4cuGZ859" + ecP521PubKeyBase58 = "4SsRN7NAk3175KrnPVQn5XTZE49MKdFKiq4XhWdhfx3QEUb2e96A3YLonFC6B21sa4uU776QMxEnxAQP6GWko8f3aNV" + x25519KeyBase64 = "egRLO+ygwW/VNHjQhZiHw1vhHwVj4KmzeRnKIEDz6gE=" +) + +func TestFromEdPublicKey(t *testing.T) { + pubKey, _, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + + jwk := jwksupport.FromEdPublicKey(pubKey) + + _, _, err = jwksupport.CreateDIDKeyByJwk(jwk) + require.NoError(t, err) +} + +func TestFromEdPrivateKey(t *testing.T) { + _, privKey, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + + jwk := jwksupport.FromEdPrivateKey(privKey) + require.NotEmpty(t, jwk.X.Bytes()) +} + +func TestJWKFromX25519Key(t *testing.T) { + keyBytes, err := base64.StdEncoding.DecodeString(x25519KeyBase64) + require.NoError(t, err) + + jwk, err := jwksupport.JWKFromX25519Key(keyBytes) + require.NoError(t, err) + + _, _, err = jwksupport.CreateDIDKeyByJwk(jwk) + require.NoError(t, err) + + t.Run("Failure", func(t *testing.T) { + _, err = jwksupport.JWKFromX25519Key([]byte{}) + require.ErrorContains(t, err, "invalid key") + }) +} + +func TestFromEcdsaPubKeyBytes(t *testing.T) { + + t.Run("P256", func(t *testing.T) { + jwk, err := jwksupport.FromEcdsaPubKeyBytes(elliptic.P256(), base58.Decode(ecP256PubKeyBase58)) + require.NoError(t, err) + + _, _, err = jwksupport.CreateDIDKeyByJwk(jwk) + require.NoError(t, err) + }) + + t.Run("P384", func(t *testing.T) { + jwk, err := jwksupport.FromEcdsaPubKeyBytes(elliptic.P384(), base58.Decode(ecP384PubKeyBase58)) + require.NoError(t, err) + + _, _, err = jwksupport.CreateDIDKeyByJwk(jwk) + require.NoError(t, err) + }) + + t.Run("P521", func(t *testing.T) { + jwk, err := jwksupport.FromEcdsaPubKeyBytes(elliptic.P521(), base58.Decode(ecP521PubKeyBase58)) + require.NoError(t, err) + + _, _, err = jwksupport.CreateDIDKeyByJwk(jwk) + require.NoError(t, err) + }) + + t.Run("Failure", func(t *testing.T) { + _, err := jwksupport.FromEcdsaPubKeyBytes(elliptic.P256(), []byte{}) + require.ErrorContains(t, err, "error unmarshalling key bytes") + + _, err = jwksupport.FromEcdsaContent(jwksupport.EcdsaContent{Curve: elliptic.P224()}) + require.ErrorContains(t, err, "unsupported/unknown elliptic curve") + }) +} + +func TestFromBLS12381G2(t *testing.T) { + bbsPubKey, _, err := bbs12381g2pub.GenerateKeyPair(sha256.New, nil) + require.NoError(t, err) + + jwk, err := jwksupport.FromBLS12381G2(bbsPubKey) + require.NoError(t, err) + require.NotEmpty(t, jwk.X.Bytes()) +} + +func TestCreateDIDKeyByJwk(t *testing.T) { + t.Run("Failure", func(t *testing.T) { + _, _, err := jwksupport.CreateDIDKeyByJwk(&jwk.JWK{ + Kty: "OKP", + }) + require.ErrorContains(t, err, "unsupported kty \"OKP\" and crv \"\" combination") + + _, _, err = jwksupport.CreateDIDKeyByJwk(&jwk.JWK{}) + + require.ErrorContains(t, err, "unsupported kty \"\"") + + _, _, err = jwksupport.CreateDIDKeyByJwk(&jwk.JWK{ + Kty: "OKP", + Crv: "X25519", + X: jwk.NewBuffer([]byte{}), + }) + require.ErrorContains(t, err, "invalid JWK") + + _, _, err = jwksupport.CreateDIDKeyByJwk(&jwk.JWK{ + Kty: "OKP", + Crv: "Ed25519", + }) + require.ErrorContains(t, err, "invalid Ed key") + + _, _, err = jwksupport.CreateDIDKeyByJwk(&jwk.JWK{ + Kty: "EC", + Crv: "Ed25519", + }) + require.ErrorContains(t, err, "unsupported crv Ed25519") + }) +} diff --git a/crypto-ext/jwksupport/parse.go b/crypto-ext/jwksupport/parse.go new file mode 100644 index 0000000..fb65195 --- /dev/null +++ b/crypto-ext/jwksupport/parse.go @@ -0,0 +1,20 @@ +package jwksupport + +import ( + "fmt" + + "github.com/trustbloc/did-go/doc/jose/jwk" +) + +const ( + ed25519PublicKeySize = 32 +) + +func ToED25519PublicKeyBytes(key *jwk.JWK) ([]byte, error) { + if key.X == nil { + return nil, fmt.Errorf("invalid Ed key, missing x value") + } + publicKey := make([]byte, ed25519PublicKeySize) + copy(publicKey[0:ed25519PublicKeySize], key.X.Bytes()) + return publicKey, nil +} diff --git a/doc/did/doc.go b/doc/did/doc.go index fbc074d..c915653 100644 --- a/doc/did/doc.go +++ b/doc/did/doc.go @@ -21,9 +21,10 @@ import ( "github.com/btcsuite/btcutil/base58" "github.com/multiformats/go-multibase" - "github.com/trustbloc/kms-go/doc/jose/jwk" "github.com/xeipuuv/gojsonschema" + "github.com/trustbloc/did-go/doc/jose/jwk" + "github.com/trustbloc/did-go/doc/did/endpoint" "github.com/trustbloc/did-go/doc/ld/processor" sigproof "github.com/trustbloc/did-go/doc/ld/proof" @@ -348,11 +349,6 @@ func NewVerificationMethodFromBytes(id, keyType, controller string, value []byte // NewVerificationMethodFromJWK creates a new VerificationMethod based on JSON Web Key. func NewVerificationMethodFromJWK(id, keyType, controller string, j *jwk.JWK) (*VerificationMethod, error) { - pkBytes, err := j.PublicKeyBytes() - if err != nil { - return nil, fmt.Errorf("convert JWK to public key bytes: %w", err) - } - relativeURL := false if strings.HasPrefix(id, "#") { relativeURL = true @@ -362,7 +358,6 @@ func NewVerificationMethodFromJWK(id, keyType, controller string, j *jwk.JWK) (* ID: id, Type: keyType, Controller: controller, - Value: pkBytes, jsonWebKey: j, relativeURL: relativeURL, }, nil @@ -962,12 +957,6 @@ func decodeVMJwk(jwkMap map[string]interface{}, vm *VerificationMethod) error { return fmt.Errorf("unmarshal JWK: %w", err) } - pkBytes, err := j.PublicKeyBytes() - if err != nil { - return fmt.Errorf("failed to decode public key from JWK: %w", err) - } - - vm.Value = pkBytes vm.jsonWebKey = &j return nil diff --git a/doc/did/doc_test.go b/doc/did/doc_test.go index 6bbb6fc..e7d8db4 100644 --- a/doc/did/doc_test.go +++ b/doc/did/doc_test.go @@ -20,15 +20,15 @@ import ( "time" "github.com/btcsuite/btcutil/base58" - gojose "github.com/go-jose/go-jose/v3" "github.com/stretchr/testify/require" + + "github.com/trustbloc/did-go/crypto-ext/jwksupport" "github.com/trustbloc/did-go/doc/internal/mock/signature" "github.com/trustbloc/did-go/doc/did/endpoint" "github.com/trustbloc/did-go/doc/ld/testutil" "github.com/trustbloc/did-go/doc/signature/api" "github.com/trustbloc/did-go/doc/signature/signer" - "github.com/trustbloc/kms-go/doc/jose/jwk" ) const pemPK = `-----BEGIN PUBLIC KEY----- @@ -1306,30 +1306,13 @@ func TestNewPublicKeyFromJWK(t *testing.T) { pubKey, _, err := ed25519.GenerateKey(rand.Reader) require.NoError(t, err) - j := &jwk.JWK{ - JSONWebKey: gojose.JSONWebKey{ - Key: pubKey, - KeyID: "_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A", - }, - } + j := jwksupport.FromEdPublicKey(pubKey) + j.KeyID = "_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A" // Success. signingKey, err := NewVerificationMethodFromJWK(creator, keyType, did, j) require.NoError(t, err) require.Equal(t, j, signingKey.JSONWebKey()) - require.Equal(t, []byte(pubKey), signingKey.Value) - - // Error - invalid JWK. - j = &jwk.JWK{ - JSONWebKey: gojose.JSONWebKey{ - Key: nil, - KeyID: "_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A", - }, - } - signingKey, err = NewVerificationMethodFromJWK(creator, keyType, did, j) - require.Error(t, err) - require.Contains(t, err.Error(), "convert JWK to public key bytes") - require.Nil(t, signingKey) } func TestJSONWebKey(t *testing.T) { @@ -1338,12 +1321,8 @@ func TestJSONWebKey(t *testing.T) { pubKey, _, err := ed25519.GenerateKey(rand.Reader) require.NoError(t, err) - j := &jwk.JWK{ - JSONWebKey: gojose.JSONWebKey{ - Key: pubKey, - KeyID: "_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A", - }, - } + j := jwksupport.FromEdPublicKey(pubKey) + j.KeyID = "_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A" signingKey, err := NewVerificationMethodFromJWK(creator, keyType, did, j) require.NoError(t, err) diff --git a/doc/did/util/vmparse/vmparse.go b/doc/did/util/vmparse/vmparse.go deleted file mode 100644 index c28f0d7..0000000 --- a/doc/did/util/vmparse/vmparse.go +++ /dev/null @@ -1,64 +0,0 @@ -/* -Copyright Avast Software. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package vmparse - -import ( - "fmt" - - "github.com/trustbloc/kms-go/spi/kms" - - "github.com/trustbloc/did-go/doc/did" -) - -const ( - jsonWebKey2020 = "JsonWebKey2020" - jwsVerificationKey2020 = "JwsVerificationKey2020" - ed25519VerificationKey2018 = "Ed25519VerificationKey2018" -) - -// VMToBytesTypeCrv parses a DID doc Verification Method and returns the public key bytes, KMS KeyType, and key Curve. -func VMToBytesTypeCrv(vm *did.VerificationMethod) ([]byte, kms.KeyType, string, error) { - switch vm.Type { - case ed25519VerificationKey2018: - return vm.Value, kms.ED25519Type, "Ed25519", nil - case jsonWebKey2020, jwsVerificationKey2020: - k := vm.JSONWebKey() - - kb, err := k.PublicKeyBytes() - if err != nil { - return nil, "", "", fmt.Errorf("getting []byte key for verification key: %w", err) - } - - kt, err := k.KeyType() - if err != nil { - return nil, "", "", fmt.Errorf("getting kms.KeyType of verification key: %w", err) - } - - return kb, kt, k.Crv, nil - default: - return nil, "", "", fmt.Errorf("vm.Type '%s' not supported", vm.Type) - } -} - -// VMToTypeCrv parses a DID doc Verification Method and returns the KMS KeyType, and key Curve. -func VMToTypeCrv(vm *did.VerificationMethod) (kms.KeyType, string, error) { - switch vm.Type { - case ed25519VerificationKey2018: - return kms.ED25519Type, "Ed25519", nil - case jsonWebKey2020, jwsVerificationKey2020: - k := vm.JSONWebKey() - - kt, err := k.KeyType() - if err != nil { - return "", "", fmt.Errorf("getting kms.KeyType of verification key: %w", err) - } - - return kt, k.Crv, nil - default: - return "", "", fmt.Errorf("vm.Type '%s' not supported", vm.Type) - } -} diff --git a/doc/fingerprint/fingerprint.go b/doc/fingerprint/fingerprint.go new file mode 100644 index 0000000..dcd51bd --- /dev/null +++ b/doc/fingerprint/fingerprint.go @@ -0,0 +1,134 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package fingerprint + +import ( + "encoding/binary" + "errors" + "fmt" + + "github.com/btcsuite/btcutil/base58" +) + +const ( + // X25519PubKeyMultiCodec for Curve25519 public key in multicodec table. + // source: https://github.com/multiformats/multicodec/blob/master/table.csv. + X25519PubKeyMultiCodec = 0xec + // ED25519PubKeyMultiCodec for Ed25519 public key in multicodec table. + ED25519PubKeyMultiCodec = 0xed + // BLS12381g2PubKeyMultiCodec for BLS12-381 G2 public key in multicodec table. + BLS12381g2PubKeyMultiCodec = 0xeb + // BLS12381g1g2PubKeyMultiCodec for BLS12-381 G1G2 public key in multicodec table. + BLS12381g1g2PubKeyMultiCodec = 0xee + // P256PubKeyMultiCodec for NIST P-256 public key in multicodec table. + P256PubKeyMultiCodec = 0x1200 + // P384PubKeyMultiCodec for NIST P-384 public key in multicodec table. + P384PubKeyMultiCodec = 0x1201 + // P521PubKeyMultiCodec for NIST P-521 public key in multicodec table. + P521PubKeyMultiCodec = 0x1202 + + // Default BLS 12-381 public key length in G2 field. + bls12381G2PublicKeyLen = 96 + + // Number of bytes in G1 X coordinate. + g1CompressedSize = 48 +) + +// CreateED25519DIDKey calls CreateDIDKeyByCode with Ed25519 key code. +func CreateED25519DIDKey(pubKey []byte) (string, string) { + return CreateDIDKeyByCode(ED25519PubKeyMultiCodec, pubKey) +} + +// CreateDIDKeyByCode creates a did:key ID using the multicodec key fingerprint as per the did:key format spec found at: +// https://w3c-ccg.github.io/did-method-key/#format. It does not parse the contents of 'pubKey'. Use +// kmsdidkey.BuildDIDKeyByKeyType() for marshalled keys extracted from the KMS instead of this function. +func CreateDIDKeyByCode(code uint64, pubKey []byte) (string, string) { + methodID := KeyFingerprint(code, pubKey) + didKey := fmt.Sprintf("did:key:%s", methodID) + keyID := fmt.Sprintf("%s#%s", didKey, methodID) + + return didKey, keyID +} + +// KeyFingerprint generates a multicode fingerprint for pubKeyValue (raw key []byte). +// It is mainly used as the controller ID (methodSpecification ID) of a did key. +func KeyFingerprint(code uint64, pubKeyValue []byte) string { + multicodecValue := multicodec(code) + mcLength := len(multicodecValue) + buf := make([]uint8, mcLength+len(pubKeyValue)) + copy(buf, multicodecValue) + copy(buf[mcLength:], pubKeyValue) + + return fmt.Sprintf("z%s", base58.Encode(buf)) +} + +func multicodec(code uint64) []byte { + buf := make([]byte, binary.MaxVarintLen64) + bw := binary.PutUvarint(buf, code) + + return buf[:bw] +} + +// PubKeyFromFingerprint extracts the raw public key from a did:key fingerprint. +func PubKeyFromFingerprint(fingerprint string) ([]byte, uint64, error) { + // did:key:MULTIBASE(base58-btc, MULTICODEC(public-key-type, raw-public-key-bytes)) + // https://w3c-ccg.github.io/did-method-key/#format + const maxMulticodecBytes = 9 + + if len(fingerprint) < 2 || fingerprint[0] != 'z' { + return nil, 0, errors.New("unknown key encoding") + } + + mc := base58.Decode(fingerprint[1:]) // skip leading "z" + + code, br := binary.Uvarint(mc) + if br == 0 { + return nil, 0, errors.New("unknown key encoding") + } + + if br > maxMulticodecBytes { + return nil, 0, errors.New("code exceeds maximum size") + } + + if code == BLS12381g1g2PubKeyMultiCodec { + // for BBS+ G1G2 did:key type, return the G2 public key only (discard G1 key for now). + if len(mc[br+g1CompressedSize:]) != bls12381G2PublicKeyLen { + return nil, 0, errors.New("invalid bbs+ public key") + } + + return mc[br+g1CompressedSize:], code, nil + } + + return mc[br:], code, nil +} + +// PubKeyFromDIDKey parses the did:key DID and returns the key's raw value. +// note: for NIST P ECDSA keys, the raw value does not have the compression point. +// +// In order to use elliptic.Unmarshal() with the raw value, the uncompressed point ([]byte{4}) must be prepended. +// see https://github.com/golang/go/blob/master/src/crypto/elliptic/elliptic.go#L384. +func PubKeyFromDIDKey(didKey string) ([]byte, error) { + idMethodSpecificID, err := MethodIDFromDIDKey(didKey) + if err != nil { + return nil, fmt.Errorf("pubKeyFromDIDKey: MethodIDFromDIDKey: %w", err) + } + + pubKey, code, err := PubKeyFromFingerprint(idMethodSpecificID) + if err != nil { + return nil, err + } + + switch code { + case X25519PubKeyMultiCodec, ED25519PubKeyMultiCodec, BLS12381g2PubKeyMultiCodec, BLS12381g1g2PubKeyMultiCodec, + P256PubKeyMultiCodec, P384PubKeyMultiCodec, P521PubKeyMultiCodec: + break + default: + return nil, fmt.Errorf("pubKeyFromDIDKey: unsupported key multicodec code [0x%x]", code) + } + + return pubKey, nil +} diff --git a/doc/fingerprint/fingerprint_test.go b/doc/fingerprint/fingerprint_test.go new file mode 100644 index 0000000..3da86e4 --- /dev/null +++ b/doc/fingerprint/fingerprint_test.go @@ -0,0 +1,342 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package fingerprint_test + +import ( + "crypto/ed25519" + "crypto/elliptic" + "encoding/base64" + "math/big" + "strings" + "testing" + + "github.com/btcsuite/btcutil/base58" + "github.com/stretchr/testify/require" + + "github.com/trustbloc/did-go/crypto-ext/jwksupport" + "github.com/trustbloc/did-go/doc/fingerprint" + "github.com/trustbloc/did-go/doc/jose/jwk" +) + +func TestCreateDIDKey(t *testing.T) { + const ( + edPubKeyBase58 = "B12NYF8RrR3h41TDCTJojY59usg3mbtbjnFs7Eud1Y6u" + edExpectedDIDKey = "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH" + edExpectedDIDKeyID = "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH" //nolint:lll + + bbsPubKeyBase58 = "25EEkQtcLKsEzQ6JTo9cg4W7NHpaurn4Wg6LaNPFq6JQXnrP91SDviUz7KrJVMJd76CtAZFsRLYzvgX2JGxo2ccUHtuHk7ELCWwrkBDfrXCFVfqJKDootee9iVaF6NpdJtBE" //nolint:lll + bbsExpectedDIDKey = "did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY" //nolint:lll + bbsExpectedDIDKeyID = "did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY#zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY" //nolint:lll + + ecP256PubKeyBase58 = "3YRwdf868zp2t8c4oT4XdYfCihMsfR1zrVYyXS5SS4FwQ7wftDfoY5nohvhdgSk9LxyfzjTLzffJPmHgFBqizX9v" + ecP256ExpectedDIDKey = "did:key:zrurwcJZss4ruepVNu1H3xmSirvNbzgBk9qrCktB6kaewXnJAhYWwtP3bxACqBpzjZdN7TyHNzzGGSSH5qvZsSDir9z" //nolint:lll + ecP256ExpectedDIDKeyID = "did:key:zrurwcJZss4ruepVNu1H3xmSirvNbzgBk9qrCktB6kaewXnJAhYWwtP3bxACqBpzjZdN7TyHNzzGGSSH5qvZsSDir9z#zrurwcJZss4ruepVNu1H3xmSirvNbzgBk9qrCktB6kaewXnJAhYWwtP3bxACqBpzjZdN7TyHNzzGGSSH5qvZsSDir9z" //nolint:lll + + ecP384PubKeyBase58 = "tAjHMcvoBXs3BSihDV85trHmstc3V3vTP7o2Si72eCWdVzeGgGvRd8h5neHEbqSL989h53yNj7M7wHckB2bKpGKQjnPDD7NphDa9nUUBggCB6aCWterfdXbH5DfWPZx5oXU" //nolint:lll + ecP384ExpectedDIDKey = "did:key:zFwfeyrSyWdksRYykTGGtagWazFB5zS4CjQcxDMQSNmCTQB5QMqokx2VJz4vBB2hN1nUrYDTuYq3kd1BM5cUCfFD4awiNuzEBuoy6rZZTMCsZsdvWkDXY6832qcAnzE7YGw43KU" //nolint:lll + ecP384ExpectedDIDKeyID = "did:key:zFwfeyrSyWdksRYykTGGtagWazFB5zS4CjQcxDMQSNmCTQB5QMqokx2VJz4vBB2hN1nUrYDTuYq3kd1BM5cUCfFD4awiNuzEBuoy6rZZTMCsZsdvWkDXY6832qcAnzE7YGw43KU#zFwfeyrSyWdksRYykTGGtagWazFB5zS4CjQcxDMQSNmCTQB5QMqokx2VJz4vBB2hN1nUrYDTuYq3kd1BM5cUCfFD4awiNuzEBuoy6rZZTMCsZsdvWkDXY6832qcAnzE7YGw43KU" //nolint:lll + + ecP521PubKeyBase58 = "mTQ9pPr2wkKdiTHhVG7xmLwyJ5mrgq1FKcHFz2XJprs4zAPtjXWFiEz6vsscbseSEzGdjAVzcUhwdodT5cbrRjQqFdz8d1yYVqMHXsVCdCUrmWNNHcZLJeYCn1dCtQX9YRVdDFfnzczKFxDXe9HusLqBWTobbxVvdj9cTi7rSWVznP5Emfo" //nolint:lll + ecP521ExpectedDIDKey = "did:key:zWGhj2NTyCiehTPioanYSuSrfB7RJKwZj6bBUDNojfGEA21nr5NcBsHme7hcVSbptpWKarJpTcw814J3X8gVU9gZmeKM27JpGA5wNMzt8JZwjDyf8EzCJg5ve5GR2Xfm7d9Djp73V7s35KPeKe7VHMzmL8aPw4XBniNej5sXapPFoBs5R8m195HK" //nolint:lll + ecP521ExpectedDIDKeyID = "did:key:zWGhj2NTyCiehTPioanYSuSrfB7RJKwZj6bBUDNojfGEA21nr5NcBsHme7hcVSbptpWKarJpTcw814J3X8gVU9gZmeKM27JpGA5wNMzt8JZwjDyf8EzCJg5ve5GR2Xfm7d9Djp73V7s35KPeKe7VHMzmL8aPw4XBniNej5sXapPFoBs5R8m195HK#zWGhj2NTyCiehTPioanYSuSrfB7RJKwZj6bBUDNojfGEA21nr5NcBsHme7hcVSbptpWKarJpTcw814J3X8gVU9gZmeKM27JpGA5wNMzt8JZwjDyf8EzCJg5ve5GR2Xfm7d9Djp73V7s35KPeKe7VHMzmL8aPw4XBniNej5sXapPFoBs5R8m195HK" //nolint:lll + + bbsPubKeyG1Base58 = "6TBZrWMsPSFrJ2u7xFNyNA6VZs3gWpCwLi4jk8gB9EQ1bgNYK2Zjsxhku68mypBHke" + bbsPubKeyG2Base58 = "26jjNXrWtHvbrVaiYBKcFRkCvzyTUfg1W4odspRJjfQRfoT33jr91dEn2wqzaWVVVw1WmFwpGxrioYvy3sbvgphfu2D4nJUvrmQ7ZtoykgXA4EuJhmmV3TnnfHnBkKKBWn5q" //nolint:lll + bbsExpectedG1G2DIDKey = "did:key:z5TcDLDFhBEndYdwFKkQMgVTgtRHx2sniQisVxdiXZ96pcrRy2ehWvcHfhSrfDmozq8dQNxhu2u7y9FUKJ8R3VPZNPjEgsozTSx47WysNM9GESUMmyniFxbdbpxNdocx6SbRyf6nBTFzoXojbWjSsDN4LhNz1sAMzTXgh5HvLYtYzJXo1JtLZBwHgmvtWyEQqtxtjV2eo" //nolint:lll + bbsExpectedG1G2DIDKeyID = "did:key:z5TcDLDFhBEndYdwFKkQMgVTgtRHx2sniQisVxdiXZ96pcrRy2ehWvcHfhSrfDmozq8dQNxhu2u7y9FUKJ8R3VPZNPjEgsozTSx47WysNM9GESUMmyniFxbdbpxNdocx6SbRyf6nBTFzoXojbWjSsDN4LhNz1sAMzTXgh5HvLYtYzJXo1JtLZBwHgmvtWyEQqtxtjV2eo#z5TcDLDFhBEndYdwFKkQMgVTgtRHx2sniQisVxdiXZ96pcrRy2ehWvcHfhSrfDmozq8dQNxhu2u7y9FUKJ8R3VPZNPjEgsozTSx47WysNM9GESUMmyniFxbdbpxNdocx6SbRyf6nBTFzoXojbWjSsDN4LhNz1sAMzTXgh5HvLYtYzJXo1JtLZBwHgmvtWyEQqtxtjV2eo" //nolint:lll + ) + + tests := []struct { + name string + keyB58 string + DIDKey string + DIDKeyID string + keyCode uint64 + crv elliptic.Curve + }{ + { + name: "test ED25519", + keyB58: edPubKeyBase58, + DIDKey: edExpectedDIDKey, + DIDKeyID: edExpectedDIDKeyID, + keyCode: fingerprint.ED25519PubKeyMultiCodec, + }, + { + name: "test BBS+", + keyB58: bbsPubKeyBase58, + DIDKey: bbsExpectedDIDKey, + DIDKeyID: bbsExpectedDIDKeyID, + keyCode: fingerprint.BLS12381g2PubKeyMultiCodec, + }, + { + name: "test P-256", + keyB58: ecP256PubKeyBase58, + DIDKey: ecP256ExpectedDIDKey, + DIDKeyID: ecP256ExpectedDIDKeyID, + keyCode: fingerprint.P256PubKeyMultiCodec, + crv: elliptic.P256(), + }, + { + name: "test P-384", + keyB58: ecP384PubKeyBase58, + DIDKey: ecP384ExpectedDIDKey, + DIDKeyID: ecP384ExpectedDIDKeyID, + keyCode: fingerprint.P384PubKeyMultiCodec, + crv: elliptic.P384(), + }, + { + name: "test P-521", + keyB58: ecP521PubKeyBase58, + DIDKey: ecP521ExpectedDIDKey, + DIDKeyID: ecP521ExpectedDIDKeyID, + keyCode: fingerprint.P521PubKeyMultiCodec, + crv: elliptic.P521(), + }, + { + name: "test BBS+ with G1G2", + keyB58: bbsPubKeyG2Base58, + DIDKey: bbsExpectedG1G2DIDKey, + DIDKeyID: bbsExpectedG1G2DIDKeyID, + keyCode: fingerprint.BLS12381g1g2PubKeyMultiCodec, + }, + } + + for _, test := range tests { + tc := test + t.Run(tc.name+" CreateED25519DIDKey", func(t *testing.T) { + keyBytes := base58.Decode(tc.keyB58) + // append G1G2 public keys for Creation of DIDKey for BLS12381g1g2PubKeyMultiCodec + if tc.keyCode == fingerprint.BLS12381g1g2PubKeyMultiCodec { + g1Bytes := base58.Decode(bbsPubKeyG1Base58) + keyBytes = append(g1Bytes, keyBytes...) + } + + didKey, keyID := fingerprint.CreateDIDKeyByCode(tc.keyCode, keyBytes) + + require.Equal(t, tc.DIDKey, didKey) + require.Equal(t, tc.DIDKeyID, keyID) + }) + + t.Run(tc.name+" PubKeyFromFingerprint success", func(t *testing.T) { + pubKey, code, err := fingerprint.PubKeyFromFingerprint(strings.Split(tc.DIDKeyID, "#")[1]) + require.Equal(t, tc.keyCode, code) + require.NoError(t, err) + + require.Equal(t, base58.Encode(pubKey), tc.keyB58) + }) + + t.Run(tc.name+" PubKeyFromDIDKey", func(t *testing.T) { + pubKey, err := fingerprint.PubKeyFromDIDKey(tc.DIDKey) + if tc.crv != nil { + mKeyCompressed := append([]byte{4}, pubKey...) + x, y := elliptic.Unmarshal(tc.crv, mKeyCompressed) + mKey := elliptic.Marshal(tc.crv, x, y) + require.EqualValues(t, mKeyCompressed, mKey) + } + + require.Equal(t, tc.keyB58, base58.Encode(pubKey)) + require.NoError(t, err) + }) + } + + t.Run("test PubKeyFromFingerprint fail", func(t *testing.T) { + badDIDKeyID := "AB" + strings.Split(edExpectedDIDKeyID, "#")[1][2:] + + _, _, err := fingerprint.PubKeyFromFingerprint(badDIDKeyID) + require.EqualError(t, err, "unknown key encoding") + }) + + t.Run("invalid fingerprint", func(t *testing.T) { + _, _, err := fingerprint.PubKeyFromFingerprint("") + require.Error(t, err) + + _, _, err = fingerprint.PubKeyFromFingerprint("a6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH") + require.Error(t, err) + }) +} + +func readBigInt(t *testing.T, b64 string) *big.Int { + buf, err := base64.RawURLEncoding.DecodeString(b64) + require.Nil(t, err, "can't parse string as b64: %v\n%s", err, b64) + + var x big.Int + x = *x.SetBytes(buf) + + return &x +} + +func TestCreateDIDKeyByJwk(t *testing.T) { + tests := []struct { + name string + kty string + curve elliptic.Curve + valB58 string + x string + y string + DIDKey string + DIDKeyID string + }{ + { + name: "test Ed25519", + kty: "OKP", + valB58: "B12NYF8RrR3h41TDCTJojY59usg3mbtbjnFs7Eud1Y6u", + DIDKey: "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH", + DIDKeyID: "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH", //nolint:lll + }, + { + name: "test X25519", + kty: "OKP", + valB58: "4Dy8E9UaZscuPUf2GLxV44RCNL7oxmEXXkgWXaug1WKV", + DIDKey: "did:key:z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F", + DIDKeyID: "did:key:z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F#z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F", //nolint:lll + }, + { + name: "test P-256", + kty: "EC", + curve: elliptic.P256(), + x: "igrFmi0whuihKnj9R3Om1SoMph72wUGeFaBbzG2vzns", + y: "efsX5b10x8yjyrj4ny3pGfLcY7Xby1KzgqOdqnsrJIM", + DIDKey: "did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv", + DIDKeyID: "did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv#zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv", //nolint:lll + }, + { + name: "test P-384", + kty: "EC", + curve: elliptic.P384(), + x: "lInTxl8fjLKp_UCrxI0WDklahi-7-_6JbtiHjiRvMvhedhKVdHBfi2HCY8t_QJyc", + y: "y6N1IC-2mXxHreETBW7K3mBcw0qGr3CWHCs-yl09yCQRLcyfGv7XhqAngHOu51Zv", + DIDKey: "did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9", + DIDKeyID: "did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9#z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9", //nolint:lll + }, + { + name: "test P-521", + kty: "EC", + curve: elliptic.P521(), + x: "ASUHPMyichQ0QbHZ9ofNx_l4y7luncn5feKLo3OpJ2nSbZoC7mffolj5uy7s6KSKXFmnNWxGJ42IOrjZ47qqwqyS", + y: "AW9ziIC4ZQQVSNmLlp59yYKrjRY0_VqO-GOIYQ9tYpPraBKUloEId6cI_vynCzlZWZtWpgOM3HPhYEgawQ703RjC", + DIDKey: "did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7", + DIDKeyID: "did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7#z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7", //nolint:lll + }, + { + name: "test EC with invalid curve", + kty: "EC", + curve: &elliptic.CurveParams{}, + x: "ASUHPMyichQ0QbHZ9ofNx_l4y7luncn5feKLo3OpJ2nSbZoC7mffolj5uy7s6KSKXFmnNWxGJ42IOrjZ47qqwqyS", + y: "AW9ziIC4ZQQVSNmLlp59yYKrjRY0_VqO-GOIYQ9tYpPraBKUloEId6cI_vynCzlZWZtWpgOM3HPhYEgawQ703RjC", + DIDKey: "did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7", + DIDKeyID: "did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7#z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7", //nolint:lll + }, + } + + for _, test := range tests { + tc := test + + var ( + jwkKey *jwk.JWK + err error + ) + + t.Run(tc.name+" CreateDIDKeyByJwk", func(t *testing.T) { + switch tc.name { + case "test Ed25519": + edKey := ed25519.PublicKey(base58.Decode(tc.valB58)) + jwkKey = jwksupport.FromEdPublicKey(edKey) + require.NoError(t, err) + case "test X25519": + jwkKey, err = jwksupport.JWKFromX25519Key(base58.Decode(tc.valB58)) + require.NoError(t, err) + default: + x := readBigInt(t, tc.x) + y := readBigInt(t, tc.y) + publicKey := jwksupport.EcdsaContent{ + Curve: tc.curve, + X: x, + Y: y, + } + + jwkKey, err = jwksupport.FromEcdsaContent(publicKey) + + if tc.name == "test EC with invalid curve" { + require.EqualError(t, err, "unsupported/unknown elliptic curve") + jwkKey = &jwk.JWK{ + Kty: "EC", + Crv: "invalid", + } + } else { + require.NoError(t, err) + } + } + + didKey, keyID, err := jwksupport.CreateDIDKeyByJwk(jwkKey) + + if tc.name == "test EC with invalid curve" { + require.EqualError(t, err, "unsupported crv invalid") + return + } + + require.NoError(t, err) + require.Equal(t, tc.DIDKey, didKey) + require.Equal(t, tc.DIDKeyID, keyID) + }) + } + + t.Run("nil input", func(t *testing.T) { + _, _, err := jwksupport.CreateDIDKeyByJwk(nil) + require.Error(t, err) + require.Contains(t, err.Error(), "jsonWebKey is required") + }) + + t.Run("test invalid type", func(t *testing.T) { + jwkKey := jwk.JWK{ + Kty: "XX", + Crv: elliptic.P256().Params().Name, + } + _, _, err := jwksupport.CreateDIDKeyByJwk(&jwkKey) + require.Error(t, err) + require.Contains(t, err.Error(), "unsupported kty") + }) +} + +func TestDIDKeyEd25519(t *testing.T) { + const ( + k1 = "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH" + k1Base58 = "B12NYF8RrR3h41TDCTJojY59usg3mbtbjnFs7Eud1Y6u" + k1KeyID = "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH" //nolint:lll + ) + + didKey, keyID := fingerprint.CreateED25519DIDKey(base58.Decode(k1Base58)) + + require.Equal(t, didKey, k1) + require.Equal(t, keyID, k1KeyID) + + pubKey, err := fingerprint.PubKeyFromDIDKey(k1) + require.Equal(t, k1Base58, base58.Encode(pubKey)) + require.NoError(t, err) +} + +func TestDIDKeyX25519(t *testing.T) { + const ( + x25519DIDKey = "did:key:z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F" + x25519Base58 = "4Dy8E9UaZscuPUf2GLxV44RCNL7oxmEXXkgWXaug1WKV" + keyIDX25519 = "did:key:z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F#z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F" //nolint:lll + ) + + didKey, keyID := fingerprint.CreateDIDKeyByCode(fingerprint.X25519PubKeyMultiCodec, base58.Decode(x25519Base58)) + + require.Equal(t, x25519DIDKey, didKey) + require.Equal(t, keyID, keyIDX25519) + + pubKey, err := fingerprint.PubKeyFromDIDKey(x25519DIDKey) + require.NoError(t, err) + require.Equal(t, x25519Base58, base58.Encode(pubKey)) +} + +func TestPubKeyFromDIDKeyFailure(t *testing.T) { + _, err := fingerprint.PubKeyFromDIDKey("did:key:****") + require.EqualError(t, err, "pubKeyFromDIDKey: MethodIDFromDIDKey: not a valid did:key identifier "+ + "(not a base58btc multicodec): did:key:****") +} diff --git a/doc/fingerprint/parse.go b/doc/fingerprint/parse.go new file mode 100644 index 0000000..02ad92b --- /dev/null +++ b/doc/fingerprint/parse.go @@ -0,0 +1,38 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package fingerprint + +import ( + "fmt" + "strings" +) + +// MethodIDFromDIDKey parses the did:key DID and returns it's specific Method ID. +func MethodIDFromDIDKey(didKey string) (string, error) { + msID, err := getMethodSpecificID(didKey) + if err != nil { + return "", err + } + + // did:key is hard-coded to base58btc: + // - https://w3c-ccg.github.io/did-method-key/ + // - https://github.com/multiformats/multibase#multibase-table + if !strings.HasPrefix(msID, "z") { + return "", fmt.Errorf("not a valid did:key identifier (not a base58btc multicodec): %s", didKey) + } + + return msID, nil +} + +func getMethodSpecificID(did string) (string, error) { + parts := strings.SplitN(did, ":", 3) + + if len(parts) < 3 { + return "", fmt.Errorf("invalid did") + } + + return parts[2], nil +} diff --git a/doc/fingerprint/parse_test.go b/doc/fingerprint/parse_test.go new file mode 100644 index 0000000..4425a22 --- /dev/null +++ b/doc/fingerprint/parse_test.go @@ -0,0 +1,123 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package fingerprint_test + +import ( + "testing" + + "github.com/btcsuite/btcutil/base58" + "github.com/stretchr/testify/require" + + "github.com/trustbloc/kms-go/doc/util/fingerprint" +) + +func TestParseCreateDIDKey(t *testing.T) { + const ( + edPubKeyBase58 = "B12NYF8RrR3h41TDCTJojY59usg3mbtbjnFs7Eud1Y6u" + edExpectedDIDKey = "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH" + edExpectedDIDKeyID = "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH" //nolint:lll + + bbsPubKeyBase58 = "25EEkQtcLKsEzQ6JTo9cg4W7NHpaurn4Wg6LaNPFq6JQXnrP91SDviUz7KrJVMJd76CtAZFsRLYzvgX2JGxo2ccUHtuHk7ELCWwrkBDfrXCFVfqJKDootee9iVaF6NpdJtBE" //nolint:lll + bbsExpectedDIDKey = "did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY" //nolint:lll + bbsExpectedDIDKeyID = "did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY#zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY" //nolint:lll + + ecP256PubKeyBase58 = "3YRwdf868zp2t8c4oT4XdYfCihMsfR1zrVYyXS5SS4FwQ7wftDfoY5nohvhdgSk9LxyfzjTLzffJPmHgFBqizX9v" + ecP256ExpectedDIDKey = "did:key:zrurwcJZss4ruepVNu1H3xmSirvNbzgBk9qrCktB6kaewXnJAhYWwtP3bxACqBpzjZdN7TyHNzzGGSSH5qvZsSDir9z" //nolint:lll + ecP256ExpectedDIDKeyID = "did:key:zrurwcJZss4ruepVNu1H3xmSirvNbzgBk9qrCktB6kaewXnJAhYWwtP3bxACqBpzjZdN7TyHNzzGGSSH5qvZsSDir9z#zrurwcJZss4ruepVNu1H3xmSirvNbzgBk9qrCktB6kaewXnJAhYWwtP3bxACqBpzjZdN7TyHNzzGGSSH5qvZsSDir9z" //nolint:lll + + ecP384PubKeyBase58 = "tAjHMcvoBXs3BSihDV85trHmstc3V3vTP7o2Si72eCWdVzeGgGvRd8h5neHEbqSL989h53yNj7M7wHckB2bKpGKQjnPDD7NphDa9nUUBggCB6aCWterfdXbH5DfWPZx5oXU" //nolint:lll + ecP384ExpectedDIDKey = "did:key:zFwfeyrSyWdksRYykTGGtagWazFB5zS4CjQcxDMQSNmCTQB5QMqokx2VJz4vBB2hN1nUrYDTuYq3kd1BM5cUCfFD4awiNuzEBuoy6rZZTMCsZsdvWkDXY6832qcAnzE7YGw43KU" //nolint:lll + ecP384ExpectedDIDKeyID = "did:key:zFwfeyrSyWdksRYykTGGtagWazFB5zS4CjQcxDMQSNmCTQB5QMqokx2VJz4vBB2hN1nUrYDTuYq3kd1BM5cUCfFD4awiNuzEBuoy6rZZTMCsZsdvWkDXY6832qcAnzE7YGw43KU#zFwfeyrSyWdksRYykTGGtagWazFB5zS4CjQcxDMQSNmCTQB5QMqokx2VJz4vBB2hN1nUrYDTuYq3kd1BM5cUCfFD4awiNuzEBuoy6rZZTMCsZsdvWkDXY6832qcAnzE7YGw43KU" //nolint:lll + + ecP521PubKeyBase58 = "mTQ9pPr2wkKdiTHhVG7xmLwyJ5mrgq1FKcHFz2XJprs4zAPtjXWFiEz6vsscbseSEzGdjAVzcUhwdodT5cbrRjQqFdz8d1yYVqMHXsVCdCUrmWNNHcZLJeYCn1dCtQX9YRVdDFfnzczKFxDXe9HusLqBWTobbxVvdj9cTi7rSWVznP5Emfo" //nolint:lll + ecP521ExpectedDIDKey = "did:key:zWGhj2NTyCiehTPioanYSuSrfB7RJKwZj6bBUDNojfGEA21nr5NcBsHme7hcVSbptpWKarJpTcw814J3X8gVU9gZmeKM27JpGA5wNMzt8JZwjDyf8EzCJg5ve5GR2Xfm7d9Djp73V7s35KPeKe7VHMzmL8aPw4XBniNej5sXapPFoBs5R8m195HK" //nolint:lll + ecP521ExpectedDIDKeyID = "did:key:zWGhj2NTyCiehTPioanYSuSrfB7RJKwZj6bBUDNojfGEA21nr5NcBsHme7hcVSbptpWKarJpTcw814J3X8gVU9gZmeKM27JpGA5wNMzt8JZwjDyf8EzCJg5ve5GR2Xfm7d9Djp73V7s35KPeKe7VHMzmL8aPw4XBniNej5sXapPFoBs5R8m195HK#zWGhj2NTyCiehTPioanYSuSrfB7RJKwZj6bBUDNojfGEA21nr5NcBsHme7hcVSbptpWKarJpTcw814J3X8gVU9gZmeKM27JpGA5wNMzt8JZwjDyf8EzCJg5ve5GR2Xfm7d9Djp73V7s35KPeKe7VHMzmL8aPw4XBniNej5sXapPFoBs5R8m195HK" //nolint:lll + + bbsPubKeyG2Base58 = "26jjNXrWtHvbrVaiYBKcFRkCvzyTUfg1W4odspRJjfQRfoT33jr91dEn2wqzaWVVVw1WmFwpGxrioYvy3sbvgphfu2D4nJUvrmQ7ZtoykgXA4EuJhmmV3TnnfHnBkKKBWn5q" //nolint:lll + bbsExpectedG1G2DIDKey = "did:key:z5TcDLDFhBEndYdwFKkQMgVTgtRHx2sniQisVxdiXZ96pcrRy2ehWvcHfhSrfDmozq8dQNxhu2u7y9FUKJ8R3VPZNPjEgsozTSx47WysNM9GESUMmyniFxbdbpxNdocx6SbRyf6nBTFzoXojbWjSsDN4LhNz1sAMzTXgh5HvLYtYzJXo1JtLZBwHgmvtWyEQqtxtjV2eo" //nolint:lll + bbsExpectedG1G2DIDKeyID = "did:key:z5TcDLDFhBEndYdwFKkQMgVTgtRHx2sniQisVxdiXZ96pcrRy2ehWvcHfhSrfDmozq8dQNxhu2u7y9FUKJ8R3VPZNPjEgsozTSx47WysNM9GESUMmyniFxbdbpxNdocx6SbRyf6nBTFzoXojbWjSsDN4LhNz1sAMzTXgh5HvLYtYzJXo1JtLZBwHgmvtWyEQqtxtjV2eo#z5TcDLDFhBEndYdwFKkQMgVTgtRHx2sniQisVxdiXZ96pcrRy2ehWvcHfhSrfDmozq8dQNxhu2u7y9FUKJ8R3VPZNPjEgsozTSx47WysNM9GESUMmyniFxbdbpxNdocx6SbRyf6nBTFzoXojbWjSsDN4LhNz1sAMzTXgh5HvLYtYzJXo1JtLZBwHgmvtWyEQqtxtjV2eo" //nolint:lll + ) + + tests := []struct { + name string + keyB58 string + DIDKey string + DIDKeyID string + keyCode uint64 + }{ + { + name: "test ED25519", + keyB58: edPubKeyBase58, + DIDKey: edExpectedDIDKey, + DIDKeyID: edExpectedDIDKeyID, + }, + { + name: "test BBS+", + keyB58: bbsPubKeyBase58, + DIDKey: bbsExpectedDIDKey, + DIDKeyID: bbsExpectedDIDKeyID, + }, + { + name: "test P-256", + keyB58: ecP256PubKeyBase58, + DIDKey: ecP256ExpectedDIDKey, + DIDKeyID: ecP256ExpectedDIDKeyID, + }, + { + name: "test P-384", + keyB58: ecP384PubKeyBase58, + DIDKey: ecP384ExpectedDIDKey, + DIDKeyID: ecP384ExpectedDIDKeyID, + }, + { + name: "test P-521", + keyB58: ecP521PubKeyBase58, + DIDKey: ecP521ExpectedDIDKey, + DIDKeyID: ecP521ExpectedDIDKeyID, + }, + { + name: "test BBS+ with G1G2", + keyB58: bbsPubKeyG2Base58, + DIDKey: bbsExpectedG1G2DIDKey, + DIDKeyID: bbsExpectedG1G2DIDKeyID, + }, + } + + for _, test := range tests { + tc := test + t.Run(tc.name+" PubKeyFromDIDKey", func(t *testing.T) { + methodID, err := fingerprint.MethodIDFromDIDKey(tc.DIDKey) + require.EqualValues(t, tc.DIDKey[8:], methodID) + require.NoError(t, err) + }) + } +} + +func TestParseDIDKeyEd25519(t *testing.T) { + const ( + k1 = "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH" + k1Base58 = "B12NYF8RrR3h41TDCTJojY59usg3mbtbjnFs7Eud1Y6u" + k1KeyID = "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH" //nolint:lll + ) + + didKey, keyID := fingerprint.CreateDIDKey(base58.Decode(k1Base58)) + + require.Equal(t, didKey, k1) + require.Equal(t, keyID, k1KeyID) + + methodID, err := fingerprint.MethodIDFromDIDKey(k1) + require.EqualValues(t, k1[8:], methodID) + require.NoError(t, err) +} + +func TestMethodIDFromDIDKeyFailure(t *testing.T) { + _, err := fingerprint.MethodIDFromDIDKey("did:key:****") + require.EqualError(t, err, "not a valid did:key identifier (not a base58btc multicodec): did:key:****") + + _, err = fingerprint.MethodIDFromDIDKey("did:key:x6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH") + require.EqualError(t, err, "not a valid did:key identifier (not a base58btc multicodec): "+ + "did:key:x6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH") +} diff --git a/doc/jose/jwk/jwk.go b/doc/jose/jwk/jwk.go index 81d3e1e..f0b3e2a 100644 --- a/doc/jose/jwk/jwk.go +++ b/doc/jose/jwk/jwk.go @@ -7,502 +7,40 @@ SPDX-License-Identifier: Apache-2.0 package jwk import ( - "crypto/ecdsa" "crypto/elliptic" - "crypto/rsa" - "crypto/x509" "encoding/base64" "encoding/json" "errors" - "fmt" "math/big" "strings" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/go-jose/go-jose/v3" - "github.com/trustbloc/bbs-signature-go/bbs12381g2pub" - "golang.org/x/crypto/ed25519" - - "github.com/trustbloc/kms-go/spi/kms" - "github.com/trustbloc/kms-go/util/cryptoutil" ) const ( - secp256k1Alg = "ES256K" - secp256k1Crv = "secp256k1" - secp256k1Size = 32 - bitsPerByte = 8 - ecKty = "EC" - okpKty = "OKP" - x25519Crv = "X25519" - ed25519Crv = "Ed25519" - bls12381G2Crv = "BLS12381_G2" - bls12381G2Size = 96 - blsComprPrivSz = 32 + bitsPerByte = 8 ) -// JWK (JSON Web Key) is a JSON data structure that represents a cryptographic key. +// JWK represents a public or private key in JWK format, used for parsing/serializing. type JWK struct { - jose.JSONWebKey - - Kty string - Crv string -} - -// PublicKeyBytes converts a public key to bytes. -// Note: the Public() member function is in go-jose, this means keys not supported by go-jose are not supported using -// j.Public(). Instead use this function to get the public raw bytes. -func (j *JWK) PublicKeyBytes() ([]byte, error) { //nolint:gocyclo - if j.isBLS12381G2() { - switch bbsKey := j.Key.(type) { - case *bbs12381g2pub.PrivateKey: - return bbsKey.PublicKey().Marshal() - case *bbs12381g2pub.PublicKey: - return bbsKey.Marshal() - } - } - - if j.isX25519() { - x25519Key, ok := j.Key.([]byte) - if !ok { - return nil, fmt.Errorf("invalid public key in kid '%s'", j.KeyID) - } - - return x25519Key, nil - } - - if j.isSecp256k1() { - var ecPubKey *ecdsa.PublicKey - - ecPubKey, ok := j.Key.(*ecdsa.PublicKey) - if !ok { - ecPubKey = &j.Key.(*ecdsa.PrivateKey).PublicKey - } - - x := &btcec.FieldVal{} - x.SetByteSlice(ecPubKey.X.Bytes()) - - y := &btcec.FieldVal{} - y.SetByteSlice(ecPubKey.Y.Bytes()) - - pubKey := btcec.NewPublicKey(x, y) - - return pubKey.SerializeCompressed(), nil - } - - switch pubKey := j.Public().Key.(type) { - case ed25519.PublicKey: - return pubKey, nil - case *ecdsa.PublicKey: - return elliptic.Marshal(pubKey, pubKey.X, pubKey.Y), nil - case *rsa.PublicKey: - return x509.MarshalPKCS1PublicKey(pubKey), nil - default: - return nil, fmt.Errorf("unsupported public key type in kid '%s'", j.KeyID) - } -} - -// UnmarshalJSON reads a key from its JSON representation. -func (j *JWK) UnmarshalJSON(jwkBytes []byte) error { - var key jsonWebKey - - marshalErr := json.Unmarshal(jwkBytes, &key) - if marshalErr != nil { - return fmt.Errorf("unable to read JWK: %w", marshalErr) - } - - // nolint: gocritic, nestif - 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 if isBLS12381G2(key.Kty, key.Crv) { - jwk, err := unmarshalBLS12381G2(&key) - if err != nil { - return fmt.Errorf("unable to read BBS+ JWE: %w", err) - } - - *j = *jwk - } else if isX25519(key.Kty, key.Crv) { - jwk, err := unmarshalX25519(&key) - if err != nil { - return fmt.Errorf("unable to read X25519 JWE: %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 - } - - j.Kty = key.Kty - j.Crv = key.Crv - - return nil -} - -// MarshalJSON serializes the given key to its JSON representation. -func (j *JWK) MarshalJSON() ([]byte, error) { - if j.isSecp256k1() { - return marshalSecp256k1(j) - } - - if j.isX25519() { - return marshalX25519(j) - } - - if j.isBLS12381G2() { - return marshalBLS12381G2(j) - } - - return (&j.JSONWebKey).MarshalJSON() -} - -// KeyType returns the kms KeyType of the JWK, or an error if the JWK is of an unrecognized type. -func (j *JWK) KeyType() (kms.KeyType, error) { - switch key := j.Key.(type) { - case ed25519.PublicKey, ed25519.PrivateKey: - return kms.ED25519Type, nil - case *bbs12381g2pub.PublicKey, *bbs12381g2pub.PrivateKey: - return kms.BLS12381G2Type, nil - case *ecdsa.PublicKey: - return ecdsaPubKeyType(key) - case *ecdsa.PrivateKey: - return ecdsaPubKeyType(&(key.PublicKey)) - case *rsa.PublicKey, *rsa.PrivateKey: - return kms.RSAPS256Type, nil - } - - switch { - case isX25519(j.Kty, j.Crv): - return kms.X25519ECDHKWType, nil - case isEd25519(j.Kty, j.Crv): - return kms.ED25519Type, nil - case isSecp256k1(j.Algorithm, j.Kty, j.Crv): - return kms.ECDSASecp256k1TypeIEEEP1363, nil - default: - return "", fmt.Errorf("no keytype recognized for jwk") - } -} - -func ecdsaPubKeyType(pub *ecdsa.PublicKey) (kms.KeyType, error) { - switch pub.Curve { - case btcec.S256(): - return kms.ECDSASecp256k1TypeIEEEP1363, nil - case elliptic.P256(): - return kms.ECDSAP256TypeIEEEP1363, nil - case elliptic.P384(): - return kms.ECDSAP384TypeIEEEP1363, nil - case elliptic.P521(): - return kms.ECDSAP521TypeIEEEP1363, nil - } - - return "", fmt.Errorf("no keytype recognized for ecdsa jwk") -} - -func (j *JWK) isX25519() bool { - switch j.Key.(type) { - case []byte: - return isX25519(j.Kty, j.Crv) - default: - return false - } -} - -func (j *JWK) isBLS12381G2() bool { - switch j.Key.(type) { - case *bbs12381g2pub.PublicKey, *bbs12381g2pub.PrivateKey: - return true - default: - return false - } -} - -func (j *JWK) isSecp256k1() bool { - return isSecp256k1Key(j.Key) || isSecp256k1(j.Algorithm, j.Kty, j.Crv) -} - -func isSecp256k1Key(pubKey interface{}) bool { - switch key := pubKey.(type) { - case *ecdsa.PublicKey: - return key.Curve == btcec.S256() - case *ecdsa.PrivateKey: - return key.Curve == btcec.S256() - default: - return false - } -} - -func isX25519(kty, crv string) bool { - return strings.EqualFold(kty, okpKty) && strings.EqualFold(crv, x25519Crv) -} - -func isEd25519(kty, crv string) bool { - return strings.EqualFold(kty, okpKty) && strings.EqualFold(crv, ed25519Crv) -} - -func isBLS12381G2(kty, crv string) bool { - return strings.EqualFold(kty, ecKty) && strings.EqualFold(crv, bls12381G2Crv) -} - -func isSecp256k1(alg, kty, crv string) bool { - return strings.EqualFold(alg, secp256k1Alg) || - (strings.EqualFold(kty, ecKty) && strings.EqualFold(crv, secp256k1Crv)) -} - -func unmarshalSecp256k1(jwk *jsonWebKey) (*JWK, error) { - if jwk.X == nil { - return nil, ErrInvalidKey - } - - if jwk.Y == nil { - return nil, ErrInvalidKey - } - - curve := btcec.S256() - - if curveSize(curve) != len(jwk.X.data) { - return nil, ErrInvalidKey - } - - if curveSize(curve) != len(jwk.Y.data) { - return nil, ErrInvalidKey - } - - if jwk.D != nil && dSize(curve) != len(jwk.D.data) { - return nil, ErrInvalidKey - } - - x := jwk.X.bigInt() - y := jwk.Y.bigInt() - - if !curve.IsOnCurve(x, y) { - return nil, ErrInvalidKey - } - - 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 unmarshalX25519(jwk *jsonWebKey) (*JWK, error) { - if jwk.X == nil { - return nil, ErrInvalidKey - } - - if len(jwk.X.data) != cryptoutil.Curve25519KeySize { - return nil, ErrInvalidKey - } - - return &JWK{ - JSONWebKey: jose.JSONWebKey{ - Key: jwk.X.data, KeyID: jwk.Kid, Algorithm: jwk.Alg, Use: jwk.Use, - }, - Crv: jwk.Crv, - Kty: jwk.Kty, - }, nil -} - -func marshalX25519(jwk *JWK) ([]byte, error) { - var raw jsonWebKey - - key, ok := jwk.Key.([]byte) - if !ok { - return nil, errors.New("marshalX25519: invalid key") - } - - if len(key) != cryptoutil.Curve25519KeySize { - return nil, errors.New("marshalX25519: invalid key") - } - - raw = jsonWebKey{ - Kty: okpKty, - Crv: x25519Crv, - X: newFixedSizeBuffer(key, cryptoutil.Curve25519KeySize), - } - - raw.Kid = jwk.KeyID - raw.Alg = jwk.Algorithm - raw.Use = jwk.Use - - return json.Marshal(raw) -} - -func unmarshalBLS12381G2(jwk *jsonWebKey) (*JWK, error) { - if jwk.X == nil { - return nil, ErrInvalidKey - } - - if len(jwk.X.data) != bls12381G2Size { - return nil, ErrInvalidKey - } - - if jwk.D != nil && blsComprPrivSz != len(jwk.D.data) { - return nil, ErrInvalidKey - } - - var ( - key interface{} - err error - ) - - if jwk.D != nil { - key, err = bbs12381g2pub.UnmarshalPrivateKey(jwk.D.data) - if err != nil { - return nil, fmt.Errorf("jwk invalid private key unmarshal: %w", err) - } - } else { - key, err = bbs12381g2pub.UnmarshalPublicKey(jwk.X.data) - if err != nil { - return nil, fmt.Errorf("jwk invalid public key unmarshal: %w", err) - } - } - - return &JWK{ - JSONWebKey: jose.JSONWebKey{ - Key: key, KeyID: jwk.Kid, Algorithm: jwk.Alg, Use: jwk.Use, - }, - Crv: jwk.Crv, - Kty: jwk.Kty, - }, nil -} - -func marshalBLS12381G2(jwk *JWK) ([]byte, error) { - var raw jsonWebKey - - switch key := jwk.Key.(type) { - case *bbs12381g2pub.PublicKey: - mKey, err := key.Marshal() - if err != nil { - return nil, err - } - - if len(mKey) != bls12381G2Size { - return nil, errors.New("marshal BBS public key: invalid key") - } - - raw = jsonWebKey{ - Kty: ecKty, - Crv: bls12381G2Crv, - X: newFixedSizeBuffer(mKey, bls12381G2Size), - } - case *bbs12381g2pub.PrivateKey: - mPubKey, err := key.PublicKey().Marshal() - if err != nil { - return nil, err - } - - if len(mPubKey) != bls12381G2Size { - return nil, errors.New("marshal BBS public key: invalid key") - } - - mPrivKey, err := key.Marshal() - if err != nil { - return nil, err - } - - if len(mPrivKey) != blsComprPrivSz { - return nil, errors.New("marshal BBS private key: invalid key") - } - - raw = jsonWebKey{ - Kty: ecKty, - Crv: bls12381G2Crv, - X: newFixedSizeBuffer(mPubKey, bls12381G2Size), - D: newFixedSizeBuffer(mPrivKey, blsComprPrivSz), - } - default: - return nil, errors.New("marshalBLS12381G2: invalid key") - } - - raw.Kid = jwk.KeyID - raw.Alg = jwk.Algorithm - raw.Use = jwk.Use - - return json.Marshal(raw) -} - -func marshalSecp256k1(jwk *JWK) ([]byte, error) { - var raw jsonWebKey - - switch ecdsaKey := jwk.Key.(type) { - case *ecdsa.PublicKey: - raw = jsonWebKey{ - Kty: ecKty, - Crv: secp256k1Crv, - X: newFixedSizeBuffer(ecdsaKey.X.Bytes(), secp256k1Size), - Y: newFixedSizeBuffer(ecdsaKey.Y.Bytes(), secp256k1Size), - } - - case *ecdsa.PrivateKey: - raw = jsonWebKey{ - Kty: ecKty, - Crv: secp256k1Crv, - X: newFixedSizeBuffer(ecdsaKey.X.Bytes(), secp256k1Size), - Y: newFixedSizeBuffer(ecdsaKey.Y.Bytes(), secp256k1Size), - D: newFixedSizeBuffer(ecdsaKey.D.Bytes(), dSize(ecdsaKey.Curve)), - } - } - - raw.Kid = jwk.KeyID - raw.Alg = jwk.Algorithm - raw.Use = jwk.Use - - return json.Marshal(raw) -} - -// JSONWebKey represents a public or private key in JWK format, used for parsing/serializing. -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"` - K *byteBuffer `json:"k,omitempty"` - X *byteBuffer `json:"x,omitempty"` - Y *byteBuffer `json:"y,omitempty"` - N *byteBuffer `json:"n,omitempty"` - E *byteBuffer `json:"e,omitempty"` + Use string `json:"use,omitempty"` + Kty string `json:"kty,omitempty"` + KeyID string `json:"kid,omitempty"` + Crv string `json:"crv,omitempty"` + Alg string `json:"alg,omitempty"` + K *ByteBuffer `json:"k,omitempty"` + X *ByteBuffer `json:"x,omitempty"` + Y *ByteBuffer `json:"y,omitempty"` + N *ByteBuffer `json:"n,omitempty"` + E *ByteBuffer `json:"e,omitempty"` // -- Following fields are only used for private keys -- // RSA uses D, P and Q, while ECDSA uses only D. Fields Dp, Dq, and Qi are // completely optional. Therefore for RSA/ECDSA, D != nil is a contract that // we have a private key whereas D == nil means we have only a public key. - D *byteBuffer `json:"d,omitempty"` - P *byteBuffer `json:"p,omitempty"` - Q *byteBuffer `json:"q,omitempty"` - Dp *byteBuffer `json:"dp,omitempty"` - Dq *byteBuffer `json:"dq,omitempty"` - Qi *byteBuffer `json:"qi,omitempty"` + D *ByteBuffer `json:"d,omitempty"` + P *ByteBuffer `json:"p,omitempty"` + Q *ByteBuffer `json:"q,omitempty"` + Dp *ByteBuffer `json:"dp,omitempty"` + Dq *ByteBuffer `json:"dq,omitempty"` + Qi *ByteBuffer `json:"qi,omitempty"` // Certificates X5c []string `json:"x5c,omitempty"` X5u string `json:"x5u,omitempty"` @@ -510,7 +48,7 @@ type JSONWebKey struct { X5tSHA256 string `json:"x5t#S256,omitempty"` } -// Get size of curve in bytes. +// Get size of curve in Bytes. func curveSize(crv elliptic.Curve) int { bits := crv.Params().BitSize @@ -524,26 +62,31 @@ 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 +// ByteBuffer represents a slice of Bytes that can be serialized to url-safe Base64. +type ByteBuffer struct { + data []byte +} - if bitLen%bitsPerByte != 0 { - size++ +func NewBuffer(data []byte) *ByteBuffer { + return &ByteBuffer{ + data: data, } +} - return size +func NewFixedSizeBuffer(data []byte, length int) *ByteBuffer { + if len(data) > length { + panic("go-jose/go-jose: invalid call to newFixedSizeBuffer (len(data) > length)") + } + pad := make([]byte, length-len(data)) + return NewBuffer(append(pad, data...)) } -// byteBuffer represents a slice of bytes that can be serialized to url-safe base64. -type byteBuffer struct { - data []byte +func (b *ByteBuffer) MarshalJSON() ([]byte, error) { + return json.Marshal(b.Base64()) } -func (b *byteBuffer) UnmarshalJSON(data []byte) error { +func (b *ByteBuffer) UnmarshalJSON(data []byte) error { var encoded string - err := json.Unmarshal(data, &encoded) if err != nil { return err @@ -553,36 +96,32 @@ func (b *byteBuffer) UnmarshalJSON(data []byte) error { return nil } - decoded, err := base64.RawURLEncoding.DecodeString(encoded) + decoded, err := base64URLDecode(encoded) if err != nil { return err } - *b = byteBuffer{ - data: decoded, - } + *b = *NewBuffer(decoded) return nil } -func (b *byteBuffer) MarshalJSON() ([]byte, error) { - return json.Marshal(b.base64()) +func (b *ByteBuffer) Base64() string { + return base64.RawURLEncoding.EncodeToString(b.data) } -func (b *byteBuffer) base64() string { - return base64.RawURLEncoding.EncodeToString(b.data) +func (b *ByteBuffer) Bytes() []byte { + return b.data } -func (b byteBuffer) bigInt() *big.Int { +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...), - } +// base64URLDecode is implemented as defined in https://www.rfc-editor.org/rfc/rfc7515.html#appendix-C +func base64URLDecode(value string) ([]byte, error) { + value = strings.TrimRight(value, "=") + return base64.RawURLEncoding.DecodeString(value) } // ErrInvalidKey is returned when passed JWK is invalid. diff --git a/doc/jose/jwk/jwk_private_test.go b/doc/jose/jwk/jwk_private_test.go new file mode 100644 index 0000000..8509ef3 --- /dev/null +++ b/doc/jose/jwk/jwk_private_test.go @@ -0,0 +1,23 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package jwk + +import ( + "crypto/elliptic" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/stretchr/testify/require" +) + +func TestCurveSize(t *testing.T) { + require.Equal(t, 32, curveSize(btcec.S256())) + require.Equal(t, 32, curveSize(elliptic.P256())) + require.Equal(t, 28, curveSize(elliptic.P224())) + require.Equal(t, 48, curveSize(elliptic.P384())) + require.Equal(t, 66, curveSize(elliptic.P521())) +} diff --git a/doc/jose/jwk/jwk_test.go b/doc/jose/jwk/jwk_test.go index 8f00dad..ad03fc8 100644 --- a/doc/jose/jwk/jwk_test.go +++ b/doc/jose/jwk/jwk_test.go @@ -4,360 +4,19 @@ Copyright SecureKey Technologies Inc. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -package jwk +package jwk_test import ( - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/sha256" + "encoding/json" "fmt" "testing" - "github.com/btcsuite/btcd/btcec/v2" - "github.com/go-jose/go-jose/v3" - "github.com/go-jose/go-jose/v3/json" "github.com/stretchr/testify/require" - "github.com/trustbloc/bbs-signature-go/bbs12381g2pub" - "github.com/trustbloc/kms-go/spi/kms" -) - -func TestDecodePublicKey(t *testing.T) { - t.Run("Test decode public key failure", func(t *testing.T) { - tests := []struct { - name string - jwkJSON string - err string - }{ - { - name: "attempt public key bytes from invalid JSON bytes", - jwkJSON: `}`, - err: "invalid character", - }, - { - name: "attempt public key bytes from invalid curve", - jwkJSON: `{ - "kty": "EC", - "use": "enc", - "crv": "sec12341", - "kid": "sample@sample.id", - "x": "wQehEGTVCu32yp8IwTaBCqPUIYslyd-WoFRsfDKE9II", - "y": "rIJO8RmkExUecJ5i15L9OC7rl7pwmYFR8QQgdM1ERWI", - "alg": "ES256" - }`, - err: "unsupported elliptic curve 'sec12341'", - }, - { - name: "attempt public key bytes from invalid JSON bytes", - jwkJSON: `{ - "kty": "EC", - "use": "enc", - "crv": "secp256k1", - "kid": "sample@sample.id", - "x": "", - "y": "", - "alg": "ES256" - }`, - err: "unable to read JWK: invalid JWK", - }, - { - name: "attempt public key bytes from invalid JSON bytes", - jwkJSON: `{ - "kty": "EC", - "use": "enc", - "crv": "secp256k1", - "kid": "sample@sample.id", - "x": "wQehEGTVCu32yp8IwTaBCqPUIYslyd-WoFRsfDKE9II", - "y": "", - "alg": "ES256" - }`, - err: "unable to read JWK: invalid JWK", - }, - { - name: "attempt public key bytes from invalid JSON bytes", - jwkJSON: `{ - "kty": "EC", - "use": "enc", - "crv": "secp256k1", - "kid": "sample@sample.id", - "x": "x", - "y": "y", - "alg": "ES256" - }`, - 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: "invalid JWK", - }, - { - name: "X is not defined X25519", - jwkJSON: `{ - "kty": "OKP", - "use": "enc", - "crv": "X25519", - "kid": "sample@sample.id" - }`, - err: "invalid JWK", - }, - { - name: "Y is not defined", - jwkJSON: `{ - "kty": "EC", - "use": "enc", - "crv": "secp256k1", - "kid": "sample@sample.id", - "x": "wQehEGTVCu32yp8IwTaBCqPUIYslyd-WoFRsfDKE9II", - "alg": "ES256" - }`, - err: "invalid JWK", - }, - { - name: "D is not defined", - jwkJSON: `{ - "kty": "EC", - "use": "enc", - "crv": "secp256k1", - "kid": "sample@sample.id", - "x": "wQehEGTVCu32yp8IwTaBCqPUIYslyd-WoFRsfDKE9II", - "y": "rIJO8RmkExUecJ5i15L9OC7rl7pwmYFR8QQgdM1ERWI", - "d": "", - "alg": "ES256" - }`, - err: "invalid JWK", - }, - { - name: "Y is not defined", - jwkJSON: `{ - "kty": "EC", - "use": "enc", - "crv": "secp256k1", - "kid": "sample@sample.id", - "x": "wQehEGTVCu32yp8IwTaBCqPUIYslyd-WoFRsfDKE9II", - "y": "rIJO8RmkExUecJ5i15L9OC7rl7pwmYFR8QQgdM1ERWO", - "alg": "ES256" - }`, - err: "unable to read JWK: invalid JWK", - }, - { - name: "attempt public key bytes from invalid JSON bytes", - jwkJSON: `{ - "kty": "EC", - "use": "enc", - "crv": "secp256k1", - "kid": "sample@sample.id", - "x": "{", - "y": "y", - "alg": "ES256" - }`, - err: "unable to read JWK", - }, - { - name: "invalid X25519", - jwkJSON: `{ - "kty": "OKP", - "use": "enc", - "crv": "X25519", - "x": "wQehEGTVCu32yp8IwTaBCqPUIYslyd-WoFRsfDKE9IIrIJO8RmkExUecJ5i15L9OC7rl7pwmYFR8QQgdM1ERWO", - "kid": "sample@sample.id" - }`, - err: "unable to read X25519 JWE: invalid JWK", - }, - } - - t.Parallel() - - for _, test := range tests { - tc := test - t.Run(tc.name, func(t *testing.T) { - var j JWK - err := json.Unmarshal([]byte(tc.jwkJSON), &j) - require.Error(t, err) - require.Contains(t, err.Error(), tc.err) - }) - } - }) -} - -func TestByteBufferUnmarshalFailure(t *testing.T) { - bb := &byteBuffer{} - err := bb.UnmarshalJSON([]byte("{")) - require.Error(t, err) -} - -func TestCurveSize(t *testing.T) { - require.Equal(t, 32, curveSize(btcec.S256())) - require.Equal(t, 32, curveSize(elliptic.P256())) - require.Equal(t, 28, curveSize(elliptic.P224())) - require.Equal(t, 48, curveSize(elliptic.P384())) - require.Equal(t, 66, curveSize(elliptic.P521())) -} - -func TestJWKFromX25519KeyFailure(t *testing.T) { - key := &JWK{ - JSONWebKey: jose.JSONWebKey{ - Key: "abc", // try to create an invalid X25519 key type (string instead of []byte) - }, - } - - _, err := marshalX25519(key) - require.EqualError(t, err, "marshalX25519: invalid key") - - invalidKey := make([]byte, 10) - - n, err := rand.Read(invalidKey) - require.NoError(t, err) - require.Equal(t, 10, n) - - key.Key = invalidKey // try with key larger than X25519 key length - - _, err = marshalX25519(key) - require.EqualError(t, err, "marshalX25519: invalid key") -} - -func TestJWK_PublicKeyBytesValidation(t *testing.T) { - jwk := &JWK{ - JSONWebKey: jose.JSONWebKey{ - Key: "key of invalid type", - KeyID: "pubkey#123", - }, - } - - // unsupported public key 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) -} - -func TestJWK_BBSKeyValidation(t *testing.T) { - _, privateKey, err := bbs12381g2pub.GenerateKeyPair(sha256.New, nil) - require.NoError(t, err) - - jwkKey := &JWK{ - JSONWebKey: jose.JSONWebKey{ - Key: privateKey, - }, - Kty: ecKty, - Crv: bls12381G2Crv, - } - - t.Run("test MarshalJSON/UnmarshalJSON", func(t *testing.T) { - var mJWK []byte - - mJWK, err = jwkKey.MarshalJSON() - require.NoError(t, err) - - t.Logf("marshaled JWK: %s", mJWK) - - jwk2 := &JWK{} - err = jwk2.UnmarshalJSON(mJWK) - require.NoError(t, err) - require.EqualValues(t, jwkKey, jwk2) - }) - - t.Run("test BBS private key jwk.PublicKeyBytes()", func(t *testing.T) { - var pubKeyBytes []byte - - pubKeyBytes, err = jwkKey.PublicKeyBytes() - require.NoError(t, err) - require.NotEmpty(t, pubKeyBytes) - }) - - t.Run("test UnmarshalJSON of valid BBS private key JWK - with both x and d headers", func(t *testing.T) { - //nolint:lll - goodJWK := `{ - "kty":"EC", - "crv":"BLS12381_G2", - "x":"oUd1c-NsWZy2oCaST4CRW1naLjgYY3OhHgTMie4uzgrB5VuVqx0pdYf4XWWlnEkZERnpMhgo2re4tQtdCguhI4OIGyAXFaML8D6E1ZYO8B0WmysMZUnC5BWWEfOid1lu", - "d":"MhYilAbhICa8T6m0U2gLAgLvPEsF05XN1yYHZgkfAK4" -}` - jwk4 := &JWK{} - - err = jwk4.UnmarshalJSON([]byte(goodJWK)) - require.NoError(t, err) - }) - - t.Run("test UnmarshalJSON of invalid BBS private key JWK - no x header", func(t *testing.T) { - goodJWK := `{ - "kty":"EC", - "crv":"BLS12381_G2", - "d":"MhYilAbhICa8T6m0U2gLAgLvPEsF05XN1yYHZgkfAK4" -}` - - jwk4 := &JWK{} - - err = jwk4.UnmarshalJSON([]byte(goodJWK)) - require.EqualError(t, err, "unable to read BBS+ JWE: invalid JWK") - }) - - t.Run("test UnmarshalJSON of valid BBS public key JWK", func(t *testing.T) { - //nolint:lll - goodJWK := `{ - "kty":"EC", - "crv":"BLS12381_G2", - "x":"oUd1c-NsWZy2oCaST4CRW1naLjgYY3OhHgTMie4uzgrB5VuVqx0pdYf4XWWlnEkZERnpMhgo2re4tQtdCguhI4OIGyAXFaML8D6E1ZYO8B0WmysMZUnC5BWWEfOid1lu" -}` - - jwk4 := &JWK{} - - err = jwk4.UnmarshalJSON([]byte(goodJWK)) - require.NoError(t, err) - }) - - t.Run("test UnmarshalJSON of invalid BBS public key JWK - x wrong size", func(t *testing.T) { - goodJWK := `{ - "kty":"EC", - "crv":"BLS12381_G2", - "x":"oUd1" -}` - - jwk4 := &JWK{} - - err = jwk4.UnmarshalJSON([]byte(goodJWK)) - require.EqualError(t, err, "unable to read BBS+ JWE: invalid JWK") - }) - - t.Run("test UnmarshalJSON of invalid BBS private key JWK - d wrong size", func(t *testing.T) { - //nolint:lll - goodJWK := `{ - "kty":"EC", - "crv":"BLS12381_G2", - "x":"oUd1c-NsWZy2oCaST4CRW1naLjgYY3OhHgTMie4uzgrB5VuVqx0pdYf4XWWlnEkZERnpMhgo2re4tQtdCguhI4OIGyAXFaML8D6E1ZYO8B0WmysMZUnC5BWWEfOid1lu", - "d":"MhYi" -}` - - jwk4 := &JWK{} - - err = jwk4.UnmarshalJSON([]byte(goodJWK)) - require.EqualError(t, err, "unable to read BBS+ JWE: invalid JWK") - }) - - t.Run("test UnmarshalJSON of invalid BBS public key JWK - x wrong value", func(t *testing.T) { - //nolint:lll - goodJWK := `{ - "kty":"EC", - "crv":"BLS12381_G2", - "x":"pUd1c-NsWZy2oCaST4CRW1naLjgYY3OhHgTMie4uzgrB5VuVqx0pdYf4XWWlnEkZERnpMhko2re4tQtdCguhI4OIGyAXFaML8D6E1ZYO8B0WmysMZUnC5BWWEfOhc6tv" -}` - - jwk4 := &JWK{} - - err = jwk4.UnmarshalJSON([]byte(goodJWK)) - require.EqualError(t, err, "unable to read BBS+ JWE: jwk invalid public key unmarshal: deserialize "+ - "public key: failure [set bytes failed [point is not on curve]]") - }) -} + "github.com/trustbloc/did-go/crypto-ext/jwksupport" + "github.com/trustbloc/did-go/doc/jose/jwk" +) func TestJWK_KeyType(t *testing.T) { t.Run("success: get KeyType from JWK", func(t *testing.T) { @@ -386,29 +45,6 @@ func TestJWK_KeyType(t *testing.T) { }`, keyType: kms.X25519ECDHKWType, }, - { - //nolint:lll - jwk: `{ - "kty": "EC", - "use": "enc", - "crv": "BLS12381_G2", - "kid": "sample@sample.id", - "x": "tKWJu0SOY7onl4tEyOOH11XBriQN2JgzV-UmjgBMSsNkcAx3_l97SVYViSDBouTVBkBfrLh33C5icDD-4UEDxNO3Wn1ijMHvn2N63DU4pkezA3kGN81jGbwbrsMPpiOF" - }`, - keyType: kms.BLS12381G2Type, - }, - { - jwk: `{ - "kty": "EC", - "use": "enc", - "crv": "secp256k1", - "kid": "sample@sample.id", - "x": "YRrvJocKf39GpdTnd-zBFE0msGDqawR-Cmtc6yKoFsM", - "y": "kE-dMH9S3mxnTXo0JFEhraCU_tVYFDfpu9tpP1LfVKQ", - "alg": "ES256K" - }`, - keyType: kms.ECDSASecp256k1TypeIEEEP1363, - }, { jwk: `{ "kty": "EC", @@ -444,41 +80,22 @@ func TestJWK_KeyType(t *testing.T) { }`, keyType: kms.ECDSAP521TypeIEEEP1363, }, - { - jwk: `{ - "kty": "RSA", - "e": "AQAB", - "use": "enc", - "kid": "sample@sample.id", - "alg": "RS256", - "n": "1hOl09BUnwY7jFBqoZKa4XDmIuc0YFb4y_5ThiHhLRW68aNG5Vo23n3ugND2GK3PsguZqJ_HrWCGVuVlKTmFg` + - `JWQD9ZnVcYqScgHpQRhxMBi86PIvXR01D_PWXZZjvTRakpvQxUT5bVBdWnaBHQoxDBt0YIVi5a7x-gXB1aDlts4RTMpfS9BPmEjX` + - `4lciozwS6Ow_wTO3C2YGa_Our0ptIxr-x_3sMbPCN8Fe_iaBDezeDAm39xCNjFa1E735ipXA4eUW_6SzFJ5-bM2UKba2WE6xUaEa5G1` + - `MDDHCG5LKKd6Mhy7SSAzPOR2FTKYj89ch2asCPlbjHTu8jS6Iy8" - }`, - keyType: kms.RSAPS256Type, - }, } t.Parallel() for _, testCase := range testCases { t.Run(fmt.Sprintf("KeyType %s", testCase.keyType), func(t *testing.T) { - j := JWK{} - e := j.UnmarshalJSON([]byte(testCase.jwk)) + j := &jwk.JWK{} + e := json.Unmarshal([]byte(testCase.jwk), j) require.NoError(t, e) - kt, e := j.KeyType() + _, _, e = jwksupport.CreateDIDKeyByJwk(j) require.NoError(t, e) - require.Equal(t, testCase.keyType, kt) - mJWK, err := j.MarshalJSON() + mJWK, err := json.Marshal(j) require.NoError(t, err) require.NotEmpty(t, mJWK) - - keyBytes, err := j.PublicKeyBytes() - require.NoError(t, err) - require.NotEmpty(t, keyBytes) }) } }) @@ -493,18 +110,10 @@ func TestJWK_KeyType(t *testing.T) { "alg": "EdDSA" }` - j := JWK{} - e := j.UnmarshalJSON([]byte(jwkJSON)) + j := &jwk.JWK{} + e := json.Unmarshal([]byte(jwkJSON), j) require.NoError(t, e) - k, err := j.PublicKeyBytes() - require.NoError(t, err) - - j.Key = k - - kt, e := j.KeyType() - require.NoError(t, e) - require.Equal(t, kms.ED25519Type, kt) }) t.Run("test secp256k1 with []byte key material", func(t *testing.T) { @@ -518,27 +127,43 @@ func TestJWK_KeyType(t *testing.T) { "alg": "ES256K" }` - j := JWK{} - e := j.UnmarshalJSON([]byte(jwkJSON)) + j := &jwk.JWK{} + e := json.Unmarshal([]byte(jwkJSON), j) require.NoError(t, e) + }) +} - pkb, err := j.PublicKeyBytes() - require.NoError(t, err) +func TestNewFixedSizeBuffer(t *testing.T) { + data := make([]byte, 32) + require.Len(t, jwk.NewFixedSizeBuffer(data, 48).Bytes(), 48) +} - j.Key = pkb +func TestByteBufferUnmarshal(t *testing.T) { + t.Run("Success", func(t *testing.T) { + bb := &jwk.ByteBuffer{} + err := bb.UnmarshalJSON([]byte("\"YRrvJocKf39GpdTnd-zBFE0msGDqawR-Cmtc6yKoFsM\"")) - kt, e := j.KeyType() - require.NoError(t, e) - require.Equal(t, kms.ECDSASecp256k1TypeIEEEP1363, kt) + require.NoError(t, err) + require.Len(t, bb.Bytes(), 32) }) - t.Run("fail to get ecdsa keytype for (unsupported) p-224", func(t *testing.T) { - eckey, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader) + t.Run("Empty", func(t *testing.T) { + bb := &jwk.ByteBuffer{} + err := bb.UnmarshalJSON([]byte("\"\"")) + require.NoError(t, err) + require.Empty(t, bb.Bytes()) + }) + + t.Run("Failure", func(t *testing.T) { + bb := &jwk.ByteBuffer{} + err := bb.UnmarshalJSON([]byte("{")) + require.Error(t, err) + }) - kt, err := ecdsaPubKeyType(&eckey.PublicKey) + t.Run("Failure2", func(t *testing.T) { + bb := &jwk.ByteBuffer{} + err := bb.UnmarshalJSON([]byte("\"{\"")) require.Error(t, err) - require.Contains(t, err.Error(), "no keytype recognized for ecdsa jwk") - require.Equal(t, kms.KeyType(""), kt) }) } diff --git a/doc/signature/api/api.go b/doc/signature/api/api.go index d55244b..2437018 100644 --- a/doc/signature/api/api.go +++ b/doc/signature/api/api.go @@ -9,8 +9,8 @@ package api import ( "time" + "github.com/trustbloc/did-go/doc/jose/jwk" "github.com/trustbloc/did-go/doc/ld/proof" - "github.com/trustbloc/kms-go/doc/jose/jwk" "github.com/trustbloc/did-go/doc/ld/processor" ) diff --git a/method/jwk/creator.go b/method/jwk/creator.go index b475869..a5887a3 100644 --- a/method/jwk/creator.go +++ b/method/jwk/creator.go @@ -8,11 +8,13 @@ package jwk import ( "encoding/base64" + "encoding/json" "fmt" - "github.com/trustbloc/kms-go/doc/jose/jwk" "github.com/trustbloc/sidetree-go/pkg/canonicalizer" + "github.com/trustbloc/did-go/doc/jose/jwk" + "github.com/trustbloc/did-go/doc/did" vdrapi "github.com/trustbloc/did-go/vdr/api" ) @@ -47,7 +49,7 @@ func createDID(key *jwk.JWK) (string, error) { return "", fmt.Errorf("missing JWK") } - keyBytes, err := key.MarshalJSON() + keyBytes, err := json.Marshal(key) if err != nil { return "", fmt.Errorf("marshal key: %w", err) } diff --git a/method/jwk/creator_test.go b/method/jwk/creator_test.go index 8b8ebbe..4f1ac93 100644 --- a/method/jwk/creator_test.go +++ b/method/jwk/creator_test.go @@ -13,10 +13,11 @@ import ( "testing" "github.com/stretchr/testify/require" - jwkapi "github.com/trustbloc/kms-go/doc/jose/jwk" - "github.com/trustbloc/kms-go/doc/jose/jwk/jwksupport" "github.com/trustbloc/sidetree-go/pkg/canonicalizer" + "github.com/trustbloc/did-go/crypto-ext/jwksupport" + jwkapi "github.com/trustbloc/did-go/doc/jose/jwk" + "github.com/trustbloc/did-go/doc/did" "github.com/trustbloc/did-go/method/jwk" ) @@ -121,8 +122,7 @@ func TestCreate(t *testing.T) { _, pk, err := ed25519.GenerateKey(rand.Reader) require.NoError(t, err) - key, err := jwksupport.JWKFromKey(pk) - require.NoError(t, err) + key := jwksupport.FromEdPrivateKey(pk) vm, err := did.NewVerificationMethodFromJWK("", "JsonWebKey2020", "", key) require.NoError(t, err) diff --git a/method/jwk/resolver.go b/method/jwk/resolver.go index d770bb5..14c03d9 100644 --- a/method/jwk/resolver.go +++ b/method/jwk/resolver.go @@ -11,7 +11,7 @@ import ( "encoding/json" "fmt" - "github.com/trustbloc/kms-go/doc/jose/jwk" + "github.com/trustbloc/did-go/doc/jose/jwk" "github.com/trustbloc/did-go/doc/did" vdrapi "github.com/trustbloc/did-go/vdr/api" diff --git a/method/key/creator.go b/method/key/creator.go index a371e4b..a1f678a 100644 --- a/method/key/creator.go +++ b/method/key/creator.go @@ -10,9 +10,11 @@ import ( "fmt" "time" - "github.com/trustbloc/kms-go/doc/util/fingerprint" "github.com/trustbloc/kms-go/util/cryptoutil" + "github.com/trustbloc/did-go/crypto-ext/jwksupport" + "github.com/trustbloc/did-go/doc/fingerprint" + "github.com/trustbloc/did-go/doc/did" vdrapi "github.com/trustbloc/did-go/vdr/api" ) @@ -50,7 +52,13 @@ func (v *VDR) Create(didDoc *did.Doc, opts ...vdrapi.DIDMethodOption) (*did.DocR switch didDoc.VerificationMethod[0].Type { case jsonWebKey2020: - didKey, keyID, err = fingerprint.CreateDIDKeyByJwk(didDoc.VerificationMethod[0].JSONWebKey()) + didKey, keyID, err = jwksupport.CreateDIDKeyByJwk(didDoc.VerificationMethod[0].JSONWebKey()) + if err != nil { + return nil, err + } + + publicKey, err = did.NewVerificationMethodFromJWK(keyID, didDoc.VerificationMethod[0].Type, didKey, + didDoc.VerificationMethod[0].JSONWebKey()) if err != nil { return nil, err } @@ -61,10 +69,10 @@ func (v *VDR) Create(didDoc *did.Doc, opts ...vdrapi.DIDMethodOption) (*did.DocR } didKey, keyID = fingerprint.CreateDIDKeyByCode(keyCode, didDoc.VerificationMethod[0].Value) - } - publicKey = did.NewVerificationMethodFromBytes(keyID, didDoc.VerificationMethod[0].Type, didKey, - didDoc.VerificationMethod[0].Value) + publicKey = did.NewVerificationMethodFromBytes(keyID, didDoc.VerificationMethod[0].Type, didKey, + didDoc.VerificationMethod[0].Value) + } if didDoc.VerificationMethod[0].Type == ed25519VerificationKey2018 { keyAgr, err = keyAgreementFromEd25519(didKey, didDoc.VerificationMethod[0].Value) diff --git a/method/key/creator_test.go b/method/key/creator_test.go index 0152b29..29d0d5a 100644 --- a/method/key/creator_test.go +++ b/method/key/creator_test.go @@ -7,7 +7,6 @@ SPDX-License-Identifier: Apache-2.0 package key import ( - "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "math/big" @@ -15,9 +14,10 @@ import ( "github.com/btcsuite/btcutil/base58" "github.com/stretchr/testify/require" - "github.com/trustbloc/kms-go/doc/jose/jwk/jwksupport" + "github.com/trustbloc/did-go/crypto-ext/jwksupport" "github.com/trustbloc/did-go/doc/did" + "github.com/trustbloc/did-go/doc/jose/jwk" ) func TestBuild(t *testing.T) { @@ -81,12 +81,12 @@ func TestBuild(t *testing.T) { x, y := elliptic.Unmarshal(elliptic.P256(), base58.Decode(pubKeyBase58P256)) require.NotNil(t, x, "error parsing pubKeyBase58P256 public key") - key := ecdsa.PublicKey{ + ecdsaContent := jwksupport.EcdsaContent{ Curve: elliptic.P256(), X: x, Y: y, } - j, err := jwksupport.JWKFromKey(&key) + j, err := jwksupport.FromEcdsaContent(ecdsaContent) require.NoError(t, err, "error creating JWK from public key: %v", err) vm, err := did.NewVerificationMethodFromJWK("id", jsonWebKey2020, "", j) @@ -110,12 +110,12 @@ func TestBuild(t *testing.T) { x, y := elliptic.Unmarshal(elliptic.P384(), base58.Decode(pubKeyBase58P384)) require.NotNil(t, x, "error parsing pubKeyBase58P384 public key") - key := ecdsa.PublicKey{ + ecdsaContent := jwksupport.EcdsaContent{ Curve: elliptic.P384(), X: x, Y: y, } - j, err := jwksupport.JWKFromKey(&key) + j, err := jwksupport.FromEcdsaContent(ecdsaContent) require.NoError(t, err, "error creating JWK from public key: %v", err) vm, err := did.NewVerificationMethodFromJWK("id", jsonWebKey2020, "", j) @@ -139,12 +139,12 @@ func TestBuild(t *testing.T) { x, y := elliptic.Unmarshal(elliptic.P521(), base58.Decode(pubKeyBase58P521)) require.NotNil(t, x, "error parsing pubKeyBase58P521 public key") - key := ecdsa.PublicKey{ + ecdsaContent := jwksupport.EcdsaContent{ Curve: elliptic.P521(), X: x, Y: y, } - j, err := jwksupport.JWKFromKey(&key) + j, err := jwksupport.FromEcdsaContent(ecdsaContent) require.NoError(t, err, "error creating JWK from public key: %v", err) vm, err := did.NewVerificationMethodFromJWK("id", jsonWebKey2020, "", j) @@ -224,8 +224,7 @@ func assertP256Doc(t *testing.T, doc *did.Doc) { pubKeyBase58 = "Q1sFNywhsHf5Wds93YN1b97jrFiUQchN3nDgboS64kqzbTrPNN6ESCibhyNEidDMHa6M1V43dVeiFpBaUa4RXxMa" ) - assertDualBase58Doc(t, doc, didKey, didKeyID, jsonWebKey2020, pubKeyBase58, - "", "", "") + assertJWKDoc(t, doc, didKey, didKeyID, jsonWebKey2020, parseECDSAPubKey(t, pubKeyBase58, elliptic.P256())) } func assertP384Doc(t *testing.T, doc *did.Doc) { @@ -236,8 +235,7 @@ func assertP384Doc(t *testing.T, doc *did.Doc) { pubKeyBase58 = "7xunFyusHxhJS3tbNWcX7xHCLRPnsScaBJJQUWw8KPpTTPfUSw9RbdyQYCBaLopw6eVQJv1G4ZD4EWgnE3zmkuiGHTq5y1KAwPAUv9Q4XXBricnzAxKamSHJiX29uQqGtbux" //nolint:lll ) - assertDualBase58Doc(t, doc, didKey, didKeyID, jsonWebKey2020, pubKeyBase58, - "", "", "") + assertJWKDoc(t, doc, didKey, didKeyID, jsonWebKey2020, parseECDSAPubKey(t, pubKeyBase58, elliptic.P384())) } func assertP521Doc(t *testing.T, doc *did.Doc) { @@ -248,8 +246,7 @@ func assertP521Doc(t *testing.T, doc *did.Doc) { pubKeyBase58 = "CqTBHvN1FwpkcrhNddXM3zSZRF7rUNSCCBuPWRxBmNAGBMa91by5XebadFwGJ2d1AVJMbUUKmUiBGXaCDDVEDn5fthbSBosoFG4anpQextGkuHHJohZxeLrGuyHc4JZYGyWFbAXVRKTMFRxuF8eQ88zqvjEV6k8oNbQ6vELYFp9CjQudG7cqP" //nolint:lll ) - assertDualBase58Doc(t, doc, didKey, didKeyID, jsonWebKey2020, pubKeyBase58, - "", "", "") + assertJWKDoc(t, doc, didKey, didKeyID, jsonWebKey2020, parseECDSAPubKey(t, pubKeyBase58, elliptic.P521())) } func assertBase58Doc(t *testing.T, doc *did.Doc, didKey, didKeyID, didKeyType, pubKeyBase58 string) { @@ -311,6 +308,49 @@ func assertDualBase58Doc(t *testing.T, doc *did.Doc, didKey, didKeyID, didKeyTyp } } +func assertJWKDoc(t *testing.T, doc *did.Doc, didKey, didKeyID, didKeyType string, pubJWK *jwk.JWK) { + var context string + switch ctx := doc.Context.(type) { + case string: + context = ctx + case []string: + context = ctx[0] + case []interface{}: + var ok bool + context, ok = ctx[0].(string) + require.True(t, ok) + } + + // validate @context + require.Equal(t, schemaDIDV1, context) + + // validate id + require.Equal(t, didKey, doc.ID) + + expectedPubKey, err := did.NewVerificationMethodFromJWK( + didKeyID, + didKeyType, + didKey, + pubJWK, + ) + require.NoError(t, err) + + // validate publicKey + assertPubKey(t, expectedPubKey, &doc.VerificationMethod[0]) + + // validate assertionMethod + assertPubKey(t, expectedPubKey, &doc.AssertionMethod[0].VerificationMethod) + + // validate authentication + assertPubKey(t, expectedPubKey, &doc.Authentication[0].VerificationMethod) + + // validate capabilityDelegation + assertPubKey(t, expectedPubKey, &doc.CapabilityDelegation[0].VerificationMethod) + + // validate capabilityInvocation + assertPubKey(t, expectedPubKey, &doc.CapabilityInvocation[0].VerificationMethod) +} + func assertPubKey(t *testing.T, expectedPubKey, actualPubKey *did.VerificationMethod) { require.NotNil(t, actualPubKey) require.Equal(t, expectedPubKey.ID, actualPubKey.ID) @@ -327,12 +367,12 @@ func assertJSONWebKeyDoc(t *testing.T, doc *did.Doc, didKey, didKeyID string, func createVerificationMethodFromXAndY(t *testing.T, didKeyID, didKey string, curve elliptic.Curve, x, y *big.Int) *did.VerificationMethod { - publicKey := ecdsa.PublicKey{ + ecdsaContent := jwksupport.EcdsaContent{ Curve: curve, X: x, Y: y, } - j, err := jwksupport.JWKFromKey(&publicKey) + j, err := jwksupport.FromEcdsaContent(ecdsaContent) require.Nil(t, err, "error creating expected JWK from public key %w", err) verificationMethod, err := did.NewVerificationMethodFromJWK(didKeyID, jsonWebKey2020, didKey, j) @@ -399,11 +439,21 @@ func assertPubJSONWebKey(t *testing.T, expectedPubKey, actualPubKey *did.Verific require.Equal(t, expectedPubKey.JSONWebKey().Kty, actualPubKey.JSONWebKey().Kty) require.Equal(t, expectedPubKey.JSONWebKey().Crv, actualPubKey.JSONWebKey().Crv) - expectedEcdsa, ok := expectedPubKey.JSONWebKey().Key.(*ecdsa.PublicKey) - require.True(t, ok, "unexpected key type") - actualEcdsa, ok := actualPubKey.JSONWebKey().Key.(*ecdsa.PublicKey) - require.True(t, ok, "unexpected key type") + require.Equal(t, expectedPubKey.JSONWebKey().X, actualPubKey.JSONWebKey().X, "incorrect X") + require.Equal(t, expectedPubKey.JSONWebKey().Y, actualPubKey.JSONWebKey().Y, "incorrect Y") +} + +func parseECDSAPubKey(t *testing.T, pubKeyBase58 string, curve elliptic.Curve) *jwk.JWK { + x, y := elliptic.Unmarshal(curve, base58.Decode(pubKeyBase58)) + require.NotNil(t, x, "error parsing pubKeyBase58P256 public key") + ecdsaContent := jwksupport.EcdsaContent{ + Curve: curve, + X: x, + Y: y, + } + + jwk, err := jwksupport.FromEcdsaContent(ecdsaContent) + require.NoError(t, err, "error creating JWK from public key: %v", err) - require.Equal(t, expectedEcdsa.X, actualEcdsa.X, "incorrect X") - require.Equal(t, expectedEcdsa.Y, actualEcdsa.Y, "incorrect Y") + return jwk } diff --git a/method/key/resolver.go b/method/key/resolver.go index 7e559b8..83f9041 100644 --- a/method/key/resolver.go +++ b/method/key/resolver.go @@ -7,15 +7,13 @@ SPDX-License-Identifier: Apache-2.0 package key import ( - "crypto/ecdsa" "crypto/elliptic" "fmt" "regexp" - "github.com/trustbloc/kms-go/doc/jose/jwk/jwksupport" - "github.com/trustbloc/kms-go/doc/util/fingerprint" - + "github.com/trustbloc/did-go/crypto-ext/jwksupport" "github.com/trustbloc/did-go/doc/did" + "github.com/trustbloc/did-go/doc/fingerprint" vdrapi "github.com/trustbloc/did-go/vdr/api" ) @@ -89,18 +87,7 @@ func createJSONWebKey2020DIDDoc(kid string, code uint64, pubKeyBytes []byte) (*d return nil, fmt.Errorf("unsupported key multicodec code for JsonWebKey2020 [0x%x]", code) } - x, y := elliptic.UnmarshalCompressed(curve, pubKeyBytes) - if x == nil { - return nil, fmt.Errorf("error unmarshalling key bytes") - } - - publicKey := ecdsa.PublicKey{ - Curve: curve, - X: x, - Y: y, - } - - j, err := jwksupport.JWKFromKey(&publicKey) + j, err := jwksupport.FromEcdsaPubKeyBytes(curve, pubKeyBytes) if err != nil { return nil, fmt.Errorf("error creating JWK %w", err) } diff --git a/method/sidetreelongform/sidetree/client_test.go b/method/sidetreelongform/sidetree/client_test.go index f141114..14968e1 100644 --- a/method/sidetreelongform/sidetree/client_test.go +++ b/method/sidetreelongform/sidetree/client_test.go @@ -18,9 +18,7 @@ import ( "testing" "github.com/btcsuite/btcutil/base58" - gojose "github.com/go-jose/go-jose/v3" "github.com/stretchr/testify/require" - "github.com/trustbloc/kms-go/doc/jose/jwk" "github.com/trustbloc/sidetree-go/pkg/commitment" "github.com/trustbloc/sidetree-go/pkg/jws" "github.com/trustbloc/sidetree-go/pkg/util/ecsigner" @@ -28,6 +26,7 @@ import ( "github.com/trustbloc/sidetree-go/pkg/util/pubkey" "github.com/trustbloc/sidetree-go/pkg/versions/1_0/client" + "github.com/trustbloc/did-go/crypto-ext/jwksupport" "github.com/trustbloc/did-go/doc/did" model "github.com/trustbloc/did-go/doc/did/endpoint" "github.com/trustbloc/did-go/method/sidetreelongform/sidetree" @@ -239,7 +238,7 @@ func TestClient_RecoverDID(t *testing.T) { recovery.WithNextUpdatePublicKey(pubKey), recovery.WithPublicKey(&doc.PublicKey{ ID: "key3", Type: doc.Ed25519VerificationKey2018, - JWK: jwk.JWK{JSONWebKey: gojose.JSONWebKey{Key: pubKey}}, + JWK: jwksupport.FromEdPublicKey(pubKey), })) require.Error(t, err) require.Contains(t, err.Error(), "sidetree get endpoints func is required") @@ -252,7 +251,7 @@ func TestClient_RecoverDID(t *testing.T) { recovery.WithNextUpdatePublicKey(pubKey), recovery.WithPublicKey(&doc.PublicKey{ ID: "key3", Type: doc.Ed25519VerificationKey2018, - JWK: jwk.JWK{JSONWebKey: gojose.JSONWebKey{Key: pubKey}}, + JWK: jwksupport.FromEdPublicKey(pubKey), }), recovery.WithSidetreeEndpoint(func(disableCache bool) ([]string, error) { i++ @@ -334,7 +333,6 @@ func TestClient_RecoverDID(t *testing.T) { }), recovery.WithNextUpdatePublicKey(pubKey), recovery.WithPublicKey(&doc.PublicKey{ ID: "key3", Type: doc.JWSVerificationKey2020, - JWK: jwk.JWK{}, })) require.Error(t, err) require.Contains(t, err.Error(), "public key must contain either a jwk or base58 key") @@ -368,7 +366,7 @@ func TestClient_RecoverDID(t *testing.T) { recovery.WithNextUpdatePublicKey(pubKey), recovery.WithPublicKey(&doc.PublicKey{ ID: "key3", Type: doc.Ed25519VerificationKey2018, - JWK: jwk.JWK{JSONWebKey: gojose.JSONWebKey{Key: pubKey}}, + JWK: jwksupport.FromEdPublicKey(pubKey), }), recovery.WithService(&did.Service{ID: "svc3"})) require.Error(t, err) @@ -392,7 +390,7 @@ func TestClient_RecoverDID(t *testing.T) { recovery.WithNextUpdatePublicKey(pubKey), recovery.WithPublicKey(&doc.PublicKey{ ID: "key3", Type: doc.Ed25519VerificationKey2018, - JWK: jwk.JWK{JSONWebKey: gojose.JSONWebKey{Key: pubKey}}, + JWK: jwksupport.FromEdPublicKey(pubKey), }), recovery.WithService(&did.Service{ID: "svc3"})) require.Error(t, err) @@ -434,7 +432,7 @@ func TestClient_RecoverDID(t *testing.T) { recovery.WithNextUpdatePublicKey(pubKey), recovery.WithPublicKey(&doc.PublicKey{ ID: "key3", Type: doc.Ed25519VerificationKey2018, - JWK: jwk.JWK{JSONWebKey: gojose.JSONWebKey{Key: pubKey}}, + JWK: jwksupport.FromEdPublicKey(pubKey), }), recovery.WithService(&did.Service{ID: "svc3"}), recovery.WithAlsoKnownAs("firstIdentityURI"), @@ -539,16 +537,15 @@ func TestClient_UpdateDID(t *testing.T) { }), } - t.Run("public key error: no jwk in JsonWebKey2020 key", func(t *testing.T) { + t.Run("public key error: public key must contain either a jwk or base58 key", func(t *testing.T) { err = v.UpdateDID("did:ex:123", append(defaultOptions, update.WithAddPublicKey(&doc.PublicKey{ - ID: "key3", - Type: doc.JWK2020Type, - B58Key: base58.Encode(pubKey), + ID: "key3", + Type: doc.JWK2020Type, }), )...) require.Error(t, err) - require.Contains(t, err.Error(), "no valid jwk in JsonWebKey2020 key") + require.Contains(t, err.Error(), "public key must contain either a jwk or base58 key") }) }) @@ -595,7 +592,7 @@ func TestClient_UpdateDID(t *testing.T) { update.WithRemovePublicKey("k2"), update.WithAddPublicKey(&doc.PublicKey{ ID: "key3", Type: doc.Ed25519VerificationKey2018, - JWK: jwk.JWK{JSONWebKey: gojose.JSONWebKey{Key: pubKey}}, + JWK: jwksupport.FromEdPublicKey(pubKey), }), update.WithAddService(&did.Service{ID: "svc3"})) require.Error(t, err) @@ -619,7 +616,7 @@ func TestClient_UpdateDID(t *testing.T) { update.WithRemovePublicKey("k2"), update.WithAddPublicKey(&doc.PublicKey{ ID: "key3", Type: doc.Ed25519VerificationKey2018, - JWK: jwk.JWK{JSONWebKey: gojose.JSONWebKey{Key: pubKey}}, + JWK: jwksupport.FromEdPublicKey(pubKey), }), update.WithAddService(&did.Service{ID: "svc3"})) require.Error(t, err) @@ -679,7 +676,7 @@ func TestClient_CreateDID(t *testing.T) { create.WithPublicKey(&doc.PublicKey{ ID: "key1", Type: doc.JWSVerificationKey2020, - JWK: jwk.JWK{JSONWebKey: gojose.JSONWebKey{Key: ed25519RecoveryPubKey}}, + JWK: jwksupport.FromEdPublicKey(ed25519RecoveryPubKey), Purposes: []string{doc.KeyPurposeAuthentication}, })) @@ -691,7 +688,7 @@ func TestClient_CreateDID(t *testing.T) { create.WithPublicKey(&doc.PublicKey{ ID: "key1", Type: doc.JWSVerificationKey2020, - JWK: jwk.JWK{JSONWebKey: gojose.JSONWebKey{Key: ed25519RecoveryPubKey}}, + JWK: jwksupport.FromEdPublicKey(ed25519RecoveryPubKey), Purposes: []string{doc.KeyPurposeAuthentication}, }), create.WithSidetreeEndpoint(func(bool) ([]string, error) { @@ -796,6 +793,13 @@ func TestClient_CreateDID(t *testing.T) { ecPrivKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) + ecJWK, err := jwksupport.FromEcdsaContent(jwksupport.EcdsaContent{ + Curve: ecPrivKey.Curve, + X: ecPrivKey.X, + Y: ecPrivKey.Y, + }) + require.NoError(t, err) + v := sidetree.New(sidetree.WithHTTPClient(&http.Client{})) didResol, err := v.CreateDID(create.WithRecoveryPublicKey(ed25519RecoveryPubKey), @@ -807,13 +811,13 @@ func TestClient_CreateDID(t *testing.T) { create.WithPublicKey(&doc.PublicKey{ ID: "key1", Type: doc.JWSVerificationKey2020, - JWK: jwk.JWK{JSONWebKey: gojose.JSONWebKey{Key: ed25519RecoveryPubKey}}, + JWK: jwksupport.FromEdPublicKey(ed25519RecoveryPubKey), Purposes: []string{doc.KeyPurposeAuthentication}, }), create.WithPublicKey(&doc.PublicKey{ ID: "key2", Type: doc.JWSVerificationKey2020, - JWK: jwk.JWK{JSONWebKey: gojose.JSONWebKey{Key: ecPrivKey.Public()}}, + JWK: ecJWK, Purposes: []string{doc.KeyPurposeAuthentication}, }), create.WithService(&did.Service{ diff --git a/method/sidetreelongform/sidetree/doc/doc.go b/method/sidetreelongform/sidetree/doc/doc.go index 9eb0d0a..b58e7d9 100644 --- a/method/sidetreelongform/sidetree/doc/doc.go +++ b/method/sidetreelongform/sidetree/doc/doc.go @@ -11,7 +11,7 @@ import ( "encoding/json" "fmt" - "github.com/trustbloc/kms-go/doc/jose/jwk" + "github.com/trustbloc/did-go/doc/jose/jwk" docdid "github.com/trustbloc/did-go/doc/did" ) @@ -68,7 +68,7 @@ type PublicKey struct { ID string Type string Purposes []string - JWK jwk.JWK + JWK *jwk.JWK B58Key string } @@ -122,25 +122,28 @@ func populateRawPublicKey(pk *PublicKey) (map[string]interface{}, error) { rawPK[jsonldType] = pk.Type rawPK[jsonldPurposes] = pk.Purposes - jwkBytes, err := pk.JWK.MarshalJSON() - switch { - case err == nil: + case pk.JWK != nil: + jwkBytes, err := json.Marshal(pk.JWK) + if err != nil { + return nil, fmt.Errorf("jwk marshaling failed: %w", err) + } + rawJWK := make(map[string]interface{}) if err := json.Unmarshal(jwkBytes, &rawJWK); err != nil { return nil, err } rawPK[jsonldPublicKeyJwk] = rawJWK - case pk.Type == JWK2020Type: - return nil, fmt.Errorf("no valid jwk in JsonWebKey2020 key") + return rawPK, nil + case pk.B58Key != "": rawPK[jsonldPublicKeyBase58] = pk.B58Key + return rawPK, nil + default: return nil, fmt.Errorf("public key must contain either a jwk or base58 key") } - - return rawPK, nil } // PopulateRawServices populate raw services. diff --git a/method/sidetreelongform/vdr.go b/method/sidetreelongform/vdr.go index 445cc71..07a4e3f 100644 --- a/method/sidetreelongform/vdr.go +++ b/method/sidetreelongform/vdr.go @@ -346,7 +346,7 @@ func getSidetreePublicKeys(didDoc *docdid.Doc) (map[string]*pk, error) { //nolin ID: id, Type: v.VerificationMethod.Type, Purposes: []string{purpose}, - JWK: *v.VerificationMethod.JSONWebKey(), + JWK: v.VerificationMethod.JSONWebKey(), }, value: v.VerificationMethod.Value, } diff --git a/method/sidetreelongform/vdr_test.go b/method/sidetreelongform/vdr_test.go index 9db6b18..29bc0d9 100644 --- a/method/sidetreelongform/vdr_test.go +++ b/method/sidetreelongform/vdr_test.go @@ -6,7 +6,6 @@ SPDX-License-Identifier: Apache-2.0 package sidetreelongform import ( - "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rand" @@ -16,13 +15,14 @@ import ( "github.com/stretchr/testify/require" "github.com/trustbloc/bbs-signature-go/bbs12381g2pub" + "github.com/trustbloc/sidetree-go/pkg/document" + + "github.com/trustbloc/did-go/crypto-ext/jwksupport" + "github.com/trustbloc/did-go/doc/jose/jwk" ld "github.com/trustbloc/did-go/doc/ld/documentloader" mockldstore "github.com/trustbloc/did-go/doc/ld/mock" ldstore "github.com/trustbloc/did-go/doc/ld/store" "github.com/trustbloc/did-go/method/sidetreelongform/sidetree/option/create" - "github.com/trustbloc/kms-go/doc/jose/jwk" - "github.com/trustbloc/kms-go/doc/jose/jwk/jwksupport" - "github.com/trustbloc/sidetree-go/pkg/document" ariesdid "github.com/trustbloc/did-go/doc/did" model "github.com/trustbloc/did-go/doc/did/endpoint" @@ -471,14 +471,14 @@ func createVerificationMethod(keyType string, pubKey []byte, kid, case p256KeyType: x, y := elliptic.Unmarshal(elliptic.P256(), pubKey) - j, err = jwksupport.JWKFromKey(&ecdsa.PublicKey{X: x, Y: y, Curve: elliptic.P256()}) + j, err = jwksupport.FromEcdsaContent(jwksupport.EcdsaContent{X: x, Y: y, Curve: elliptic.P256()}) if err != nil { return nil, err } case p384KeyType: x, y := elliptic.Unmarshal(elliptic.P384(), pubKey) - j, err = jwksupport.JWKFromKey(&ecdsa.PublicKey{X: x, Y: y, Curve: elliptic.P384()}) + j, err = jwksupport.FromEcdsaContent(jwksupport.EcdsaContent{X: x, Y: y, Curve: elliptic.P384()}) if err != nil { return nil, err } @@ -488,15 +488,13 @@ func createVerificationMethod(keyType string, pubKey []byte, kid, return nil, e } - j, err = jwksupport.JWKFromKey(pk) + j, err = jwksupport.FromBLS12381G2(pk) if err != nil { return nil, err } default: - j, err = jwksupport.JWKFromKey(ed25519.PublicKey(pubKey)) - if err != nil { - return nil, err - } + j = jwksupport.FromEdPublicKey(ed25519.PublicKey(pubKey)) + } return ariesdid.NewVerificationMethodFromJWK(kid, signatureSuite, "", j)