From 4da0d318d0a82ad61995e1f0f913e0d4f85e9970 Mon Sep 17 00:00:00 2001 From: Filip Burlacu Date: Tue, 17 Dec 2019 15:38:45 -0500 Subject: [PATCH] refactor: Moved generic utility functions out of didexchange - DID doc parsing - Destination struct construction Signed-off-by: Filip Burlacu --- pkg/didcomm/common/service/destination.go | 50 +++++ .../common/service/destination_test.go | 146 ++++++++++++++ pkg/didcomm/common/service/service.go | 8 - pkg/didcomm/protocol/didexchange/service.go | 7 +- .../protocol/didexchange/service_test.go | 48 +---- pkg/didcomm/protocol/didexchange/states.go | 103 +--------- .../protocol/didexchange/states_test.go | 181 ++---------------- pkg/doc/did/helpers.go | 72 +++++++ pkg/doc/did/helpers_test.go | 102 ++++++++++ pkg/internal/mock/diddoc/mock_diddoc.go | 54 ++++++ 10 files changed, 457 insertions(+), 314 deletions(-) create mode 100644 pkg/didcomm/common/service/destination.go create mode 100644 pkg/didcomm/common/service/destination_test.go create mode 100644 pkg/doc/did/helpers.go create mode 100644 pkg/doc/did/helpers_test.go create mode 100644 pkg/internal/mock/diddoc/mock_diddoc.go diff --git a/pkg/didcomm/common/service/destination.go b/pkg/didcomm/common/service/destination.go new file mode 100644 index 0000000000..d0cc32a43d --- /dev/null +++ b/pkg/didcomm/common/service/destination.go @@ -0,0 +1,50 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package service + +import ( + diddoc "github.com/hyperledger/aries-framework-go/pkg/doc/did" + "github.com/hyperledger/aries-framework-go/pkg/framework/aries/api/vdri" +) + +// Destination provides the recipientKeys, routingKeys, and serviceEndpoint for an outbound message. +// Can be populated from an Invitation or DIDDoc. +type Destination struct { + RecipientKeys []string + ServiceEndpoint string + RoutingKeys []string + TransportReturnRoute string +} + +// GetDestination constructs a Destination struct based on the given DID and parameters +// It resolves the DID using the given VDR, and collects relevant data from the resolved DIDDoc. +func GetDestination(did, serviceType, keyType string, vdr vdri.Registry) (*Destination, error) { + didDoc, err := vdr.Resolve(did) + if err != nil { + return nil, err + } + + return MakeDestination(didDoc, serviceType, keyType) +} + +// MakeDestination makes a Destination object from a DID Doc, following the given parameters. +func MakeDestination(didDoc *diddoc.Doc, serviceType, keyType string) (*Destination, error) { + didCommService, err := diddoc.GetDIDCommService(didDoc, serviceType) + if err != nil { + return nil, err + } + + recipientKeys, err := diddoc.GetRecipientKeys(didDoc, serviceType, keyType) + if err != nil { + return nil, err + } + + return &Destination{ + RecipientKeys: recipientKeys, + ServiceEndpoint: didCommService.ServiceEndpoint, + }, nil +} diff --git a/pkg/didcomm/common/service/destination_test.go b/pkg/didcomm/common/service/destination_test.go new file mode 100644 index 0000000000..694ba8cd2a --- /dev/null +++ b/pkg/didcomm/common/service/destination_test.go @@ -0,0 +1,146 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package service + +import ( + "crypto/ed25519" + "crypto/rand" + "errors" + "fmt" + "testing" + "time" + + "github.com/btcsuite/btcutil/base58" + "github.com/stretchr/testify/require" + + "github.com/hyperledger/aries-framework-go/pkg/doc/did" + mockdiddoc "github.com/hyperledger/aries-framework-go/pkg/internal/mock/diddoc" + mockvdri "github.com/hyperledger/aries-framework-go/pkg/internal/mock/vdri" +) + +func TestGetDestinationFromDID(t *testing.T) { + doc := createDIDDoc() + ed25519KeyType := "Ed25519VerificationKey2018" + didCommServiceType := "did-communication" + + t.Run("successfully getting destination from public DID", func(t *testing.T) { + vdr := mockvdri.MockVDRIRegistry{ResolveValue: doc} + destination, err := GetDestination(doc.ID, didCommServiceType, ed25519KeyType, &vdr) + require.NoError(t, err) + require.NotNil(t, destination) + }) + + t.Run("test public key not found", func(t *testing.T) { + doc.PublicKey = nil + vdr := mockvdri.MockVDRIRegistry{ResolveValue: doc} + destination, err := GetDestination(doc.ID, didCommServiceType, ed25519KeyType, &vdr) + require.Error(t, err) + require.Contains(t, err.Error(), "key not found in DID document") + require.Nil(t, destination) + }) + + t.Run("test service not found", func(t *testing.T) { + doc2 := createDIDDoc() + doc2.Service = nil + vdr := mockvdri.MockVDRIRegistry{ResolveValue: doc2} + destination, err := GetDestination(doc2.ID, didCommServiceType, ed25519KeyType, &vdr) + require.Error(t, err) + require.Contains(t, err.Error(), "service not found in DID document: did-communication") + require.Nil(t, destination) + }) + + t.Run("test did document not found", func(t *testing.T) { + vdr := mockvdri.MockVDRIRegistry{ResolveErr: errors.New("resolver error")} + destination, err := GetDestination(doc.ID, didCommServiceType, ed25519KeyType, &vdr) + require.Error(t, err) + require.Contains(t, err.Error(), "resolver error") + require.Nil(t, destination) + }) +} + +func TestPrepareDestination(t *testing.T) { + ed25519KeyType := "Ed25519VerificationKey2018" + didCommServiceType := "did-communication" + + t.Run("successfully prepared destination", func(t *testing.T) { + dest, err := MakeDestination(mockdiddoc.GetMockDIDDoc(), didCommServiceType, ed25519KeyType) + require.NoError(t, err) + require.NotNil(t, dest) + require.Equal(t, dest.ServiceEndpoint, "https://localhost:8090") + }) + + t.Run("error while getting service", func(t *testing.T) { + didDoc := mockdiddoc.GetMockDIDDoc() + didDoc.Service = nil + + dest, err := MakeDestination(didDoc, didCommServiceType, ed25519KeyType) + require.Error(t, err) + require.Contains(t, err.Error(), "service not found in DID document: did-communication") + require.Nil(t, dest) + }) + + t.Run("error while getting recipient keys from did doc", func(t *testing.T) { + didDoc := mockdiddoc.GetMockDIDDoc() + didDoc.Service[0].RecipientKeys = []string{} + + recipientKeys, err := did.GetRecipientKeys(didDoc, didCommServiceType, ed25519KeyType) + require.Error(t, err) + require.Contains(t, err.Error(), "missing recipient keys in did-communication service") + require.Nil(t, recipientKeys) + }) +} + +func createDIDDoc() *did.Doc { + pubKey, _ := generateKeyPair() + return createDIDDocWithKey(pubKey) +} + +func generateKeyPair() (string, []byte) { + pubKey, privKey, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + panic(err) + } + + return base58.Encode(pubKey[:]), privKey +} + +func createDIDDocWithKey(pub string) *did.Doc { + const ( + didFormat = "did:%s:%s" + didPKID = "%s#keys-%d" + didServiceID = "%s#endpoint-%d" + method = "test" + ) + + id := fmt.Sprintf(didFormat, method, pub[:16]) + pubKeyID := fmt.Sprintf(didPKID, id, 1) + pubKey := did.PublicKey{ + ID: pubKeyID, + Type: "Ed25519VerificationKey2018", + Controller: id, + Value: []byte(pub), + } + services := []did.Service{ + { + ID: fmt.Sprintf(didServiceID, id, 1), + Type: "did-communication", + ServiceEndpoint: "http://localhost:58416", + Priority: 0, + RecipientKeys: []string{pubKeyID}, + }, + } + createdTime := time.Now() + didDoc := &did.Doc{ + Context: []string{did.Context}, + ID: id, + PublicKey: []did.PublicKey{pubKey}, + Service: services, + Created: &createdTime, + Updated: &createdTime, + } + + return didDoc +} diff --git a/pkg/didcomm/common/service/service.go b/pkg/didcomm/common/service/service.go index b23d0a4be9..d3ed34a5d0 100644 --- a/pkg/didcomm/common/service/service.go +++ b/pkg/didcomm/common/service/service.go @@ -107,11 +107,3 @@ func (m *DIDCommMsg) ThreadID() (string, error) { return "", ErrThreadIDNotFound } - -// Destination provides the recipientKeys, routingKeys, and serviceEndpoint populated from Invitation -type Destination struct { - RecipientKeys []string - ServiceEndpoint string - RoutingKeys []string - TransportReturnRoute string -} diff --git a/pkg/didcomm/protocol/didexchange/service.go b/pkg/didcomm/protocol/didexchange/service.go index 05cd0d418c..8deb80ccd3 100644 --- a/pkg/didcomm/protocol/didexchange/service.go +++ b/pkg/didcomm/protocol/didexchange/service.go @@ -636,7 +636,12 @@ func (s *Service) CreateImplicitInvitation(inviterLabel, inviterDID, inviteeLabe return "", fmt.Errorf("resolve public did[%s]: %w", inviterDID, err) } - dest, err := prepareDestination(didDoc) + // TODO: hardcoded key type + dest, err := service.MakeDestination(didDoc, didCommServiceType, ed25519KeyType) + if err != nil { + return "", err + } + thID := generateRandomID() connRecord := &ConnectionRecord{ ConnectionID: generateRandomID(), diff --git a/pkg/didcomm/protocol/didexchange/service_test.go b/pkg/didcomm/protocol/didexchange/service_test.go index 0d2f4f0f75..f43d79ed71 100644 --- a/pkg/didcomm/protocol/didexchange/service_test.go +++ b/pkg/didcomm/protocol/didexchange/service_test.go @@ -14,15 +14,14 @@ import ( "testing" "time" - "github.com/btcsuite/btcutil/base58" "github.com/google/uuid" "github.com/stretchr/testify/require" "github.com/hyperledger/aries-framework-go/pkg/didcomm/common/model" "github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service" "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/decorator" - "github.com/hyperledger/aries-framework-go/pkg/doc/did" "github.com/hyperledger/aries-framework-go/pkg/internal/mock/didcomm/protocol" + mockdiddoc "github.com/hyperledger/aries-framework-go/pkg/internal/mock/diddoc" mockstorage "github.com/hyperledger/aries-framework-go/pkg/internal/mock/storage" mockvdri "github.com/hyperledger/aries-framework-go/pkg/internal/mock/vdri" "github.com/hyperledger/aries-framework-go/pkg/storage" @@ -542,47 +541,6 @@ func (m *mockStore) Iterator(start, limit string) storage.StoreIterator { return nil } -func getMockDID() *did.Doc { - return &did.Doc{ - Context: []string{"https://w3id.org/did/v1"}, - ID: "did:peer:123456789abcdefghi#inbox", - Service: []did.Service{ - { - ServiceEndpoint: "https://localhost:8090", - Type: "did-communication", - Priority: 0, - RecipientKeys: []string{"did:example:123456789abcdefghi#keys-2"}, - }, - { - ServiceEndpoint: "https://localhost:8090", - Type: "did-communication", - Priority: 1, - RecipientKeys: []string{"did:example:123456789abcdefghi#keys-1"}, - }, - }, - PublicKey: []did.PublicKey{ - { - ID: "did:example:123456789abcdefghi#keys-1", - Controller: "did:example:123456789abcdefghi", - Type: "Secp256k1VerificationKey2018", - Value: base58.Decode("H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV"), - }, - { - ID: "did:example:123456789abcdefghi#keys-2", - Controller: "did:example:123456789abcdefghi", - Type: "Ed25519VerificationKey2018", - Value: base58.Decode("H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV"), - }, - { - ID: "did:example:123456789abcdefghw#key2", - Controller: "did:example:123456789abcdefghw", - Type: "RsaVerificationKey2018", - Value: base58.Decode("H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV"), - }, - }, - } -} - func randomString() string { u := uuid.New() return u.String() @@ -639,7 +597,7 @@ func TestEventsSuccess(t *testing.T) { } func TestContinueWithPublicDID(t *testing.T) { - didDoc := getMockDID() + didDoc := mockdiddoc.GetMockDIDDoc() svc, err := New(&protocol.MockProvider{}) require.NoError(t, err) @@ -1384,7 +1342,7 @@ func TestFetchConnectionRecord(t *testing.T) { func generateRequestMsgPayload(t *testing.T, prov provider, id, invitationID string) *service.DIDCommMsg { store := mockstorage.NewMockStoreProvider() ctx := context{outboundDispatcher: prov.OutboundDispatcher(), - vdriRegistry: &mockvdri.MockVDRIRegistry{CreateValue: getMockDID()}, + vdriRegistry: &mockvdri.MockVDRIRegistry{CreateValue: mockdiddoc.GetMockDIDDoc()}, connectionStore: NewConnectionRecorder(nil, store.Store)} newDidDoc, err := ctx.vdriRegistry.Create(testMethod) require.NoError(t, err) diff --git a/pkg/didcomm/protocol/didexchange/states.go b/pkg/didcomm/protocol/didexchange/states.go index a6f43367d2..edaba2b42a 100644 --- a/pkg/didcomm/protocol/didexchange/states.go +++ b/pkg/didcomm/protocol/didexchange/states.go @@ -308,7 +308,7 @@ func (ctx *context) handleInboundInvitation(invitation *Invitation, } connRec.MyDID = request.Connection.DID - senderVerKeys, err := getRecipientKeys(didDoc) + senderVerKeys, err := did.GetRecipientKeys(didDoc, didCommServiceType, ed25519KeyType) if err != nil { return nil, nil, fmt.Errorf("getting sender verification keys: %w", err) } @@ -351,12 +351,12 @@ func (ctx *context) handleInboundRequest(request *Request, options *options, con connRec.MyDID = connection.DID connRec.TheirLabel = request.Label - destination, err := prepareDestination(requestDidDoc) + destination, err := service.MakeDestination(requestDidDoc, didCommServiceType, ed25519KeyType) if err != nil { return nil, nil, err } - senderVerKeys, err := getRecipientKeys(responseDidDoc) + senderVerKeys, err := did.GetRecipientKeys(responseDidDoc, didCommServiceType, ed25519KeyType) if err != nil { return nil, nil, err } @@ -385,7 +385,7 @@ func getLabel(options *options) string { func (ctx *context) getDestination(invitation *Invitation) (*service.Destination, error) { if invitation.DID != "" { - return ctx.getDestinationFromDID(invitation.DID) + return service.GetDestination(invitation.DID, didCommServiceType, ed25519KeyType, ctx.vdriRegistry) } return &service.Destination{ @@ -432,95 +432,6 @@ func (ctx *context) resolveDidDocFromConnection(conn *Connection) (*did.Doc, err return didDoc, nil } -func (ctx *context) getDestinationFromDID(id string) (*service.Destination, error) { - didDoc, err := ctx.vdriRegistry.Resolve(id) - if err != nil { - return nil, err - } - - return prepareDestination(didDoc) -} - -func getRecipientKeys(didDoc *did.Doc) ([]string, error) { - didCommService, err := getDidCommService(didDoc) - if err != nil { - return nil, err - } - - if len(didCommService.RecipientKeys) == 0 { - return nil, fmt.Errorf("missing recipient keys in did-communication service") - } - - var recipientKeys []string - - for _, keyID := range didCommService.RecipientKeys { - key, err := getPublicKey(keyID, didDoc) - if err != nil { - return nil, err - } - - if isSupportedKeyType(key.Type) { - recipientKeys = append(recipientKeys, string(key.Value)) - } - } - - if len(recipientKeys) == 0 { - return nil, fmt.Errorf("recipient keys in did-communication service not supported") - } - - return recipientKeys, nil -} - -func getPublicKey(id string, didDoc *did.Doc) (*did.PublicKey, error) { - for _, key := range didDoc.PublicKey { - if key.ID == id { - return &key, nil - } - } - - return nil, fmt.Errorf("key not found in DID document: %s", id) -} - -func isSupportedKeyType(keyType string) bool { - return keyType == ed25519KeyType -} - -func getDidCommService(didDoc *did.Doc) (*did.Service, error) { - const notFound = -1 - index := notFound - - for i, s := range didDoc.Service { - if s.Type == didCommServiceType { - if index == notFound || didDoc.Service[index].Priority > s.Priority { - index = i - } - } - } - - if index == notFound { - return nil, fmt.Errorf("service not found in DID document: %s", didCommServiceType) - } - - return &didDoc.Service[index], nil -} - -func prepareDestination(didDoc *did.Doc) (*service.Destination, error) { - didCommService, err := getDidCommService(didDoc) - if err != nil { - return nil, err - } - - recipientKeys, err := getRecipientKeys(didDoc) - if err != nil { - return nil, err - } - - return &service.Destination{ - RecipientKeys: recipientKeys, - ServiceEndpoint: didCommService.ServiceEndpoint, - }, nil -} - // Encode the connection and convert to Connection Signature as per the spec: // https://github.com/hyperledger/aries-rfcs/tree/master/features/0023-did-exchange func (ctx *context) prepareConnectionSignature(connection *Connection, @@ -597,7 +508,7 @@ func (ctx *context) handleInboundResponse(response *Response) (stateAction, *Con return nil, nil, fmt.Errorf("resolve did doc from exchange response connection: %w", err) } - destination, err := prepareDestination(responseDidDoc) + destination, err := service.MakeDestination(responseDidDoc, didCommServiceType, ed25519KeyType) if err != nil { return nil, nil, fmt.Errorf("prepare destination from response did doc: %w", err) } @@ -607,7 +518,7 @@ func (ctx *context) handleInboundResponse(response *Response) (stateAction, *Con return nil, nil, fmt.Errorf("fetching did document: %w", err) } - senderVerKeys, err := getRecipientKeys(myDidDoc) + senderVerKeys, err := did.GetRecipientKeys(myDidDoc, didCommServiceType, ed25519KeyType) if err != nil { return nil, nil, fmt.Errorf("get public keys: %w", err) } @@ -672,7 +583,7 @@ func (ctx *context) getInvitationRecipientKey(invitation *Invitation) (string, e return "", fmt.Errorf("get invitation recipient key: %w", err) } - recipientKeys, err := getRecipientKeys(didDoc) + recipientKeys, err := did.GetRecipientKeys(didDoc, didCommServiceType, ed25519KeyType) if err != nil { return "", fmt.Errorf("get recipient keys from did: %w", err) } diff --git a/pkg/didcomm/protocol/didexchange/states_test.go b/pkg/didcomm/protocol/didexchange/states_test.go index 7adad15aea..fc27f5b95a 100644 --- a/pkg/didcomm/protocol/didexchange/states_test.go +++ b/pkg/didcomm/protocol/didexchange/states_test.go @@ -26,6 +26,7 @@ import ( "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/decorator" diddoc "github.com/hyperledger/aries-framework-go/pkg/doc/did" "github.com/hyperledger/aries-framework-go/pkg/internal/mock/didcomm/protocol" + mockdiddoc "github.com/hyperledger/aries-framework-go/pkg/internal/mock/diddoc" mockstorage "github.com/hyperledger/aries-framework-go/pkg/internal/mock/storage" mockvdri "github.com/hyperledger/aries-framework-go/pkg/internal/mock/vdri" "github.com/hyperledger/aries-framework-go/pkg/storage" @@ -303,7 +304,7 @@ func TestRequestedState_Execute(t *testing.T) { ThreadID: "test", ConnectionID: "123", } - didDoc := getMockDID() + didDoc := mockdiddoc.GetMockDIDDoc() didDoc.Service[0].RecipientKeys = []string{"invalid"} ctx2 := &context{outboundDispatcher: prov.OutboundDispatcher(), vdriRegistry: &mockvdri.MockVDRIRegistry{CreateValue: didDoc}, @@ -369,7 +370,7 @@ func TestRespondedState_Execute(t *testing.T) { require.Equal(t, (&completed{}).Name(), followup.Name()) }) t.Run("handle inbound request public key error", func(t *testing.T) { - didDoc := getMockDID() + didDoc := mockdiddoc.GetMockDIDDoc() didDoc.Service[0].RecipientKeys = []string{"invalid"} ctx2 := &context{outboundDispatcher: prov.OutboundDispatcher(), vdriRegistry: &mockvdri.MockVDRIRegistry{CreateValue: didDoc}, signer: &mockSigner{}, @@ -441,7 +442,7 @@ func TestCompletedState_Execute(t *testing.T) { } err = ctx.connectionStore.saveNewConnectionRecord(connRec) require.NoError(t, err) - ctx.vdriRegistry = &mockvdri.MockVDRIRegistry{ResolveValue: getMockDID()} + ctx.vdriRegistry = &mockvdri.MockVDRIRegistry{ResolveValue: mockdiddoc.GetMockDIDDoc()} require.NoError(t, err) _, followup, _, e := (&completed{}).ExecuteInbound(&stateMachineMsg{ header: &service.Header{Type: ResponseMsgType}, @@ -698,7 +699,7 @@ func TestPrepareConnectionSignature(t *testing.T) { ctx := &context{signer: &mockSigner{err: errors.New("sign error")}, connectionStore: NewConnectionRecorder(nil, store)} connection := &Connection{ - DIDDoc: getMockDID(), + DIDDoc: mockdiddoc.GetMockDIDDoc(), } connectionSignature, err := ctx.prepareConnectionSignature(connection, invitation.ID) require.Error(t, err) @@ -707,35 +708,6 @@ func TestPrepareConnectionSignature(t *testing.T) { }) } -func TestPrepareDestination(t *testing.T) { - t.Run("successfully prepared destination", func(t *testing.T) { - dest, err := prepareDestination(getMockDID()) - require.NoError(t, err) - require.NotNil(t, dest) - require.Equal(t, dest.ServiceEndpoint, "https://localhost:8090") - }) - - t.Run("error while getting service", func(t *testing.T) { - didDoc := getMockDID() - didDoc.Service = nil - - dest, err := prepareDestination(didDoc) - require.Error(t, err) - require.Contains(t, err.Error(), "service not found in DID document: did-communication") - require.Nil(t, dest) - }) - - t.Run("error while getting recipient keys from did doc", func(t *testing.T) { - didDoc := getMockDID() - didDoc.Service[0].RecipientKeys = []string{} - - recipientKeys, err := getRecipientKeys(didDoc) - require.Error(t, err) - require.Contains(t, err.Error(), "missing recipient keys in did-communication service") - require.Nil(t, recipientKeys) - }) -} - func TestNewRequestFromInvitation(t *testing.T) { invitation := &Invitation{ Type: InvitationMsgType, @@ -807,9 +779,12 @@ func TestNewResponseFromRequest(t *testing.T) { require.NotNil(t, connRec.TheirDID) }) t.Run("unsuccessful new response from request due to create did error", func(t *testing.T) { - didDoc := getMockDID() + didDoc := mockdiddoc.GetMockDIDDoc() ctx := &context{ - vdriRegistry: &mockvdri.MockVDRIRegistry{CreateErr: fmt.Errorf("create DID error"), ResolveValue: getMockDID()}} + vdriRegistry: &mockvdri.MockVDRIRegistry{ + CreateErr: fmt.Errorf("create DID error"), + ResolveValue: mockdiddoc.GetMockDIDDoc(), + }} request := &Request{Connection: &Connection{DID: didDoc.ID, DIDDoc: didDoc}} _, connRec, err := ctx.handleInboundRequest(request, &options{}, &ConnectionRecord{}) require.Error(t, err) @@ -817,7 +792,7 @@ func TestNewResponseFromRequest(t *testing.T) { require.Nil(t, connRec) }) t.Run("unsuccessful new response from request due to sign error", func(t *testing.T) { - ctx := &context{vdriRegistry: &mockvdri.MockVDRIRegistry{CreateValue: getMockDID()}, + ctx := &context{vdriRegistry: &mockvdri.MockVDRIRegistry{CreateValue: mockdiddoc.GetMockDIDDoc()}, signer: &mockSigner{err: errors.New("sign error")}, connectionStore: NewConnectionRecorder(nil, store)} request, err := createRequest(ctx) @@ -887,7 +862,7 @@ func TestGetInvitationRecipientKey(t *testing.T) { require.Equal(t, invitation.RecipientKeys[0], recKey) }) t.Run("failed to get invitation recipient key", func(t *testing.T) { - doc := getMockDID() + doc := mockdiddoc.GetMockDIDDoc() ctx := context{vdriRegistry: &mockvdri.MockVDRIRegistry{ResolveValue: doc}} invitation := &Invitation{ Type: InvitationMsgType, @@ -916,7 +891,7 @@ func TestGetPublicKey(t *testing.T) { ctx := getContext(prov, nil) newDidDoc, err := ctx.vdriRegistry.Create(testMethod) require.NoError(t, err) - pubkey, err := getPublicKey(newDidDoc.PublicKey[0].ID, newDidDoc) + pubkey, err := diddoc.GetPublicKey(newDidDoc.PublicKey[0].ID, newDidDoc) require.NoError(t, err) require.NotNil(t, pubkey) }) @@ -925,95 +900,13 @@ func TestGetPublicKey(t *testing.T) { ctx := getContext(prov, nil) newDidDoc, err := ctx.vdriRegistry.Create(testMethod) require.NoError(t, err) - pubkey, err := getPublicKey("invalid-key", newDidDoc) + pubkey, err := diddoc.GetPublicKey("invalid-key", newDidDoc) require.Error(t, err) require.Contains(t, err.Error(), "key not found in DID document: invalid-key") require.Nil(t, pubkey) }) } -func TestGetDidCommService(t *testing.T) { - t.Run("successfully getting did-communication service", func(t *testing.T) { - didDoc := getMockDID() - - s, err := getDidCommService(didDoc) - require.NoError(t, err) - require.Equal(t, "did-communication", s.Type) - require.Equal(t, uint(0), s.Priority) - }) - - t.Run("error due to missing service", func(t *testing.T) { - didDoc := getMockDID() - didDoc.Service = nil - - s, err := getDidCommService(didDoc) - require.Error(t, err) - require.Contains(t, err.Error(), "service not found in DID document: did-communication") - require.Nil(t, s) - }) - - t.Run("error due to missing did-communication service", func(t *testing.T) { - didDoc := getMockDID() - didDoc.Service[0].Type = "some-type" - didDoc.Service[1].Type = "other-type" - - s, err := getDidCommService(didDoc) - require.Error(t, err) - require.Contains(t, err.Error(), "service not found in DID document: did-communication") - require.Nil(t, s) - }) -} - -func TestGetRecipientKeys(t *testing.T) { - t.Run("successfully getting recipient keys", func(t *testing.T) { - didDoc := getMockDID() - - recipientKeys, err := getRecipientKeys(didDoc) - require.NoError(t, err) - require.Equal(t, 1, len(recipientKeys)) - }) - - t.Run("error due to missing did-communication service", func(t *testing.T) { - didDoc := getMockDID() - didDoc.Service = nil - - recipientKeys, err := getRecipientKeys(didDoc) - require.Error(t, err) - require.Contains(t, err.Error(), "service not found in DID document: did-communication") - require.Nil(t, recipientKeys) - }) - - t.Run("error due to missing recipient keys in did-communication service", func(t *testing.T) { - didDoc := getMockDID() - didDoc.Service[0].RecipientKeys = []string{} - - recipientKeys, err := getRecipientKeys(didDoc) - require.Error(t, err) - require.Contains(t, err.Error(), "missing recipient keys in did-communication service") - require.Nil(t, recipientKeys) - }) - - t.Run("error due to missing public key in did doc", func(t *testing.T) { - didDoc := getMockDID() - didDoc.Service[0].RecipientKeys = []string{"invalid"} - - recipientKeys, err := getRecipientKeys(didDoc) - require.Error(t, err) - require.Contains(t, err.Error(), "key not found in DID document: invalid") - require.Nil(t, recipientKeys) - }) - - t.Run("error due to unsupported key types", func(t *testing.T) { - didDoc := getMockDID() - didDoc.Service[0].RecipientKeys = []string{didDoc.PublicKey[0].ID} - - recipientKeys, err := getRecipientKeys(didDoc) - require.Error(t, err) - require.Contains(t, err.Error(), "recipient keys in did-communication service not supported") - require.Nil(t, recipientKeys) - }) -} - func TestGetDIDDocAndConnection(t *testing.T) { t.Run("successfully getting did doc and connection for public did", func(t *testing.T) { doc := createDIDDoc() @@ -1041,7 +934,9 @@ func TestGetDIDDocAndConnection(t *testing.T) { require.Nil(t, conn) }) t.Run("successfully created peer did", func(t *testing.T) { - ctx := context{vdriRegistry: &mockvdri.MockVDRIRegistry{CreateValue: getMockDID()}} + ctx := context{ + vdriRegistry: &mockvdri.MockVDRIRegistry{CreateValue: mockdiddoc.GetMockDIDDoc()}, + } didDoc, conn, err := ctx.getDIDDocAndConnection("") require.NoError(t, err) require.NotNil(t, didDoc) @@ -1050,48 +945,6 @@ func TestGetDIDDocAndConnection(t *testing.T) { }) } -func TestGetDestinationFromDID(t *testing.T) { - doc := createDIDDoc() - - t.Run("successfully getting destination from public DID", func(t *testing.T) { - ctx := context{vdriRegistry: &mockvdri.MockVDRIRegistry{ResolveValue: doc}} - destination, err := ctx.getDestinationFromDID(doc.ID) - require.NoError(t, err) - require.NotNil(t, destination) - }) - t.Run("test public key not found", func(t *testing.T) { - doc.PublicKey = nil - ctx := context{vdriRegistry: &mockvdri.MockVDRIRegistry{ResolveValue: doc}} - destination, err := ctx.getDestinationFromDID(doc.ID) - require.Error(t, err) - require.Contains(t, err.Error(), "key not found in DID document") - require.Nil(t, destination) - }) - t.Run("test service not found", func(t *testing.T) { - doc2 := createDIDDoc() - doc2.Service = nil - ctx := context{vdriRegistry: &mockvdri.MockVDRIRegistry{ResolveValue: doc2}} - destination, err := ctx.getDestinationFromDID(doc2.ID) - require.Error(t, err) - require.Contains(t, err.Error(), "service not found in DID document: did-communication") - require.Nil(t, destination) - }) - t.Run("get destination by invitation", func(t *testing.T) { - ctx := context{vdriRegistry: &mockvdri.MockVDRIRegistry{ResolveValue: createDIDDoc()}} - invitation := &Invitation{DID: "test"} - destination, err := ctx.getDestination(invitation) - require.NoError(t, err) - require.NotNil(t, destination) - }) - t.Run("test did document not found", func(t *testing.T) { - ctx := context{vdriRegistry: &mockvdri.MockVDRIRegistry{ResolveErr: errors.New("resolver error")}} - destination, err := ctx.getDestinationFromDID(doc.ID) - require.Error(t, err) - require.Contains(t, err.Error(), "resolver error") - require.Nil(t, destination) - }) -} - type mockSigner struct { privateKey []byte err error diff --git a/pkg/doc/did/helpers.go b/pkg/doc/did/helpers.go new file mode 100644 index 0000000000..a1d3f27e3f --- /dev/null +++ b/pkg/doc/did/helpers.go @@ -0,0 +1,72 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package did + +import ( + "fmt" +) + +// GetDIDCommService returns the service from the given DIDDoc matching the given service type. +func GetDIDCommService(didDoc *Doc, serviceType string) (*Service, error) { + const notFound = -1 + index := notFound + + for i, s := range didDoc.Service { + if s.Type == serviceType { + if index == notFound || didDoc.Service[index].Priority > s.Priority { + index = i + } + } + } + + if index == notFound { + return nil, fmt.Errorf("service not found in DID document: %s", serviceType) + } + + return &didDoc.Service[index], nil +} + +// GetRecipientKeys gets the recipient keys from the did doc which match the given parameters. +func GetRecipientKeys(didDoc *Doc, serviceType, keyType string) ([]string, error) { + didCommService, err := GetDIDCommService(didDoc, serviceType) + if err != nil { + return nil, err + } + + if len(didCommService.RecipientKeys) == 0 { + return nil, fmt.Errorf("missing recipient keys in did-communication service") + } + + var recipientKeys []string + + for _, keyID := range didCommService.RecipientKeys { + key, err := GetPublicKey(keyID, didDoc) + if err != nil { + return nil, err + } + + if key.Type == keyType { + recipientKeys = append(recipientKeys, string(key.Value)) + } + } + + if len(recipientKeys) == 0 { + return nil, fmt.Errorf("recipient keys in did-communication service not supported") + } + + return recipientKeys, nil +} + +// GetPublicKey returns the public key with the given id from the given DID Doc +func GetPublicKey(id string, didDoc *Doc) (*PublicKey, error) { + for _, key := range didDoc.PublicKey { + if key.ID == id { + return &key, nil + } + } + + return nil, fmt.Errorf("key not found in DID document: %s", id) +} diff --git a/pkg/doc/did/helpers_test.go b/pkg/doc/did/helpers_test.go new file mode 100644 index 0000000000..db1b4b5e32 --- /dev/null +++ b/pkg/doc/did/helpers_test.go @@ -0,0 +1,102 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package did_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + . "github.com/hyperledger/aries-framework-go/pkg/doc/did" + mockdiddoc "github.com/hyperledger/aries-framework-go/pkg/internal/mock/diddoc" +) + +func TestGetRecipientKeys(t *testing.T) { + ed25519KeyType := "Ed25519VerificationKey2018" + didCommServiceType := "did-communication" + + t.Run("successfully getting recipient keys", func(t *testing.T) { + didDoc := mockdiddoc.GetMockDIDDoc() + + recipientKeys, err := GetRecipientKeys(didDoc, didCommServiceType, ed25519KeyType) + require.NoError(t, err) + require.Equal(t, 1, len(recipientKeys)) + }) + + t.Run("error due to missing did-communication service", func(t *testing.T) { + didDoc := mockdiddoc.GetMockDIDDoc() + didDoc.Service = nil + + recipientKeys, err := GetRecipientKeys(didDoc, didCommServiceType, ed25519KeyType) + require.Error(t, err) + require.Contains(t, err.Error(), "service not found in DID document: did-communication") + require.Nil(t, recipientKeys) + }) + + t.Run("error due to missing recipient keys in did-communication service", func(t *testing.T) { + didDoc := mockdiddoc.GetMockDIDDoc() + didDoc.Service[0].RecipientKeys = []string{} + + recipientKeys, err := GetRecipientKeys(didDoc, didCommServiceType, ed25519KeyType) + require.Error(t, err) + require.Contains(t, err.Error(), "missing recipient keys in did-communication service") + require.Nil(t, recipientKeys) + }) + + t.Run("error due to missing public key in did doc", func(t *testing.T) { + didDoc := mockdiddoc.GetMockDIDDoc() + didDoc.Service[0].RecipientKeys = []string{"invalid"} + + recipientKeys, err := GetRecipientKeys(didDoc, didCommServiceType, ed25519KeyType) + require.Error(t, err) + require.Contains(t, err.Error(), "key not found in DID document: invalid") + require.Nil(t, recipientKeys) + }) + + t.Run("error due to unsupported key types", func(t *testing.T) { + didDoc := mockdiddoc.GetMockDIDDoc() + didDoc.Service[0].RecipientKeys = []string{didDoc.PublicKey[0].ID} + + recipientKeys, err := GetRecipientKeys(didDoc, didCommServiceType, ed25519KeyType) + require.Error(t, err) + require.Contains(t, err.Error(), "recipient keys in did-communication service not supported") + require.Nil(t, recipientKeys) + }) +} + +func TestGetDidCommService(t *testing.T) { + didCommServiceType := "did-communication" + + t.Run("successfully getting did-communication service", func(t *testing.T) { + didDoc := mockdiddoc.GetMockDIDDoc() + + s, err := GetDIDCommService(didDoc, didCommServiceType) + require.NoError(t, err) + require.Equal(t, "did-communication", s.Type) + require.Equal(t, uint(0), s.Priority) + }) + + t.Run("error due to missing service", func(t *testing.T) { + didDoc := mockdiddoc.GetMockDIDDoc() + didDoc.Service = nil + + s, err := GetDIDCommService(didDoc, didCommServiceType) + require.Error(t, err) + require.Contains(t, err.Error(), "service not found in DID document: did-communication") + require.Nil(t, s) + }) + + t.Run("error due to missing did-communication service", func(t *testing.T) { + didDoc := mockdiddoc.GetMockDIDDoc() + didDoc.Service[0].Type = "some-type" + didDoc.Service[1].Type = "other-type" + + s, err := GetDIDCommService(didDoc, didCommServiceType) + require.Error(t, err) + require.Contains(t, err.Error(), "service not found in DID document: did-communication") + require.Nil(t, s) + }) +} diff --git a/pkg/internal/mock/diddoc/mock_diddoc.go b/pkg/internal/mock/diddoc/mock_diddoc.go new file mode 100644 index 0000000000..3a82ab807c --- /dev/null +++ b/pkg/internal/mock/diddoc/mock_diddoc.go @@ -0,0 +1,54 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package mockdiddoc + +import ( + "github.com/btcsuite/btcutil/base58" + + "github.com/hyperledger/aries-framework-go/pkg/doc/did" +) + +// GetMockDIDDoc creates a mock DID Doc for testing. +func GetMockDIDDoc() *did.Doc { + return &did.Doc{ + Context: []string{"https://w3id.org/did/v1"}, + ID: "did:peer:123456789abcdefghi#inbox", + Service: []did.Service{ + { + ServiceEndpoint: "https://localhost:8090", + Type: "did-communication", + Priority: 0, + RecipientKeys: []string{"did:example:123456789abcdefghi#keys-2"}, + }, + { + ServiceEndpoint: "https://localhost:8090", + Type: "did-communication", + Priority: 1, + RecipientKeys: []string{"did:example:123456789abcdefghi#keys-1"}, + }, + }, + PublicKey: []did.PublicKey{ + { + ID: "did:example:123456789abcdefghi#keys-1", + Controller: "did:example:123456789abcdefghi", + Type: "Secp256k1VerificationKey2018", + Value: base58.Decode("H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV"), + }, + { + ID: "did:example:123456789abcdefghi#keys-2", + Controller: "did:example:123456789abcdefghi", + Type: "Ed25519VerificationKey2018", + Value: base58.Decode("H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV"), + }, + { + ID: "did:example:123456789abcdefghw#key2", + Controller: "did:example:123456789abcdefghw", + Type: "RsaVerificationKey2018", + Value: base58.Decode("H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV"), + }, + }, + } +}