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

Commit

Permalink
refactor: A few refactoring
Browse files Browse the repository at this point in the history
- Add new legacy http header to handle legacy requests. Cover with unit tests
- Encode invitation Recipient key to base58
- Change embedded verification to referenced while creating verification methods
- Add legacy file into doc package to convert DIDdoc to raw doc. Cover with unit test
- Add handling DID id's where did method is not specified
- Add JSONBytes and ParseConnection methods to use them while handling connection data
- Change AckMsgType content to proper one
- Add method to convert array of did-keys to base58 keys
- Enable saving decryption key while handling envelope at connection-request state
- Add new InvitationRecipientKeys into connection.Record model
- Refactor requestMsgRecord, handleInboundRequest and prepareConnectionSignature methods and their unit-tests
- Save Recipient keys while handling connection request
- Use SaveDID instead SaveDIDByResolving

Signed-off-by: Abdulbois <abdulbois.tursunov@avast.com>
  • Loading branch information
Abdulbois committed Aug 22, 2022
1 parent 730ac30 commit 07cf93f
Show file tree
Hide file tree
Showing 18 changed files with 759 additions and 157 deletions.
17 changes: 14 additions & 3 deletions pkg/didcomm/dispatcher/inbound/inbound_message_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,20 @@ func (handler *MessageHandler) HandleInboundEnvelope(envelope *transport.Envelop
}

if foundService != nil {
props := make(map[string]interface{})

switch foundService.Name() {
// perf: DID exchange and legacy-connection doesn't require myDID and theirDID
case didexchange.DIDExchange, legacyconnection.LegacyConnection:
// perf: DID exchange doesn't require myDID and theirDID
case didexchange.DIDExchange:
// perf: legacy-connection requires envelope.ToKey when sending Connection Response (it will sign connection
// data with this key)
case legacyconnection.LegacyConnection:
// When type of envelope.Message is connections/request, the key which was used to decrypt message is the
// same key which was sent during invitation. If ParentThreadID is missed (Interop issues), that key will be
// used to sign connection-data while sending connection response
if msg.Type() == legacyconnection.RequestMsgType && msg.ParentThreadID() == "" {
props[legacyconnection.InvitationRecipientKey] = base58.Encode(envelope.ToKey)
}
default:
if !gotDIDs {
myDID, theirDID, err = handler.getDIDs(envelope, msg)
Expand All @@ -167,7 +178,7 @@ func (handler *MessageHandler) HandleInboundEnvelope(envelope *transport.Envelop
}
}

_, err = foundService.HandleInbound(msg, service.NewDIDCommContext(myDID, theirDID, nil))
_, err = foundService.HandleInbound(msg, service.NewDIDCommContext(myDID, theirDID, props))

return err
}
Expand Down
5 changes: 2 additions & 3 deletions pkg/didcomm/protocol/legacyconnection/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@ func (ctx *context) createNewKeyAndVM(didDoc *did.Doc) error {
}

didDoc.VerificationMethod = append(didDoc.VerificationMethod, *vm)
didDoc.Authentication = append(didDoc.Authentication, *did.NewEmbeddedVerification(vm, did.Authentication))
// FIXME is KeyAgreement needed?
didDoc.KeyAgreement = append(didDoc.KeyAgreement, *did.NewEmbeddedVerification(kaVM, did.KeyAgreement))
didDoc.Authentication = append(didDoc.Authentication, *did.NewReferencedVerification(vm, did.Authentication))
didDoc.KeyAgreement = append(didDoc.KeyAgreement, *did.NewReferencedVerification(kaVM, did.KeyAgreement))

return nil
}
Expand Down
84 changes: 84 additions & 0 deletions pkg/didcomm/protocol/legacyconnection/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ SPDX-License-Identifier: Apache-2.0
package legacyconnection

import (
"encoding/json"
"errors"
"fmt"
"time"

"github.com/mitchellh/mapstructure"

"github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/decorator"
"github.com/hyperledger/aries-framework-go/pkg/doc/did"
)
Expand Down Expand Up @@ -76,3 +83,80 @@ type Connection struct {
DID string `json:"DID,omitempty"`
DIDDoc *did.Doc `json:"DIDDoc,omitempty"`
}

type connectionRaw struct {
DID string `json:"DID,omitempty"`
DIDDoc *rawDoc `json:"DIDDoc,omitempty"`
}

