UNPKG

@bedrock/vc-delivery

Version:
731 lines (698 loc) 16.2 kB
/*! * Copyright (c) 2022-2025 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' ] }; 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: { anyOf: [ verifiableCredential(), envelopedVerifiableCredential, { type: 'array', minItems: 1, items: { anyOf: [verifiableCredential(), envelopedVerifiableCredential] } } ] }, 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 expectedCredentialRequest = { type: 'object', additionalProperties: false, required: ['credential_definition'], properties: { credential_definition: credentialDefinition, format: { type: 'string', enum: ['di_vc', 'ldp_vc', 'jwt_vc_json-ld', 'jwt_vc_json'] } } }; const openIdExchangeOptions = { title: 'OpenID Exchange options', type: 'object', additionalProperties: false, required: ['expectedCredentialRequests', 'preAuthorizedCode', 'oauth2'], properties: { expectedCredentialRequests: { title: 'OpenID Expected Credential Requests', type: 'array', minItems: 1, items: expectedCredentialRequest }, 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: { type: 'object', additionalProperties: false, required: ['privateKeyJwk', 'publicKeyJwk'], properties: { privateKeyJwk: { type: 'object' }, publicKeyJwk: { type: 'object' } } }, maxClockSkew: { type: 'number' } } } } }; export const createExchangeBody = { title: 'Create Exchange', type: 'object', additionalProperties: false, properties: { ttl: { type: 'number' }, 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 const credentialTemplates = { 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 issuerInstance = { title: 'Issuer Instance', type: 'object', required: ['supportedFormats', 'zcapReferenceIds'], additionalProperties: false, properties: { id: { type: 'string' }, supportedFormats: vcFormats, zcapReferenceIds: { ...zcapReferenceIds, required: ['issue'], properties: { issue: { type: 'string' } } } } }; export const issuerInstances = { 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: { credentialTemplateId: { type: 'string' }, credentialTemplateIndex: { type: 'number' }, // optionally specify different variables variables: { oneOf: [{type: 'string'}, {type: 'object'}] } } }; const step = { title: 'Exchange Step', type: 'object', minProperties: 1, additionalProperties: false, // step can either use a template so it will be generated using variables // associated with the exchange, or static values can be provided oneOf: [{ // `stepTemplate` must be present and nothing else required: ['stepTemplate'], not: { required: [ 'allowUnprotectedPresentation', 'callback', 'createChallenge', 'issueRequests', 'jwtDidProofRequest', 'nextStep', 'openId', 'presentationSchema', 'verifiablePresentationRequest' ] } }, { // anything except `stepTemplate` can be used not: { required: ['stepTemplate'] } }], properties: { allowUnprotectedPresentation: { type: 'boolean' }, callback: { type: 'object', required: ['url'], additionalProperties: false, properties: { url: { type: 'string' } } }, createChallenge: { type: 'boolean' }, 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' } } } }, nextStep: { type: 'string' }, // required to support OID4VP (but can be provided by step template instead) openId: { 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' } } }, presentationSchema: { type: 'object', required: ['type', 'jsonSchema'], additionalProperties: false, properties: { type: { type: 'string' }, jsonSchema: { type: 'object' } } }, stepTemplate: typedTemplate, 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' } } } } }; export const steps = { title: 'Exchange Steps', type: 'object', additionalProperties: false, patternProperties: { '^.*$': step } }; export const initialStep = { title: 'Initial Exchange Step', type: 'string' }; export function useExchangeBody() { return { title: 'Use Exchange', type: 'object', additionalProperties: false, properties: { verifiablePresentation: { anyOf: [ envelopedVerifiablePresentation, verifiablePresentation() ] } } }; } const openIdCredentialRequest = { title: 'OpenID 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, required: ['proof_type', 'jwt'], properties: { proof_type: { type: 'string', enum: ['jwt'] }, jwt: { type: 'string' } } } } }; export const openIdCredentialBody = openIdCredentialRequest; export const openIdBatchCredentialBody = { title: 'OpenID Batch Credential Request', type: 'object', additionalProperties: false, required: ['credential_requests'], properties: { credential_requests: { title: 'OpenID Credential Requests', type: 'array', minItems: 1, items: openIdCredentialRequest } } }; export const openIdTokenBody = { title: 'OpenID Token Request', type: 'object', additionalProperties: false, required: ['grant_type'], properties: { grant_type: { type: 'string' }, 'pre-authorized_code': { 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 const presentationSubmission = { 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, required: ['presentation_submission', 'vp_token'], 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' }, state: { type: 'string' } } }; }