Skip to content
This repository has been archived by the owner on Mar 27, 2024. It is now read-only.

Commit

Permalink
fix: fixes for didexchange interop with aca-py
Browse files Browse the repository at this point in the history
accept old and new didex. message formats and mime types:
- application/ssi-agent-wire is accepted as an inbound mime type
- did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/didexchange/1.0 is accepted as
  an inbound PIURI for didexchange messages
- did doc can be parsed from "did_doc~attach" attachment in request
  messages, as per didexchange spec, in addition to being accepted
  inside "connection" field, as per connection-request spec

did doc format:
- Parse legacy peer did docs with publicKey members even if their
  context states they are using a newer format.
- Translate raw ed25519 public key values to did:key when reading
  recipient keys or routing keys

Signed-off-by: Filip Burlacu <[email protected]>
  • Loading branch information
Filip Burlacu committed Mar 22, 2021
1 parent a73e5a2 commit bab6fc9
Show file tree
Hide file tree
Showing 12 changed files with 521 additions and 39 deletions.
65 changes: 64 additions & 1 deletion pkg/didcomm/common/service/destination.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ package service
import (
"fmt"

"github.com/btcsuite/btcutil/base58"

diddoc "github.com/hyperledger/aries-framework-go/pkg/doc/did"
vdrapi "github.com/hyperledger/aries-framework-go/pkg/framework/aries/api/vdr"
"github.com/hyperledger/aries-framework-go/pkg/vdr/fingerprint"
)

