UNPKG

@bedrock/vc-delivery

Version:
983 lines (938 loc) 24.4 kB
/*! * Copyright (c) 2022-2026 Digital Bazaar, Inc. All rights reserved. */ import {MAX_ISSUER_INSTANCES} from '../lib/constants.js'; import {schemas} from '@bedrock/validation'; const VC_CONTEXT_1 = 'https://www.w3.org/2018/credentials/v1'; const VC_CONTEXT_2 = 'https://www.w3.org/ns/credentials/v2'; const vcContext = { type: 'array', minItems: 1, // the first context must be the VC context items: [{ oneOf: [ {const: VC_CONTEXT_1}, {const: VC_CONTEXT_2} ] }], // additional contexts maybe strings or objects additionalItems: { anyOf: [{type: 'string'}, {type: 'object'}] } }; const vcContext2StringOrArray = { oneOf: [{ const: VC_CONTEXT_2 }, { type: 'array', minItems: 1, // the first context must be the VC 2.0 context items: [{const: VC_CONTEXT_2}], // additional contexts maybe strings or objects additionalItems: { anyOf: [{type: 'string'}, {type: 'object'}] } }] }; function idOrObjectWithId() { return { title: 'identifier or an object with an id', anyOf: [ schemas.identifier(), { type: 'object', required: ['id'], additionalProperties: true, properties: {id: schemas.identifier()} } ] }; } function verifiableCredential() { return { title: 'Verifiable Credential', type: 'object', required: [ '@context', 'credentialSubject', 'issuer', 'type' ], additionalProperties: true, properties: { '@context': vcContext, credentialSubject: { anyOf: [ {type: 'object'}, {type: 'array', minItems: 1, items: {type: 'object'}} ] }, id: { type: 'string' }, issuer: idOrObjectWithId(), type: { type: 'array', minItems: 1, // this first type must be VerifiableCredential items: [ {const: 'VerifiableCredential'}, ], // additional types must be strings additionalItems: { type: 'string' } }, proof: schemas.proof() } }; } const envelopedVerifiableCredential = { title: 'Enveloped Verifiable Credential', type: 'object', additionalProperties: true, properties: { '@context': vcContext2StringOrArray, id: {type: 'string'}, type: {const: 'EnvelopedVerifiableCredential'} }, required: ['@context', 'id', 'type'] }; const envelopedVerifiablePresentation = { title: 'Enveloped Verifiable Presentation', type: 'object', additionalProperties: true, properties: { '@context': vcContext2StringOrArray, id: {type: 'string'}, type: {const: 'EnvelopedVerifiablePresentation'} }, required: ['@context', 'id', 'type'] }; const jwkKeyPair = { type: 'object', additionalProperties: false, required: ['privateKeyJwk', 'publicKeyJwk'], properties: { privateKeyJwk: {type: 'object'}, publicKeyJwk: {type: 'object'} } }; function verifiableOrEnvelopedCredentials() { return { anyOf: [ verifiableCredential(), envelopedVerifiableCredential, { type: 'array', minItems: 1, items: { anyOf: [verifiableCredential(), envelopedVerifiableCredential] } } ] }; } export function verifiablePresentation() { return { title: 'Verifiable Presentation', type: 'object', required: ['@context', 'type'], additionalProperties: true, properties: { '@context': vcContext, id: {type: 'string'}, type: { type: 'array', minItems: 1, // this first type must be VerifiablePresentation items: [ {const: 'VerifiablePresentation'}, ], // additional types must be strings additionalItems: { type: 'string' } }, verifiableCredential: verifiableOrEnvelopedCredentials(), holder: idOrObjectWithId(), proof: schemas.proof() } }; } const credentialDefinition = { title: 'OID4VCI Verifiable Credential Definition', type: 'object', additionalProperties: false, required: ['@context', 'type'], properties: { '@context': { type: 'array', minItems: 1, item: {type: 'string'} }, type: { type: 'array', minItems: 1, item: {type: 'string'} }, // allow `types` to be flexible for OID4VCI draft 20 implementers types: { type: 'array', minItems: 1, item: {type: 'string'} } } }; const supportedProofTypeConfiguration = { title: 'OID4VCI Supported Proof Type Configuration', type: 'object', required: ['proof_signing_alg_values_supported'], additionalProperties: false, properties: { proof_signing_alg_values_supported: { type: 'array', minItems: 1, items: {type: 'string'} } } }; function credentialConfiguration() { return { title: 'OID4VCI Credential Configuration', type: 'object', required: ['credential_definition', 'format'], additionalProperties: false, properties: { credential_definition: credentialDefinition, format: { type: 'string', enum: ['di_vc', 'ldp_vc', 'jwt_vc_json-ld', 'jwt_vc_json'] }, proof_types_supported: { type: 'object', properties: { di_vp: supportedProofTypeConfiguration, jwt: supportedProofTypeConfiguration } } } }; } const openIdExchangeOptions = { title: 'OpenID Exchange options', type: 'object', additionalProperties: false, required: ['preAuthorizedCode', 'oauth2'], properties: { // deprecated; for backwards compatibility only, use // `supportedCredentialConfigurations` in each `issuerInstance` instead expectedCredentialRequests: { title: 'OpenID Expected Credential Requests', type: 'array', minItems: 1, items: { ...credentialConfiguration(), // only `credential_definition` is required here as `format` is // auto-populated in backwards compatibility mode required: ['credential_definition'] } }, preAuthorizedCode: {type: 'string'}, oauth2: { title: 'OpenID Exchange OAuth2 Options', type: 'object', additionalProperties: false, oneOf: [ {required: ['keyPair']}, {required: ['generateKeyPair']} ], properties: { generateKeyPair: { type: 'object', additionalProperties: false, required: ['algorithm'], properties: { algorithm: { enum: ['EdDSA', 'ES256', 'ES256K', 'ES384'] } } }, keyPair: jwkKeyPair, maxClockSkew: {type: 'number'} } } } }; export function createExchangeBody() { return { title: 'Create Exchange', type: 'object', additionalProperties: false, // optionally use either `expires` or `ttl`, but NOT both not: {required: ['ttl', 'expires']}, properties: { ttl: {type: 'number'}, expires: schemas.w3cDateTime(), variables: { type: 'object', additionalProperties: true }, openId: openIdExchangeOptions } }; } const typedTemplate = { title: 'Typed Template', type: 'object', required: ['type', 'template'], additionalProperties: false, properties: { id: {type: 'string'}, type: { type: 'string', enum: ['jsonata'] }, template: {type: 'string'} } }; export function credentialTemplates() { return { title: 'Credential Templates', type: 'array', minItems: 1, items: typedTemplate }; } // to be updated in specific locations with `properties` and `required` const zcapReferenceIds = { title: 'Authorization Capability Reference IDs', type: 'object', additionalProperties: false }; const vcFormats = { title: 'Verifiable Credential formats', type: 'array', minItems: 1, items: { type: 'string' } }; const vcMediaTypes = { title: 'Verifiable Credential Media Types', type: 'array', minItems: 1, items: { type: 'string' } }; const issuerInstance = { title: 'Issuer Instance', type: 'object', // use only one of: `supportedMediaTypes` (preferred) or // `supportedFormats` (deprecated) oneOf: [ {required: ['supportedMediaTypes', 'zcapReferenceIds']}, {required: ['supportedFormats', 'zcapReferenceIds']} ], additionalProperties: false, properties: { id: {type: 'string'}, oid4vci: { type: 'object', required: ['supportedCredentialConfigurations'], additionalProperties: false, properties: { supportedCredentialConfigurations: { type: 'object', additionalProperties: false, patternProperties: { '^.*$': credentialConfiguration() } } } }, supportedFormats: vcFormats, supportedMediaTypes: vcMediaTypes, zcapReferenceIds: { ...zcapReferenceIds, required: ['issue'], properties: { issue: {type: 'string'} } } } }; export function issuerInstances() { return { title: 'Issuer Instances', type: 'array', minItems: 1, maxItems: MAX_ISSUER_INSTANCES, items: issuerInstance }; } const issueRequestParameters = { title: 'Issue Request Parameters', type: 'object', oneOf: [{ required: ['credentialTemplateId'] }, { required: ['credentialTemplateIndex'] }], additionalProperties: false, properties: { // optionally explicitly reference an issuer instance to use issuerInstanceId: {type: 'string'}, credentialTemplateId: {type: 'string'}, credentialTemplateIndex: {type: 'number'}, oid4vci: { type: 'object', required: ['credentialConfigurationId'], additionalProperties: false, properties: { credentialConfigurationId: {type: 'string'} } }, // optional specify where to store the issued VCs instead of automatically // including it in a VP to be returned to the client result: {type: 'string'}, // optionally specify different variables variables: { oneOf: [{type: 'string'}, {type: 'object'}] } } }; export function inviteResponseBody() { return { title: 'Invite Response', type: 'object', additionalProperties: false, required: ['url', 'purpose'], properties: { url: {type: 'string'}, purpose: {type: 'string'}, referenceId: {type: 'string'} } }; } const oid4vpClientProfile = { title: 'OID4VP Client Profile', type: 'object', additionalProperties: false, // an authorization request or a directive to create one can be used, // but not both oneOf: [{ required: ['createAuthorizationRequest'], // cannot also use `authorizationRequest` not: { required: ['authorizationRequest'] } }, { required: ['authorizationRequest'], // cannot also use `createAuthorizationRequest` not: { required: ['createAuthorizationRequest'] } }], properties: { // value is name of variable to store the created authz request in createAuthorizationRequest: { type: 'string' }, // ... or full authz request to use authorizationRequest: { type: 'object' }, // optional properties that will be used as overrides in any authz request client_id: {type: 'string'}, client_id_scheme: {type: 'string'}, client_metadata: {type: 'object'}, dcql_query: {type: 'object'}, expected_origins: { type: 'array', minItems: 1, items: { type: 'string' } }, nonce: {type: 'string'}, presentation_definition: {type: 'object'}, response_mode: {type: 'string'}, response_uri: {type: 'string'}, response_uri_method: { type: 'string', enum: ['get', 'post'] }, // optional parameters for signing authorization requests authorizationRequestSigningParameters: { type: 'object', required: ['x5c'], additionalProperties: false, properties: { x5c: { type: 'array', minItems: 1, items: { type: 'string' } } } }, // optional protocol URL parameters protocolUrlParameters: { type: 'object', required: ['name', 'scheme'], additionalProperties: false, properties: { name: { type: 'string' }, scheme: { type: 'string' }, version: { type: 'string', enum: ['OID4VP-draft18', 'OID4VP-1.0'] } } }, // optional references to any zcaps for any purpose zcapReferenceIds: { ...zcapReferenceIds, required: ['signAuthorizationRequest'], properties: { signAuthorizationRequest: { type: 'string' } } } } }; export function oid4vpClientProfiles() { return { title: 'OID4VP Client Profiles', type: 'object', additionalProperties: false, patternProperties: { '^.*$': oid4vpClientProfile } }; } function computedStep() { return { title: 'Computed Exchange Step', type: 'object', additionalProperties: false, properties: { allowUnprotectedPresentation: {type: 'boolean'}, callback: { type: 'object', required: ['url'], additionalProperties: false, properties: { url: {type: 'string'} } }, createChallenge: {type: 'boolean'}, // issue request parameters for VCs that are to be issued and delivered // during this step issueRequests: { type: 'array', minItems: 0, items: issueRequestParameters }, jwtDidProofRequest: { type: 'object', additionalProperties: false, properties: { acceptedMethods: { title: 'Accepted DID Methods', type: 'array', minItems: 1, items: { title: 'Accepted DID Method', type: 'object', additionalProperties: false, properties: { method: {type: 'string'} } } }, allowedAlgorithms: { title: 'Allowed JWT Algorithms', type: 'array', minItems: 1, items: {type: 'string'} } } }, divpDidProofRequest: { type: 'object', additionalProperties: false, properties: { acceptedMethods: { title: 'Accepted DID Methods', type: 'array', minItems: 1, items: { title: 'Accepted DID Method', type: 'object', additionalProperties: false, properties: { method: {type: 'string'} } } }, allowedCryptosuites: { title: 'Allowed DI Cryptosuites', type: 'array', minItems: 1, items: {type: 'string'} } } }, nextStep: {type: 'string'}, // required to support OID4VP openId: { // either a single top-level client profile is specified here or // `clientProfiles` is specified with nested client profiles oneOf: [{ oid4vpClientProfile }, { type: 'object', required: ['clientProfiles'], additionalProperties: false, properties: { clientProfiles: oid4vpClientProfiles() } }] }, presentationSchema: { type: 'object', required: ['type', 'jsonSchema'], additionalProperties: false, properties: { type: {type: 'string'}, jsonSchema: {type: 'object'} } }, redirectUrl: {type: 'string'}, // the base verifiable presentation to use in this step; any VCs that // are issued in this step (see: `issueRequests`) will be added to this // VP, in which case any proofs on it will be invalidated; VCs that were // previously issued but are to be delivered during this step can be // present in `verifiablePresentation.verifiableCredential` and any newly // issued ones will be appended to that field; if not present and any // VCs are to be issued, this will be auto-generated verifiablePresentation: verifiablePresentation(), verifiablePresentationRequest: { type: 'object' }, verifyPresentationOptions: { type: 'object', properties: { checks: { type: 'object' } }, additionalProperties: true }, verifyPresentationResultSchema: { type: 'object', required: ['type', 'jsonSchema'], additionalProperties: false, properties: { type: {type: 'string'}, jsonSchema: {type: 'object'} } } } }; } function templatedStep() { return { title: 'Templated Exchange Step', type: 'object', minProperties: 1, additionalProperties: false, required: ['stepTemplate'], properties: { stepTemplate: typedTemplate, } }; } function step() { return { title: 'Exchange Step', // step can either use a template so it will be generated using variables // associated with the exchange, or static values can be provided that // would be the same as those computed from a template oneOf: [templatedStep(), computedStep()] }; } export function steps() { return { title: 'Exchange Steps', type: 'object', additionalProperties: false, patternProperties: {'^.*$': step()} }; } export function initialStep() { return { title: 'Initial Exchange Step', type: 'string' }; } export function useExchangeBody() { return { title: 'Use Exchange', type: 'object', additionalProperties: false, properties: { referenceId: {type: 'string'}, verifiablePresentationRequest: {type: 'object'}, verifiablePresentation: { anyOf: [ envelopedVerifiablePresentation, verifiablePresentation() ] } } }; } function openIdCredentialRequestDraft13() { return { title: 'OID4VCI-draft13 Credential Request', type: 'object', additionalProperties: false, required: ['credential_definition', 'format'], properties: { credential_definition: credentialDefinition, format: { type: 'string', enum: ['di_vc', 'ldp_vc', 'jwt_vc_json-ld', 'jwt_vc_json'] }, did: {type: 'string'}, proof: { title: 'DID Authn Proof JWT', type: 'object', additionalProperties: false, anyOf: [ {required: ['proof_type', 'jwt']}, {required: ['proof_type', 'di_vp']} ], properties: { proof_type: { type: 'string', enum: ['jwt', 'di_vp'] }, jwt: {type: 'string'}, di_vp: verifiablePresentation() } } } }; } function openIdCredentialRequestVersion1() { return { title: 'OID4VCI-1.0 Credential Request', type: 'object', additionalProperties: false, // `credential_configuration_id` is for scope-identified credentials, // which is not supported required: ['credential_identifier'], properties: { credential_identifier: {type: 'string'}, proofs: { type: 'object', additionalProperties: false, anyOf: [ {required: ['jwt']}, {required: ['di_vp']} ], properties: { jwt: { type: 'array', minItems: 1, items: {type: 'string'} }, di_vp: { type: 'array', minItems: 1, items: verifiablePresentation() } } } } }; } function openIdCredentialRequest() { return { title: 'OID4VCI Credential Request', oneOf: [ openIdCredentialRequestVersion1(), openIdCredentialRequestDraft13() ] }; } export const openIdCredentialBody = openIdCredentialRequest; export function openIdBatchCredentialBody() { return { title: 'OID4VCI-draft13 Batch Credential Request', type: 'object', additionalProperties: false, required: ['credential_requests'], properties: { credential_requests: { title: 'OID4VCI-draft13 Credential Requests', type: 'array', minItems: 1, items: openIdCredentialRequestDraft13() } } }; } export function authorizationDetails() { return { title: 'Authorization Details Request', type: 'array', minItems: 1, items: { type: 'object', required: ['type', 'credential_configuration_id'], additionalProperties: false, properties: { type: {const: 'openid_credential'}, credential_configuration_id: {type: 'string'} } } }; } export function openIdTokenBody() { return { title: 'OpenID Token Request', type: 'object', additionalProperties: false, required: ['grant_type'], properties: { grant_type: { type: 'string' }, 'pre-authorized_code': { type: 'string' }, // expressed as JSON, must be parsed elsewhere authorization_details: { type: 'string' }, // FIXME: there is no implementation for using these fields yet: // user_pin: { // type: 'string' // }, // // params for `authorization_code` grant type // code: { // type: 'string' // }, // verifier: { // type: 'string' // }, // redirect_uri: { // type: 'string' // } } }; } const presentationDescriptor = { title: 'Presentation Submission Descriptor', type: 'object', additionalProperties: false, required: ['id', 'format', 'path'], properties: { id: { type: 'string' }, format: { type: 'string' }, path: { type: 'string' }, path_nested: { type: 'object' } } }; export function presentationSubmission() { return { title: 'Presentation Submission', type: 'object', additionalProperties: false, required: ['id', 'definition_id', 'descriptor_map'], properties: { id: { type: 'string' }, definition_id: { type: 'string' }, descriptor_map: { title: 'Presentation Submission Descriptor Map', type: 'array', minItems: 0, items: presentationDescriptor } } }; } export function openIdAuthorizationResponseBody() { return { title: 'OID4VP Authorization Response', type: 'object', additionalProperties: false, oneOf: [{ // for response_mode == 'direct_post' required: ['vp_token'], // cannot also use `response` not: { required: ['response'] } }, { // for response_mode == 'direct_post.jwt' required: ['response'], // cannot also use any other params not: { required: ['presentation_submission', 'vp_token', 'state'] } }], properties: { // is a JSON string in the x-www-form-urlencoded body presentation_submission: { type: 'string' }, // is a JSON-encoded string or object in the x-www-form-urlencoded body /* Note: This can also be a simple base64url string for backwards/forwards compatibility. While submitting VPs directly as JSON objects has never changed in the OID4* specs, submitting VPs that are wrapped in some envelope that is expressed as a string (e.g., a JWT) has changed back and forth throughout the draft history. Sometimes these vp_tokens are required to be JSON-encoded strings other times non-JSON strings, i.e., no "extra/JSON quotes" around the string value inside the x-www-form-urlencoded field value delimiting quotes. For example, both of these: `...&vp_token="non-string JSON"` `...&vp_token="\"JSON string\""` are accepted for these reasons. */ vp_token: { type: 'string' }, response: { // must be an encrypted JWT type: 'string' }, state: { type: 'string' } } }; }