github.com/hyperledger/aries-framework-go@v0.3.2/pkg/didcomm/protocol/didexchange/states.go (about) 1 /* 2 Copyright SecureKey Technologies Inc. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package didexchange 8 9 import ( 10 "crypto/ed25519" 11 "encoding/base64" 12 "encoding/json" 13 "errors" 14 "fmt" 15 "strings" 16 17 "github.com/google/uuid" 18 "github.com/mitchellh/mapstructure" 19 20 "github.com/hyperledger/aries-framework-go/pkg/doc/util/jwkkid" 21 22 "github.com/hyperledger/aries-framework-go/pkg/common/model" 23 "github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service" 24 "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/decorator" 25 "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/mediator" 26 "github.com/hyperledger/aries-framework-go/pkg/didcomm/transport" 27 "github.com/hyperledger/aries-framework-go/pkg/doc/did" 28 vdrapi "github.com/hyperledger/aries-framework-go/pkg/framework/aries/api/vdr" 29 "github.com/hyperledger/aries-framework-go/pkg/internal/didcommutil" 30 "github.com/hyperledger/aries-framework-go/pkg/kms" 31 connectionstore "github.com/hyperledger/aries-framework-go/pkg/store/connection" 32 "github.com/hyperledger/aries-framework-go/pkg/vdr/fingerprint" 33 "github.com/hyperledger/aries-framework-go/spi/storage" 34 ) 35 36 const ( 37 stateNameNoop = "noop" 38 stateNameNull = "null" 39 // StateIDInvited marks the invited phase of the did-exchange protocol. 40 StateIDInvited = "invited" 41 // StateIDRequested marks the requested phase of the did-exchange protocol. 42 StateIDRequested = "requested" 43 // StateIDResponded marks the responded phase of the did-exchange protocol. 44 StateIDResponded = "responded" 45 // StateIDCompleted marks the completed phase of the did-exchange protocol. 46 StateIDCompleted = "completed" 47 // StateIDAbandoned marks the abandoned phase of the did-exchange protocol. 48 StateIDAbandoned = "abandoned" 49 ackStatusOK = "ok" 50 didCommServiceType = "did-communication" 51 // legacyDIDCommServiceType for aca-py interop. 52 legacyDIDCommServiceType = "IndyAgent" 53 // DIDComm V2 service type ref: https://identity.foundation/didcomm-messaging/spec/#did-document-service-endpoint 54 didCommV2ServiceType = "DIDCommMessaging" 55 ed25519VerificationKey2018 = "Ed25519VerificationKey2018" 56 bls12381G2Key2020 = "Bls12381G2Key2020" 57 jsonWebKey2020 = "JsonWebKey2020" 58 didMethod = "peer" 59 x25519KeyAgreementKey2019 = "X25519KeyAgreementKey2019" 60 ) 61 62 var errVerKeyNotFound = errors.New("verkey not found") 63 64 // state action for network call. 65 type stateAction func() error 66 67 // The did-exchange protocol's state. 68 type state interface { 69 // Name of this state. 70 Name() string 71 72 // Whether this state allows transitioning into the next state. 73 CanTransitionTo(next state) bool 74 75 // ExecuteInbound this state, returning a followup state to be immediately executed as well. 76 // The 'noOp' state should be returned if the state has no followup. 77 ExecuteInbound(msg *stateMachineMsg, thid string, ctx *context) (connRecord *connectionstore.Record, 78 state state, action stateAction, err error) 79 } 80 81 // Returns the state towards which the protocol will transition to if the msgType is processed. 82 func stateFromMsgType(msgType string) (state, error) { 83 switch msgType { 84 case InvitationMsgType, oobMsgType: 85 return &invited{}, nil 86 case RequestMsgType: 87 return &requested{}, nil 88 case ResponseMsgType: 89 return &responded{}, nil 90 case AckMsgType, CompleteMsgType: 91 return &completed{}, nil 92 default: 93 return nil, fmt.Errorf("unrecognized msgType: %s", msgType) 94 } 95 } 96 97 // Returns the state representing the name. 98 func stateFromName(name string) (state, error) { 99 switch name { 100 case stateNameNoop: 101 return &noOp{}, nil 102 case stateNameNull: 103 return &null{}, nil 104 case StateIDInvited: 105 return &invited{}, nil 106 case StateIDRequested: 107 return &requested{}, nil 108 case StateIDResponded: 109 return &responded{}, nil 110 case StateIDCompleted: 111 return &completed{}, nil 112 case StateIDAbandoned: 113 return &abandoned{}, nil 114 default: 115 return nil, fmt.Errorf("invalid state name %s", name) 116 } 117 } 118 119 type noOp struct{} 120 121 func (s *noOp) Name() string { 122 return stateNameNoop 123 } 124 125 func (s *noOp) CanTransitionTo(_ state) bool { 126 return false 127 } 128 129 func (s *noOp) ExecuteInbound(_ *stateMachineMsg, thid string, ctx *context) (*connectionstore.Record, 130 state, stateAction, error) { 131 return nil, nil, nil, errors.New("cannot execute no-op") 132 } 133 134 // null state. 135 type null struct{} 136 137 func (s *null) Name() string { 138 return stateNameNull 139 } 140 141 func (s *null) CanTransitionTo(next state) bool { 142 return StateIDInvited == next.Name() || StateIDRequested == next.Name() 143 } 144 145 func (s *null) ExecuteInbound(msg *stateMachineMsg, thid string, ctx *context) (*connectionstore.Record, 146 state, stateAction, error) { 147 return &connectionstore.Record{}, &noOp{}, nil, nil 148 } 149 150 // invited state. 151 type invited struct{} 152 153 func (s *invited) Name() string { 154 return StateIDInvited 155 } 156 157 func (s *invited) CanTransitionTo(next state) bool { 158 return StateIDRequested == next.Name() 159 } 160 161 func (s *invited) ExecuteInbound(msg *stateMachineMsg, _ string, _ *context) (*connectionstore.Record, 162 state, stateAction, error) { 163 if msg.Type() != InvitationMsgType && msg.Type() != oobMsgType { 164 return nil, nil, nil, fmt.Errorf("illegal msg type %s for state %s", msg.Type(), s.Name()) 165 } 166 167 return msg.connRecord, &requested{}, func() error { return nil }, nil 168 } 169 170 // requested state. 171 type requested struct{} 172 173 func (s *requested) Name() string { 174 return StateIDRequested 175 } 176 177 func (s *requested) CanTransitionTo(next state) bool { 178 return StateIDResponded == next.Name() 179 } 180 181 func (s *requested) ExecuteInbound(msg *stateMachineMsg, thid string, ctx *context) (*connectionstore.Record, 182 state, stateAction, error) { 183 switch msg.Type() { 184 case oobMsgType: 185 oobInvitation := &OOBInvitation{} 186 187 err := msg.Decode(oobInvitation) 188 if err != nil { 189 return nil, nil, nil, fmt.Errorf("failed to decode oob invitation: %w", err) 190 } 191 192 action, record, err := ctx.handleInboundOOBInvitation(oobInvitation, thid, msg.options, msg.connRecord) 193 if err != nil { 194 return nil, nil, nil, fmt.Errorf("failed to handle inbound oob invitation : %w", err) 195 } 196 197 return record, &noOp{}, action, nil 198 case InvitationMsgType: 199 invitation := &Invitation{} 200 201 err := msg.Decode(invitation) 202 if err != nil { 203 return nil, nil, nil, fmt.Errorf("JSON unmarshalling of invitation: %w", err) 204 } 205 206 action, connRecord, err := ctx.handleInboundInvitation(invitation, thid, msg.options, msg.connRecord) 207 if err != nil { 208 return nil, nil, nil, fmt.Errorf("handle inbound invitation: %w", err) 209 } 210 211 return connRecord, &noOp{}, action, nil 212 case RequestMsgType: 213 return msg.connRecord, &responded{}, func() error { return nil }, nil 214 default: 215 return nil, nil, nil, fmt.Errorf("illegal msg type %s for state %s", msg.Type(), s.Name()) 216 } 217 } 218 219 // responded state. 220 type responded struct{} 221 222 func (s *responded) Name() string { 223 return StateIDResponded 224 } 225 226 func (s *responded) CanTransitionTo(next state) bool { 227 return StateIDCompleted == next.Name() 228 } 229 230 func (s *responded) ExecuteInbound(msg *stateMachineMsg, thid string, ctx *context) (*connectionstore.Record, 231 state, stateAction, error) { 232 switch msg.Type() { 233 case RequestMsgType: 234 request := &Request{} 235 236 err := msg.Decode(request) 237 if err != nil { 238 return nil, nil, nil, fmt.Errorf("JSON unmarshalling of request: %w", err) 239 } 240 241 action, connRecord, err := ctx.handleInboundRequest(request, msg.options, msg.connRecord) 242 if err != nil { 243 return nil, nil, nil, fmt.Errorf("handle inbound request: %w", err) 244 } 245 246 return connRecord, &noOp{}, action, nil 247 case ResponseMsgType, CompleteMsgType: 248 return msg.connRecord, &completed{}, func() error { return nil }, nil 249 default: 250 return nil, nil, nil, fmt.Errorf("illegal msg type %s for state %s", msg.Type(), s.Name()) 251 } 252 } 253 254 // completed state. 255 type completed struct{} 256 257 func (s *completed) Name() string { 258 return StateIDCompleted 259 } 260 261 func (s *completed) CanTransitionTo(next state) bool { 262 return false 263 } 264 265 func (s *completed) ExecuteInbound(msg *stateMachineMsg, thid string, ctx *context) (*connectionstore.Record, 266 state, stateAction, error) { 267 switch msg.Type() { 268 case ResponseMsgType: 269 response := &Response{} 270 271 err := msg.Decode(response) 272 if err != nil { 273 return nil, nil, nil, fmt.Errorf("JSON unmarshalling of response: %w", err) 274 } 275 276 action, connRecord, err := ctx.handleInboundResponse(response) 277 if err != nil { 278 return nil, nil, nil, fmt.Errorf("handle inbound response: %w", err) 279 } 280 281 return connRecord, &noOp{}, action, nil 282 case AckMsgType: 283 action := func() error { return nil } 284 return msg.connRecord, &noOp{}, action, nil 285 case CompleteMsgType: 286 complete := &Complete{} 287 288 err := msg.Decode(complete) 289 if err != nil { 290 return nil, nil, nil, fmt.Errorf("JSON unmarshalling of complete: %w", err) 291 } 292 293 action := func() error { return nil } 294 295 if msg.connRecord == nil { 296 return nil, &noOp{}, action, nil 297 } 298 299 connRec := *msg.connRecord 300 301 return &connRec, &noOp{}, action, nil 302 default: 303 return nil, nil, nil, fmt.Errorf("illegal msg type %s for state %s", msg.Type(), s.Name()) 304 } 305 } 306 307 // abandoned state. 308 type abandoned struct{} 309 310 func (s *abandoned) Name() string { 311 return StateIDAbandoned 312 } 313 314 func (s *abandoned) CanTransitionTo(next state) bool { 315 return false 316 } 317 318 func (s *abandoned) ExecuteInbound(msg *stateMachineMsg, thid string, ctx *context) (*connectionstore.Record, 319 state, stateAction, error) { 320 return nil, nil, nil, errors.New("not implemented") 321 } 322 323 func (ctx *context) handleInboundOOBInvitation(oobInv *OOBInvitation, thid string, options *options, 324 connRec *connectionstore.Record) (stateAction, *connectionstore.Record, error) { 325 svc, err := ctx.getServiceBlock(oobInv) 326 if err != nil { 327 return nil, nil, fmt.Errorf("failed to get service block: %w", err) 328 } 329 330 dest := &service.Destination{ 331 RecipientKeys: svc.RecipientKeys, 332 ServiceEndpoint: svc.ServiceEndpoint, 333 RoutingKeys: svc.RoutingKeys, 334 MediaTypeProfiles: svc.Accept, 335 } 336 337 connRec.ThreadID = thid 338 339 return ctx.createInvitedRequest(dest, oobInv.MyLabel, thid, connRec.ParentThreadID, options, connRec) 340 } 341 342 func (ctx *context) handleInboundInvitation(invitation *Invitation, thid string, options *options, 343 connRec *connectionstore.Record) (stateAction, *connectionstore.Record, error) { 344 // create a destination from invitation 345 destination, err := ctx.getDestination(invitation) 346 if err != nil { 347 return nil, nil, err 348 } 349 350 pid := invitation.ID 351 if connRec.Implicit { 352 pid = invitation.DID 353 } 354 355 return ctx.createInvitedRequest(destination, getLabel(options), thid, pid, options, connRec) 356 } 357 358 func (ctx *context) createInvitedRequest(destination *service.Destination, label, thid, pthid string, options *options, 359 connRec *connectionstore.Record) (stateAction, *connectionstore.Record, error) { 360 request := &Request{ 361 Type: RequestMsgType, 362 ID: thid, 363 Label: label, 364 Thread: &decorator.Thread{ 365 PID: pthid, 366 }, 367 } 368 369 accept, err := destination.ServiceEndpoint.Accept() // didcomm v2 370 if err != nil { 371 accept = destination.MediaTypeProfiles // didcomm v1 372 } 373 374 // get did document to use in exchange request 375 myDIDDoc, err := ctx.getMyDIDDoc(getPublicDID(options), getRouterConnections(options), 376 serviceTypeByMediaProfile(accept)) 377 if err != nil { 378 return nil, nil, err 379 } 380 381 connRec.MyDID = myDIDDoc.ID 382 383 senderKey, err := recipientKeyAsDIDKey(myDIDDoc) 384 if err != nil { 385 return nil, nil, fmt.Errorf("getting recipient key: %w", err) 386 } 387 388 // Interop: aca-py issue https://github.com/hyperledger/aries-cloudagent-python/issues/1048 389 requestDidDoc, err := convertPeerToSov(myDIDDoc) 390 if err != nil { 391 return nil, nil, fmt.Errorf("converting my did doc to a 'sov' doc for request message: %w", err) 392 } 393 394 // Interop: aca-py issue https://github.com/hyperledger/aries-cloudagent-python/issues/1048 395 if ctx.doACAPyInterop { 396 request.DID = strings.TrimPrefix(myDIDDoc.ID, "did:sov:") 397 } else { 398 request.DID = myDIDDoc.ID 399 } 400 401 request.DocAttach, err = ctx.didDocAttachment(requestDidDoc, senderKey) 402 if err != nil { 403 return nil, nil, fmt.Errorf("creating did doc attachment for request: %w", err) 404 } 405 406 return func() error { 407 return ctx.outboundDispatcher.Send(request, senderKey, destination) 408 }, connRec, nil 409 } 410 411 func serviceTypeByMediaProfile(mediaTypeProfiles []string) string { 412 serviceType := didCommServiceType 413 414 for _, mtp := range mediaTypeProfiles { 415 var breakFor bool 416 417 switch mtp { 418 case transport.MediaTypeDIDCommV2Profile, transport.MediaTypeAIP2RFC0587Profile, 419 transport.MediaTypeV2EncryptedEnvelope, transport.MediaTypeV2EncryptedEnvelopeV1PlaintextPayload, 420 transport.MediaTypeV1EncryptedEnvelope: 421 serviceType = didCommV2ServiceType 422 423 breakFor = true 424 } 425 426 if breakFor { 427 break 428 } 429 } 430 431 return serviceType 432 } 433 434 // nolint:gocyclo,funlen 435 func (ctx *context) handleInboundRequest(request *Request, options *options, 436 connRec *connectionstore.Record) (stateAction, *connectionstore.Record, error) { 437 logger.Debugf("handling request: %#v", request) 438 439 // Interop: aca-py issue https://github.com/hyperledger/aries-cloudagent-python/issues/1048 440 if ctx.doACAPyInterop && !strings.HasPrefix(request.DID, "did") { 441 request.DID = "did:peer:" + request.DID 442 } 443 444 requestDidDoc, err := ctx.resolveDidDocFromMessage(request.DID, request.DocAttach) 445 if err != nil { 446 return nil, nil, fmt.Errorf("resolve did doc from exchange request: %w", err) 447 } 448 449 // get did document that will be used in exchange response 450 // (my did doc) 451 myDID := getPublicDID(options) 452 453 destination, err := service.CreateDestination(requestDidDoc) 454 if err != nil { 455 return nil, nil, err 456 } 457 458 var serviceType string 459 if len(requestDidDoc.Service) > 0 { 460 serviceType = didcommutil.GetServiceType(requestDidDoc.Service[0].Type) 461 } else { 462 accept, e := destination.ServiceEndpoint.Accept() 463 if e != nil { 464 accept = []string{} 465 } 466 467 serviceType = serviceTypeByMediaProfile(accept) 468 } 469 470 responseDidDoc, err := ctx.getMyDIDDoc(myDID, getRouterConnections(options), serviceType) 471 if err != nil { 472 return nil, nil, fmt.Errorf("get response did doc and connection: %w", err) 473 } 474 475 var senderVerKey string 476 477 if myDID != "" { // empty myDID means a new DID was just created and not exchanged yet, use did:key instead 478 senderVerKey, err = recipientKey(responseDidDoc) 479 if err != nil { 480 return nil, nil, fmt.Errorf("get recipient key: %w", err) 481 } 482 } else { 483 senderVerKey, err = recipientKeyAsDIDKey(responseDidDoc) 484 if err != nil { 485 return nil, nil, fmt.Errorf("get recipient key as did:key: %w", err) 486 } 487 } 488 489 connRec.MyDID = responseDidDoc.ID 490 491 if ctx.doACAPyInterop { 492 // Interop: aca-py issue https://github.com/hyperledger/aries-cloudagent-python/issues/1048 493 responseDidDoc, err = convertPeerToSov(responseDidDoc) 494 if err != nil { 495 return nil, nil, fmt.Errorf("converting my did doc to a 'sov' doc for response message: %w", err) 496 } 497 } 498 499 response, err := ctx.prepareResponse(request, responseDidDoc) 500 if err != nil { 501 return nil, nil, fmt.Errorf("preparing response: %w", err) 502 } 503 504 connRec.TheirDID = request.DID 505 connRec.TheirLabel = request.Label 506 507 accept, err := destination.ServiceEndpoint.Accept() 508 if err != nil { 509 accept = []string{} 510 } 511 512 if len(accept) > 0 { 513 connRec.MediaTypeProfiles = accept 514 } 515 516 // send exchange response 517 return func() error { 518 return ctx.outboundDispatcher.Send(response, senderVerKey, destination) 519 }, connRec, nil 520 } 521 522 func (ctx *context) prepareResponse(request *Request, responseDidDoc *did.Doc) (*Response, error) { 523 // prepare the response 524 response := &Response{ 525 Type: ResponseMsgType, 526 ID: uuid.New().String(), 527 Thread: &decorator.Thread{ 528 ID: request.ID, 529 }, 530 } 531 532 if request.Thread != nil { 533 response.Thread.PID = request.Thread.PID 534 } 535 536 invitationKey, err := ctx.getVerKey(request.Thread.PID) 537 if err != nil { 538 return nil, fmt.Errorf("getting sender verkey: %w", err) 539 } 540 541 docAttach, err := ctx.didDocAttachment(responseDidDoc, invitationKey) 542 if err != nil { 543 return nil, err 544 } 545 546 // Interop: aca-py expects naked DID method-specific identifier for sov DIDs 547 // https://github.com/hyperledger/aries-cloudagent-python/issues/1048 548 response.DID = strings.TrimPrefix(responseDidDoc.ID, "did:sov:") 549 response.DocAttach = docAttach 550 551 return response, nil 552 } 553 554 func (ctx *context) didDocAttachment(doc *did.Doc, myVerKey string) (*decorator.Attachment, error) { 555 docBytes, err := doc.SerializeInterop() 556 if err != nil { 557 return nil, fmt.Errorf("marshaling did doc: %w", err) 558 } 559 560 docAttach := &decorator.Attachment{ 561 MimeType: "application/json", 562 Data: decorator.AttachmentData{ 563 Base64: base64.StdEncoding.EncodeToString(docBytes), 564 }, 565 } 566 567 // Interop: signing did_doc~attach has been removed from the spec, but aca-py still verifies signatures 568 // TODO make aca-py issue 569 if ctx.doACAPyInterop { 570 pubKeyBytes, err := ctx.resolvePublicKey(myVerKey) 571 if err != nil { 572 return nil, fmt.Errorf("failed to resolve public key: %w", err) 573 } 574 575 // TODO: use dynamic context KeyType 576 signingKID, err := jwkkid.CreateKID(pubKeyBytes, kms.ED25519Type) 577 if err != nil { 578 return nil, fmt.Errorf("failed to generate KID from public key: %w", err) 579 } 580 581 kh, err := ctx.kms.Get(signingKID) 582 if err != nil { 583 return nil, fmt.Errorf("failed to get key handle: %w", err) 584 } 585 586 err = docAttach.Data.Sign(ctx.crypto, kh, ed25519.PublicKey(pubKeyBytes), pubKeyBytes) 587 if err != nil { 588 return nil, fmt.Errorf("signing did_doc~attach: %w", err) 589 } 590 } 591 592 return docAttach, nil 593 } 594 595 func (ctx *context) resolvePublicKey(kid string) ([]byte, error) { 596 if strings.HasPrefix(kid, "did:key:") { 597 pubKeyBytes, err := fingerprint.PubKeyFromDIDKey(kid) 598 if err != nil { 599 return nil, fmt.Errorf("failed to extract pubKeyBytes from did:key [%s]: %w", kid, err) 600 } 601 602 return pubKeyBytes, nil 603 } else if strings.HasPrefix(kid, "did:") { 604 vkDID := strings.Split(kid, "#")[0] 605 606 pubDoc, err := ctx.vdRegistry.Resolve(vkDID) 607 if err != nil { 608 return nil, fmt.Errorf("failed to resolve public did for key ID '%s': %w", kid, err) 609 } 610 611 vm, ok := did.LookupPublicKey(kid, pubDoc.DIDDocument) 612 if !ok { 613 return nil, fmt.Errorf("failed to lookup public key for ID %s", kid) 614 } 615 616 return vm.Value, nil 617 } 618 619 return nil, fmt.Errorf("failed to resolve public key value from kid '%s'", kid) 620 } 621 622 func getPublicDID(options *options) string { 623 if options == nil { 624 return "" 625 } 626 627 return options.publicDID 628 } 629 630 func getRouterConnections(options *options) []string { 631 if options == nil { 632 return nil 633 } 634 635 return options.routerConnections 636 } 637 638 // returns the label given in the options, otherwise an empty string. 639 func getLabel(options *options) string { 640 if options == nil { 641 return "" 642 } 643 644 return options.label 645 } 646 647 func (ctx *context) getDestination(invitation *Invitation) (*service.Destination, error) { 648 if invitation.DID != "" { 649 return service.GetDestination(invitation.DID, ctx.vdRegistry) 650 } 651 652 accept := ctx.mediaTypeProfiles 653 654 var dest *service.Destination 655 656 if isDIDCommV2(accept) { 657 dest = &service.Destination{ 658 RecipientKeys: invitation.RecipientKeys, 659 ServiceEndpoint: model.NewDIDCommV2Endpoint([]model.DIDCommV2Endpoint{ 660 {URI: invitation.ServiceEndpoint, Accept: accept, RoutingKeys: invitation.RoutingKeys}, 661 }), 662 } 663 } else { 664 dest = &service.Destination{ 665 RecipientKeys: invitation.RecipientKeys, 666 ServiceEndpoint: model.NewDIDCommV1Endpoint(invitation.ServiceEndpoint), 667 MediaTypeProfiles: accept, 668 RoutingKeys: invitation.RoutingKeys, 669 } 670 } 671 672 return dest, nil 673 } 674 675 // nolint:gocyclo,funlen 676 func (ctx *context) getMyDIDDoc(pubDID string, routerConnections []string, serviceType string) (*did.Doc, error) { 677 if pubDID != "" { 678 logger.Debugf("using public did[%s] for connection", pubDID) 679 680 docResolution, err := ctx.vdRegistry.Resolve(pubDID) 681 if err != nil { 682 return nil, fmt.Errorf("resolve public did[%s]: %w", pubDID, err) 683 } 684 685 err = ctx.connectionStore.SaveDIDFromDoc(docResolution.DIDDocument) 686 if err != nil { 687 return nil, err 688 } 689 690 return docResolution.DIDDocument, nil 691 } 692 693 logger.Debugf("creating new '%s' did for connection", didMethod) 694 695 var ( 696 services []did.Service 697 newService bool 698 ) 699 700 for _, connID := range routerConnections { 701 // get the route configs (pass empty service endpoint, as default service endpoint added in VDR) 702 serviceEndpoint, routingKeys, err := mediator.GetRouterConfig(ctx.routeSvc, connID, "") 703 if err != nil { 704 return nil, fmt.Errorf("did doc - fetch router config: %w", err) 705 } 706 707 var svc did.Service 708 709 switch serviceType { 710 case didCommServiceType, legacyDIDCommServiceType: 711 svc = did.Service{ 712 Type: didCommServiceType, 713 ServiceEndpoint: model.NewDIDCommV1Endpoint(serviceEndpoint), 714 RoutingKeys: routingKeys, 715 } 716 case didCommV2ServiceType: 717 svc = did.Service{ 718 Type: didCommV2ServiceType, 719 ServiceEndpoint: model.NewDIDCommV2Endpoint([]model.DIDCommV2Endpoint{ 720 {URI: serviceEndpoint, RoutingKeys: routingKeys}, 721 }), 722 } 723 } 724 725 services = append(services, svc) 726 } 727 728 if len(services) == 0 { 729 newService = true 730 731 services = append(services, did.Service{Type: serviceType}) 732 } 733 734 newDID := &did.Doc{Service: services} 735 736 err := ctx.createNewKeyAndVM(newDID) 737 if err != nil { 738 return nil, fmt.Errorf("failed to create and export public key: %w", err) 739 } 740 741 if newService { 742 switch didcommutil.GetServiceType(newDID.Service[0].Type) { 743 case didCommServiceType, "IndyAgent": 744 recKey, _ := fingerprint.CreateDIDKey(newDID.VerificationMethod[0].Value) 745 newDID.Service[0].RecipientKeys = []string{recKey} 746 case didCommV2ServiceType: 747 var recKeys []string 748 749 for _, r := range newDID.KeyAgreement { 750 recKeys = append(recKeys, r.VerificationMethod.ID) 751 } 752 753 newDID.Service[0].RecipientKeys = recKeys 754 755 default: 756 return nil, fmt.Errorf("getMyDIDDoc: invalid DID Doc service type: '%v'", newDID.Service[0].Type) 757 } 758 } 759 760 // by default use peer did 761 docResolution, err := ctx.vdRegistry.Create(didMethod, newDID) 762 if err != nil { 763 return nil, fmt.Errorf("create %s did: %w", didMethod, err) 764 } 765 766 if len(routerConnections) != 0 { 767 err = ctx.addRouterKeys(docResolution.DIDDocument, routerConnections) 768 if err != nil { 769 return nil, err 770 } 771 } 772 773 err = ctx.connectionStore.SaveDIDFromDoc(docResolution.DIDDocument) 774 if err != nil { 775 return nil, err 776 } 777 778 return docResolution.DIDDocument, nil 779 } 780 781 func (ctx *context) addRouterKeys(doc *did.Doc, routerConnections []string) error { 782 // try DIDComm V2 and use it if found, else use default DIDComm v1 bloc. 783 _, ok := did.LookupService(doc, didCommV2ServiceType) 784 if ok { 785 // use KeyAgreement.ID as recKey for DIDComm V2 786 for _, ka := range doc.KeyAgreement { 787 for _, connID := range routerConnections { 788 // TODO https://github.com/hyperledger/aries-framework-go/issues/1105 Support to Add multiple 789 // recKeys to the Router. (DIDComm V2 uses list of keyAgreements as router keys here, double check 790 // if this issue can be closed). 791 kaID := ka.VerificationMethod.ID 792 if strings.HasPrefix(kaID, "#") { 793 kaID = doc.ID + kaID 794 } 795 796 if err := mediator.AddKeyToRouter(ctx.routeSvc, connID, kaID); err != nil { 797 return fmt.Errorf("did doc - add key to the router: %w", err) 798 } 799 } 800 } 801 802 return nil 803 } 804 805 svc, ok := did.LookupService(doc, didCommServiceType) 806 if ok { 807 for _, recKey := range svc.RecipientKeys { 808 for _, connID := range routerConnections { 809 // TODO https://github.com/hyperledger/aries-framework-go/issues/1105 Support to Add multiple 810 // recKeys to the Router 811 if err := mediator.AddKeyToRouter(ctx.routeSvc, connID, recKey); err != nil { 812 return fmt.Errorf("did doc - add key to the router: %w", err) 813 } 814 } 815 } 816 } 817 818 return nil 819 } 820 821 func (ctx *context) isPrivateDIDMethod(method string) bool { 822 // todo: find better solution to forcing test dids to be treated as private dids 823 if method == "local" || method == "test" { 824 return true 825 } 826 827 // Interop: treat sov as a peer did: aca-py issue https://github.com/hyperledger/aries-cloudagent-python/issues/1048 828 return method == "peer" || (ctx.doACAPyInterop && method == "sov") 829 } 830 831 // nolint:gocyclo 832 func (ctx *context) resolveDidDocFromMessage(didValue string, attachment *decorator.Attachment) (*did.Doc, error) { 833 parsedDID, err := did.Parse(didValue) 834 // Interop: aca-py dids missing schema:method:, ignore error and skip checking if it's a public did 835 // aca-py issue https://github.com/hyperledger/aries-cloudagent-python/issues/1048 836 if err != nil && !ctx.doACAPyInterop { 837 return nil, fmt.Errorf("failed to parse did: %w", err) 838 } 839 840 if err == nil && !ctx.isPrivateDIDMethod(parsedDID.Method) { 841 docResolution, e := ctx.vdRegistry.Resolve(didValue) 842 if e != nil { 843 return nil, fmt.Errorf("failed to resolve public did %s: %w", didValue, e) 844 } 845 846 return docResolution.DIDDocument, nil 847 } 848 849 if attachment == nil { 850 return nil, fmt.Errorf("missing did_doc~attach") 851 } 852 853 docData, err := attachment.Data.Fetch() 854 if err != nil { 855 return nil, fmt.Errorf("failed to parse base64 attachment data: %w", err) 856 } 857 858 didDoc, err := did.ParseDocument(docData) 859 if err != nil { 860 logger.Errorf("failed to parse doc bytes: '%s'", string(docData)) 861 862 return nil, fmt.Errorf("failed to parse did document: %w", err) 863 } 864 865 // Interop: accommodate aca-py issue https://github.com/hyperledger/aries-cloudagent-python/issues/1048 866 var method string 867 868 if parsedDID != nil && parsedDID.Method != "sov" { 869 method = parsedDID.Method 870 } else { 871 method = "peer" 872 } 873 874 // Interop: part of above issue https://github.com/hyperledger/aries-cloudagent-python/issues/1048 875 if ctx.doACAPyInterop { 876 didDoc.ID = didValue 877 } 878 879 // store provided did document 880 _, err = ctx.vdRegistry.Create(method, didDoc, vdrapi.WithOption("store", true)) 881 if err != nil { 882 return nil, fmt.Errorf("failed to store provided did document: %w", err) 883 } 884 885 return didDoc, nil 886 } 887 888 func (ctx *context) handleInboundResponse(response *Response) (stateAction, *connectionstore.Record, error) { 889 nsThID, err := connectionstore.CreateNamespaceKey(myNSPrefix, response.Thread.ID) 890 if err != nil { 891 return nil, nil, err 892 } 893 894 connRecord, err := ctx.connectionRecorder.GetConnectionRecordByNSThreadID(nsThID) 895 if err != nil { 896 return nil, nil, fmt.Errorf("get connection record: %w", err) 897 } 898 899 // Interop: aca-py issue https://github.com/hyperledger/aries-cloudagent-python/issues/1048 900 if ctx.doACAPyInterop && !strings.HasPrefix(response.DID, "did") { 901 response.DID = "did:peer:" + response.DID 902 } 903 904 connRecord.TheirDID = response.DID 905 906 responseDidDoc, err := ctx.resolveDidDocFromMessage(response.DID, response.DocAttach) 907 if err != nil { 908 return nil, nil, fmt.Errorf("resolve response did doc: %w", err) 909 } 910 911 destination, err := service.CreateDestination(responseDidDoc) 912 if err != nil { 913 return nil, nil, fmt.Errorf("prepare destination from response did doc: %w", err) 914 } 915 916 docResolution, err := ctx.vdRegistry.Resolve(connRecord.MyDID) 917 if err != nil { 918 return nil, nil, fmt.Errorf("fetching did document: %w", err) 919 } 920 921 recKey, err := recipientKey(docResolution.DIDDocument) 922 if err != nil { 923 return nil, nil, fmt.Errorf("handle inbound response: %w", err) 924 } 925 926 completeMsg := &Complete{ 927 Type: CompleteMsgType, 928 ID: uuid.New().String(), 929 Thread: &decorator.Thread{ 930 ID: response.Thread.ID, 931 PID: connRecord.ParentThreadID, 932 }, 933 } 934 935 return func() error { 936 return ctx.outboundDispatcher.Send(completeMsg, recKey, destination) 937 }, connRecord, nil 938 } 939 940 func (ctx *context) getVerKey(invitationID string) (string, error) { 941 pubKey, err := ctx.getVerKeyFromOOBInvitation(invitationID) 942 if err != nil && !errors.Is(err, errVerKeyNotFound) { 943 return "", fmt.Errorf("failed to get my verkey from oob invitation: %w", err) 944 } 945 946 if err == nil { 947 return pubKey, nil 948 } 949 950 var invitation Invitation 951 if isDID(invitationID) { 952 invitation = Invitation{ID: invitationID, DID: invitationID} 953 } else { 954 err = ctx.connectionRecorder.GetInvitation(invitationID, &invitation) 955 if err != nil { 956 return "", fmt.Errorf("get invitation for signature [invitationID=%s]: %w", invitationID, err) 957 } 958 } 959 960 invPubKey, err := ctx.getInvitationRecipientKey(&invitation) 961 if err != nil { 962 return "", fmt.Errorf("get invitation recipient key: %w", err) 963 } 964 965 return invPubKey, nil 966 } 967 968 func (ctx *context) getInvitationRecipientKey(invitation *Invitation) (string, error) { 969 if invitation.DID != "" { 970 docResolution, err := ctx.vdRegistry.Resolve(invitation.DID) 971 if err != nil { 972 return "", fmt.Errorf("get invitation recipient key: %w", err) 973 } 974 975 recKey, err := recipientKey(docResolution.DIDDocument) 976 if err != nil { 977 return "", fmt.Errorf("getInvitationRecipientKey: %w", err) 978 } 979 980 return recKey, nil 981 } 982 983 return invitation.RecipientKeys[0], nil 984 } 985 986 func (ctx *context) getVerKeyFromOOBInvitation(invitationID string) (string, error) { 987 logger.Debugf("invitationID=%s", invitationID) 988 989 var invitation OOBInvitation 990 991 err := ctx.connectionRecorder.GetInvitation(invitationID, &invitation) 992 if errors.Is(err, storage.ErrDataNotFound) { 993 return "", errVerKeyNotFound 994 } 995 996 if err != nil { 997 return "", fmt.Errorf("failed to load oob invitation: %w", err) 998 } 999 1000 if invitation.Type != oobMsgType { 1001 return "", errVerKeyNotFound 1002 } 1003 1004 unmarshalServiceEndpointInOOBTarget(&invitation) 1005 1006 pubKey, err := ctx.resolveVerKey(&invitation) 1007 if err != nil { 1008 return "", fmt.Errorf("failed to get my verkey: %w", err) 1009 } 1010 1011 return pubKey, nil 1012 } 1013 1014 //nolint:nestif 1015 func unmarshalServiceEndpointInOOBTarget(invitation *OOBInvitation) { 1016 // for DIDCommV1, oobInvitation's target serviceEndpoint is a string, transform it to model.Endpoint map equivalent 1017 // for a successful service decode(). 1018 // for DIDCommV2, transform the target from map[string]interface{} to model.Endpoint 1019 if targetMap, ok := invitation.Target.(map[string]interface{}); ok { 1020 if se, ok := targetMap["serviceEndpoint"]; ok { 1021 seStr, ok := se.(string) 1022 if ok { 1023 targetMap["serviceEndpoint"] = model.NewDIDCommV1Endpoint(seStr) 1024 } else if seMap, ok := se.(map[string]interface{}); ok { 1025 seStr, ok = seMap["uri"].(string) 1026 if !ok { 1027 seStr = "" 1028 } 1029 1030 accept, ok := seMap["accept"].([]string) 1031 if !ok { 1032 accept = []string{} 1033 } 1034 1035 routingKeys, ok := seMap["routingKeys"].([]string) 1036 if !ok { 1037 routingKeys = []string{} 1038 } 1039 1040 targetMap["serviceEndpoint"] = model.NewDIDCommV2Endpoint([]model.DIDCommV2Endpoint{ 1041 {URI: seStr, Accept: accept, RoutingKeys: routingKeys}, 1042 }) 1043 } 1044 } 1045 } 1046 } 1047 1048 // nolint:gocyclo,funlen 1049 func (ctx *context) getServiceBlock(i *OOBInvitation) (*did.Service, error) { 1050 logger.Debugf("extracting service block from oobinvitation=%+v", i) 1051 1052 var block *did.Service 1053 1054 switch svc := i.Target.(type) { 1055 case string: 1056 docResolution, err := ctx.vdRegistry.Resolve(svc) 1057 if err != nil { 1058 return nil, fmt.Errorf("failed to resolve service=%s : %w", svc, err) 1059 } 1060 1061 s, found := did.LookupService(docResolution.DIDDocument, didCommV2ServiceType) 1062 if found { 1063 // s.recipientKeys are keyAgreement[].VerificationMethod.ID for didComm V2. They are not officially part of 1064 // the service bloc. 1065 block = s 1066 1067 break 1068 } 1069 1070 s, found = did.LookupService(docResolution.DIDDocument, didCommServiceType) 1071 if !found { 1072 if ctx.doACAPyInterop { 1073 s, err = interopSovService(docResolution.DIDDocument) 1074 if err != nil { 1075 return nil, fmt.Errorf("failed to get interop doc service: %w", err) 1076 } 1077 } else { 1078 return nil, fmt.Errorf( 1079 "no valid service block found on OOB invitation DID=%s with serviceType=%s", 1080 svc, didCommServiceType) 1081 } 1082 } 1083 1084 block = s 1085 case *did.Service: 1086 block = svc 1087 case map[string]interface{}: 1088 var s did.Service 1089 1090 decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{TagName: "json", Result: &s}) 1091 if err != nil { 1092 return nil, fmt.Errorf("failed to initialize decoder : %w", err) 1093 } 1094 1095 err = decoder.Decode(svc) 1096 if err != nil { 1097 // TODO this error check depend on mapstructure decoding 'ServiceEndpoint' section of service. 1098 // TODO Find a better way to build it. 1099 // for DIDCommV2, decoder.Decode(svc) doesn't support serviceEndpoint as []interface{} representing an array 1100 // for model.Endpoint. Manually build the endpoint here in this case. 1101 if strings.Contains(err.Error(), "'serviceEndpoint' expected a map, got 'slice'") { 1102 extractDIDCommV2EndpointIntoService(svc, &s) 1103 } else { 1104 return nil, fmt.Errorf("failed to decode service block : %w", err) 1105 } 1106 } 1107 1108 block = &s 1109 default: 1110 return nil, fmt.Errorf("unsupported target type: %+v", svc) 1111 } 1112 1113 //nolint:nestif 1114 if len(i.MediaTypeProfiles) > 0 { 1115 // marshal/unmarshal to "clone" service block 1116 blockBytes, err := json.Marshal(block) 1117 if err != nil { 1118 return nil, fmt.Errorf("service block marhsal error: %w", err) 1119 } 1120 1121 block = &did.Service{} 1122 1123 err = json.Unmarshal(blockBytes, block) 1124 if err != nil { 1125 return nil, fmt.Errorf("service block unmarhsal error: %w", err) 1126 } 1127 1128 // updating Accept header requires a cloned service block to avoid Data Race errors. 1129 // RFC0587: In case the accept property is set in both the DID service block and the out-of-band message, 1130 // the out-of-band property takes precedence. 1131 if isDIDCommV2(i.MediaTypeProfiles) { 1132 block.Type = didCommV2ServiceType 1133 1134 uri, err := block.ServiceEndpoint.URI() 1135 if err != nil { 1136 logger.Debugf("block ServiceEndpoint URI empty for DIDcomm V2, skipping it.") 1137 } 1138 1139 routingKeys, err := block.ServiceEndpoint.RoutingKeys() 1140 if err != nil { 1141 logger.Debugf("block ServiceEndpoint RoutingKeys empty for DIDcomm V2, skipping these.") 1142 } 1143 1144 block.ServiceEndpoint = model.NewDIDCommV2Endpoint([]model.DIDCommV2Endpoint{ 1145 {URI: uri, Accept: i.MediaTypeProfiles, RoutingKeys: routingKeys}, 1146 }) 1147 } else { 1148 block.Type = didCommServiceType 1149 block.Accept = i.MediaTypeProfiles 1150 } 1151 } 1152 1153 logger.Debugf("extracted service block=%+v", block) 1154 1155 return block, nil 1156 } 1157 1158 //nolint:gocognit,gocyclo,nestif 1159 func extractDIDCommV2EndpointIntoService(svc map[string]interface{}, s *did.Service) { 1160 if svcEndpointModel, ok := svc["serviceEndpoint"]; ok { 1161 if svcEndpointArr, ok := svcEndpointModel.([]interface{}); ok && len(svcEndpointArr) > 0 { 1162 if svcEndpointMap, ok := svcEndpointArr[0].(map[string]interface{}); ok { 1163 var ( 1164 uri string 1165 accept []string 1166 routingKeys []string 1167 ) 1168 1169 if uriVal, ok := svcEndpointMap["uri"]; ok { 1170 if uri, ok = uriVal.(string); !ok { 1171 uri = "" 1172 } 1173 } 1174 1175 if acceptVal, ok := svcEndpointMap["accept"]; ok { 1176 if acceptArr, ok := acceptVal.([]interface{}); ok { 1177 for _, a := range acceptArr { 1178 accept = append(accept, a.(string)) 1179 } 1180 } 1181 } 1182 1183 if routingKeysVal, ok := svcEndpointMap["routingKeys"]; ok { 1184 if routingKeysArr, ok := routingKeysVal.([]interface{}); ok { 1185 for _, r := range routingKeysArr { 1186 routingKeys = append(routingKeys, r.(string)) 1187 } 1188 } 1189 } 1190 1191 s.ServiceEndpoint = model.NewDIDCommV2Endpoint([]model.DIDCommV2Endpoint{ 1192 {URI: uri, Accept: accept, RoutingKeys: routingKeys}, 1193 }) 1194 } 1195 } 1196 } 1197 } 1198 1199 func isDIDCommV2(mediaTypeProfiles []string) bool { 1200 for _, mtp := range mediaTypeProfiles { 1201 switch mtp { 1202 case transport.MediaTypeDIDCommV2Profile, transport.MediaTypeAIP2RFC0587Profile: 1203 return true 1204 } 1205 } 1206 1207 return false 1208 } 1209 1210 func interopSovService(doc *did.Doc) (*did.Service, error) { 1211 s, found := did.LookupService(doc, "endpoint") 1212 if !found { 1213 return nil, fmt.Errorf("no valid service block found on OOB invitation DID=%s with serviceType=%s", 1214 doc.ID, "endpoint") 1215 } 1216 1217 if len(s.RecipientKeys) == 0 { 1218 for _, vm := range doc.VerificationMethod { 1219 didKey, _ := fingerprint.CreateDIDKey(vm.Value) 1220 1221 s.RecipientKeys = append(s.RecipientKeys, didKey) 1222 } 1223 } 1224 1225 return s, nil 1226 } 1227 1228 func (ctx *context) resolveVerKey(i *OOBInvitation) (string, error) { 1229 logger.Debugf("extracting verkey from oobinvitation=%+v", i) 1230 1231 svc, err := ctx.getServiceBlock(i) 1232 if err != nil { 1233 return "", fmt.Errorf("failed to get service block from oobinvitation : %w", err) 1234 } 1235 1236 logger.Debugf("extracted verkey=%s", svc.RecipientKeys[0]) 1237 1238 // use RecipientKeys[0] (DIDComm V1) 1239 return svc.RecipientKeys[0], nil 1240 } 1241 1242 func isDID(str string) bool { 1243 const didPrefix = "did:" 1244 return strings.HasPrefix(str, didPrefix) 1245 } 1246 1247 // returns the did:key ID of the first element in the doc's destination RecipientKeys. 1248 func recipientKey(doc *did.Doc) (string, error) { 1249 dest, err := service.CreateDestination(doc) 1250 if err != nil { 1251 return "", fmt.Errorf("failed to create destination: %w", err) 1252 } 1253 1254 return dest.RecipientKeys[0], nil 1255 } 1256 1257 func recipientKeyAsDIDKey(doc *did.Doc) (string, error) { 1258 var ( 1259 key string 1260 err error 1261 ) 1262 1263 serviceType := didcommutil.GetServiceType(doc.Service[0].Type) 1264 1265 switch serviceType { 1266 case vdrapi.DIDCommServiceType: 1267 return recipientKey(doc) 1268 case vdrapi.DIDCommV2ServiceType: 1269 // DIDComm V2 recipientKeys are KeyAgreement.ID, convert corresponding verification material to did:key since 1270 // recipient doesn't have the DID 'doc' yet. 1271 switch doc.KeyAgreement[0].VerificationMethod.Type { 1272 case x25519KeyAgreementKey2019: 1273 key, _ = fingerprint.CreateDIDKeyByCode(fingerprint.X25519PubKeyMultiCodec, 1274 doc.KeyAgreement[0].VerificationMethod.Value) 1275 case jsonWebKey2020: 1276 key, _, err = fingerprint.CreateDIDKeyByJwk(doc.KeyAgreement[0].VerificationMethod.JSONWebKey()) 1277 if err != nil { 1278 return "", fmt.Errorf("recipientKeyAsDIDKey: unable to create did:key from JWK: %w", err) 1279 } 1280 default: 1281 return "", fmt.Errorf("keyAgreement type '%v' not supported", doc.KeyAgreement[0].VerificationMethod.Type) 1282 } 1283 1284 return key, nil 1285 default: 1286 return interopRecipientKey(doc) 1287 } 1288 }