From c3e7434f674df4ee858ce51914c5a6fdff5464d1 Mon Sep 17 00:00:00 2001 From: Derek Trider Date: Mon, 11 Sep 2023 18:23:44 -0400 Subject: [PATCH] feat(sdk): Improved did:key create function Added a new function for creating did:key DIDs. This new standalone function directly takes in a key instead of a keyWriter or keyReader, giving the caller more flexibility in terms of key management. It also minimizes input parameters to only what's required, helping reduce confusion caused by unused parameters. Signed-off-by: Derek Trider --- cmd/wallet-sdk-gomobile/didkey/didkey.go | 38 ++++++++++ cmd/wallet-sdk-gomobile/didkey/didkey_test.go | 63 +++++++++++++++++ pkg/did/creator/key/key.go | 43 +++++++++++- pkg/did/creator/key/key_test.go | 70 +++++++++++++++++++ test/integration/pkg/helpers/openid4ci.go | 36 +++++++--- 5 files changed, 239 insertions(+), 11 deletions(-) create mode 100644 cmd/wallet-sdk-gomobile/didkey/didkey.go create mode 100644 cmd/wallet-sdk-gomobile/didkey/didkey_test.go create mode 100644 pkg/did/creator/key/key_test.go diff --git a/cmd/wallet-sdk-gomobile/didkey/didkey.go b/cmd/wallet-sdk-gomobile/didkey/didkey.go new file mode 100644 index 00000000..bd0dbb5b --- /dev/null +++ b/cmd/wallet-sdk-gomobile/didkey/didkey.go @@ -0,0 +1,38 @@ +/* +Copyright Gen Digital Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +// Package didkey contains a function that can be used to create did:key documents. +package didkey + +import ( + "errors" + + "github.com/trustbloc/wallet-sdk/pkg/walleterror" + + "github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/api" + "github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/wrapper" + "github.com/trustbloc/wallet-sdk/pkg/did/creator/key" +) + +// Create creates a new did:key document using the given JWK. +func Create(jwk *api.JSONWebKey) (*api.DIDDocResolution, error) { + if jwk == nil { + return nil, wrapper.ToMobileError(walleterror.NewInvalidSDKUsageError( + key.ErrorModule, errors.New("jwk object cannot be null/nil"))) + } + + didDocResolution, err := key.Create(jwk.JWK) + if err != nil { + return nil, wrapper.ToMobileError(err) + } + + didDocResolutionBytes, err := didDocResolution.JSONBytes() + if err != nil { + return nil, wrapper.ToMobileError(err) + } + + return &api.DIDDocResolution{Content: string(didDocResolutionBytes)}, nil +} diff --git a/cmd/wallet-sdk-gomobile/didkey/didkey_test.go b/cmd/wallet-sdk-gomobile/didkey/didkey_test.go new file mode 100644 index 00000000..51ca7264 --- /dev/null +++ b/cmd/wallet-sdk-gomobile/didkey/didkey_test.go @@ -0,0 +1,63 @@ +/* +Copyright Gen Digital Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package didkey_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/trustbloc/kms-go/doc/jose/jwk" + + "github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/api" + "github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/didkey" + "github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/localkms" +) + +func TestCreate(t *testing.T) { + t.Run("Using an ED25519 key", func(t *testing.T) { + localKMS := createTestKMS(t) + + jsonWebKey, err := localKMS.Create(localkms.KeyTypeED25519) + require.NoError(t, err) + + didDoc, err := didkey.Create(jsonWebKey) + require.NoError(t, err) + require.NotNil(t, didDoc) + }) + t.Run("Using a P-384 key", func(t *testing.T) { + localKMS := createTestKMS(t) + + jsonWebKey, err := localKMS.Create(localkms.KeyTypeP384) + require.NoError(t, err) + + didDoc, err := didkey.Create(jsonWebKey) + require.NoError(t, err) + require.NotNil(t, didDoc) + }) + t.Run("Nil JWK", func(t *testing.T) { + didDoc, err := didkey.Create(nil) + require.Contains(t, err.Error(), "jwk object cannot be null/nil") + require.Nil(t, didDoc) + }) + t.Run("Fail to create verification method from JWK", func(t *testing.T) { + didDoc, err := didkey.Create(&api.JSONWebKey{JWK: &jwk.JWK{}}) + require.Contains(t, err.Error(), + "convert JWK to public key bytes: unsupported public key type in kid ''") + require.Nil(t, didDoc) + }) +} + +func createTestKMS(t *testing.T) *localkms.KMS { + t.Helper() + + kmsStore := localkms.NewMemKMSStore() + + localKMS, err := localkms.NewKMS(kmsStore) + require.NoError(t, err) + + return localKMS +} diff --git a/pkg/did/creator/key/key.go b/pkg/did/creator/key/key.go index 298cd5bc..156390af 100644 --- a/pkg/did/creator/key/key.go +++ b/pkg/did/creator/key/key.go @@ -8,23 +8,64 @@ SPDX-License-Identifier: Apache-2.0 package key import ( + "errors" + "github.com/trustbloc/did-go/doc/did" "github.com/trustbloc/did-go/method/key" + "github.com/trustbloc/kms-go/doc/jose/jwk" + "github.com/trustbloc/wallet-sdk/pkg/walleterror" ) +// ErrorModule is the error module name used for errors relating to did:key creation. +const ErrorModule = "DIDKEY" + // Creator is used for creating did:key DID Documents. type Creator struct { vdr *key.VDR } // NewCreator returns a new did:key document Creator. +// Deprecated: The standalone Create function should be used instead. func NewCreator() *Creator { return &Creator{vdr: key.New()} } -// Create creates a new did:key document using the given rawKey and verificationMethodType. +// Create creates a new did:key document using the given verification method. +// Deprecated: The standalone Create function should be used instead. func (d *Creator) Create(vm *did.VerificationMethod) (*did.DocResolution, error) { didDocArgument := &did.Doc{VerificationMethod: []did.VerificationMethod{*vm}} return d.vdr.Create(didDocArgument) } + +// Create creates a new did:key document using the given verification method. +func Create(jsonWebKey *jwk.JWK) (*did.DocResolution, error) { + if jsonWebKey == nil { + return nil, walleterror.NewInvalidSDKUsageError( + ErrorModule, errors.New("jwk object cannot be nil")) + } + + var vm *did.VerificationMethod + + if jsonWebKey.Crv == "Ed25519" { + // Workaround: when the did:key VDR creates a DID for ed25519, Ed25519VerificationKey2018 is the expected + // verification method. + publicKeyBytes, err := jsonWebKey.PublicKeyBytes() + if err != nil { + return nil, err + } + + vm = &did.VerificationMethod{Value: publicKeyBytes, Type: "Ed25519VerificationKey2018"} + } else { + var err error + + vm, err = did.NewVerificationMethodFromJWK("", "JsonWebKey2020", "", jsonWebKey) + if err != nil { + return nil, err + } + } + + didDocArgument := &did.Doc{VerificationMethod: []did.VerificationMethod{*vm}} + + return key.New().Create(didDocArgument) +} diff --git a/pkg/did/creator/key/key_test.go b/pkg/did/creator/key/key_test.go new file mode 100644 index 00000000..b9cdd15e --- /dev/null +++ b/pkg/did/creator/key/key_test.go @@ -0,0 +1,70 @@ +/* +Copyright Gen Digital Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package key_test + +import ( + "testing" + + "github.com/trustbloc/kms-go/doc/jose/jwk" + + kmsspi "github.com/trustbloc/kms-go/spi/kms" + "github.com/trustbloc/wallet-sdk/pkg/did/creator/key" + + "github.com/stretchr/testify/require" + + "github.com/trustbloc/wallet-sdk/pkg/localkms" +) + +func TestCreate(t *testing.T) { + t.Run("Using an ED25519 key", func(t *testing.T) { + localKMS := createTestKMS(t) + + _, jsonWebKey, err := localKMS.Create(kmsspi.ED25519) + require.NoError(t, err) + + didDoc, err := key.Create(jsonWebKey) + require.NoError(t, err) + require.NotNil(t, didDoc) + }) + t.Run("Using a P-384 key", func(t *testing.T) { + localKMS := createTestKMS(t) + + _, jsonWebKey, err := localKMS.Create(kmsspi.ECDSAP384IEEEP1363) + require.NoError(t, err) + + didDoc, err := key.Create(jsonWebKey) + require.NoError(t, err) + require.NotNil(t, didDoc) + }) + t.Run("Nil JWK", func(t *testing.T) { + didDoc, err := key.Create(nil) + require.Contains(t, err.Error(), "jwk object cannot be nil") + require.Nil(t, didDoc) + }) + t.Run("Fail to get public key bytes", func(t *testing.T) { + didDoc, err := key.Create(&jwk.JWK{Crv: "Ed25519"}) + require.Contains(t, err.Error(), "unsupported public key type in kid ''") + require.Nil(t, didDoc) + }) + t.Run("Fail to create verification method from JWK", func(t *testing.T) { + didDoc, err := key.Create(&jwk.JWK{}) + require.Contains(t, err.Error(), + "convert JWK to public key bytes: unsupported public key type in kid ''") + require.Nil(t, didDoc) + }) +} + +func createTestKMS(t *testing.T) *localkms.LocalKMS { + t.Helper() + + kmsStore := localkms.NewMemKMSStore() + + localKMS, err := localkms.NewLocalKMS(localkms.Config{Storage: kmsStore}) + require.NoError(t, err) + + return localKMS +} diff --git a/test/integration/pkg/helpers/openid4ci.go b/test/integration/pkg/helpers/openid4ci.go index ec67a911..07861de3 100644 --- a/test/integration/pkg/helpers/openid4ci.go +++ b/test/integration/pkg/helpers/openid4ci.go @@ -17,6 +17,7 @@ import ( "github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/activitylogger/mem" "github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/api" "github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/did" + "github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/didkey" "github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/display" "github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/localkms" goapi "github.com/trustbloc/wallet-sdk/pkg/api" @@ -36,20 +37,35 @@ func NewCITestHelper(t *testing.T, didMethod string, keyType string) *CITestHelp kms, err := localkms.NewKMS(localkms.NewMemKMSStore()) require.NoError(t, err) - // create DID - c, err := did.NewCreator(kms) - require.NoError(t, err) + var didDoc *api.DIDDocResolution - metricsLogger := metricslogger.NewMetricsLogger() + if didMethod == "key" { + // Create the DID using our new DID creation pattern. - opts := did.NewCreateOpts() - opts.SetKeyType(keyType) - opts.SetMetricsLogger(metricsLogger) + if keyType == "" { + keyType = localkms.KeyTypeED25519 + } - didDoc, err := c.Create(didMethod, opts) - require.NoError(t, err) + jwk, err := kms.Create(keyType) + require.NoError(t, err) + + didDoc, err = didkey.Create(jwk) + require.NoError(t, err) + } else { + c, err := did.NewCreator(kms) + require.NoError(t, err) + + metricsLogger := metricslogger.NewMetricsLogger() + + opts := did.NewCreateOpts() + opts.SetKeyType(keyType) + opts.SetMetricsLogger(metricsLogger) - checkDIDCreationMetricsLoggerEvents(t, metricsLogger) + didDoc, err = c.Create(didMethod, opts) + require.NoError(t, err) + + checkDIDCreationMetricsLoggerEvents(t, metricsLogger) + } return &CITestHelper{ KMS: kms,