type rawDoc struct {
Context interface{} `json:"@context,omitempty"`
ID string `json:"id,omitempty"`
AlsoKnownAs []interface{} `json:"alsoKnownAs,omitempty"`
VerificationMethod []map[string]interface{} `json:"verificationMethod,omitempty"`
PublicKey []map[string]interface{} `json:"publicKey,omitempty"`
Service []map[string]interface{} `json:"service,omitempty"`
Authentication []interface{} `json:"authentication,omitempty"`
AssertionMethod []interface{} `json:"assertionMethod,omitempty"`
CapabilityDelegation []interface{} `json:"capabilityDelegation,omitempty"`
CapabilityInvocation []interface{} `json:"capabilityInvocation,omitempty"`
KeyAgreement []interface{} `json:"keyAgreement,omitempty"`
Created *time.Time `json:"created,omitempty"`
Updated *time.Time `json:"updated,omitempty"`
Proof []interface{} `json:"proof,omitempty"`
}

// JSONBytes converts Connection to json bytes.
func (con *Connection) toLegacyJSONBytes() ([]byte, error) {
if con.DIDDoc == nil {
return nil, fmt.Errorf("DIDDoc field cannot be empty")
}

legacyDoc, err := con.DIDDoc.ToLegacyRawDoc()
if err != nil {
return nil, fmt.Errorf("converting to Legacy Raw Doc failed: %w", err)
}

connDoc := &rawDoc{}

_ = mapstructure.Decode(legacyDoc, connDoc) //nolint: errcheck

conRaw := connectionRaw{
DID: con.DID,
DIDDoc: connDoc,
}

byteConn, err := json.Marshal(conRaw)
if err != nil {
return nil, fmt.Errorf("JSON marshalling of connection raw failed: %w", err)
}

return byteConn, nil
}

// ParseConnection creates an instance of Connection by reading a JSON connection from bytes.
func parseLegacyJSONBytes(data []byte) (*Connection, error) {
connRaw := &connectionRaw{}

err := json.Unmarshal(data, &connRaw)
if err != nil {
return nil, fmt.Errorf("JSON umarshalling of connection data bytes failed: %w", err)
} else if connRaw.DIDDoc == nil {
return nil, errors.New("connection DIDDoc field is missed")
}

docRaw, err := json.Marshal(connRaw.DIDDoc)
if err != nil {
return nil, fmt.Errorf("JSON marshaling failed: %w", err)
}

doc, err := did.ParseDocument(docRaw)
if err != nil {
return nil, fmt.Errorf("parcing did document failed: %w", err)
}

return &Connection{
DID: connRaw.DID,
DIDDoc: doc,
}, nil
}
110 changes: 110 additions & 0 deletions pkg/didcomm/protocol/legacyconnection/models_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
Copyright Avast Software. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package legacyconnection

import (
_ "embed"
"encoding/json"
"testing"

"github.com/btcsuite/btcutil/base58"
"github.com/stretchr/testify/require"

"github.com/hyperledger/aries-framework-go/pkg/doc/did"
)

//nolint:gochecknoglobals
var (
//go:embed testdata/valid_conn_data.jsonld
validConnectionData string
)

