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  }