github.com/hyperledger/aries-framework-go@v0.3.2/pkg/didcomm/protocol/outofbandv2/service.go (about)

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package outofbandv2
     8  
     9  import (
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"strings"
    14  
    15  	gojose "github.com/go-jose/go-jose/v3"
    16  	"github.com/google/uuid"
    17  
    18  	"github.com/hyperledger/aries-framework-go/pkg/common/log"
    19  	"github.com/hyperledger/aries-framework-go/pkg/common/model"
    20  	"github.com/hyperledger/aries-framework-go/pkg/crypto"
    21  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service"
    22  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/dispatcher"
    23  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/decorator"
    24  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/mediator"
    25  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/transport"
    26  	"github.com/hyperledger/aries-framework-go/pkg/doc/did"
    27  	"github.com/hyperledger/aries-framework-go/pkg/doc/jose/jwk"
    28  	"github.com/hyperledger/aries-framework-go/pkg/doc/jose/jwk/jwksupport"
    29  	vdrapi "github.com/hyperledger/aries-framework-go/pkg/framework/aries/api/vdr"
    30  	"github.com/hyperledger/aries-framework-go/pkg/internal/logutil"
    31  	"github.com/hyperledger/aries-framework-go/pkg/kms"
    32  	"github.com/hyperledger/aries-framework-go/pkg/store/connection"
    33  	"github.com/hyperledger/aries-framework-go/pkg/vdr/peer"
    34  	"github.com/hyperledger/aries-framework-go/spi/storage"
    35  )
    36  
    37  const (
    38  	// Name of this protocol service.
    39  	Name = "out-of-band/2.0"
    40  	// dbName is the name of this service's db stores.
    41  	dbName = "_OutOfBand2"
    42  	// PIURI is the Out-of-Band protocol's protocol instance URI.
    43  	PIURI = "https://didcomm.org/" + Name
    44  	// InvitationMsgType is the '@type' for the invitation message.
    45  	InvitationMsgType = PIURI + "/invitation"
    46  
    47  	// TODO channel size - https://github.com/hyperledger/aries-framework-go/issues/246
    48  	callbackChannelSize = 10
    49  
    50  	contextKey = "context_%s"
    51  
    52  	ed25519VerificationKey2018 = "Ed25519VerificationKey2018"
    53  	bls12381G2Key2020          = "Bls12381G2Key2020"
    54  	jsonWebKey2020             = "JsonWebKey2020"
    55  	x25519KeyAgreementKey2019  = "X25519KeyAgreementKey2019"
    56  )
    57  
    58  var logger = log.New(fmt.Sprintf("aries-framework/%s/service", Name))
    59  
    60  // Service implements the Out-Of-Band V2 protocol.
    61  type Service struct {
    62  	service.Action
    63  	service.Message
    64  	vdrRegistry            vdrapi.Registry
    65  	callbackChannel        chan *callback
    66  	transientStore         storage.Store
    67  	connectionRecorder     *connection.Recorder
    68  	inboundHandler         func() service.InboundHandler
    69  	listenerFunc           func()
    70  	messenger              service.Messenger
    71  	myMediaTypeProfiles    []string
    72  	kms                    kms.KeyManager
    73  	keyType                kms.KeyType
    74  	keyAgreementType       kms.KeyType
    75  	msgTypeServicesTargets map[string]string
    76  	allServices            []dispatcher.ProtocolService
    77  	routeSvc               mediator.ProtocolService
    78  	initialized            bool
    79  }
    80  
    81  type callback struct {
    82  	msg service.DIDCommMsg
    83  }
    84  
    85  // Provider provides this service's dependencies.
    86  type Provider interface {
    87  	Service(id string) (interface{}, error)
    88  	StorageProvider() storage.Provider
    89  	VDRegistry() vdrapi.Registry
    90  	ProtocolStateStorageProvider() storage.Provider
    91  	InboundDIDCommMessageHandler() func() service.InboundHandler
    92  	Messenger() service.Messenger
    93  	KMS() kms.KeyManager
    94  	KeyType() kms.KeyType
    95  	KeyAgreementType() kms.KeyType
    96  	MediaTypeProfiles() []string
    97  	ServiceMsgTypeTargets() []dispatcher.MessageTypeTarget
    98  	AllServices() []dispatcher.ProtocolService
    99  }
   100  
   101  // New creates a new instance of the out-of-band service.
   102  func New(p Provider) (*Service, error) {
   103  	svc := Service{}
   104  
   105  	err := svc.Initialize(p)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  
   110  	return &svc, nil
   111  }
   112  
   113  // Initialize initializes the Service. If Initialize succeeds, any further call is a no-op.
   114  func (s *Service) Initialize(prov interface{}) error { // nolint:funlen
   115  	if s.initialized {
   116  		return nil
   117  	}
   118  
   119  	p, ok := prov.(Provider)
   120  	if !ok {
   121  		return fmt.Errorf("oob/2.0 expected provider of type `%T`, got type `%T`", Provider(nil), p)
   122  	}
   123  
   124  	store, err := p.ProtocolStateStorageProvider().OpenStore(dbName)
   125  	if err != nil {
   126  		return fmt.Errorf("oob/2.0 failed to open the transientStore : %w", err)
   127  	}
   128  
   129  	err = p.ProtocolStateStorageProvider().SetStoreConfig(dbName,
   130  		storage.StoreConfiguration{TagNames: []string{contextKey}})
   131  	if err != nil {
   132  		return fmt.Errorf("oob/2.0 failed to set transientStore config in protocol state transientStore: %w", err)
   133  	}
   134  
   135  	msgTypeServicesTargets := map[string]string{}
   136  
   137  	for _, v := range p.ServiceMsgTypeTargets() {
   138  		msgTypeServicesTargets[v.Target] = v.MsgType
   139  	}
   140  
   141  	connRecorder, err := connection.NewRecorder(p)
   142  	if err != nil {
   143  		return fmt.Errorf("failed to initialize connection recorder: %w", err)
   144  	}
   145  
   146  	routeSvcBase, err := p.Service(mediator.Coordination)
   147  	if err != nil {
   148  		return err
   149  	}
   150  
   151  	routeSvc, ok := routeSvcBase.(mediator.ProtocolService)
   152  	if !ok {
   153  		return errors.New("cast service to Route Service failed")
   154  	}
   155  
   156  	s.callbackChannel = make(chan *callback, callbackChannelSize)
   157  	s.transientStore = store
   158  	s.vdrRegistry = p.VDRegistry()
   159  	s.connectionRecorder = connRecorder
   160  	s.inboundHandler = p.InboundDIDCommMessageHandler()
   161  	s.messenger = p.Messenger()
   162  	s.myMediaTypeProfiles = p.MediaTypeProfiles()
   163  	s.msgTypeServicesTargets = msgTypeServicesTargets
   164  	s.kms = p.KMS()
   165  	s.keyType = p.KeyType()
   166  	s.keyAgreementType = p.KeyAgreementType()
   167  	s.allServices = p.AllServices()
   168  	s.listenerFunc = listener(s.callbackChannel, s.handleCallback)
   169  	s.routeSvc = routeSvc
   170  
   171  	go s.listenerFunc()
   172  
   173  	s.initialized = true
   174  
   175  	return nil
   176  }
   177  
   178  // Name is this service's name.
   179  func (s *Service) Name() string {
   180  	return Name
   181  }
   182  
   183  // Accept determines whether this service can handle the given type of message.
   184  func (s *Service) Accept(msgType string) bool {
   185  	return msgType == InvitationMsgType
   186  }
   187  
   188  // HandleInbound handles inbound messages.
   189  func (s *Service) HandleInbound(msg service.DIDCommMsg, didCommCtx service.DIDCommContext) (string, error) {
   190  	logger.Debugf("oob/2.0 inbound message: %s", msg)
   191  
   192  	if msg == nil {
   193  		return "", fmt.Errorf("oob/2.0 cannot handle nil inbound message")
   194  	}
   195  
   196  	if !s.Accept(msg.Type()) {
   197  		return "", fmt.Errorf("oob/2.0 unsupported message type %s", msg.Type())
   198  	}
   199  
   200  	return "", nil
   201  }
   202  
   203  // HandleOutbound handles outbound messages.
   204  func (s *Service) HandleOutbound(_ service.DIDCommMsg, _, _ string) (string, error) {
   205  	// TODO implement
   206  	return "", errors.New("oob/2.0 not implemented")
   207  }
   208  
   209  type acceptOpts struct {
   210  	routerConnections []string
   211  }
   212  
   213  // AcceptOption boilerplate adds configured parameter to Service.AcceptInvitation.
   214  type AcceptOption func(opts *acceptOpts)
   215  
   216  // WithRouterConnections option sets router connections for the connection created with this Invitation acceptance.
   217  func WithRouterConnections(routerConnections []string) AcceptOption {
   218  	return func(opts *acceptOpts) {
   219  		opts.routerConnections = routerConnections
   220  	}
   221  }
   222  
   223  // AcceptInvitation from another agent.
   224  //nolint:funlen,gocyclo
   225  func (s *Service) AcceptInvitation(i *Invitation, opts ...AcceptOption) (string, error) {
   226  	options := &acceptOpts{}
   227  
   228  	for _, opt := range opts {
   229  		opt(options)
   230  	}
   231  
   232  	msg := service.NewDIDCommMsgMap(i)
   233  
   234  	err := validateInvitationAcceptance(msg, s.myMediaTypeProfiles)
   235  	if err != nil {
   236  		return "", fmt.Errorf("oob/2.0 unable to accept invitation: %w", err)
   237  	}
   238  
   239  	if i.From == "" {
   240  		return "", fmt.Errorf("oob/2.0 does not have from field")
   241  	}
   242  
   243  	clbk := &callback{
   244  		msg: msg,
   245  	}
   246  
   247  	err = s.handleCallback(clbk)
   248  	if err != nil {
   249  		return "", fmt.Errorf("oob/2.0 failed to accept invitation : %w", err)
   250  	}
   251  
   252  	newDID := &did.Doc{}
   253  
   254  	err = s.createNewKeyAndVM(newDID)
   255  	if err != nil {
   256  		return "", fmt.Errorf("oob/2.0 AcceptInvitation: creating new keys and VMS for DID document failed: %w", err)
   257  	}
   258  
   259  	recKey := newDID.KeyAgreement[0].VerificationMethod.ID
   260  
   261  	services := []did.Service{}
   262  
   263  	for _, connID := range options.routerConnections {
   264  		// get the route configs (pass empty service endpoint, as default service endpoint added in VDR)
   265  		serviceEndpoint, routingKeys, e := mediator.GetRouterConfig(s.routeSvc, connID, "")
   266  		if e != nil {
   267  			return "", fmt.Errorf("did doc - fetch router config: %w", e)
   268  		}
   269  
   270  		services = append(services, did.Service{
   271  			ServiceEndpoint: model.NewDIDCommV2Endpoint([]model.DIDCommV2Endpoint{
   272  				{URI: serviceEndpoint, Accept: s.myMediaTypeProfiles, RoutingKeys: routingKeys},
   273  			}),
   274  			RecipientKeys: []string{recKey},
   275  			Type:          vdrapi.DIDCommV2ServiceType,
   276  		})
   277  	}
   278  
   279  	if len(services) == 0 {
   280  		services = []did.Service{
   281  			{
   282  				RecipientKeys: []string{recKey},
   283  				Type:          vdrapi.DIDCommV2ServiceType,
   284  			},
   285  		}
   286  	}
   287  
   288  	newDID.Service = services
   289  
   290  	if i.Body != nil && i.Body.GoalCode != "" {
   291  		serviceURL := s.msgTypeServicesTargets[i.Body.GoalCode]
   292  		for _, srvc := range s.allServices {
   293  			if strings.Contains(serviceURL, srvc.Name()) {
   294  				connID := s.handleInboundService(serviceURL, srvc, i.From, i.Requests, newDID)
   295  
   296  				if connID != "" {
   297  					logger.Debugf("oob/2.0 matching target service found for url '%v' and executed, "+
   298  						"oobv2.AcceptInvitation() is done.", serviceURL)
   299  					return connID, nil
   300  				}
   301  			}
   302  		}
   303  
   304  		logger.Debugf("oob/2.0 no matching target service found for url '%v', oobv2.AcceptInvitation() is done but"+
   305  			" no target service triggered", serviceURL)
   306  	}
   307  
   308  	logger.Debugf("oob/2.0 request body or Goal code is empty, oobv2.AcceptInvitation() is done but no" +
   309  		"target service triggered, generating a new peer DID for the first valid attachment and return it")
   310  
   311  	senderDoc, err := s.vdrRegistry.Resolve(i.From)
   312  	if err != nil {
   313  		return "", fmt.Errorf("oob/2.0 failed to resolve inviter DID: %w", err)
   314  	}
   315  
   316  	myDID, err := s.vdrRegistry.Create(peer.DIDMethod, newDID)
   317  	if err != nil {
   318  		return "", fmt.Errorf("oob/2.0 creating new DID via VDR failed: %w", err)
   319  	}
   320  
   321  	if len(options.routerConnections) != 0 {
   322  		err = s.addRouterKeys(myDID.DIDDocument, options.routerConnections)
   323  		if err != nil {
   324  			return "", err
   325  		}
   326  	}
   327  
   328  	destination, err := service.CreateDestination(senderDoc.DIDDocument)
   329  	if err != nil {
   330  		return "", fmt.Errorf("oob/2.0 failed to create destination: %w", err)
   331  	}
   332  
   333  	initialState, err := peer.UnsignedGenesisDelta(myDID.DIDDocument)
   334  	if err != nil {
   335  		return "", fmt.Errorf("marshalling peer DID into initialState: %w", err)
   336  	}
   337  
   338  	connRecord := &connection.Record{
   339  		ConnectionID:        uuid.New().String(),
   340  		ParentThreadID:      i.ID,
   341  		State:               connection.StateNameCompleted,
   342  		InvitationID:        i.ID,
   343  		ServiceEndPoint:     destination.ServiceEndpoint,
   344  		RecipientKeys:       destination.RecipientKeys,
   345  		TheirLabel:          i.Label,
   346  		TheirDID:            i.From,
   347  		MyDID:               myDID.DIDDocument.ID,
   348  		Namespace:           connection.MyNSPrefix,
   349  		Implicit:            true,
   350  		InvitationDID:       myDID.DIDDocument.ID,
   351  		DIDCommVersion:      service.V2,
   352  		PeerDIDInitialState: initialState,
   353  	}
   354  
   355  	if err := s.connectionRecorder.SaveConnectionRecord(connRecord); err != nil {
   356  		return "", fmt.Errorf("oob/2.0 failed to create connection: %w", err)
   357  	}
   358  
   359  	return connRecord.ConnectionID, nil
   360  }
   361  
   362  // TODO refactor: mostly copied from didexchange svc.
   363  func (s *Service) addRouterKeys(doc *did.Doc, routerConnections []string) error {
   364  	// use KeyAgreement.ID as recKey for DIDComm V2
   365  	for _, ka := range doc.KeyAgreement {
   366  		kaID := ka.VerificationMethod.ID
   367  		if strings.HasPrefix(kaID, "#") {
   368  			kaID = doc.ID + kaID
   369  		}
   370  
   371  		for _, connID := range routerConnections {
   372  			// TODO https://github.com/hyperledger/aries-framework-go/issues/1105 add multiple keys at once.
   373  			if err := mediator.AddKeyToRouter(s.routeSvc, connID, kaID); err != nil {
   374  				return fmt.Errorf("did doc - add key to the router: %w", err)
   375  			}
   376  		}
   377  	}
   378  
   379  	return nil
   380  }
   381  
   382  // SaveInvitation saves OOB v2 Invitation.
   383  func (s *Service) SaveInvitation(inv *Invitation) error {
   384  	return s.connectionRecorder.SaveOOBv2Invitation(inv.From, *inv)
   385  }
   386  
   387  func (s *Service) handleInboundService(serviceURL string, srvc dispatcher.ProtocolService, senderDID string,
   388  	attachments []*decorator.AttachmentV2, newDID *did.Doc) string {
   389  	for _, atchmnt := range attachments {
   390  		serviceRequest, err := atchmnt.Data.Fetch()
   391  		if err != nil {
   392  			logger.Debugf("oob/2.0 fetching target service '%v' for url '%v' attachment request failed:"+
   393  				" %v, skipping attachment entry..", srvc.Name(), serviceURL, err)
   394  
   395  			continue
   396  		}
   397  
   398  		didCommMsgRequest := service.DIDCommMsgMap{}
   399  
   400  		err = didCommMsgRequest.UnmarshalJSON(serviceRequest)
   401  		if err != nil {
   402  			logger.Debugf("oob/2.0 fetching target service '%v' for url '%v' attachment request failed:"+
   403  				" %v, skipping attachment entry..", srvc.Name(), serviceURL, err)
   404  
   405  			continue
   406  		}
   407  
   408  		myDID, err := s.vdrRegistry.Create(peer.DIDMethod, newDID)
   409  		if err != nil {
   410  			logger.Debugf("oob/2.0 fetching target service '%v' for url '%v' creating new DID via VDR "+
   411  				"failed: %v, skipping attachment entry..", srvc.Name(), serviceURL, err)
   412  
   413  			continue
   414  		}
   415  
   416  		// TODO bug: most services don't return a connection ID from handleInbound, we can't expect it from there.
   417  		connID, err := srvc.HandleInbound(didCommMsgRequest, service.NewDIDCommContext(myDID.DIDDocument.ID,
   418  			senderDID, nil))
   419  		if err != nil {
   420  			logger.Debugf("oob/2.0 executing target service '%v' for url '%v' failed: %v, skipping "+
   421  				"attachment entry..", srvc.Name(), serviceURL, err)
   422  
   423  			continue
   424  		}
   425  
   426  		logger.Debugf("oob/2.0 successfully executed target service '%v' for target url: '%v', returned id: %v",
   427  			srvc.Name(), serviceURL, connID)
   428  
   429  		return connID
   430  	}
   431  
   432  	return ""
   433  }
   434  
   435  // TODO below function and sub functions are copied from pkg/didcomm/protocol/didexchange/keys.go
   436  //      move code in a common location and remove duplicate code.
   437  func (s *Service) createNewKeyAndVM(didDoc *did.Doc) error {
   438  	vm, err := s.createSigningVM()
   439  	if err != nil {
   440  		return err
   441  	}
   442  
   443  	kaVM, err := s.createEncryptionVM()
   444  	if err != nil {
   445  		return err
   446  	}
   447  
   448  	didDoc.VerificationMethod = append(didDoc.VerificationMethod, *vm)
   449  
   450  	didDoc.Authentication = append(didDoc.Authentication, *did.NewReferencedVerification(vm, did.Authentication))
   451  	didDoc.KeyAgreement = append(didDoc.KeyAgreement, *did.NewReferencedVerification(kaVM, did.KeyAgreement))
   452  
   453  	return nil
   454  }
   455  
   456  // nolint:gochecknoglobals
   457  var vmType = map[kms.KeyType]string{
   458  	kms.ED25519Type:            ed25519VerificationKey2018,
   459  	kms.BLS12381G2Type:         bls12381G2Key2020,
   460  	kms.ECDSAP256TypeDER:       jsonWebKey2020,
   461  	kms.ECDSAP256TypeIEEEP1363: jsonWebKey2020,
   462  	kms.ECDSAP384TypeDER:       jsonWebKey2020,
   463  	kms.ECDSAP384TypeIEEEP1363: jsonWebKey2020,
   464  	kms.ECDSAP521TypeDER:       jsonWebKey2020,
   465  	kms.ECDSAP521TypeIEEEP1363: jsonWebKey2020,
   466  	kms.X25519ECDHKWType:       x25519KeyAgreementKey2019,
   467  	kms.NISTP256ECDHKWType:     jsonWebKey2020,
   468  	kms.NISTP384ECDHKWType:     jsonWebKey2020,
   469  	kms.NISTP521ECDHKWType:     jsonWebKey2020,
   470  }
   471  
   472  func getVerMethodType(kt kms.KeyType) string {
   473  	return vmType[kt]
   474  }
   475  
   476  func (s *Service) createSigningVM() (*did.VerificationMethod, error) {
   477  	vmType := getVerMethodType(s.keyType)
   478  
   479  	_, pubKeyBytes, err := s.kms.CreateAndExportPubKeyBytes(s.keyType)
   480  	if err != nil {
   481  		return nil, fmt.Errorf("createSigningVM: %w", err)
   482  	}
   483  
   484  	vmID := "#key-1"
   485  
   486  	switch vmType {
   487  	case ed25519VerificationKey2018, bls12381G2Key2020:
   488  		return did.NewVerificationMethodFromBytes(vmID, vmType, "", pubKeyBytes), nil
   489  	case jsonWebKey2020:
   490  		j, err := jwksupport.PubKeyBytesToJWK(pubKeyBytes, s.keyType)
   491  		if err != nil {
   492  			return nil, fmt.Errorf("createSigningVM: failed to convert public key to JWK for VM: %w", err)
   493  		}
   494  
   495  		return did.NewVerificationMethodFromJWK(vmID, vmType, "", j)
   496  	default:
   497  		return nil, fmt.Errorf("createSigningVM: unsupported verification method: '%s'", vmType)
   498  	}
   499  }
   500  
   501  func (s *Service) createEncryptionVM() (*did.VerificationMethod, error) {
   502  	encKeyType := s.keyAgreementType
   503  
   504  	vmType := getVerMethodType(encKeyType)
   505  
   506  	_, kaPubKeyBytes, err := s.kms.CreateAndExportPubKeyBytes(encKeyType)
   507  	if err != nil {
   508  		return nil, fmt.Errorf("createEncryptionVM: %w", err)
   509  	}
   510  
   511  	vmID := "#key-2"
   512  
   513  	switch vmType {
   514  	case x25519KeyAgreementKey2019:
   515  		key := &crypto.PublicKey{}
   516  
   517  		err = json.Unmarshal(kaPubKeyBytes, key)
   518  		if err != nil {
   519  			return nil, fmt.Errorf("createEncryptionVM: unable to unmarshal X25519 key: %w", err)
   520  		}
   521  
   522  		return did.NewVerificationMethodFromBytes(vmID, vmType, "", key.X), nil
   523  	case jsonWebKey2020:
   524  		j, err := buildJWKFromBytes(kaPubKeyBytes)
   525  		if err != nil {
   526  			return nil, fmt.Errorf("createEncryptionVM: %w", err)
   527  		}
   528  
   529  		vm, err := did.NewVerificationMethodFromJWK(vmID, vmType, "", j)
   530  		if err != nil {
   531  			return nil, fmt.Errorf("createEncryptionVM: %w", err)
   532  		}
   533  
   534  		return vm, nil
   535  	default:
   536  		return nil, fmt.Errorf("unsupported verification method for KeyAgreement: '%s'", vmType)
   537  	}
   538  }
   539  
   540  func buildJWKFromBytes(pubKeyBytes []byte) (*jwk.JWK, error) {
   541  	pubKey := &crypto.PublicKey{}
   542  
   543  	err := json.Unmarshal(pubKeyBytes, pubKey)
   544  	if err != nil {
   545  		return nil, fmt.Errorf("failed to unmarshal JWK for KeyAgreement: %w", err)
   546  	}
   547  
   548  	var j *jwk.JWK
   549  
   550  	switch pubKey.Type {
   551  	case "EC":
   552  		ecKey, err := crypto.ToECKey(pubKey)
   553  		if err != nil {
   554  			return nil, err
   555  		}
   556  
   557  		j = &jwk.JWK{
   558  			JSONWebKey: gojose.JSONWebKey{
   559  				Key:   ecKey,
   560  				KeyID: pubKey.KID,
   561  			},
   562  			Kty: pubKey.Type,
   563  			Crv: pubKey.Curve,
   564  		}
   565  	case "OKP":
   566  		j = &jwk.JWK{
   567  			JSONWebKey: gojose.JSONWebKey{
   568  				Key:   pubKey.X,
   569  				KeyID: pubKey.KID,
   570  			},
   571  			Kty: pubKey.Type,
   572  			Crv: pubKey.Curve,
   573  		}
   574  	}
   575  
   576  	return j, nil
   577  }
   578  
   579  func listener(
   580  	callbacks chan *callback,
   581  	handleCallbackFunc func(*callback) error) func() {
   582  	return func() {
   583  		for c := range callbacks {
   584  			switch c.msg.Type() {
   585  			case InvitationMsgType:
   586  				err := handleCallbackFunc(c)
   587  				if err != nil {
   588  					logutil.LogError(logger, Name, "handleCallback", err.Error(),
   589  						logutil.CreateKeyValueString("msgType", c.msg.Type()),
   590  						logutil.CreateKeyValueString("msgID", c.msg.ID()))
   591  
   592  					continue
   593  				}
   594  			default:
   595  				logutil.LogError(logger, Name, "callbackChannel", "oob/2.0 unsupported msg type",
   596  					logutil.CreateKeyValueString("msgType", c.msg.Type()),
   597  					logutil.CreateKeyValueString("msgID", c.msg.ID()))
   598  			}
   599  		}
   600  	}
   601  }
   602  
   603  func (s *Service) handleCallback(c *callback) error {
   604  	switch c.msg.Type() {
   605  	case InvitationMsgType:
   606  		return s.handleInvitationCallback(c)
   607  	default:
   608  		return fmt.Errorf("unsupported message type: %s", c.msg.Type())
   609  	}
   610  }
   611  
   612  func (s *Service) handleInvitationCallback(c *callback) error {
   613  	logger.Debugf("oob/2.0 input: %+v", c)
   614  
   615  	err := validateInvitationAcceptance(c.msg, s.myMediaTypeProfiles)
   616  	if err != nil {
   617  		return fmt.Errorf("unable to handle invitation: %w", err)
   618  	}
   619  
   620  	return nil
   621  }
   622  
   623  func validateInvitationAcceptance(msg service.DIDCommMsg, myProfiles []string) error {
   624  	if msg.Type() != InvitationMsgType {
   625  		return nil
   626  	}
   627  
   628  	inv := &Invitation{}
   629  
   630  	err := msg.Decode(inv)
   631  	if err != nil {
   632  		return fmt.Errorf("validateInvitationAcceptance: failed to decode invitation: %w", err)
   633  	}
   634  
   635  	if !matchMediaTypeProfiles(inv.Body.Accept, myProfiles) {
   636  		return fmt.Errorf("no acceptable media type profile found in invitation, invitation Accept property: [%v], "+
   637  			"agent mediatypeprofiles: [%v]", inv.Body.Accept, myProfiles)
   638  	}
   639  
   640  	return nil
   641  }
   642  
   643  func matchMediaTypeProfiles(theirProfiles, myProfiles []string) bool {
   644  	if theirProfiles == nil {
   645  		// we use our preferred media type profile instead of confirming an overlap exists
   646  		return true
   647  	}
   648  
   649  	if myProfiles == nil {
   650  		myProfiles = transport.MediaTypeProfiles()
   651  	}
   652  
   653  	profiles := list2set(myProfiles)
   654  
   655  	for _, a := range theirProfiles {
   656  		if _, valid := profiles[a]; valid {
   657  			return true
   658  		}
   659  	}
   660  
   661  	return false
   662  }
   663  
   664  func list2set(list []string) map[string]struct{} {
   665  	set := map[string]struct{}{}
   666  
   667  	for _, e := range list {
   668  		set[e] = struct{}{}
   669  	}
   670  
   671  	return set
   672  }