// nolint:lll
func TestJSONConversion(t *testing.T) {
con, err := parseLegacyJSONBytes([]byte(validConnectionData))
require.NoError(t, err)
require.NotEmpty(t, con)

require.Equal(t, con.DID, "did:example:21tDAKCERh95uGgKbJNHYp")
require.Equal(t, con.DIDDoc.ID, "did:example:21tDAKCERh95uGgKbJNHYp")
require.Contains(t, con.DIDDoc.Context, "https://w3id.org/did/v0.11")
require.Equal(t, con.DIDDoc.AlsoKnownAs[0], "did:example:123")
require.Equal(t, con.DIDDoc.VerificationMethod[0].Type, "Secp256k1VerificationKey2018")
require.Equal(t, base58.Encode(con.DIDDoc.VerificationMethod[0].Value), "H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV")
require.Equal(t, con.DIDDoc.Authentication[0].VerificationMethod.Type, "Secp256k1VerificationKey2018")
require.Equal(t, base58.Encode(con.DIDDoc.Authentication[0].VerificationMethod.Value), "H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV")
require.Equal(t, con.DIDDoc.Service[0].Type, "IndyAgent")
uri, err := con.DIDDoc.Service[0].ServiceEndpoint.URI()
require.NoError(t, err)
require.Contains(t, uri, "https://agent.example.com/")
require.Equal(t, con.DIDDoc.Service[0].RecipientKeys, []string{"did:example:123456789abcdefghi#key2"})
require.Equal(t, con.DIDDoc.Service[0].RoutingKeys, []string{"did:example:123456789abcdefghi#key2"})

conBytes, err := con.toLegacyJSONBytes()
require.NoError(t, err)
require.NotEmpty(t, conBytes)

rawString := string(conBytes)

require.Contains(t, rawString, "\"DID\":\"did:example:21tDAKCERh95uGgKbJNHYp\"")
require.Contains(t, rawString, "\"@context\":\"https://w3id.org/did/v1\"")
require.Contains(t, rawString, "\"controller\":\"did:example:123456789abcdefghi\",\"id\":\"did:example:123456789abcdefghi#keys-1\",\"publicKeyBase58\":\"H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV\",\"type\":\"Secp256k1VerificationKey2018\"")
require.Contains(t, rawString, "\"authentication\":[{\"publicKey\":\"did:example:123456789abcdefghi#keys-1\",\"type\":\"Secp256k1VerificationKey2018\"},{\"publicKey\":\"did:example:123456789abcdefghs#key3\",\"type\":\"RsaVerificationKey2018\"}]")
require.Contains(t, rawString, "\"service\":[{\"id\":\"did:example:123456789abcdefghi#did-communication\",\"priority\":0,\"recipientKeys\":[\"did:example:123456789abcdefghi#key2\"],\"routingKeys\":[\"did:example:123456789abcdefghi#key2\"],\"serviceEndpoint\":\"https://agent.example.com/\",\"type\":\"IndyAgent\"}]")
}

func TestToLegacyJSONBytes(t *testing.T) {
con := &Connection{
DID: "did:example:21tDAKCERh95uGgKbJNHYp",
}

// Empty DIDDoc data
legacyDoc, err := con.toLegacyJSONBytes()
require.ErrorContains(t, err, "DIDDoc field cannot be empty")
require.Empty(t, legacyDoc)

// Success

con.DIDDoc = &did.Doc{
Context: "https://w3id.org/did/v0.11",
ID: "did:example:21tDAKCERh95uGgKbJNHYp",
}
legacyDoc, err = con.toLegacyJSONBytes()

require.NoError(t, err)
require.NotEmpty(t, legacyDoc)
require.Contains(t, string(legacyDoc), "\"DID\":\"did:example:21tDAKCERh95uGgKbJNHYp\"")
require.Contains(t, string(legacyDoc), "\"@context\":\"https://w3id.org/did/v1\"")
}

func TestParseJSONBytes(t *testing.T) {
// Nil payload
conRaw, err := parseLegacyJSONBytes(nil)
require.ErrorContains(t, err, "JSON umarshalling of connection data bytes failed")
require.Empty(t, conRaw)

// Empty payload
conRaw, err = parseLegacyJSONBytes([]byte{})
require.ErrorContains(t, err, "JSON umarshalling of connection data bytes failed")
require.Empty(t, conRaw)

// Empty DIDDoc
docBytes, err := json.Marshal(Connection{
DID: "did:example:21tDAKCERh95uGgKbJNHYp",
})
require.NoError(t, err)
require.NotEmpty(t, docBytes)

conRaw, err = parseLegacyJSONBytes(docBytes)
require.ErrorContains(t, err, "connection DIDDoc field is missed")
require.Empty(t, conRaw)

// Success
con, err := parseLegacyJSONBytes([]byte(validConnectionData))
require.NoError(t, err)
require.NotEmpty(t, con)
}
46 changes: 29 additions & 17 deletions pkg/didcomm/protocol/legacyconnection/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const (
// ResponseMsgType defines the legacy-connection response message type.
ResponseMsgType = PIURI + "/response"
// AckMsgType defines the legacy-connection ack message type.
AckMsgType = PIURI + "/ack"
AckMsgType = "https://didcomm.org/notification/1.0/ack"
routerConnsMetadataKey = "routerConnections"
)

Expand All @@ -55,6 +55,8 @@ const (
// TODO: https://github.com/hyperledger/aries-framework-go/issues/556 It will not be constant, this namespace
// will need to be figured with verification key
theirNSPrefix = "their"
// InvitationRecipientKey is map key constant.
InvitationRecipientKey = "invRecipientKey"
)

