github.com/hyperledger/aries-framework-go@v0.3.2/pkg/didcomm/protocol/middleware/presentproof/middlewares.go (about) 1 /* 2 Copyright SecureKey Technologies Inc. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package presentproof 8 9 import ( 10 "encoding/json" 11 "errors" 12 "fmt" 13 "strings" 14 15 "github.com/google/uuid" 16 "github.com/piprate/json-gold/ld" 17 18 "github.com/hyperledger/aries-framework-go/pkg/crypto" 19 "github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service" 20 "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/decorator" 21 "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/presentproof" 22 "github.com/hyperledger/aries-framework-go/pkg/doc/presexch" 23 "github.com/hyperledger/aries-framework-go/pkg/doc/signature/jsonld" 24 "github.com/hyperledger/aries-framework-go/pkg/doc/signature/suite" 25 "github.com/hyperledger/aries-framework-go/pkg/doc/signature/suite/bbsblssignature2020" 26 "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" 27 vdrapi "github.com/hyperledger/aries-framework-go/pkg/framework/aries/api/vdr" 28 "github.com/hyperledger/aries-framework-go/pkg/kms" 29 storeverifiable "github.com/hyperledger/aries-framework-go/pkg/store/verifiable" 30 "github.com/hyperledger/aries-framework-go/pkg/vdr/fingerprint" 31 ) 32 33 const ( 34 stateNamePresentationReceived = "presentation-received" 35 stateNameRequestReceived = "request-received" 36 myDIDKey = "myDID" 37 theirDIDKey = "theirDID" 38 namesKey = "names" 39 40 mimeTypeApplicationLdJSON = "application/ld+json" 41 mimeTypeAll = "*" 42 43 peDefinitionFormat = "dif/presentation-exchange/definitions@v1.0" 44 peSubmissionFormat = "dif/presentation-exchange/submission@v1.0" 45 bbsContext = "https://w3id.org/security/bbs/v1" 46 ) 47 48 // Metadata is an alias to the original Metadata. 49 type Metadata presentproof.Metadata 50 51 // Provider contains dependencies for the SavePresentation middleware function. 52 type Provider interface { 53 VerifiableStore() storeverifiable.Store 54 VDRegistry() vdrapi.Registry 55 KMS() kms.KeyManager 56 Crypto() crypto.Crypto 57 JSONLDDocumentLoader() ld.DocumentLoader 58 } 59 60 // SavePresentation the helper function for the present proof protocol which saves the presentations. 61 func SavePresentation(p Provider) presentproof.Middleware { 62 vdr := p.VDRegistry() 63 store := p.VerifiableStore() 64 documentLoader := p.JSONLDDocumentLoader() 65 66 return func(next presentproof.Handler) presentproof.Handler { 67 return presentproof.HandlerFunc(func(metadata presentproof.Metadata) error { 68 if metadata.StateName() != stateNamePresentationReceived { 69 return next.Handle(metadata) 70 } 71 72 msg := metadata.Message() 73 74 attachments, err := getAttachments(msg) 75 if err != nil { 76 return fmt.Errorf("get attachments: %w", err) 77 } 78 79 presentations, err := toVerifiablePresentation(vdr, attachments, documentLoader) 80 if err != nil { 81 return fmt.Errorf("to verifiable presentation: %w", err) 82 } 83 84 if len(presentations) == 0 { 85 return errors.New("presentations were not provided") 86 } 87 88 var names []string 89 properties := metadata.Properties() 90 91 // nolint: errcheck 92 myDID, _ := properties[myDIDKey].(string) 93 // nolint: errcheck 94 theirDID, _ := properties[theirDIDKey].(string) 95 if myDID == "" || theirDID == "" { 96 return errors.New("myDID or theirDID is absent") 97 } 98 99 for i, presentation := range presentations { 100 names = append(names, getName(i, presentation.ID, metadata)) 101 102 err := store.SavePresentation(names[i], presentation, 103 storeverifiable.WithMyDID(myDID), 104 storeverifiable.WithTheirDID(theirDID), 105 ) 106 if err != nil { 107 return fmt.Errorf("save presentation: %w", err) 108 } 109 } 110 111 properties[namesKey] = names 112 113 return next.Handle(metadata) 114 }) 115 } 116 } 117 118 func getAttachments(msg service.DIDCommMsg) ([]decorator.AttachmentData, error) { 119 if strings.HasPrefix(msg.Type(), presentproof.SpecV3) { 120 presentation := presentproof.PresentationV3{} 121 if err := msg.Decode(&presentation); err != nil { 122 return nil, fmt.Errorf("decode: %w", err) 123 } 124 125 return filterByMediaType(presentation.Attachments, mimeTypeAll), nil 126 } 127 128 presentation := presentproof.PresentationV2{} 129 if err := msg.Decode(&presentation); err != nil { 130 return nil, fmt.Errorf("decode: %w", err) 131 } 132 133 return filterByMimeType(presentation.PresentationsAttach, mimeTypeAll), nil 134 } 135 136 type presentationExchangePayload struct { 137 Challenge string `json:"challenge"` 138 Domain string `json:"domain"` 139 PresentationDefinition *presexch.PresentationDefinition `json:"presentation_definition"` 140 } 141 142 // OptPD represents option function for the PresentationDefinition middleware. 143 type OptPD func(o *pdOptions) 144 145 // WithAddProofFn allows providing function that will sign the Presentation. 146 func WithAddProofFn(sign func(presentation *verifiable.Presentation) error) OptPD { 147 return func(o *pdOptions) { 148 o.addProof = sign 149 } 150 } 151 152 type pdOptions struct { 153 addProof func(presentation *verifiable.Presentation) error 154 } 155 156 func defaultPdOptions() *pdOptions { 157 return &pdOptions{ 158 addProof: func(presentation *verifiable.Presentation) error { 159 return nil 160 }, 161 } 162 } 163 164 // AddBBSProofFn add BBS+ proof to the Presentation. 165 func AddBBSProofFn(p Provider) func(presentation *verifiable.Presentation) error { 166 km, cr := p.KMS(), p.Crypto() 167 documentLoader := p.JSONLDDocumentLoader() 168 169 return func(presentation *verifiable.Presentation) error { 170 kid, pubKey, err := km.CreateAndExportPubKeyBytes(kms.BLS12381G2Type) 171 if err != nil { 172 return err 173 } 174 175 _, didKey := fingerprint.CreateDIDKeyByCode(fingerprint.BLS12381g2PubKeyMultiCodec, pubKey) 176 177 presentation.Context = append(presentation.Context, bbsContext) 178 179 return presentation.AddLinkedDataProof(&verifiable.LinkedDataProofContext{ 180 SignatureType: "BbsBlsSignature2020", 181 SignatureRepresentation: verifiable.SignatureProofValue, 182 Suite: bbsblssignature2020.New(suite.WithSigner(newBBSSigner(km, cr, kid))), 183 VerificationMethod: didKey, 184 }, jsonld.WithDocumentLoader(documentLoader)) 185 } 186 } 187 188 // PresentationDefinition the helper function for the present proof protocol that creates VP based on credentials that 189 // were provided in the attachments according to the requested presentation definition. 190 func PresentationDefinition(p Provider, opts ...OptPD) presentproof.Middleware { // nolint: funlen,gocyclo,gocognit 191 vdr := p.VDRegistry() 192 documentLoader := p.JSONLDDocumentLoader() 193 194 options := defaultPdOptions() 195 196 for i := range opts { 197 opts[i](options) 198 } 199 200 return func(next presentproof.Handler) presentproof.Handler { 201 return presentproof.HandlerFunc(func(metadata presentproof.Metadata) error { 202 if metadata.StateName() != stateNameRequestReceived { 203 return next.Handle(metadata) 204 } 205 206 var ( 207 msg = metadata.Message() 208 attachments []decorator.AttachmentData 209 src []byte 210 fmtIdx int 211 err error 212 ) 213 214 if strings.HasPrefix(msg.Type(), presentproof.SpecV3) { // nolint: nestif 215 request := presentproof.RequestPresentationV3{} 216 if err = msg.Decode(&request); err != nil { 217 return fmt.Errorf("decode: %w", err) 218 } 219 220 if metadata.PresentationV3() == nil || 221 !hasFormat(toFormats(request.Attachments), peDefinitionFormat) || 222 hasFormat(toFormats(metadata.PresentationV3().Attachments), peSubmissionFormat) { 223 return next.Handle(metadata) 224 } 225 226 src, err = getAttachmentByFormatV2(toFormats(request.Attachments), 227 request.Attachments, peDefinitionFormat) 228 229 attachments = filterByMediaType(metadata.PresentationV3().Attachments, mimeTypeApplicationLdJSON) 230 } else { 231 request := presentproof.RequestPresentationV2{} 232 if err = msg.Decode(&request); err != nil { 233 return fmt.Errorf("decode: %w", err) 234 } 235 236 if metadata.Presentation() == nil || 237 !hasFormat(request.Formats, peDefinitionFormat) || 238 hasFormat(metadata.Presentation().Formats, peSubmissionFormat) { 239 return next.Handle(metadata) 240 } 241 242 src, fmtIdx, err = getAttachmentByFormat(request.Formats, 243 request.RequestPresentationsAttach, peDefinitionFormat) 244 attachments = filterByMimeType(metadata.Presentation().PresentationsAttach, mimeTypeApplicationLdJSON) 245 } 246 247 if err != nil { 248 return fmt.Errorf("get attachment by format: %w", err) 249 } 250 251 var payload *presentationExchangePayload 252 253 if err = json.Unmarshal(src, &payload); err != nil { 254 return fmt.Errorf("unmarshal definition: %w", err) 255 } 256 257 credentials, err := parseCredentials(vdr, attachments, documentLoader) 258 if err != nil { 259 return fmt.Errorf("parse credentials: %w", err) 260 } 261 262 if len(credentials) > 0 { // nolint: nestif 263 presentation, err := payload.PresentationDefinition.CreateVP(credentials, documentLoader, 264 verifiable.WithPublicKeyFetcher(verifiable.NewVDRKeyResolver(vdr).PublicKeyFetcher()), 265 verifiable.WithJSONLDDocumentLoader(documentLoader)) 266 if err != nil { 267 return fmt.Errorf("create VP: %w", err) 268 } 269 270 signFn := metadata.GetAddProofFn() 271 if signFn == nil { 272 signFn = options.addProof 273 } 274 275 err = signFn(presentation) 276 if err != nil { 277 return fmt.Errorf("add proof: %w", err) 278 } 279 280 if strings.HasPrefix(msg.Type(), presentproof.SpecV3) { 281 metadata.PresentationV3().Attachments = []decorator.AttachmentV2{{ 282 ID: uuid.New().String(), 283 MediaType: mimeTypeApplicationLdJSON, 284 Data: decorator.AttachmentData{JSON: presentation}, 285 }} 286 } else { 287 newID := uuid.New().String() 288 289 formats := metadata.Presentation().Formats 290 if len(formats) > fmtIdx { 291 formats[fmtIdx].AttachID = newID 292 } 293 294 metadata.Presentation().PresentationsAttach = []decorator.Attachment{{ 295 ID: newID, 296 MimeType: mimeTypeApplicationLdJSON, 297 Data: decorator.AttachmentData{JSON: presentation}, 298 }} 299 } 300 } 301 302 return next.Handle(metadata) 303 }) 304 } 305 } 306 307 func contains(s []string, e string) bool { 308 for _, a := range s { 309 if a == e { 310 return true 311 } 312 } 313 314 return false 315 } 316 317 func filterByMediaType(attachments []decorator.AttachmentV2, mediaType string) []decorator.AttachmentData { 318 var result []decorator.AttachmentData 319 320 for i := range attachments { 321 if attachments[i].MediaType != mediaType && mediaType != mimeTypeAll { 322 continue 323 } 324 325 result = append(result, attachments[i].Data) 326 } 327 328 return result 329 } 330 331 func filterByMimeType(attachments []decorator.Attachment, mimeType string) []decorator.AttachmentData { 332 var result []decorator.AttachmentData 333 334 for i := range attachments { 335 if attachments[i].MimeType != mimeType && mimeType != mimeTypeAll { 336 continue 337 } 338 339 result = append(result, attachments[i].Data) 340 } 341 342 return result 343 } 344 345 func parseCredentials(vdr vdrapi.Registry, attachments []decorator.AttachmentData, 346 documentLoader ld.DocumentLoader) ([]*verifiable.Credential, error) { 347 var credentials []*verifiable.Credential 348 349 for i := range attachments { 350 src, err := attachments[i].Fetch() 351 if err != nil { 352 return nil, err 353 } 354 355 var types struct { 356 Type interface{} `json:"type"` 357 } 358 359 err = json.Unmarshal(src, &types) 360 if err != nil { 361 return nil, err 362 } 363 364 var credentialTypes []string 365 366 switch v := types.Type.(type) { 367 case string: 368 credentialTypes = []string{v} 369 case []interface{}: 370 for _, e := range v { 371 if val, ok := e.(string); ok { 372 credentialTypes = append(credentialTypes, val) 373 } 374 } 375 } 376 377 if !contains(credentialTypes, verifiable.VCType) { 378 continue 379 } 380 381 credential, err := verifiable.ParseCredential(src, 382 verifiable.WithPublicKeyFetcher( 383 verifiable.NewVDRKeyResolver(vdr).PublicKeyFetcher(), 384 ), 385 verifiable.WithJSONLDDocumentLoader(documentLoader), 386 ) 387 if err != nil { 388 return nil, err 389 } 390 391 credentials = append(credentials, credential) 392 } 393 394 return credentials, nil 395 } 396 397 func getAttachmentByFormat(fms []presentproof.Format, attachments []decorator.Attachment, name string, 398 ) ([]byte, int, error) { 399 for fmtIdx, format := range fms { 400 if format.Format == name { 401 for i := range attachments { 402 if attachments[i].ID == format.AttachID { 403 data, err := attachments[i].Data.Fetch() 404 return data, fmtIdx, err 405 } 406 } 407 } 408 } 409 410 return nil, 0, errors.New("not found") 411 } 412 413 func getAttachmentByFormatV2(fms []presentproof.Format, attachs []decorator.AttachmentV2, name string) ([]byte, error) { 414 for _, format := range fms { 415 if format.Format == name { 416 for i := range attachs { 417 if attachs[i].ID == format.AttachID { 418 data, err := attachs[i].Data.Fetch() 419 return data, err 420 } 421 } 422 } 423 } 424 425 return nil, errors.New("not found") 426 } 427 428 func hasFormat(formats []presentproof.Format, format string) bool { 429 for _, fm := range formats { 430 if fm.Format == format { 431 return true 432 } 433 } 434 435 return false 436 } 437 438 func toFormats(attachments []decorator.AttachmentV2) []presentproof.Format { 439 var result []presentproof.Format 440 for i := range attachments { 441 result = append(result, presentproof.Format{AttachID: attachments[i].ID, Format: attachments[i].Format}) 442 } 443 444 return result 445 } 446 447 func getName(idx int, id string, metadata presentproof.Metadata) string { 448 name := id 449 if len(metadata.PresentationNames()) > idx { 450 name = metadata.PresentationNames()[idx] 451 } 452 453 if name != "" { 454 return name 455 } 456 457 return uuid.New().String() 458 } 459 460 func toVerifiablePresentation(vdr vdrapi.Registry, data []decorator.AttachmentData, 461 documentLoader ld.DocumentLoader) ([]*verifiable.Presentation, error) { 462 var presentations []*verifiable.Presentation 463 464 for i := range data { 465 raw, err := data[i].Fetch() 466 if err != nil { 467 return nil, fmt.Errorf("fetch: %w", err) 468 } 469 470 presentation, err := verifiable.ParsePresentation(raw, 471 verifiable.WithPresPublicKeyFetcher( 472 verifiable.NewVDRKeyResolver(vdr).PublicKeyFetcher(), 473 ), 474 verifiable.WithPresJSONLDDocumentLoader(documentLoader), 475 ) 476 if err != nil { 477 return nil, fmt.Errorf("parse presentation: %w", err) 478 } 479 480 presentations = append(presentations, presentation) 481 } 482 483 return presentations, nil 484 } 485 486 type bbsSigner struct { 487 km kms.KeyManager 488 cr crypto.Crypto 489 keyID string 490 } 491 492 func newBBSSigner(km kms.KeyManager, cr crypto.Crypto, keyID string) *bbsSigner { 493 return &bbsSigner{km: km, cr: cr, keyID: keyID} 494 } 495 496 func (s *bbsSigner) Sign(data []byte) ([]byte, error) { 497 kh, err := s.km.Get(s.keyID) 498 if err != nil { 499 return nil, err 500 } 501 502 return s.cr.SignMulti(s.textToLines(string(data)), kh) 503 } 504 505 func (s *bbsSigner) Alg() string { 506 return "Bls12381G2Key2020" 507 } 508 509 func (s *bbsSigner) textToLines(txt string) [][]byte { 510 lines := strings.Split(txt, "\n") 511 linesBytes := make([][]byte, 0, len(lines)) 512 513 for i := range lines { 514 if strings.TrimSpace(lines[i]) != "" { 515 linesBytes = append(linesBytes, []byte(lines[i])) 516 } 517 } 518 519 return linesBytes 520 }