// Destination provides the recipientKeys, routingKeys, and serviceEndpoint for an outbound message.
Expand All @@ -23,6 +26,8 @@ type Destination struct {

const (
didCommServiceType = "did-communication"
// legacyDIDCommServiceType is the non-spec service type used by legacy didcomm agent systems.
legacyDIDCommServiceType = "IndyAgent"
)

// GetDestination constructs a Destination struct based on the given DID and parameters
Expand All @@ -41,7 +46,7 @@ func GetDestination(did string, vdr vdrapi.Registry) (*Destination, error) {
func CreateDestination(didDoc *diddoc.Doc) (*Destination, error) {
didCommService, ok := diddoc.LookupService(didDoc, didCommServiceType)
if !ok {
return nil, fmt.Errorf("create destination: missing DID doc service")
return createDestinationFromIndy(didDoc)
}

if didCommService.ServiceEndpoint == "" {
Expand All @@ -61,3 +66,61 @@ func CreateDestination(didDoc *diddoc.Doc) (*Destination, error) {
RoutingKeys: didCommService.RoutingKeys,
}, nil
}

func createDestinationFromIndy(didDoc *diddoc.Doc) (*Destination, error) {
didCommService, ok := diddoc.LookupService(didDoc, legacyDIDCommServiceType)
if !ok {
return nil, fmt.Errorf("create destination: missing DID doc service")
}

if didCommService.ServiceEndpoint == "" {
return nil, fmt.Errorf("create destination: no service endpoint on didcomm service block in diddoc: %+v", didDoc)
}

if len(didCommService.RecipientKeys) == 0 {
return nil, fmt.Errorf("create destination: no recipient keys on didcomm service block in diddoc: %+v", didDoc)
}

// TODO ensure recipient keys are did:key's
// https://github.com/hyperledger/aries-framework-go/issues/1604

// convert plain base58 keys to did:key
recKeys := lookupIndyRecipientKeys(didDoc, didCommService.RecipientKeys)
routeKeys := lookupIndyRecipientKeys(didDoc, didCommService.RoutingKeys)

return &Destination{
RecipientKeys: recKeys,
ServiceEndpoint: didCommService.ServiceEndpoint,
RoutingKeys: routeKeys,
}, nil
}

func lookupIndyRecipientKeys(didDoc *diddoc.Doc, recipientKeys []string) []string {
b58VMkeys := map[string]int{}

for i, vm := range didDoc.VerificationMethod {
b58Key := base58.Encode(vm.Value)
b58VMkeys[b58Key] = i
}

var didKeys []string

for _, key := range recipientKeys {
vmIdx, ok := b58VMkeys[key]
if !ok {
continue
}

vm := didDoc.VerificationMethod[vmIdx]
if vm.Type != "Ed25519VerificationKey2018" {
// TODO: handle further key types
continue
}

didKey, _ := fingerprint.CreateDIDKey(vm.Value)

didKeys = append(didKeys, didKey)
}

return didKeys
}
86 changes: 86 additions & 0 deletions pkg/didcomm/common/service/destination_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/hyperledger/aries-framework-go/pkg/doc/did"
mockdiddoc "github.com/hyperledger/aries-framework-go/pkg/mock/diddoc"
mockvdr "github.com/hyperledger/aries-framework-go/pkg/mock/vdr"
"github.com/hyperledger/aries-framework-go/pkg/vdr/fingerprint"
)

func TestGetDestinationFromDID(t *testing.T) {
Expand Down Expand Up @@ -110,6 +111,91 @@ func TestPrepareDestination(t *testing.T) {
})
}

func TestCreateDestinationFromLegacyDoc(t *testing.T) {
t.Run("successfully prepared destination", func(t *testing.T) {
doc := mockdiddoc.GetMockIndyDoc(t)
dest, err := CreateDestination(doc)
require.NoError(t, err)
require.NotNil(t, dest)
require.Equal(t, dest.ServiceEndpoint, "https://localhost:8090")
require.Equal(t, doc.Service[0].RoutingKeys, dest.RoutingKeys)
})

t.Run("error while getting service", func(t *testing.T) {
didDoc := mockdiddoc.GetMockIndyDoc(t)
didDoc.Service = nil

dest, err := createDestinationFromIndy(didDoc)
require.Error(t, err)
require.Contains(t, err.Error(), "missing DID doc service")
require.Nil(t, dest)
})

t.Run("missing service endpoint", func(t *testing.T) {
didDoc := mockdiddoc.GetMockIndyDoc(t)
didDoc.Service = []did.Service{{
Type: legacyDIDCommServiceType,
}}

dest, err := createDestinationFromIndy(didDoc)
require.Error(t, err)
require.Contains(t, err.Error(), "no service endpoint")
require.Nil(t, dest)
})

t.Run("missing recipient keys", func(t *testing.T) {
didDoc := mockdiddoc.GetMockIndyDoc(t)
didDoc.Service = []did.Service{{
Type: legacyDIDCommServiceType,
ServiceEndpoint: "localhost:8080",
}}

dest, err := createDestinationFromIndy(didDoc)
require.Error(t, err)
require.Contains(t, err.Error(), "no recipient keys")
require.Nil(t, dest)
})
}

func TestLookupIndyKeys(t *testing.T) {
t.Run("lookup recipient keys", func(t *testing.T) {
didDoc := mockdiddoc.GetMockIndyDoc(t)

recipientKeys := lookupIndyRecipientKeys(didDoc, didDoc.Service[0].RecipientKeys)
require.NotNil(t, recipientKeys)
require.Len(t, recipientKeys, 1)

pk, err := fingerprint.PubKeyFromDIDKey(recipientKeys[0])
require.NoError(t, err)
require.ElementsMatch(t, didDoc.VerificationMethod[0].Value, pk)
})

t.Run("no keys to lookup", func(t *testing.T) {
didDoc := mockdiddoc.GetMockIndyDoc(t)

recipientKeys := lookupIndyRecipientKeys(didDoc, nil)
require.Nil(t, recipientKeys)
})

t.Run("skip key that isn't found in verificationmethod list", func(t *testing.T) {
didDoc := mockdiddoc.GetMockIndyDoc(t)

didDoc.Service[0].RecipientKeys = []string{"bad key"}

recipientKeys := lookupIndyRecipientKeys(didDoc, didDoc.Service[0].RecipientKeys)
require.Len(t, recipientKeys, 0)
})

t.Run("skip keys that aren't of handled type", func(t *testing.T) {
didDoc := mockdiddoc.GetMockIndyDoc(t)

didDoc.VerificationMethod[0].Type = "bad type"

recipientKeys := lookupIndyRecipientKeys(didDoc, didDoc.Service[0].RecipientKeys)
require.Len(t, recipientKeys, 0)
})
}

func createDIDDoc() *did.Doc {
pubKey, _ := generateKeyPair()
return createDIDDocWithKey(pubKey)
Expand Down
5 changes: 5 additions & 0 deletions pkg/didcomm/protocol/didexchange/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ type Request struct {
Label string `json:"label,omitempty"`
Connection *Connection `json:"connection,omitempty"`
Thread *decorator.Thread `json:"~thread,omitempty"`
// DID the did of the requester. Optional, may be present within `connection` instead.
DID string `json:"did,omitempty"`
// DocAttach an attachment containing the did doc of the requester.
// Optional, may be present within `connection` instead.
DocAttach *decorator.Attachment `json:"did_doc~attach,omitempty"`
}

// Response defines a2a DID exchange response
Expand Down
70 changes: 64 additions & 6 deletions pkg/didcomm/protocol/didexchange/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ SPDX-License-Identifier: Apache-2.0
package didexchange

import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"strings"

"github.com/google/uuid"

Expand Down Expand Up @@ -44,6 +46,17 @@ const (
ResponseMsgType = PIURI + "/response"
// AckMsgType defines the did-exchange ack message type.
AckMsgType = PIURI + "/ack"
// OldPIURI is the old did-exchange protocol identifier URI.
OldPIURI = "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/didexchange/1.0"
// OldInvitationMsgType defines the old format's did-exchange invite message type.
OldInvitationMsgType = OldPIURI + "/invitation"
// OldRequestMsgType defines the old format's did-exchange request message type.
OldRequestMsgType = OldPIURI + "/request"
// OldResponseMsgType defines the old format's did-exchange response message type.
OldResponseMsgType = OldPIURI + "/response"
// OldAckMsgType defines the old format's did-exchange ack message type.
OldAckMsgType = OldPIURI + "/ack"

// oobMsgType is the internal message type for the oob invitation that the didexchange service receives.
oobMsgType = "oob-invitation"
routerConnsMetadataKey = "routerConnections"
Expand Down Expand Up @@ -246,7 +259,11 @@ func (s *Service) Accept(msgType string) bool {
return msgType == InvitationMsgType ||
msgType == RequestMsgType ||
msgType == ResponseMsgType ||
msgType == AckMsgType
msgType == AckMsgType ||
msgType == OldInvitationMsgType ||
msgType == OldRequestMsgType ||
msgType == OldResponseMsgType ||
msgType == OldAckMsgType
}

// HandleOutbound handles outbound didexchange messages.
Expand Down Expand Up @@ -629,13 +646,13 @@ func (s *Service) connectionRecord(msg service.DIDCommMsg) (*connection.Record,
switch msg.Type() {
case oobMsgType:
return s.oobInvitationMsgRecord(msg)
case InvitationMsgType:
case InvitationMsgType, OldInvitationMsgType:
return s.invitationMsgRecord(msg)
case RequestMsgType:
case RequestMsgType, OldRequestMsgType:
return s.requestMsgRecord(msg)
case ResponseMsgType:
case ResponseMsgType, OldResponseMsgType:
return s.responseMsgRecord(msg)
case AckMsgType:
case AckMsgType, OldAckMsgType:
return s.ackMsgRecord(msg)
}

Expand Down Expand Up @@ -722,6 +739,42 @@ func (s *Service) invitationMsgRecord(msg service.DIDCommMsg) (*connection.Recor
return connRecord, nil
}

// nolint:gomnd
func pad(b64 string) string {
mod := len(b64) % 4
if mod <= 1 {
return b64
}

return b64 + strings.Repeat("=", 4-mod)
}

func getRequestConnection(r *Request) (*Connection, error) {
if r.Connection != nil {
return r.Connection, nil
}

if r.DocAttach == nil {
return nil, fmt.Errorf("missing did_doc~attach from request")
}

docData, err := base64.StdEncoding.DecodeString(pad(r.DocAttach.Data.Base64))
if err != nil {
return nil, fmt.Errorf("failed to parse base64 attachment data: %w", err)
}

doc, err := did.ParseDocument(docData)
if err != nil {
logger.Errorf("doc bytes: '%s'", string(docData))
return nil, fmt.Errorf("failed to parse did document: %w", err)
}

return &Connection{
DID: r.DID,
DIDDoc: doc,
}, nil
}

func (s *Service) requestMsgRecord(msg service.DIDCommMsg) (*connection.Record, error) {
request := Request{}

Expand All @@ -740,11 +793,16 @@ func (s *Service) requestMsgRecord(msg service.DIDCommMsg) (*connection.Record,
ConnectionID: generateRandomID(),
ThreadID: request.ID,
State: stateNameNull,
TheirDID: request.Connection.DID,
InvitationID: invitationID,
Namespace: theirNSPrefix,
}

if request.Connection != nil {
connRecord.TheirDID = request.Connection.DID
} else {
connRecord.TheirDID = request.DID
}

if err := s.connectionRecorder.SaveConnectionRecord(connRecord); err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit bab6fc9

Please sign in to comment.