// message type to store data for eventing. This is retrieved during callback.
Expand Down Expand Up @@ -222,7 +224,7 @@ func retrievingRouterConnections(msg service.DIDCommMsg) []string {
}

// HandleInbound handles inbound connection messages.
func (s *Service) HandleInbound(msg service.DIDCommMsg, _ service.DIDCommContext) (string, error) {
func (s *Service) HandleInbound(msg service.DIDCommMsg, ctx service.DIDCommContext) (string, error) {
logger.Debugf("receive inbound message : %s", msg)

// fetch the thread id
Expand All @@ -238,7 +240,7 @@ func (s *Service) HandleInbound(msg service.DIDCommMsg, _ service.DIDCommContext
}

// connection record
connRecord, err := s.connectionRecord(msg)
connRecord, err := s.connectionRecord(msg, ctx)
if err != nil {
return "", fmt.Errorf("failed to fetch connection record : %w", err)
}
Expand Down Expand Up @@ -376,7 +378,7 @@ func (s *Service) handle(msg *message, aEvent chan<- service.DIDCommAction) erro
}

if connectionRecord.State == StateIDCompleted {
err = s.connectionStore.SaveDIDByResolving(connectionRecord.TheirDID, connectionRecord.RecipientKeys...)
err = s.connectionStore.SaveDID(connectionRecord.TheirDID, connectionRecord.RecipientKeys...)
if err != nil {
return fmt.Errorf("save theirDID: %w", err)
}
Expand Down Expand Up @@ -627,12 +629,12 @@ func (s *Service) CreateConnection(record *connection.Record, theirDID *did.Doc)
return s.connectionRecorder.SaveConnectionRecord(record)
}

func (s *Service) connectionRecord(msg service.DIDCommMsg) (*connection.Record, error) {
func (s *Service) connectionRecord(msg service.DIDCommMsg, ctx service.DIDCommContext) (*connection.Record, error) {
switch msg.Type() {
case InvitationMsgType:
return s.invitationMsgRecord(msg)
case RequestMsgType:
return s.requestMsgRecord(msg)
return s.requestMsgRecord(msg, ctx)
case ResponseMsgType:
return s.responseMsgRecord(msg)
case AckMsgType:
Expand Down Expand Up @@ -680,8 +682,11 @@ func (s *Service) invitationMsgRecord(msg service.DIDCommMsg) (*connection.Recor
return connRecord, nil
}

func (s *Service) requestMsgRecord(msg service.DIDCommMsg) (*connection.Record, error) {
request := Request{}
func (s *Service) requestMsgRecord(msg service.DIDCommMsg, ctx service.DIDCommContext) (*connection.Record, error) {
var (
request Request
invitationRecKey []string
)

err := msg.Decode(&request)
if err != nil {
Expand All @@ -690,22 +695,29 @@ func (s *Service) requestMsgRecord(msg service.DIDCommMsg) (*connection.Record,

invitationID := msg.ParentThreadID()
if invitationID == "" {
return nil, fmt.Errorf("missing parent thread ID on connection request with @id=%s", request.ID)
// try to retrieve key which was assigned at HandleInboundEnvelope method of inbound handler
if verKey, ok := ctx.All()[InvitationRecipientKey].(string); ok && verKey != "" {
invitationRecKey = append(invitationRecKey, verKey)
} else {
return nil, fmt.Errorf("missing parent thread ID and invitation recipient key"+
" on connection request with @id=%s", request.ID)
}
}

if request.Connection == nil {
return nil, fmt.Errorf("missing connection field on connection request with @id=%s", request.ID)
}

connRecord := &connection.Record{
TheirLabel: request.Label,
ConnectionID: generateRandomID(),
ThreadID: request.ID,
State: stateNameNull,
TheirDID: request.Connection.DID,
InvitationID: invitationID,
Namespace: theirNSPrefix,
DIDCommVersion: service.V1,
TheirLabel: request.Label,
ConnectionID: generateRandomID(),
ThreadID: request.ID,
State: stateNameNull,
TheirDID: request.Connection.DID,
InvitationID: invitationID,
InvitationRecipientKeys: invitationRecKey,
Namespace: theirNSPrefix,
DIDCommVersion: service.V1,
}

if !strings.HasPrefix(connRecord.TheirDID, "did") {
Expand Down

0 comments on commit 07cf93f

Please sign in to comment.