@bedrock/vc-delivery
Version:
Bedrock Verifiable Credential Delivery
983 lines (938 loc) • 24.4 kB
JavaScript
/*!
* 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'
}
}
};
}