From 71cc3fdc10b0908308359f40235b85f1fe880001 Mon Sep 17 00:00:00 2001 From: Filip Burlacu Date: Fri, 25 Feb 2022 14:12:34 -0500 Subject: [PATCH] fix: authorization cred DID docs support didcomm/aip2;env=rfc587 Signed-off-by: Filip Burlacu --- pkg/restapi/issuer/operation/operations.go | 2 + pkg/restapi/issuer/operation/support_test.go | 3 + pkg/restapi/rp/operation/operations.go | 2 + pkg/route/service.go | 105 ++++++-- pkg/route/service_test.go | 266 +++++++++++++++++++ pkg/route/support_test.go | 25 ++ 6 files changed, 386 insertions(+), 17 deletions(-) diff --git a/pkg/restapi/issuer/operation/operations.go b/pkg/restapi/issuer/operation/operations.go index a96dcade..db9302b4 100644 --- a/pkg/restapi/issuer/operation/operations.go +++ b/pkg/restapi/issuer/operation/operations.go @@ -262,6 +262,8 @@ func New(config *Config) (*Operation, error) { // nolint:funlen,gocyclo,cyclop ConnectionLookup: connectionLookup, MediatorSvc: mediatorSvc, KeyManager: config.AriesCtx.KMS(), + KeyType: config.AriesCtx.KeyType(), + KeyAgrType: config.AriesCtx.KeyAgreementType(), }) if err != nil { return nil, fmt.Errorf("create message service : %w", err) diff --git a/pkg/restapi/issuer/operation/support_test.go b/pkg/restapi/issuer/operation/support_test.go index f4b3898b..d3b410b5 100644 --- a/pkg/restapi/issuer/operation/support_test.go +++ b/pkg/restapi/issuer/operation/support_test.go @@ -31,6 +31,7 @@ import ( presentproofsvc "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/presentproof" "github.com/hyperledger/aries-framework-go/pkg/doc/did" "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" + "github.com/hyperledger/aries-framework-go/pkg/kms" mockcrypto "github.com/hyperledger/aries-framework-go/pkg/mock/crypto" mocksvc "github.com/hyperledger/aries-framework-go/pkg/mock/didcomm/protocol/didexchange" mockroute "github.com/hyperledger/aries-framework-go/pkg/mock/didcomm/protocol/mediator" @@ -79,6 +80,8 @@ func getAriesCtx(t *testing.T) aries.CtxProvider { CreateValue: mockdiddoc.GetMockDIDDoc("did:example:def567"), ResolveValue: mockdiddoc.GetMockDIDDoc("did:example:def567"), }, + KeyTypeValue: kms.ED25519Type, + KeyAgreementTypeValue: kms.ED25519Type, }, } } diff --git a/pkg/restapi/rp/operation/operations.go b/pkg/restapi/rp/operation/operations.go index 976b954c..e47f14ac 100644 --- a/pkg/restapi/rp/operation/operations.go +++ b/pkg/restapi/rp/operation/operations.go @@ -1858,6 +1858,8 @@ func createRouteSvc(config *Config, connectionLookup connectionRecorder) (routeS ConnectionLookup: connectionLookup, MediatorSvc: mediatorSvc, KeyManager: config.AriesContextProvider.KMS(), + KeyType: config.AriesContextProvider.KeyType(), + KeyAgrType: config.AriesContextProvider.KeyAgreementType(), }) if err != nil { return nil, fmt.Errorf("create service : %w", err) diff --git a/pkg/route/service.go b/pkg/route/service.go index d16bbecc..67a98983 100644 --- a/pkg/route/service.go +++ b/pkg/route/service.go @@ -7,15 +7,19 @@ SPDX-License-Identifier: Apache-2.0 package route import ( + "encoding/json" "errors" "fmt" + "strings" "github.com/google/uuid" "github.com/hyperledger/aries-framework-go/pkg/client/didexchange" + ariescrypto "github.com/hyperledger/aries-framework-go/pkg/crypto" "github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service" "github.com/hyperledger/aries-framework-go/pkg/didcomm/messaging/msghandler" mediatorsvc "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/mediator" "github.com/hyperledger/aries-framework-go/pkg/doc/did" + "github.com/hyperledger/aries-framework-go/pkg/doc/util/jwkkid" "github.com/hyperledger/aries-framework-go/pkg/framework/aries/api/vdr" "github.com/hyperledger/aries-framework-go/pkg/kms" "github.com/hyperledger/aries-framework-go/pkg/vdr/peer" @@ -23,7 +27,6 @@ import ( "github.com/trustbloc/edge-core/pkg/log" "github.com/trustbloc/edge-adapter/pkg/aries/message" - "github.com/trustbloc/edge-adapter/pkg/crypto" ) // Msg svc constants. @@ -36,8 +39,9 @@ const ( ) const ( - txnStoreName = "msgsvc_txn" - didCommServiceType = "did-communication" + txnStoreName = "msgsvc_txn" + didCommServiceType = "did-communication" + didCommV2ServiceType = "DIDCommMessaging" ) var logger = log.New("edge-adapter/msgsvc") @@ -69,6 +73,8 @@ type Config struct { ConnectionLookup connectionRecorder MediatorSvc mediatorsvc.ProtocolService KeyManager kms.KeyManager + KeyType kms.KeyType + KeyAgrType kms.KeyType } // Service svc. @@ -82,6 +88,8 @@ type Service struct { connectionLookup connectionRecorder mediatorSvc mediatorsvc.ProtocolService keyManager kms.KeyManager + keyType kms.KeyType + keyAgrType kms.KeyType } // New returns a new Service. @@ -102,6 +110,8 @@ func New(config *Config) (*Service, error) { // TODO https://github.com/trustbloc/edge-adapter/issues/361 use function from client mediatorSvc: config.MediatorSvc, keyManager: config.KeyManager, + keyType: config.KeyType, + keyAgrType: config.KeyAgrType, } msgCh := make(chan message.Msg, 1) @@ -122,11 +132,18 @@ func New(config *Config) (*Service, error) { // GetDIDDoc returns the did doc with router endpoint/keys if its registered, else returns the doc // with default endpoint. func (o *Service) GetDIDDoc(connID string, requiresBlindedRoute bool) (*did.Doc, error) { //nolint:gocyclo,funlen,cyclop - verMethod, err := o.newVerificationMethod() + verMethod, err := o.newVerificationMethod(kms.ED25519Type) if err != nil { return nil, fmt.Errorf("failed to create new verification method: %w", err) } + kaVM, err := o.newVerificationMethod(o.keyAgrType) + if err != nil { + return nil, fmt.Errorf("failed to create new keyagreement VM: %w", err) + } + + ka := did.NewReferencedVerification(kaVM, did.KeyAgreement) + // get routers connection ID routerConnID, err := o.store.Get(connID) if err != nil && !errors.Is(err, storage.ErrDataNotFound) { @@ -145,6 +162,7 @@ func (o *Service) GetDIDDoc(connID string, requiresBlindedRoute bool) (*did.Doc, ServiceEndpoint: o.endpoint, }}, VerificationMethod: []did.VerificationMethod{*verMethod}, + KeyAgreement: []did.Verification{*ka}, }, ) if errCreate != nil { @@ -167,6 +185,7 @@ func (o *Service) GetDIDDoc(connID string, requiresBlindedRoute bool) (*did.Doc, RoutingKeys: config.Keys(), }}, VerificationMethod: []did.VerificationMethod{*verMethod}, + KeyAgreement: []did.Verification{*ka}, }, ) if err != nil { @@ -177,7 +196,10 @@ func (o *Service) GetDIDDoc(connID string, requiresBlindedRoute bool) (*did.Doc, didSvc, ok := did.LookupService(newDidDoc, didCommServiceType) if !ok { - return nil, fmt.Errorf("did document missing %s service type", didCommServiceType) + didSvc, ok = did.LookupService(newDidDoc, didCommV2ServiceType) + if !ok { + return nil, fmt.Errorf("did document missing %s service type", didCommServiceType) + } } for _, val := range didSvc.RecipientKeys { @@ -187,6 +209,18 @@ func (o *Service) GetDIDDoc(connID string, requiresBlindedRoute bool) (*did.Doc, } } + for _, kaV := range newDidDoc.KeyAgreement { + kaID := kaV.VerificationMethod.ID + if strings.HasPrefix(kaID, "#") { + kaID = newDidDoc.ID + kaID + } + + err = mediatorsvc.AddKeyToRouter(o.mediatorSvc, string(routerConnID), kaID) + if err != nil { + return nil, fmt.Errorf("register did doc keyAgreement key : %w", err) + } + } + return newDidDoc, nil } @@ -237,11 +271,18 @@ func (o *Service) didCommMsgListener(ch <-chan message.Msg) { } func (o *Service) handleDIDDocReq(msg service.DIDCommMsg) (service.DIDCommMsgMap, error) { - verMethod, err := o.newVerificationMethod() + verMethod, err := o.newVerificationMethod(kms.ED25519Type) if err != nil { return nil, fmt.Errorf("failed to create new verification method: %w", err) } + kaVM, err := o.newVerificationMethod(o.keyAgrType) + if err != nil { + return nil, fmt.Errorf("failed to create new keyagreement VM: %w", err) + } + + ka := did.NewReferencedVerification(kaVM, did.KeyAgreement) + docResolution, err := o.vdriRegistry.Create( peer.DIDMethod, &did.Doc{ @@ -249,6 +290,7 @@ func (o *Service) handleDIDDocReq(msg service.DIDCommMsg) (service.DIDCommMsgMap ServiceEndpoint: o.endpoint, }}, VerificationMethod: []did.VerificationMethod{*verMethod}, + KeyAgreement: []did.Verification{*ka}, }) if err != nil { return nil, fmt.Errorf("failed to create peer did: %w", err) @@ -276,21 +318,50 @@ func (o *Service) handleDIDDocReq(msg service.DIDCommMsg) (service.DIDCommMsgMap }), nil } -func (o *Service) newVerificationMethod() (*did.VerificationMethod, error) { - // TODO - keytype should be configurable - keyID, pubKeyBytes, err := o.keyManager.CreateAndExportPubKeyBytes(kms.ED25519Type) +const ( + ed25519VerificationKey2018 = "Ed25519VerificationKey2018" + x25519KeyAgreementKey2019 = "X25519KeyAgreementKey2019" + jsonWebKey2020 = "JsonWebKey2020" +) + +// TODO: copied from hub-router, should push shared code upstream +func (o *Service) newVerificationMethod(kt kms.KeyType) (*did.VerificationMethod, error) { + kid, pkBytes, err := o.keyManager.CreateAndExportPubKeyBytes(kt) if err != nil { - return nil, fmt.Errorf("kms failed to create public key: %w", err) + return nil, fmt.Errorf("creating public key: %w", err) } - verMethod := did.NewVerificationMethodFromBytes( - "#"+keyID, - crypto.Ed25519VerificationKey2018, - "", - pubKeyBytes, - ) + id := "#" + kid + + var vm *did.VerificationMethod + + switch kt { // nolint:exhaustive // most cases can use the default. + case kms.ED25519Type: + vm = did.NewVerificationMethodFromBytes(id, ed25519VerificationKey2018, "", pkBytes) + case kms.X25519ECDHKWType: + key := &ariescrypto.PublicKey{} + + err = json.Unmarshal(pkBytes, key) + if err != nil { + return nil, fmt.Errorf("unmarshal X25519 key: %w", err) + } + + vm = did.NewVerificationMethodFromBytes(id, x25519KeyAgreementKey2019, "", key.X) + default: + j, err := jwkkid.BuildJWK(pkBytes, kt) + if err != nil { + return nil, fmt.Errorf("creating jwk: %w", err) + } + + j.KeyID = kid + + vm, err = did.NewVerificationMethodFromJWK(id, jsonWebKey2020, "", j) + if err != nil { + return nil, fmt.Errorf("creating verification method: %w", err) + } + } - return verMethod, nil + return vm, nil } func (o *Service) handleRouteRegistration(msg message.Msg) (service.DIDCommMsgMap, error) { // nolint: gocyclo,cyclop diff --git a/pkg/route/service_test.go b/pkg/route/service_test.go index ba570fe1..84789e01 100644 --- a/pkg/route/service_test.go +++ b/pkg/route/service_test.go @@ -18,8 +18,10 @@ import ( "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/decorator" mediatorsvc "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/mediator" "github.com/hyperledger/aries-framework-go/pkg/doc/did" + "github.com/hyperledger/aries-framework-go/pkg/kms" mockroute "github.com/hyperledger/aries-framework-go/pkg/mock/didcomm/protocol/mediator" mockdiddoc "github.com/hyperledger/aries-framework-go/pkg/mock/diddoc" + mockkms "github.com/hyperledger/aries-framework-go/pkg/mock/kms" mockvdr "github.com/hyperledger/aries-framework-go/pkg/mock/vdr" "github.com/hyperledger/aries-framework-go/spi/storage" "github.com/stretchr/testify/require" @@ -214,6 +216,88 @@ func TestDIDCommMsgListener(t *testing.T) { func TestDIDDocReq(t *testing.T) { t.Parallel() + t.Run("create did doc vm error", func(t *testing.T) { + t.Parallel() + + config := config() + + expectErr := "expected err" + + config.KeyManager = &mockkms.KeyManager{CrAndExportPubKeyErr: errors.New(expectErr)} + + done := make(chan struct{}) + config.AriesMessenger = &messenger.MockMessenger{ + ReplyToFunc: func(msgID string, msg service.DIDCommMsgMap, _ ...service.Opt) error { + pMsg := &ErrorResp{} + dErr := msg.Decode(pMsg) + require.NoError(t, dErr) + require.Equal(t, pMsg.Type, didDocResp) + require.Contains(t, pMsg.Data.ErrorMsg, expectErr) + + done <- struct{}{} + + return nil + }, + } + + c, err := New(config) + require.NoError(t, err) + + msgCh := make(chan message.Msg, 1) + go c.didCommMsgListener(msgCh) + + msgCh <- message.Msg{DIDCommMsg: service.NewDIDCommMsgMap(DIDDocReq{ + ID: uuid.New().String(), + Type: didDocReq, + })} + + select { + case <-done: + case <-time.After(5 * time.Second): + require.Fail(t, "tests are not validated due to timeout") + } + }) + + t.Run("create did doc keyagreement vm error", func(t *testing.T) { + t.Parallel() + + config := config() + + config.KeyAgrType = "foo" + + done := make(chan struct{}) + config.AriesMessenger = &messenger.MockMessenger{ + ReplyToFunc: func(msgID string, msg service.DIDCommMsgMap, _ ...service.Opt) error { + pMsg := &ErrorResp{} + dErr := msg.Decode(pMsg) + require.NoError(t, dErr) + require.Equal(t, pMsg.Type, didDocResp) + require.Contains(t, pMsg.Data.ErrorMsg, "failed to create new keyagreement VM") + + done <- struct{}{} + + return nil + }, + } + + c, err := New(config) + require.NoError(t, err) + + msgCh := make(chan message.Msg, 1) + go c.didCommMsgListener(msgCh) + + msgCh <- message.Msg{DIDCommMsg: service.NewDIDCommMsgMap(DIDDocReq{ + ID: uuid.New().String(), + Type: didDocReq, + })} + + select { + case <-done: + case <-time.After(5 * time.Second): + require.Fail(t, "tests are not validated due to timeout") + } + }) + t.Run("create did doc error", func(t *testing.T) { t.Parallel() @@ -710,6 +794,45 @@ func TestGetDIDService(t *testing.T) { require.Equal(t, config.ServiceEndpoint, doc.Service[0].ServiceEndpoint) }) + t.Run("create verification method error", func(t *testing.T) { + t.Parallel() + + expectErr := errors.New("expected error") + + config := config() + config.KeyManager = &mockkms.KeyManager{CrAndExportPubKeyErr: expectErr} + + c, err := New(config) + require.NoError(t, err) + + connID := uuid.New().String() + err = c.store.Put(connID, []byte(uuid.New().String())) + require.NoError(t, err) + + _, err = c.GetDIDDoc(connID, false) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to create new verification method") + require.ErrorIs(t, err, expectErr) + }) + + t.Run("create keyagreement error", func(t *testing.T) { + t.Parallel() + + config := config() + config.KeyAgrType = "#foo" + + c, err := New(config) + require.NoError(t, err) + + connID := uuid.New().String() + err = c.store.Put(connID, []byte(uuid.New().String())) + require.NoError(t, err) + + _, err = c.GetDIDDoc(connID, false) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to create new keyagreement VM") + }) + t.Run("error when not registered and blinded routing is required", func(t *testing.T) { t.Parallel() @@ -856,4 +979,147 @@ func TestGetDIDService(t *testing.T) { require.Error(t, err) require.Contains(t, err.Error(), "register did doc recipient key") }) + + t.Run("add keyagreement key to router error", func(t *testing.T) { + t.Parallel() + + config := config() + + didDoc := &did.Doc{ + Service: []did.Service{ + { + ID: uuid.New().String(), + Type: didCommServiceType, + }, + }, + KeyAgreement: []did.Verification{ + { + VerificationMethod: did.VerificationMethod{ + ID: "foo", + }, + }, + }, + } + + config.VDRIRegistry = &mockvdr.MockVDRegistry{CreateValue: didDoc} + + config.MediatorClient = &mockmediator.MockClient{ + GetConfigFunc: func(connID string) (*mediatorsvc.Config, error) { + return &mediatorsvc.Config{}, nil + }, + } + + expectErr := errors.New("add key error") + + config.MediatorSvc = &mockroute.MockMediatorSvc{AddKeyErr: expectErr} + + c, err := New(config) + require.NoError(t, err) + + connID := uuid.New().String() + err = c.store.Put(connID, []byte(uuid.New().String())) + require.NoError(t, err) + + _, err = c.GetDIDDoc(connID, false) + require.Error(t, err) + require.Contains(t, err.Error(), "register did doc keyAgreement key") + require.ErrorIs(t, err, expectErr) + }) +} + +func TestService_newVerificationMethod(t *testing.T) { + t.Parallel() + + t.Run("success: ed25519", func(t *testing.T) { + t.Parallel() + + config := config() + + config.KeyManager = realKMS(t) + + c, err := New(config) + require.NoError(t, err) + + vm, err := c.newVerificationMethod(kms.ED25519Type) + require.NoError(t, err) + require.Equal(t, ed25519VerificationKey2018, vm.Type) + }) + + t.Run("success: x25519", func(t *testing.T) { + t.Parallel() + + config := config() + + config.KeyManager = realKMS(t) + + c, err := New(config) + require.NoError(t, err) + + vm, err := c.newVerificationMethod(kms.X25519ECDHKWType) + require.NoError(t, err) + require.Equal(t, x25519KeyAgreementKey2019, vm.Type) + }) + + t.Run("success: jwk", func(t *testing.T) { + t.Parallel() + + config := config() + + config.KeyManager = realKMS(t) + + c, err := New(config) + require.NoError(t, err) + + vm, err := c.newVerificationMethod(kms.NISTP256ECDHKWType) + require.NoError(t, err) + require.Equal(t, jsonWebKey2020, vm.Type) + }) + + t.Run("fail: create key", func(t *testing.T) { + t.Parallel() + + config := config() + + expectErr := errors.New("expected err") + + config.KeyManager = &mockkms.KeyManager{CrAndExportPubKeyErr: expectErr} + + c, err := New(config) + require.NoError(t, err) + + _, err = c.newVerificationMethod(kms.ED25519Type) + require.Error(t, err) + require.Contains(t, err.Error(), "creating public key") + require.ErrorIs(t, err, expectErr) + }) + + t.Run("fail: invalid x25519 key", func(t *testing.T) { + t.Parallel() + + config := config() + + config.KeyManager = &mockkms.KeyManager{CrAndExportPubKeyValue: []byte("foo")} + + c, err := New(config) + require.NoError(t, err) + + _, err = c.newVerificationMethod(kms.X25519ECDHKWType) + require.Error(t, err) + require.Contains(t, err.Error(), "unmarshal X25519 key") + }) + + t.Run("fail: invalid jwk key", func(t *testing.T) { + t.Parallel() + + config := config() + + config.KeyManager = &mockkms.KeyManager{CrAndExportPubKeyValue: []byte("foo")} + + c, err := New(config) + require.NoError(t, err) + + _, err = c.newVerificationMethod(kms.NISTP256ECDHKWType) + require.Error(t, err) + require.Contains(t, err.Error(), "creating jwk") + }) } diff --git a/pkg/route/support_test.go b/pkg/route/support_test.go index e596adc7..942a3bf6 100644 --- a/pkg/route/support_test.go +++ b/pkg/route/support_test.go @@ -7,13 +7,21 @@ SPDX-License-Identifier: Apache-2.0 package route import ( + "testing" + "github.com/google/uuid" "github.com/hyperledger/aries-framework-go/component/storageutil/mem" "github.com/hyperledger/aries-framework-go/pkg/didcomm/messaging/msghandler" "github.com/hyperledger/aries-framework-go/pkg/doc/did" + "github.com/hyperledger/aries-framework-go/pkg/kms" + "github.com/hyperledger/aries-framework-go/pkg/kms/localkms" mockroute "github.com/hyperledger/aries-framework-go/pkg/mock/didcomm/protocol/mediator" mockkms "github.com/hyperledger/aries-framework-go/pkg/mock/kms" + mockprovider "github.com/hyperledger/aries-framework-go/pkg/mock/provider" + mockstore "github.com/hyperledger/aries-framework-go/pkg/mock/storage" mockvdr "github.com/hyperledger/aries-framework-go/pkg/mock/vdr" + "github.com/hyperledger/aries-framework-go/pkg/secretlock/noop" + "github.com/stretchr/testify/require" mockconn "github.com/trustbloc/edge-adapter/pkg/internal/mock/connection" mockdidex "github.com/trustbloc/edge-adapter/pkg/internal/mock/didexchange" @@ -33,6 +41,8 @@ func config() *Config { ConnectionLookup: &mockconn.MockConnectionsLookup{ConnIDByDIDs: uuid.New().String()}, MediatorSvc: &mockroute.MockMediatorSvc{}, KeyManager: &mockkms.KeyManager{}, + KeyType: kms.ED25519Type, + KeyAgrType: kms.ED25519Type, } } @@ -47,3 +57,18 @@ func getDIDDoc() *did.Doc { }, } } + +func realKMS(t *testing.T) kms.KeyManager { + t.Helper() + + ctx := &mockprovider.Provider{ + StorageProviderValue: mockstore.NewMockStoreProvider(), + ProtocolStateStorageProviderValue: mockstore.NewMockStoreProvider(), + SecretLockValue: &noop.NoLock{}, + } + + keyManager, err := localkms.New("prefixname://test.kms", ctx) + require.NoError(t, err) + + return keyManager +}