github.com/hyperledger/aries-framework-go@v0.3.2/pkg/doc/verifiable/presentation.go (about) 1 /* 2 Copyright SecureKey Technologies Inc. All Rights Reserved. 3 SPDX-License-Identifier: Apache-2.0 4 */ 5 6 package verifiable 7 8 import ( 9 "encoding/json" 10 "errors" 11 "fmt" 12 13 jsonld "github.com/piprate/json-gold/ld" 14 "github.com/xeipuuv/gojsonschema" 15 16 "github.com/hyperledger/aries-framework-go/pkg/doc/jose" 17 docjsonld "github.com/hyperledger/aries-framework-go/pkg/doc/jsonld" 18 "github.com/hyperledger/aries-framework-go/pkg/doc/jwt" 19 "github.com/hyperledger/aries-framework-go/pkg/doc/signature/verifier" 20 jsonutil "github.com/hyperledger/aries-framework-go/pkg/doc/util/json" 21 ) 22 23 const basePresentationSchema = ` 24 { 25 "required": [ 26 "@context", 27 "type" 28 ], 29 "properties": { 30 "@context": { 31 "oneOf": [ 32 { 33 "type": "string", 34 "const": "https://www.w3.org/2018/credentials/v1" 35 }, 36 { 37 "type": "array", 38 "items": [ 39 { 40 "type": "string", 41 "const": "https://www.w3.org/2018/credentials/v1" 42 } 43 ], 44 "uniqueItems": true, 45 "additionalItems": { 46 "oneOf": [ 47 { 48 "type": "object" 49 }, 50 { 51 "type": "string" 52 } 53 ] 54 } 55 } 56 ] 57 }, 58 "id": { 59 "type": "string" 60 }, 61 "type": { 62 "oneOf": [ 63 { 64 "type": "array", 65 "minItems": 1, 66 "contains": { 67 "type": "string", 68 "pattern": "^VerifiablePresentation$" 69 } 70 }, 71 { 72 "type": "string", 73 "pattern": "^VerifiablePresentation$" 74 } 75 ] 76 }, 77 "verifiableCredential": { 78 "anyOf": [ 79 { 80 "type": "array" 81 }, 82 { 83 "type": "object" 84 }, 85 { 86 "type": "string" 87 }, 88 { 89 "type": "null" 90 } 91 ] 92 }, 93 "holder": { 94 "type": "string", 95 "format": "uri" 96 }, 97 "proof": { 98 "anyOf": [ 99 { 100 "type": "array", 101 "items": [ 102 { 103 "$ref": "#/definitions/proof" 104 } 105 ] 106 }, 107 { 108 "$ref": "#/definitions/proof" 109 } 110 ] 111 }, 112 "refreshService": { 113 "$ref": "#/definitions/typedID" 114 } 115 }, 116 "definitions": { 117 "typedID": { 118 "type": "object", 119 "required": [ 120 "id", 121 "type" 122 ], 123 "properties": { 124 "id": { 125 "type": "string", 126 "format": "uri" 127 }, 128 "type": { 129 "anyOf": [ 130 { 131 "type": "string" 132 }, 133 { 134 "type": "array", 135 "items": { 136 "type": "string" 137 } 138 } 139 ] 140 } 141 } 142 }, 143 "proof": { 144 "type": "object", 145 "required": [ 146 "type" 147 ], 148 "properties": { 149 "type": { 150 "type": "string" 151 } 152 } 153 } 154 } 155 } 156 ` 157 158 //nolint:gochecknoglobals 159 var basePresentationSchemaLoader = gojsonschema.NewStringLoader(basePresentationSchema) 160 161 // MarshalledCredential defines marshalled Verifiable Credential enclosed into Presentation. 162 // MarshalledCredential can be passed to verifiable.ParseCredential(). 163 type MarshalledCredential []byte 164 165 // CreatePresentationOpt are options for creating a new presentation. 166 type CreatePresentationOpt func(p *Presentation) error 167 168 // Presentation Verifiable Presentation base data model definition. 169 type Presentation struct { 170 Context []string 171 CustomContext []interface{} 172 ID string 173 Type []string 174 credentials []interface{} 175 Holder string 176 Proofs []Proof 177 JWT string 178 CustomFields CustomFields 179 } 180 181 // NewPresentation creates a new Presentation with default context and type with the provided credentials. 182 func NewPresentation(opts ...CreatePresentationOpt) (*Presentation, error) { 183 p := Presentation{ 184 Context: []string{baseContext}, 185 Type: []string{vpType}, 186 credentials: []interface{}{}, 187 } 188 189 for _, o := range opts { 190 err := o(&p) 191 if err != nil { 192 return nil, err 193 } 194 } 195 196 return &p, nil 197 } 198 199 // WithCredentials sets the provided credentials into the presentation. 200 func WithCredentials(cs ...*Credential) CreatePresentationOpt { 201 return func(p *Presentation) error { 202 for _, c := range cs { 203 p.credentials = append(p.credentials, c) 204 } 205 206 return nil 207 } 208 } 209 210 // WithJWTCredentials sets the provided base64url encoded JWT credentials into the presentation. 211 func WithJWTCredentials(cs ...string) CreatePresentationOpt { 212 return func(p *Presentation) error { 213 for _, c := range cs { 214 if !jose.IsCompactJWS(c) { 215 return errors.New("credential is not base64url encoded JWT") 216 } 217 218 p.credentials = append(p.credentials, c) 219 } 220 221 return nil 222 } 223 } 224 225 // MarshalJSON converts Verifiable Presentation to JSON bytes. 226 func (vp *Presentation) MarshalJSON() ([]byte, error) { 227 if vp.JWT != "" { 228 // If vc.JWT exists, marshal only the JWT, since all other values should be unchanged 229 // from when the JWT was parsed. 230 return []byte("\"" + vp.JWT + "\""), nil 231 } 232 233 raw, err := vp.raw() 234 if err != nil { 235 return nil, fmt.Errorf("JSON marshalling of verifiable presentation: %w", err) 236 } 237 238 byteCred, err := json.Marshal(raw) 239 if err != nil { 240 return nil, fmt.Errorf("JSON marshalling of verifiable presentation: %w", err) 241 } 242 243 return byteCred, nil 244 } 245 246 // JWTClaims converts Verifiable Presentation into JWT Presentation claims, which can be than serialized 247 // e.g. into JWS. 248 func (vp *Presentation) JWTClaims(audience []string, minimizeVP bool) (*JWTPresClaims, error) { 249 return newJWTPresClaims(vp, audience, minimizeVP) 250 } 251 252 // Credentials returns current credentials of presentation. 253 func (vp *Presentation) Credentials() []interface{} { 254 return vp.credentials 255 } 256 257 // AddCredentials adds credentials to presentation. 258 func (vp *Presentation) AddCredentials(credentials ...*Credential) { 259 for _, credential := range credentials { 260 vp.credentials = append(vp.credentials, credential) 261 } 262 } 263 264 // MarshalledCredentials provides marshalled credentials enclosed into Presentation in raw byte array format. 265 // They can be used to decode Credentials into struct. 266 func (vp *Presentation) MarshalledCredentials() ([]MarshalledCredential, error) { 267 mCreds := make([]MarshalledCredential, len(vp.credentials)) 268 269 for i := range vp.credentials { 270 cred := vp.credentials[i] 271 switch c := cred.(type) { 272 case string: 273 mCreds[i] = MarshalledCredential(c) 274 case []byte: 275 mCreds[i] = c 276 default: 277 credBytes, err := json.Marshal(cred) 278 if err != nil { 279 return nil, fmt.Errorf("marshal credentials from presentation: %w", err) 280 } 281 282 mCreds[i] = credBytes 283 } 284 } 285 286 return mCreds, nil 287 } 288 289 func (vp *Presentation) raw() (*rawPresentation, error) { 290 proof, err := proofsToRaw(vp.Proofs) 291 if err != nil { 292 return nil, err 293 } 294 295 rp := &rawPresentation{ 296 // TODO single value contexts should be compacted as part of Issue [#1730] 297 // Not compacting now to support interoperability 298 Context: vp.Context, 299 ID: vp.ID, 300 Type: typesToRaw(vp.Type), 301 Holder: vp.Holder, 302 Proof: proof, 303 CustomFields: vp.CustomFields, 304 JWT: vp.JWT, 305 } 306 307 if len(vp.credentials) > 0 { 308 rp.Credential = vp.credentials 309 } 310 311 return rp, nil 312 } 313 314 // rawPresentation is a basic verifiable credential. 315 type rawPresentation struct { 316 Context interface{} `json:"@context,omitempty"` 317 ID string `json:"id,omitempty"` 318 Type interface{} `json:"type,omitempty"` 319 Credential interface{} `json:"verifiableCredential,omitempty"` 320 Holder string `json:"holder,omitempty"` 321 Proof json.RawMessage `json:"proof,omitempty"` 322 JWT string `json:"jwt,omitempty"` 323 // All unmapped fields are put here. 324 CustomFields `json:"-"` 325 } 326 327 // MarshalJSON defines custom marshalling of rawPresentation to JSON. 328 func (rp *rawPresentation) MarshalJSON() ([]byte, error) { 329 type Alias rawPresentation 330 331 alias := (*Alias)(rp) 332 333 return jsonutil.MarshalWithCustomFields(alias, rp.CustomFields) 334 } 335 336 // UnmarshalJSON defines custom unmarshalling of rawPresentation from JSON. 337 func (rp *rawPresentation) UnmarshalJSON(data []byte) error { 338 type Alias rawPresentation 339 340 alias := (*Alias)(rp) 341 rp.CustomFields = make(CustomFields) 342 343 err := jsonutil.UnmarshalWithCustomFields(data, alias, rp.CustomFields) 344 if err != nil { 345 return err 346 } 347 348 return nil 349 } 350 351 // presentationOpts holds options for the Verifiable Presentation decoding. 352 type presentationOpts struct { 353 publicKeyFetcher PublicKeyFetcher 354 disabledProofCheck bool 355 ldpSuites []verifier.SignatureSuite 356 strictValidation bool 357 requireVC bool 358 requireProof bool 359 disableJSONLDChecks bool 360 361 jsonldCredentialOpts 362 } 363 364 // PresentationOpt is the Verifiable Presentation decoding option. 365 type PresentationOpt func(opts *presentationOpts) 366 367 // WithPresPublicKeyFetcher indicates that Verifiable Presentation should be decoded from JWS using 368 // the public key fetcher. 369 func WithPresPublicKeyFetcher(fetcher PublicKeyFetcher) PresentationOpt { 370 return func(opts *presentationOpts) { 371 opts.publicKeyFetcher = fetcher 372 } 373 } 374 375 // WithPresEmbeddedSignatureSuites defines the suites which are used to check embedded linked data proof of VP. 376 func WithPresEmbeddedSignatureSuites(suites ...verifier.SignatureSuite) PresentationOpt { 377 return func(opts *presentationOpts) { 378 opts.ldpSuites = suites 379 } 380 } 381 382 // WithPresDisabledProofCheck option for disabling of proof check. 383 func WithPresDisabledProofCheck() PresentationOpt { 384 return func(opts *presentationOpts) { 385 opts.disabledProofCheck = true 386 } 387 } 388 389 // WithPresStrictValidation enabled strict JSON-LD validation of VP. 390 // In case of JSON-LD validation, the comparison of JSON-LD VP document after compaction with original VP one is made. 391 // In case of mismatch a validation exception is raised. 392 func WithPresStrictValidation() PresentationOpt { 393 return func(opts *presentationOpts) { 394 opts.strictValidation = true 395 } 396 } 397 398 // WithPresJSONLDDocumentLoader defines custom JSON-LD document loader. If not defined, when decoding VP 399 // a new document loader will be created using CachingJSONLDLoader() if JSON-LD validation is made. 400 func WithPresJSONLDDocumentLoader(documentLoader jsonld.DocumentLoader) PresentationOpt { 401 return func(opts *presentationOpts) { 402 opts.jsonldDocumentLoader = documentLoader 403 } 404 } 405 406 // WithDisabledJSONLDChecks disables JSON-LD checks for VP parsing. 407 // By default, JSON-LD checks are enabled. 408 func WithDisabledJSONLDChecks() PresentationOpt { 409 return func(opts *presentationOpts) { 410 opts.disableJSONLDChecks = true 411 } 412 } 413 414 // ParsePresentation creates an instance of Verifiable Presentation by reading a JSON document from bytes. 415 // It also applies miscellaneous options like custom decoders or settings of schema validation. 416 func ParsePresentation(vpData []byte, opts ...PresentationOpt) (*Presentation, error) { 417 vpOpts := getPresentationOpts(opts) 418 419 vpDataDecoded, vpRaw, vpJWT, err := decodeRawPresentation(vpData, vpOpts) 420 if err != nil { 421 return nil, err 422 } 423 424 err = validateVP(vpDataDecoded, vpOpts) 425 if err != nil { 426 return nil, err 427 } 428 429 p, err := newPresentation(vpRaw, vpOpts) 430 if err != nil { 431 return nil, err 432 } 433 434 if vpOpts.requireVC && len(p.credentials) == 0 { 435 return nil, fmt.Errorf("verifiableCredential is required") 436 } 437 438 p.JWT = vpJWT 439 440 return p, nil 441 } 442 443 func getPresentationOpts(opts []PresentationOpt) *presentationOpts { 444 vpOpts := defaultPresentationOpts() 445 446 for _, opt := range opts { 447 opt(vpOpts) 448 } 449 450 return vpOpts 451 } 452 453 func newPresentation(vpRaw *rawPresentation, vpOpts *presentationOpts) (*Presentation, error) { 454 types, err := decodeType(vpRaw.Type) 455 if err != nil { 456 return nil, fmt.Errorf("fill presentation types from raw: %w", err) 457 } 458 459 context, customContext, err := decodeContext(vpRaw.Context) 460 if err != nil { 461 return nil, fmt.Errorf("fill presentation contexts from raw: %w", err) 462 } 463 464 creds, err := decodeCredentials(vpRaw.Credential, vpOpts) 465 if err != nil { 466 return nil, fmt.Errorf("decode credentials of presentation: %w", err) 467 } 468 469 proofs, err := parseProof(vpRaw.Proof) 470 if err != nil { 471 return nil, fmt.Errorf("fill credential proof from raw: %w", err) 472 } 473 474 return &Presentation{ 475 Context: context, 476 CustomContext: customContext, 477 ID: vpRaw.ID, 478 Type: types, 479 credentials: creds, 480 Holder: vpRaw.Holder, 481 Proofs: proofs, 482 CustomFields: vpRaw.CustomFields, 483 }, nil 484 } 485 486 // decodeCredentials decodes credential(s) embedded into presentation. 487 // It must be one of the following: 488 // 1) string - it could be credential decoded into e.g. JWS. 489 // 2) the same as 1) but as array - e.g. zero ore more JWS 490 // 3) struct (should be map[string]interface{}) representing credential data model 491 // 4) the same as 3) but as array - i.e. zero or more credentials structs. 492 func decodeCredentials(rawCred interface{}, opts *presentationOpts) ([]interface{}, error) { 493 // Accept the case when VP does not have any VCs. 494 if rawCred == nil { 495 return nil, nil 496 } 497 498 unmarshalSingleCredFn := func(cred interface{}) (interface{}, error) { 499 // Check the case when VC is defined in string format (e.g. JWT). 500 // Decode credential and keep result of decoding. 501 if sCred, ok := cred.(string); ok { 502 bCred := []byte(sCred) 503 504 credOpts := []CredentialOpt{ 505 WithPublicKeyFetcher(opts.publicKeyFetcher), 506 WithEmbeddedSignatureSuites(opts.ldpSuites...), 507 WithJSONLDDocumentLoader(opts.jsonldCredentialOpts.jsonldDocumentLoader), 508 } 509 510 if opts.disabledProofCheck { 511 credOpts = append(credOpts, WithDisabledProofCheck()) 512 } 513 514 vc, err := ParseCredential(bCred, credOpts...) 515 516 return vc, err 517 } 518 519 // return credential in a structure format as is 520 return cred, nil 521 } 522 523 switch cred := rawCred.(type) { 524 case []interface{}: 525 // Accept the case when VP does not have any VCs. 526 if len(cred) == 0 { 527 return nil, nil 528 } 529 530 // 1 or more credentials 531 creds := make([]interface{}, len(cred)) 532 533 for i := range cred { 534 c, err := unmarshalSingleCredFn(cred[i]) 535 if err != nil { 536 return nil, err 537 } 538 539 creds[i] = c 540 } 541 542 return creds, nil 543 default: 544 // single credential 545 c, err := unmarshalSingleCredFn(cred) 546 if err != nil { 547 return nil, err 548 } 549 550 return []interface{}{c}, nil 551 } 552 } 553 554 func validateVP(data []byte, opts *presentationOpts) error { 555 err := validateVPJSONSchema(data) 556 if err != nil { 557 return err 558 } 559 560 if opts.disableJSONLDChecks { 561 return nil 562 } 563 564 return validateVPJSONLD(data, opts) 565 } 566 567 func validateVPJSONLD(vpBytes []byte, opts *presentationOpts) error { 568 return docjsonld.ValidateJSONLD(string(vpBytes), 569 docjsonld.WithDocumentLoader(opts.jsonldCredentialOpts.jsonldDocumentLoader), 570 docjsonld.WithExternalContext(opts.jsonldCredentialOpts.externalContext), 571 docjsonld.WithStrictValidation(opts.strictValidation), 572 ) 573 } 574 575 func validateVPJSONSchema(data []byte) error { 576 loader := gojsonschema.NewStringLoader(string(data)) 577 578 result, err := gojsonschema.Validate(basePresentationSchemaLoader, loader) 579 if err != nil { 580 return fmt.Errorf("validation of verifiable credential: %w", err) 581 } 582 583 if !result.Valid() { 584 errMsg := describeSchemaValidationError(result, "verifiable presentation") 585 return errors.New(errMsg) 586 } 587 588 return nil 589 } 590 591 //nolint:gocyclo 592 func decodeRawPresentation(vpData []byte, vpOpts *presentationOpts) ([]byte, *rawPresentation, string, error) { 593 vpStr := string(unQuote(vpData)) 594 595 if jwt.IsJWS(vpStr) { 596 if !vpOpts.disabledProofCheck && vpOpts.publicKeyFetcher == nil { 597 return nil, nil, "", errors.New("public key fetcher is not defined") 598 } 599 600 vcDataFromJwt, rawCred, err := decodeVPFromJWS(vpStr, !vpOpts.disabledProofCheck, vpOpts.publicKeyFetcher) 601 if err != nil { 602 return nil, nil, "", fmt.Errorf("decoding of Verifiable Presentation from JWS: %w", err) 603 } 604 605 return vcDataFromJwt, rawCred, vpStr, nil 606 } 607 608 embeddedProofCheckOpts := &embeddedProofCheckOpts{ 609 publicKeyFetcher: vpOpts.publicKeyFetcher, 610 disabledProofCheck: vpOpts.disabledProofCheck, 611 ldpSuites: vpOpts.ldpSuites, 612 jsonldCredentialOpts: vpOpts.jsonldCredentialOpts, 613 } 614 615 if jwt.IsJWTUnsecured(vpStr) { 616 rawBytes, rawPres, err := decodeVPFromUnsecuredJWT(vpStr) 617 if err != nil { 618 return nil, nil, "", fmt.Errorf("decoding of Verifiable Presentation from unsecured JWT: %w", err) 619 } 620 621 if err := checkEmbeddedProof(rawBytes, embeddedProofCheckOpts); err != nil { 622 return nil, nil, "", err 623 } 624 625 return rawBytes, rawPres, "", nil 626 } 627 628 vpRaw, err := decodeVPFromJSON(vpData) 629 if err != nil { 630 return nil, nil, "", err 631 } 632 633 err = checkEmbeddedProof(vpData, embeddedProofCheckOpts) 634 if err != nil { 635 return nil, nil, "", err 636 } 637 638 // check that embedded proof is present, if not, it's not a verifiable presentation 639 if vpOpts.requireProof && vpRaw.Proof == nil { 640 return nil, nil, "", errors.New("embedded proof is missing") 641 } 642 643 return vpData, vpRaw, "", err 644 } 645 646 func decodeVPFromJSON(vpData []byte) (*rawPresentation, error) { 647 // unmarshal VP from JSON 648 raw := new(rawPresentation) 649 650 err := json.Unmarshal(vpData, raw) 651 if err != nil { 652 return nil, fmt.Errorf("JSON unmarshalling of verifiable presentation: %w", err) 653 } 654 655 return raw, nil 656 } 657 658 func defaultPresentationOpts() *presentationOpts { 659 return &presentationOpts{} 660 }