github.com/hyperledger/aries-framework-go@v0.3.2/pkg/didcomm/common/middleware/middleware.go (about) 1 /* 2 Copyright SecureKey Technologies Inc. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package middleware 8 9 import ( 10 "encoding/json" 11 "errors" 12 "fmt" 13 "time" 14 15 "github.com/google/uuid" 16 17 "github.com/hyperledger/aries-framework-go/pkg/common/log" 18 "github.com/hyperledger/aries-framework-go/pkg/common/model" 19 "github.com/hyperledger/aries-framework-go/pkg/crypto" 20 didcomm "github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service" 21 "github.com/hyperledger/aries-framework-go/pkg/doc/did" 22 "github.com/hyperledger/aries-framework-go/pkg/doc/jose" 23 "github.com/hyperledger/aries-framework-go/pkg/doc/util/jwkkid" 24 "github.com/hyperledger/aries-framework-go/pkg/doc/util/vmparse" 25 vdrapi "github.com/hyperledger/aries-framework-go/pkg/framework/aries/api/vdr" 26 "github.com/hyperledger/aries-framework-go/pkg/kms" 27 "github.com/hyperledger/aries-framework-go/pkg/store/connection" 28 didstore "github.com/hyperledger/aries-framework-go/pkg/store/did" 29 "github.com/hyperledger/aries-framework-go/pkg/vdr/peer" 30 "github.com/hyperledger/aries-framework-go/spi/storage" 31 ) 32 33 var logger = log.New("didcomm/common/middleware") 34 35 // DIDCommMessageMiddleware performs inbound/outbound message handling tasks that apply to all DIDComm V2 messages. 36 // - Rotates DIDs on outbound messages, and handles inbound messages that rotate DIDs. 37 type DIDCommMessageMiddleware struct { 38 kms kms.KeyManager 39 crypto crypto.Crypto 40 vdr vdrapi.Registry 41 connStore *connection.Recorder 42 didStore didstore.ConnectionStore 43 mediaTypeProfiles []string 44 } 45 46 type provider interface { 47 Crypto() crypto.Crypto 48 KMS() kms.KeyManager 49 VDRegistry() vdrapi.Registry 50 StorageProvider() storage.Provider 51 ProtocolStateStorageProvider() storage.Provider 52 MediaTypeProfiles() []string 53 DIDConnectionStore() didstore.ConnectionStore 54 } 55 56 // New creates a DIDCommMessageMiddleware. 57 func New(p provider) (*DIDCommMessageMiddleware, error) { 58 connRecorder, err := connection.NewRecorder(p) 59 if err != nil { 60 return nil, fmt.Errorf("failed to initialize connection recorder: %w", err) 61 } 62 63 return &DIDCommMessageMiddleware{ 64 kms: p.KMS(), 65 crypto: p.Crypto(), 66 vdr: p.VDRegistry(), 67 connStore: connRecorder, 68 mediaTypeProfiles: p.MediaTypeProfiles(), 69 didStore: p.DIDConnectionStore(), 70 }, nil 71 } 72 73 type rotatePayload struct { 74 Sub string `json:"sub"` 75 ISS string `json:"iss"` 76 IAT int64 `json:"iat"` 77 } 78 79 const ( 80 fromPriorJSONKey = "from_prior" 81 fromDIDJSONKey = "from" 82 bodyJSONKey = "body" 83 initialStateParam = "initialState" 84 ) 85 86 // HandleInboundMessage processes did rotation and invitee connection creation on inbound messages. 87 func (h *DIDCommMessageMiddleware) HandleInboundMessage( // nolint:gocyclo,funlen 88 msg didcomm.DIDCommMsgMap, 89 theirDID, myDID string, 90 ) error { 91 // TODO: clean up connection record management across all the handler methods. Currently correct but messy. 92 // TODO: clean up some logic: 93 // - inbound invitation acceptance cannot be a rotation 94 // GetConnectionRecordByDIDs(myDID, theirDID) 95 // if no connection, create connection 96 rec, err := h.handleInboundInvitationAcceptance(theirDID, myDID) 97 if err != nil { 98 return err 99 } 100 101 isV2, err := didcomm.IsDIDCommV2(&msg) 102 if !isV2 || err != nil { 103 return err 104 } 105 106 var updatedConnRec bool 107 108 // if they don't rotate: GetConnectionRecordByTheirDID(theirDID) 109 // if they do rotate: GetConnectionRecordByTheirDID(theirOldDID), GetConnectionRecordByTheirDID(theirNewDID) 110 rec2, stepUpdated, err := h.handleInboundRotate(msg, theirDID, myDID, rec) 111 if err != nil { 112 return err 113 } 114 115 updatedConnRec = updatedConnRec || stepUpdated 116 117 if rec2 != nil { 118 rec = rec2 119 } 120 121 if rec == nil { 122 rec, err = h.connStore.GetConnectionRecordByTheirDID(theirDID) 123 if err != nil { 124 return err 125 } 126 } 127 128 rec2, stepUpdated, err = h.handleInboundRotateAck(myDID, rec) 129 if err != nil { 130 return err 131 } 132 133 updatedConnRec = updatedConnRec || stepUpdated 134 135 if rec2 != nil { 136 rec = rec2 137 } 138 139 // handle inbound ack of peer DID 140 if rec != nil && rec.PeerDIDInitialState != "" && myDID == rec.MyDID { 141 rec.PeerDIDInitialState = "" 142 updatedConnRec = true 143 } 144 145 if updatedConnRec && rec != nil { 146 err = h.connStore.SaveConnectionRecord(rec) 147 if err != nil { 148 return fmt.Errorf("updating connection: %w", err) 149 } 150 } 151 152 return nil 153 } 154 155 // HandleOutboundMessage processes an outbound message. 156 func (h *DIDCommMessageMiddleware) HandleOutboundMessage(msg didcomm.DIDCommMsgMap, rec *connection.Record, 157 ) didcomm.DIDCommMsgMap { 158 if rec.PeerDIDInitialState != "" { 159 msg[fromDIDJSONKey] = rec.MyDID + "?" + initialStateParam + "=" + rec.PeerDIDInitialState 160 } 161 162 if isV2, err := didcomm.IsDIDCommV2(&msg); !isV2 || err != nil { 163 return msg 164 } 165 166 // if there's no from DID, add a from DID. 167 if _, ok := msg[fromDIDJSONKey]; !ok { 168 msg[fromDIDJSONKey] = rec.MyDID 169 } 170 171 // if there's no body, add a body. 172 if _, ok := msg[bodyJSONKey]; !ok { 173 msg[bodyJSONKey] = map[string]interface{}{} 174 } 175 176 if rec.MyDIDRotation != nil { 177 msg[fromPriorJSONKey] = rec.MyDIDRotation.FromPrior 178 } 179 180 return msg 181 } 182 183 type invitationStub struct { 184 Type string `json:"type"` 185 ID string `json:"id"` 186 } 187 188 func (h *DIDCommMessageMiddleware) handleInboundInvitationAcceptance(senderDID, recipientDID string, 189 ) (*connection.Record, error) { 190 didParsed, err := did.Parse(recipientDID) 191 if err != nil { 192 logger.Warnf("failed to parse inbound recipient DID: %s", err.Error()) 193 return nil, nil 194 } 195 196 if didParsed.Method == peer.DIDMethod { // TODO any more exception cases like peer? 197 // can't be an invitation DID 198 return nil, nil 199 } 200 201 inv := &invitationStub{} 202 203 err = h.connStore.GetOOBv2Invitation(recipientDID, inv) 204 if errors.Is(err, storage.ErrDataNotFound) { 205 // if there's no invitation, this message isn't an acceptance 206 return nil, nil 207 } else if err != nil { 208 return nil, err 209 } 210 211 rec, err := h.connStore.GetConnectionRecordByDIDs(recipientDID, senderDID) 212 if err == nil { 213 return rec, nil 214 } else if !errors.Is(err, storage.ErrDataNotFound) { 215 return rec, fmt.Errorf("failed to get connection record: %w", err) 216 } 217 218 // if we created an invitation with this DID, and have no connection, we create a connection. 219 220 rec = &connection.Record{ 221 ConnectionID: uuid.New().String(), 222 MyDID: recipientDID, 223 TheirDID: senderDID, 224 InvitationID: inv.ID, 225 State: connection.StateNameCompleted, 226 Namespace: connection.MyNSPrefix, 227 ServiceEndPoint: model.NewDIDCommV2Endpoint([]model.DIDCommV2Endpoint{{Accept: h.mediaTypeProfiles}}), 228 DIDCommVersion: didcomm.V2, 229 } 230 231 err = h.connStore.SaveConnectionRecord(rec) 232 if err != nil { 233 return nil, fmt.Errorf("failed to save new connection: %w", err) 234 } 235 236 return rec, nil 237 } 238 239 // HandleInboundPeerDID checks if an inbound message contains a peer DID, saving the peer DID if so. 240 func (h *DIDCommMessageMiddleware) HandleInboundPeerDID(msg didcomm.DIDCommMsgMap) error { // nolint:gocyclo 241 from, ok := msg[fromDIDJSONKey].(string) 242 if !ok { 243 return nil 244 } 245 246 didURL, err := did.ParseDIDURL(from) 247 if err != nil { 248 // special case - some didcomm v1 messages might have a "from" field, which isn't necessarily a DID 249 if isV2, e := didcomm.IsDIDCommV2(&msg); !isV2 || e != nil { 250 return nil // nolint:nilerr // ignore error from IsDIDCommV2, simply skip unusual unexpected messages. 251 } 252 253 return fmt.Errorf("parsing their DID: %w", err) 254 } 255 256 if didURL.Method != peer.DIDMethod { 257 return nil 258 } 259 260 initialState, ok := didURL.Queries[initialStateParam] 261 if !ok { 262 return nil 263 } 264 265 if len(initialState) == 0 { 266 return fmt.Errorf("expected initialState to have value") 267 } 268 269 theirDoc, err := peer.DocFromGenesisDelta(initialState[0]) 270 if err != nil { 271 return fmt.Errorf("parsing DID doc from peer DID initialState: %w", err) 272 } 273 274 _, err = h.vdr.Create(peer.DIDMethod, theirDoc, vdrapi.WithOption("store", true)) 275 if err != nil { 276 return fmt.Errorf("saving their peer DID: %w", err) 277 } 278 279 err = h.didStore.SaveDIDFromDoc(theirDoc) 280 if err != nil { 281 return fmt.Errorf("saving key to did map for their peer DID: %w", err) 282 } 283 284 msg[fromDIDJSONKey] = didURL.DID.String() 285 286 return nil 287 } 288 289 func (h *DIDCommMessageMiddleware) handleInboundRotate( // nolint:funlen,gocyclo 290 msg didcomm.DIDCommMsgMap, 291 senderDID, recipientDID string, 292 recIn *connection.Record, 293 ) (*connection.Record, bool, error) { 294 var ( 295 jws *jose.JSONWebSignature 296 payload *rotatePayload 297 err error 298 alreadyRotated bool 299 updatedConnRec bool 300 ) 301 302 fromPriorInterface, theyRotate := msg[fromPriorJSONKey] 303 if !theyRotate { 304 return recIn, false, nil 305 } 306 307 fromPrior, ok := fromPriorInterface.(string) 308 if !ok { 309 return nil, false, fmt.Errorf("didcomm message 'from_prior' field should be a string") 310 } 311 312 jws, payload, err = h.getUnverifiedJWS(senderDID, fromPrior) 313 if err != nil { 314 return nil, false, err 315 } 316 317 theirOldDID := payload.ISS 318 theirNewDID := payload.Sub 319 320 // Note: if we rotated our DID, we need to accept messages to either our old DID or our new DID. 321 // When we rotate a connection, we store the connection's record twice - once under our old DID and their DID, 322 // and once under our new DID and their DID. 323 // On top of that, when we receive a message containing a DID rotation, we might need to check for a record under 324 // their old DID and their new DID 325 326 // TODO: maybe useful if connection.Lookup would be able to look up a connection record given 327 // two candidate DIDs for myDID and two candidate DIDs for theirDID? 328 329 rec, err := h.connStore.GetConnectionRecordByDIDs(recipientDID, theirOldDID) 330 if err != nil { 331 _, err = h.connStore.GetConnectionRecordByDIDs(recipientDID, theirNewDID) 332 if err == nil { 333 // if we have a connection under their new DID, then we've already rotated. 334 alreadyRotated = true 335 } 336 } 337 338 if errors.Is(err, storage.ErrDataNotFound) { 339 // if the connection isn't found, we assume that this inbound message is the start of the communication, 340 // in which case there can be no rotation 341 return nil, false, fmt.Errorf("inbound message cannot rotate without an existing prior connection") 342 } else if err != nil { 343 return nil, false, fmt.Errorf("looking up did rotation connection record: %w", err) 344 } 345 346 if !alreadyRotated { 347 err = h.verifyJWSAndPayload(jws, payload) 348 if err != nil { 349 return nil, false, fmt.Errorf("'from_prior' validation: %w", err) 350 } 351 352 // update our connection to use their new DID 353 rec.TheirDID = payload.Sub 354 updatedConnRec = true 355 } 356 357 if rec != nil { 358 recIn = rec 359 } 360 361 return recIn, updatedConnRec, nil 362 } 363 364 func (h *DIDCommMessageMiddleware) handleInboundRotateAck(recipientDID string, rec *connection.Record, 365 ) (*connection.Record, bool, error) { 366 var updatedConnRec bool 367 368 // if we performed a did rotation, check if they acknowledge it 369 if rec.MyDIDRotation != nil { 370 // check if they sent to our old DID or our new DID 371 switch recipientDID { 372 case rec.MyDIDRotation.OldDID: 373 // they used our old DID 374 case rec.MyDIDRotation.NewDID: 375 // they used our new DID, so we don't need to rotate anymore 376 rec.MyDIDRotation = nil 377 updatedConnRec = true 378 default: 379 return nil, false, fmt.Errorf("inbound message sent to unexpected DID") 380 } 381 } 382 383 return rec, updatedConnRec, nil 384 } 385 386 // RotateConnectionDID rotates the agent's DID on the connection under connectionID. 387 func (h *DIDCommMessageMiddleware) RotateConnectionDID(connectionID, signingKID, newDID string) error { // nolint:funlen 388 record, err := h.connStore.GetConnectionRecord(connectionID) 389 if err != nil { 390 return fmt.Errorf("getting connection record: %w", err) 391 } 392 393 // TODO: known issue: if you perform multiple DID rotations without sending a message to the other party, 394 // they won't be able to validate the rotation. 395 396 oldDID := record.MyDID 397 398 oldDocRes, err := h.vdr.Resolve(oldDID) 399 if err != nil { 400 return fmt.Errorf("resolving my DID: %w", err) 401 } 402 403 fromPrior, err := h.Create(oldDocRes.DIDDocument, signingKID, newDID) 404 if err != nil { 405 return fmt.Errorf("creating did rotation from_prior: %w", err) 406 } 407 408 record.MyDIDRotation = &connection.DIDRotationRecord{ 409 NewDID: newDID, 410 OldDID: oldDID, 411 FromPrior: fromPrior, 412 } 413 414 // if newDID is a peer DID, we need to provide initialState to the recipient. 415 didParsed, err := did.Parse(newDID) 416 if err != nil { 417 return fmt.Errorf("parsing new DID: %w", err) 418 } 419 420 if didParsed.Method == peer.DIDMethod { 421 newDoc, e := h.vdr.Resolve(newDID) 422 if e != nil { 423 return fmt.Errorf("resolving new DID: %w", e) 424 } 425 426 initialState, e := peer.UnsignedGenesisDelta(newDoc.DIDDocument) 427 if e != nil { 428 return fmt.Errorf("generating peer DID initialState for new DID: %w", e) 429 } 430 431 record.PeerDIDInitialState = initialState 432 } 433 434 record.MyDID = newDID 435 436 err = h.connStore.SaveConnectionRecord(record) 437 if err != nil { 438 return fmt.Errorf("saving connection record under my new DID: %w", err) 439 } 440 441 // save a backup record under our old DID 442 record.MyDID = oldDID 443 record.ConnectionID = uuid.New().String() 444 445 err = h.connStore.SaveConnectionRecord(record) 446 if err != nil { 447 return fmt.Errorf("saving connection record under my old DID: %w", err) 448 } 449 450 return nil 451 } 452 453 // Create creates a didcomm/v2 DID rotation `from_prior`, as a compact-serialized JWS. 454 func (h *DIDCommMessageMiddleware) Create(oldDoc *did.Doc, oldKID, newDID string) (string, error) { 455 payload := rotatePayload{ 456 Sub: newDID, 457 ISS: oldDoc.ID, 458 IAT: time.Now().Unix(), 459 } 460 461 payloadBytes, err := json.Marshal(payload) 462 if err != nil { 463 return "", fmt.Errorf("marshalling did rotate payload: %w", err) 464 } 465 466 vm, found := did.LookupPublicKey(oldKID, oldDoc) 467 if !found { 468 return "", fmt.Errorf("sender KID not found in doc provided") 469 } 470 471 keyBytes, kty, crv, err := vmparse.VMToBytesTypeCrv(vm) 472 if err != nil { 473 return "", err 474 } 475 476 kmsKID, err := jwkkid.CreateKID(keyBytes, kty) 477 if err != nil { 478 return "", fmt.Errorf("get signing key KMS KID: %w", err) 479 } 480 481 kh, err := h.kms.Get(kmsKID) 482 if err != nil { 483 return "", fmt.Errorf("get signing key handle: %w", err) 484 } 485 486 var alg string 487 488 if vm.Type == ed25519VerificationKey2018 { 489 alg = "EdDSA" 490 } else if vm.Type == jsonWebKey2020 { 491 jwkKey := vm.JSONWebKey() 492 alg = jwkKey.Algorithm 493 } 494 495 protected := jose.Headers(map[string]interface{}{ 496 "typ": "JWT", 497 "alg": alg, 498 "crv": crv, 499 "kid": oldKID, 500 }) 501 502 jws, err := jose.NewJWS(protected, nil, payloadBytes, &cryptoSigner{kh: kh, crypto: h.crypto}) 503 if err != nil { 504 return "", fmt.Errorf("creating DID rotation JWS: %w", err) 505 } 506 507 return jws.SerializeCompact(false) 508 } 509 510 func (h *DIDCommMessageMiddleware) getUnverifiedJWS(senderDID, fromPrior string, 511 ) (*jose.JSONWebSignature, *rotatePayload, error) { 512 skipVerify := jose.SignatureVerifierFunc(func(_ jose.Headers, _, _, _ []byte) error { 513 return nil 514 }) 515 516 jws, err := jose.ParseJWS(fromPrior, skipVerify) 517 if err != nil { 518 return nil, nil, fmt.Errorf("parsing DID rotation JWS: %w", err) 519 } 520 521 payload := rotatePayload{} 522 523 err = json.Unmarshal(jws.Payload, &payload) 524 if err != nil { 525 return nil, nil, fmt.Errorf("parsing DID rotation payload: %w", err) 526 } 527 528 if payload.ISS == "" || payload.Sub == "" { 529 return nil, nil, fmt.Errorf("from_prior payload missing iss or sub, both are required") 530 } 531 532 if senderDID != payload.Sub { 533 return nil, nil, fmt.Errorf("from_prior payload sub must be the DID of the message sender") 534 } 535 536 return jws, &payload, nil 537 } 538 539 // Verify verifies a didcomm/v2 DID rotation. 540 // - senderDID: the DID of the sender of the message containing this DID Rotation, known from the envelope 541 // or from the message's `to` field. 542 // - fromPrior: the `from_prior` field of the rotated message. 543 // 544 // Returns the sender's old DID (superseded by the new DID), if verification succeeds, or an error otherwise. 545 func (h *DIDCommMessageMiddleware) Verify(senderDID, fromPrior string) (string, error) { 546 jws, payload, err := h.getUnverifiedJWS(senderDID, fromPrior) 547 if err != nil { 548 return "", err 549 } 550 551 err = h.verifyJWSAndPayload(jws, payload) 552 if err != nil { 553 return "", err 554 } 555 556 return payload.ISS, nil 557 } 558 559 func (h *DIDCommMessageMiddleware) verifyJWSAndPayload(jws *jose.JSONWebSignature, payload *rotatePayload) error { 560 oldKID, ok := jws.ProtectedHeaders.KeyID() 561 if !ok { 562 return fmt.Errorf("from_prior protected headers missing KID") 563 } 564 565 oldDocRes, err := h.vdr.Resolve(payload.ISS) 566 if err != nil { 567 return fmt.Errorf("resolving prior DID doc: %w", err) 568 } 569 570 vm, found := did.LookupPublicKey(oldKID, oldDocRes.DIDDocument) 571 if !found { 572 return fmt.Errorf("kid not found in doc") 573 } 574 575 keyBytes, kty, _, err := vmparse.VMToBytesTypeCrv(vm) 576 if err != nil { 577 return err 578 } 579 580 pubKH, err := h.kms.PubKeyBytesToHandle(keyBytes, kty) 581 if err != nil { 582 return fmt.Errorf("get verification key handle: %w", err) 583 } 584 585 verify := jose.DefaultSigningInputVerifier( 586 func(joseHeaders jose.Headers, payload, signingInput, signature []byte) error { 587 return h.crypto.Verify(signature, signingInput, pubKH) 588 }) 589 590 err = verify.Verify(jws.ProtectedHeaders, jws.Payload, nil, jws.Signature()) 591 if err != nil { 592 return fmt.Errorf("signature verification: %w", err) 593 } 594 595 return nil 596 } 597 598 type cryptoSigner struct { 599 kh interface{} 600 crypto crypto.Crypto 601 } 602 603 // Sign signs the input using the stored key handle. 604 func (c *cryptoSigner) Sign(data []byte) ([]byte, error) { 605 return c.crypto.Sign(data, c.kh) 606 } 607 608 // Headers returns nil, cryptoSigner doesn't add its own headers. 609 func (c *cryptoSigner) Headers() jose.Headers { 610 return nil 611 } 612 613 const ( 614 jsonWebKey2020 = "JsonWebKey2020" 615 ed25519VerificationKey2018 = "Ed25519VerificationKey2018" 616 )