@sphereon/oid4vci-issuer
Version:
OpenID 4 Verifiable Credential Issuance issuer REST endpoints
660 lines • 39.5 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.VcIssuer = void 0;
const oid4vc_common_1 = require("@sphereon/oid4vc-common");
const oid4vci_common_1 = require("@sphereon/oid4vci-common");
const ssi_types_1 = require("@sphereon/ssi-types");
const short_uuid_1 = __importDefault(require("short-uuid"));
const functions_1 = require("./functions");
const state_manager_1 = require("./state-manager");
const index_1 = require("./index");
const shortUUID = (0, short_uuid_1.default)();
class VcIssuer {
constructor(issuerMetadata, authorizationServerMetadata, args) {
var _a, _b, _c;
this._issuerMetadata = issuerMetadata;
this._authorizationServerMetadata = authorizationServerMetadata;
this._defaultCredentialOfferBaseUri = args.defaultCredentialOfferBaseUri;
this._credentialOfferSessions = (_a = args.credentialOfferSessions) !== null && _a !== void 0 ? _a : new state_manager_1.MemoryStates();
this._uris = (_b = args.uris) !== null && _b !== void 0 ? _b : new state_manager_1.MemoryStates();
this._cNonces = args.cNonces;
this._credentialSignerCallback = args === null || args === void 0 ? void 0 : args.credentialSignerCallback;
this._jwtVerifyCallback = args === null || args === void 0 ? void 0 : args.jwtVerifyCallback;
this._credentialDataSupplier = args === null || args === void 0 ? void 0 : args.credentialDataSupplier;
this._cNonceExpiresIn = ((_c = args === null || args === void 0 ? void 0 : args.cNonceExpiresIn) !== null && _c !== void 0 ? _c : (process.env.C_NONCE_EXPIRES_IN ? parseInt(process.env.C_NONCE_EXPIRES_IN) : 300));
this._asClientOpts = args === null || args === void 0 ? void 0 : args.asClientOpts;
}
getCredentialOfferSessionById(id_1) {
return __awaiter(this, arguments, void 0, function* (id, lookups = ['preAuthorizedCode', 'issuerState', 'correlationId']) {
// preAuth and issuerState can be looked up directly
if (Array.isArray(lookups) && lookups.length > 0) {
if (!this.uris) {
return Promise.reject(Error('Cannot lookup credential offer by id if URI state manager is not set'));
}
return (0, state_manager_1.lookupStateManagerMultiGetAsserted)({
id,
keyValueMapper: this._uris,
valueStateManager: this._credentialOfferSessions,
lookups: ['preAuthorizedCode', 'issuerState', 'correlationId'],
});
// return new LookupStateManager<URIState, CredentialOfferSession>(this.uris, this._credentialOfferSessions, lookup).getFromMultiple(id)
}
const session = yield this._credentialOfferSessions.get(id);
if (!session) {
return Promise.reject(Error(`No session found for id ${id}`));
}
return session;
});
}
deleteCredentialOfferSessionById(id_1) {
return __awaiter(this, arguments, void 0, function* (id, lookups = ['preAuthorizedCode', 'issuerState']) {
const session = yield this.getCredentialOfferSessionById(id, lookups);
if (session) {
if (session.preAuthorizedCode && (yield this._credentialOfferSessions.has(session.preAuthorizedCode))) {
yield this._credentialOfferSessions.delete(session.preAuthorizedCode);
}
if (session.issuerState && (yield this._credentialOfferSessions.has(session.issuerState))) {
yield this._credentialOfferSessions.delete(session.issuerState);
}
}
return session;
});
}
processNotification(_a) {
return __awaiter(this, arguments, void 0, function* ({ preAuthorizedCode, issuerState, notification, }) {
const sessionId = preAuthorizedCode !== null && preAuthorizedCode !== void 0 ? preAuthorizedCode : issuerState;
const session = sessionId ? yield this.getCredentialOfferSessionById(sessionId) : undefined;
if (!session || !sessionId) {
index_1.LOG.error(`No session or session id found ${sessionId}`);
return Error('invalid_notification_request');
}
if (notification.notification_id !== session.notification_id) {
index_1.LOG.error(`Notification id ${notification.notification_id} not found in session. session notification id ${session.notification_id}`);
return Error('invalid_notification_id');
}
else if (session.notification) {
index_1.LOG.info(`Overwriting existing notification, as a new notification came in ${session.notification_id}`);
}
yield this.updateSession({ preAuthorizedCode: preAuthorizedCode, issuerState: issuerState, notification });
index_1.LOG.info(`Processed notification ${notification} for ${session.notification_id}`);
return session;
});
}
createCredentialOfferURI(opts) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
const { offerMode = 'VALUE', correlationId = shortUUID.generate(), credential_configuration_ids, statusListOpts, credentialOfferUri, redirectUri, } = opts;
if (offerMode === 'REFERENCE' && !credentialOfferUri) {
return Promise.reject(Error('credentialOfferUri must be supplied for offerMode REFERENCE!'));
}
const grants = opts.grants ? Object.assign({}, opts.grants) : {};
// for backwards compat, would be better if user sets the prop on the grants directly
if (opts.pinLength !== undefined) {
if (grants[oid4vci_common_1.PRE_AUTH_GRANT_LITERAL]) {
grants[oid4vci_common_1.PRE_AUTH_GRANT_LITERAL].tx_code = Object.assign(Object.assign({}, grants[oid4vci_common_1.PRE_AUTH_GRANT_LITERAL].tx_code), { length: (_b = (_a = grants[oid4vci_common_1.PRE_AUTH_GRANT_LITERAL].tx_code) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : opts.pinLength });
}
}
if (((_c = grants[oid4vci_common_1.PRE_AUTH_GRANT_LITERAL]) === null || _c === void 0 ? void 0 : _c.tx_code) && !((_e = (_d = grants[oid4vci_common_1.PRE_AUTH_GRANT_LITERAL]) === null || _d === void 0 ? void 0 : _d.tx_code) === null || _e === void 0 ? void 0 : _e.length)) {
grants[oid4vci_common_1.PRE_AUTH_GRANT_LITERAL].tx_code.length = 4;
}
const baseUri = (_f = opts === null || opts === void 0 ? void 0 : opts.baseUri) !== null && _f !== void 0 ? _f : this.defaultCredentialOfferBaseUri;
const credentialOfferObject = (0, functions_1.createCredentialOfferObject)(this._issuerMetadata, Object.assign(Object.assign({}, opts), { grants, credentialOffer: credential_configuration_ids
? {
credential_issuer: this._issuerMetadata.credential_issuer,
credential_configuration_ids,
}
: undefined }));
const preAuthGrant = (_g = credentialOfferObject.credential_offer.grants) === null || _g === void 0 ? void 0 : _g[oid4vci_common_1.PRE_AUTH_GRANT_LITERAL];
const authGrant = (_h = credentialOfferObject.credential_offer.grants) === null || _h === void 0 ? void 0 : _h.authorization_code;
const preAuthorizedCode = preAuthGrant === null || preAuthGrant === void 0 ? void 0 : preAuthGrant['pre-authorized_code'];
const issuerState = authGrant === null || authGrant === void 0 ? void 0 : authGrant.issuer_state;
const txCode = preAuthGrant === null || preAuthGrant === void 0 ? void 0 : preAuthGrant.tx_code;
let userPin;
if (preAuthGrant === null || preAuthGrant === void 0 ? void 0 : preAuthGrant.tx_code) {
const pinLength = (_j = preAuthGrant.tx_code.length) !== null && _j !== void 0 ? _j : 4;
userPin = ('' + Math.round((Math.pow(10, pinLength) - 1) * Math.random())).padStart(pinLength, '0');
(0, functions_1.assertValidPinNumber)(userPin, pinLength);
}
const createdAt = +new Date();
const lastUpdatedAt = createdAt;
const expirationInMs = ((_k = opts.sessionLifeTimeInSec) !== null && _k !== void 0 ? _k : 10 * 60) * 1000;
const expiresAt = createdAt + Math.abs(expirationInMs);
if (offerMode === 'REFERENCE') {
if (!this.uris) {
throw Error('No URI state manager set, whilst apparently credential offer by reference is being used');
}
const offerUri = (_l = opts.credentialOfferUri) === null || _l === void 0 ? void 0 : _l.replace(':id', correlationId); // TODO how is this going to work with auth code flow?
if (!offerUri) {
return Promise.reject(Error('credentialOfferUri must be supplied for offerMode REFERENCE!'));
}
credentialOfferObject.credential_offer_uri = offerUri;
yield this.uris.set(correlationId, {
uri: offerUri,
createdAt: createdAt,
expiresAt,
preAuthorizedCode,
issuerState,
correlationId: correlationId,
});
}
const credentialOffer = yield (0, oid4vci_common_1.toUniformCredentialOfferRequest)({
credential_offer: credentialOfferObject.credential_offer,
credential_offer_uri: credentialOfferObject.credential_offer_uri,
}, {
version: oid4vci_common_1.OpenId4VCIVersion.VER_1_0_13,
resolve: false, // We are creating the object, so do not resolve
});
const status = oid4vci_common_1.IssueStatus.OFFER_CREATED;
const session = Object.assign(Object.assign(Object.assign(Object.assign({ redirectUri,
preAuthorizedCode,
issuerState,
createdAt,
lastUpdatedAt,
expiresAt,
status, notification_id: (0, oid4vc_common_1.uuidv4)() }, (opts.client_id && { clientId: opts.client_id })), (userPin && { txCode: userPin })), (opts.credentialDataSupplierInput && { credentialDataSupplierInput: opts.credentialDataSupplierInput })), { credentialOffer, statusLists: statusListOpts });
const uri = (0, functions_1.createCredentialOfferURIFromObject)(credentialOffer, offerMode, Object.assign(Object.assign({}, opts), { baseUri }));
if (preAuthorizedCode) {
const lookupManager = new state_manager_1.LookupStateManager(this.uris, this._credentialOfferSessions, 'correlationId');
yield lookupManager.setMapped(preAuthorizedCode, { preAuthorizedCode, uri, createdAt, expiresAt, correlationId, issuerState }, session);
// await this.credentialOfferSessions.set(preAuthorizedCode, session)
}
// todo: check whether we could have the same value for issuer state and pre auth code if both are supported.
if (issuerState) {
const lookupManager = new state_manager_1.LookupStateManager(this.uris, this._credentialOfferSessions, 'correlationId');
yield lookupManager.setMapped(issuerState, { preAuthorizedCode, uri, createdAt, expiresAt, correlationId, issuerState }, session);
// await this.credentialOfferSessions.set(issuerState, session)
}
let qrCodeDataUri;
if (opts.qrCodeOpts) {
const { AwesomeQR } = yield Promise.resolve().then(() => __importStar(require('awesome-qr')));
const qrCode = new AwesomeQR(Object.assign(Object.assign({}, opts.qrCodeOpts), { text: uri }));
qrCodeDataUri = `data:image/png;base64,${(yield qrCode.draw()).toString('base64')}`;
}
const credentialOfferResult = Object.assign({ session,
uri,
qrCodeDataUri,
correlationId,
txCode }, (userPin !== undefined && { userPin, pinLength: (_m = userPin === null || userPin === void 0 ? void 0 : userPin.length) !== null && _m !== void 0 ? _m : 0 }));
oid4vci_common_1.EVENTS.emit(oid4vci_common_1.CredentialOfferEventNames.OID4VCI_OFFER_CREATED, {
eventName: oid4vci_common_1.CredentialOfferEventNames.OID4VCI_OFFER_CREATED,
id: correlationId,
data: credentialOfferResult,
initiator: '<Unknown>',
initiatorType: ssi_types_1.InitiatorType.EXTERNAL,
system: ssi_types_1.System.OID4VCI,
issuer: this.issuerMetadata.credential_issuer,
subsystem: ssi_types_1.SubSystem.API,
createdAt,
expiresAt,
});
return credentialOfferResult;
});
}
/**
* issueCredentialFromIssueRequest
* @param opts issuerRequestParams
* - issueCredentialsRequest the credential request
* - issuerState the state of the issuer
* - jwtVerifyCallback callback that verifies the Proof of Possession JWT
* - issuerCallback callback to issue a Verifiable Credential
* - cNonce an existing c_nonce
*/
issueCredential(opts) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b;
/*if (!('credential_identifier' in opts.credentialRequest)) {
throw new Error('credential request should be of spec version 1.0.13 or above')
}*/
const credentialRequest = opts.credentialRequest;
let preAuthorizedCode;
let issuerState;
try {
if (!('credential_identifier' in credentialRequest) && !credentialRequest.format) {
throw new Error('credential request should either have a credential_identifier or format and type');
}
if (credentialRequest.format && !this.isMetadataSupportCredentialRequestFormat(credentialRequest.format)) {
throw new Error(oid4vci_common_1.TokenErrorResponse.invalid_request);
}
const validated = yield this.validateCredentialRequestProof(Object.assign(Object.assign({}, opts), { tokenExpiresIn: (_a = opts.tokenExpiresIn) !== null && _a !== void 0 ? _a : 180 }));
preAuthorizedCode = validated.preAuthorizedCode;
issuerState = validated.issuerState;
const { preAuthSession, authSession, cNonceState, jwtVerifyResult } = validated;
const did = jwtVerifyResult.did;
const jwk = jwtVerifyResult.jwk;
const kid = jwtVerifyResult.kid;
const newcNonce = opts.newCNonce ? opts.newCNonce : (0, oid4vc_common_1.uuidv4)();
const newcNonceState = Object.assign(Object.assign({ cNonce: newcNonce, createdAt: +new Date() }, ((authSession === null || authSession === void 0 ? void 0 : authSession.issuerState) && { issuerState: authSession.issuerState })), (preAuthSession && { preAuthorizedCode: preAuthSession.preAuthorizedCode }));
yield this.cNonces.set(newcNonce, newcNonceState);
if (!opts.credential && this._credentialDataSupplier === undefined && opts.credentialDataSupplier === undefined) {
throw Error(`Either a credential needs to be supplied or a credentialDataSupplier`);
}
let credential;
let format = credentialRequest.format;
let signerCallback = opts.credentialSignerCallback;
const session = preAuthorizedCode && preAuthSession ? preAuthSession : authSession;
if (opts.credential) {
credential = opts.credential;
}
else {
const credentialDataSupplier = typeof opts.credentialDataSupplier === 'function' ? opts.credentialDataSupplier : this._credentialDataSupplier;
if (typeof credentialDataSupplier !== 'function') {
throw Error('Data supplier is mandatory if no credential is supplied');
}
if (!session) {
throw Error('Either a preAuth or Auth session is required, none found');
}
const credentialOffer = session.credentialOffer;
if (!credentialOffer) {
throw Error('Credential Offer missing');
}
const credentialDataSupplierInput = (_b = opts.credentialDataSupplierInput) !== null && _b !== void 0 ? _b : session.credentialDataSupplierInput;
const result = yield credentialDataSupplier(Object.assign(Object.assign(Object.assign({}, (cNonceState ? Object.assign({}, cNonceState) : Object.assign({}, authSession))), { credentialRequest: opts.credentialRequest, credentialSupplierConfig: this._issuerMetadata.credential_supplier_config, credentialOffer /*todo: clientId: */ }), (credentialDataSupplierInput && { credentialDataSupplierInput })));
credential = result.credential;
if (result.format) {
format = result.format;
}
if (typeof result.signCallback === 'function') {
signerCallback = result.signCallback;
}
}
if (!credential) {
throw Error('A credential needs to be supplied at this point');
}
// Bind credential to the provided proof of possession
if (ssi_types_1.CredentialMapper.isSdJwtDecodedCredentialPayload(credential) && (kid || jwk) && !credential.cnf) {
if (kid) {
credential.cnf = {
kid,
};
}
// else TODO temp workaround IATAB2B-57
if (jwk) {
credential.cnf = {
jwk,
};
}
}
else if (did && !ssi_types_1.CredentialMapper.isSdJwtDecodedCredentialPayload(credential) && credential.credentialSubject !== undefined) {
const credentialSubjects = Array.isArray(credential.credentialSubject) ? credential.credentialSubject : [credential.credentialSubject];
credentialSubjects.map((subject) => {
if (!subject.id) {
subject.id = did;
}
return subject;
});
credential.credentialSubject = Array.isArray(credential.credentialSubject) ? credentialSubjects : credentialSubjects[0];
}
else {
// Mdoc Format
// Nothing to do here
}
let issuer = undefined;
if (credential.iss) {
issuer = credential.iss;
}
else if (credential.issuer) {
if (typeof credential.issuer === 'string') {
issuer = credential.issuer;
}
else if (typeof credential.issuer === 'object' && 'id' in credential.issuer && typeof credential.issuer.id === 'string') {
issuer = credential.issuer.id;
}
}
const verifiableCredential = yield this.issueCredentialImpl(Object.assign({ credentialRequest: opts.credentialRequest, format,
credential,
jwtVerifyResult,
issuer }, (session && { statusLists: session.statusLists })), signerCallback);
// TODO implement acceptance_token (deferred response)
// TODO update verification accordingly
if (!verifiableCredential) {
// credential: OPTIONAL. Contains issued Credential. MUST be present when acceptance_token is not returned. MAY be a JSON string or a JSON object, depending on the Credential format. See Appendix E for the Credential format specific encoding requirements
throw new Error(oid4vci_common_1.CREDENTIAL_MISSING_ERROR);
}
if (cNonceState) {
// remove the previous nonce
yield this.cNonces.delete(cNonceState.cNonce);
}
let notification_id;
if (preAuthorizedCode && preAuthSession) {
preAuthSession.lastUpdatedAt = +new Date();
preAuthSession.status = oid4vci_common_1.IssueStatus.CREDENTIAL_ISSUED;
notification_id = preAuthSession.notification_id;
yield this._credentialOfferSessions.set(preAuthorizedCode, preAuthSession);
}
else if (issuerState && authSession) {
// If both were set we used the pre auth flow above as well, hence the else if
authSession.lastUpdatedAt = +new Date();
authSession.status = oid4vci_common_1.IssueStatus.CREDENTIAL_ISSUED;
notification_id = authSession.notification_id;
yield this._credentialOfferSessions.set(issuerState, authSession);
}
const response = Object.assign({ credential: verifiableCredential,
// format: credentialRequest.format,
c_nonce: newcNonce, c_nonce_expires_in: this._cNonceExpiresIn }, (notification_id && { notification_id }));
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const experimentalSubjectIssuance = opts.credentialRequest.credential_subject_issuance;
if (experimentalSubjectIssuance === null || experimentalSubjectIssuance === void 0 ? void 0 : experimentalSubjectIssuance.subject_proof_mode) {
if (experimentalSubjectIssuance.subject_proof_mode !== 'proof_replace') {
throw Error('Only proof replace is supported currently');
}
response.transaction_id = authSession === null || authSession === void 0 ? void 0 : authSession.issuerState;
response.credential_subject_issuance = experimentalSubjectIssuance;
}
return response;
}
catch (error) {
yield this.updateSession({ preAuthorizedCode, issuerState, error });
throw error;
}
});
}
updateSession(_a) {
return __awaiter(this, arguments, void 0, function* ({ preAuthorizedCode, error, issuerState, notification, }) {
let issueState = undefined;
if (error) {
issueState = oid4vci_common_1.IssueStatus.ERROR;
}
else if (notification) {
if (notification.event == 'credential_accepted') {
issueState = oid4vci_common_1.IssueStatus.NOTIFICATION_CREDENTIAL_ACCEPTED;
}
else if (notification.event == 'credential_deleted') {
issueState = oid4vci_common_1.IssueStatus.NOTIFICATION_CREDENTIAL_DELETED;
}
else if (notification.event == 'credential_failure') {
issueState = oid4vci_common_1.IssueStatus.NOTIFICATION_CREDENTIAL_FAILURE;
}
}
if (preAuthorizedCode) {
const preAuthSession = yield this._credentialOfferSessions.get(preAuthorizedCode);
if (preAuthSession) {
preAuthSession.lastUpdatedAt = +new Date();
if (issueState) {
preAuthSession.status = issueState;
}
if (error) {
preAuthSession.error = error instanceof Error ? error.message : error === null || error === void 0 ? void 0 : error.toString();
}
preAuthSession.notification_id;
if (notification) {
preAuthSession.notification = notification;
}
yield this._credentialOfferSessions.set(preAuthorizedCode, preAuthSession);
}
}
if (issuerState) {
const authSession = yield this._credentialOfferSessions.get(issuerState);
if (authSession) {
authSession.lastUpdatedAt = +new Date();
if (issueState) {
authSession.status = issueState;
}
if (error) {
authSession.error = error instanceof Error ? error.message : error === null || error === void 0 ? void 0 : error.toString();
}
if (notification) {
authSession.notification = notification;
}
yield this._credentialOfferSessions.set(issuerState, authSession);
}
}
});
}
/*
private async retrieveGrantsAndCredentialOfferSession(id: string): Promise<{
clientId?: string;
grants?: Grant,
session: CredentialOfferSession
}> {
const session: CredentialOfferSession | undefined = await this._credentialOfferSessions.getAsserted(id)
const clientId = session?.clientId
const grants = session?.credentialOffer?.credential_offer?.grants
if (!grants?.authorization_code?.issuer_state && !grants?.[PRE_AUTH_GRANT_LITERAL]?.[PRE_AUTH_CODE_LITERAL]) {
throw new Error(GRANTS_MUST_NOT_BE_UNDEFINED)
}
return { session, clientId, grants }
}*/
validateCredentialRequestProof(_a) {
return __awaiter(this, arguments, void 0, function* ({ credentialRequest, jwtVerifyCallback, tokenExpiresIn, }) {
var _b, _c, _d, _e, _f, _g;
let preAuthorizedCode;
let issuerState;
const supportedIssuanceFormats = ['jwt_vc_json', 'jwt_vc_json-ld', 'vc+sd-jwt', 'ldp_vc', 'mso_mdoc'];
try {
if (credentialRequest.format && !supportedIssuanceFormats.includes(credentialRequest.format)) {
throw Error(`Format ${credentialRequest.format} not supported yet`);
}
else if (typeof this._jwtVerifyCallback !== 'function' && typeof jwtVerifyCallback !== 'function') {
throw new Error(oid4vci_common_1.JWT_VERIFY_CONFIG_ERROR);
}
else if (!credentialRequest.proof) {
throw Error('Proof of possession is required. No proof value present in credential request');
}
const jwtVerifyResult = jwtVerifyCallback
? yield jwtVerifyCallback(credentialRequest.proof)
: // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
yield this._jwtVerifyCallback(credentialRequest.proof);
const { didDocument, did, jwt } = jwtVerifyResult;
const { header, payload } = jwt;
const { iss, aud, iat, nonce } = payload;
const issuer_state = 'issuer_state' in credentialRequest && credentialRequest.issuer_state ? credentialRequest.issuer_state : undefined;
if (!nonce && !issuer_state) {
throw Error('No nonce was found in the Proof of Possession');
}
let createdAt;
let cNonceState;
if (nonce) {
cNonceState = yield this.cNonces.getAsserted(nonce);
preAuthorizedCode = cNonceState.preAuthorizedCode;
issuerState = cNonceState.issuerState;
createdAt = cNonceState.createdAt;
}
else if (issuer_state) {
const session = yield this._credentialOfferSessions.getAsserted(issuer_state);
issuerState = issuer_state;
createdAt = session.createdAt;
}
else {
throw Error('No nonce or issuer_state was found in the Proof of Possession');
}
// The verify callback should set the correct values, but let's look at the JWT ourselves to to be sure
const alg = (_b = jwtVerifyResult.alg) !== null && _b !== void 0 ? _b : header.alg;
const kid = (_c = jwtVerifyResult.kid) !== null && _c !== void 0 ? _c : header.kid;
const jwk = (_d = jwtVerifyResult.jwk) !== null && _d !== void 0 ? _d : header.jwk;
const x5c = (_e = jwtVerifyResult.x5c) !== null && _e !== void 0 ? _e : header.x5c;
const typ = header.typ;
if (typ !== 'openid4vci-proof+jwt') {
throw Error(oid4vci_common_1.TYP_ERROR);
}
else if (!alg) {
throw Error(oid4vci_common_1.ALG_ERROR);
}
else if (x5c && (kid || jwk)) {
// x5c cannot be used together with kid or jwk
throw Error(oid4vci_common_1.KID_JWK_X5C_ERROR);
}
else if (kid && !did) {
if (!jwk && !x5c) {
// Make sure the callback function extracts the DID from the kid
throw Error(oid4vci_common_1.KID_DID_NO_DID_ERROR);
}
else {
// If JWK or x5c is present, log the information and proceed
console.log(`KID present but no DID, using JWK or x5c`);
}
}
else if (did && !didDocument) {
// Make sure the callback function does DID resolution when a did is present
throw Error(oid4vci_common_1.DID_NO_DIDDOC_ERROR);
}
const preAuthSession = preAuthorizedCode ? yield this.credentialOfferSessions.get(preAuthorizedCode) : undefined;
const authSession = issuerState ? yield this.credentialOfferSessions.get(issuerState) : undefined;
if (!preAuthSession && !authSession) {
throw Error('Either a pre-authorized code or issuer state needs to be present');
}
if (preAuthSession) {
if (!preAuthSession.preAuthorizedCode || preAuthSession.preAuthorizedCode !== preAuthorizedCode) {
throw Error('Invalid pre-authorized code');
}
preAuthSession.lastUpdatedAt = +new Date();
preAuthSession.status = oid4vci_common_1.IssueStatus.CREDENTIAL_REQUEST_RECEIVED;
yield this._credentialOfferSessions.set(preAuthorizedCode, preAuthSession);
}
if (authSession) {
if (!authSession.issuerState || authSession.issuerState !== issuerState) {
throw Error('Invalid issuer state');
}
authSession.lastUpdatedAt = +new Date();
authSession.status = oid4vci_common_1.IssueStatus.CREDENTIAL_REQUEST_RECEIVED;
}
// https://www.rfc-editor.org/rfc/rfc6749.html#section-3.2.1
// A client MAY use the "client_id" request parameter to identify itself
// when sending requests to the token endpoint. In the
// "authorization_code" "grant_type" request to the token endpoint, an
// unauthenticated client MUST send its "client_id" to prevent itself
// from inadvertently accepting a code intended for a client with a
// different "client_id". This protects the client from substitution of
// the authentication code. (It provides no additional security for the
// protected resource.)
if (!iss && ((_g = (_f = authSession === null || authSession === void 0 ? void 0 : authSession.credentialOffer.credential_offer) === null || _f === void 0 ? void 0 : _f.grants) === null || _g === void 0 ? void 0 : _g.authorization_code)) {
throw new Error(oid4vci_common_1.NO_ISS_IN_AUTHORIZATION_CODE_CONTEXT);
}
// iss: OPTIONAL (string). The value of this claim MUST be the client_id of the client making the credential request.
// This claim MUST be omitted if the Access Token authorizing the issuance call was obtained from a Pre-Authorized Code Flow through anonymous access to the Token Endpoint.
// TODO We need to investigate further what the comment above means, because it's not clear if the client or the user may be authorized anonymously
// if (iss && grants && grants[PRE_AUTH_GRANT_LITERAL]) {
// throw new Error(ISS_PRESENT_IN_PRE_AUTHORIZED_CODE_CONTEXT)
// }
/*if (iss && iss !== clientId) {
throw new Error(ISS_MUST_BE_CLIENT_ID + `iss: ${iss}, client_id: ${clientId}`)
}*/
if (!aud || aud !== this._issuerMetadata.credential_issuer) {
throw new Error(oid4vci_common_1.AUD_ERROR);
}
if (!iat) {
throw new Error(oid4vci_common_1.IAT_ERROR);
}
else if (iat > Math.round(createdAt / 1000) + tokenExpiresIn) {
// createdAt is in milliseconds whilst iat and tokenExpiresIn are in seconds
throw new Error(oid4vci_common_1.IAT_ERROR);
}
// todo: Add a check of iat against current TS on server with a skew
return { jwtVerifyResult, preAuthorizedCode, preAuthSession, issuerState, authSession, cNonceState };
}
catch (error) {
yield this.updateSession({ preAuthorizedCode, issuerState, error });
throw error;
}
});
}
isMetadataSupportCredentialRequestFormat(requestFormat) {
if (!this._issuerMetadata.credential_configurations_supported) {
return false;
}
for (const credentialSupported of Object.values(this._issuerMetadata['credential_configurations_supported'])) {
if (!Array.isArray(requestFormat) && credentialSupported.format === requestFormat) {
return true;
}
else if (Array.isArray(requestFormat)) {
for (const format of requestFormat) {
if (credentialSupported.format === format) {
return true;
}
}
}
}
return false;
}
issueCredentialImpl(opts, issuerCallback) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
if ((!opts.credential && !opts.credentialRequest) || !this._credentialSignerCallback) {
throw new Error(oid4vci_common_1.ISSUER_CONFIG_ERROR);
}
const credential = issuerCallback ? yield issuerCallback(opts) : yield this._credentialSignerCallback(opts);
// TODO: Create builder
oid4vci_common_1.EVENTS.emit(oid4vci_common_1.CredentialEventNames.OID4VCI_CREDENTIAL_ISSUED, {
eventName: oid4vci_common_1.CredentialEventNames.OID4VCI_CREDENTIAL_ISSUED,
id: (0, oid4vc_common_1.uuidv4)(),
data: credential,
// TODO: Format, request etc
initiator: (_a = opts.issuer) !== null && _a !== void 0 ? _a : '<unknown>',
initiatorType: ssi_types_1.InitiatorType.EXTERNAL,
system: ssi_types_1.System.OID4VCI,
subsystem: ssi_types_1.SubSystem.VC_ISSUER,
});
return credential;
});
}
get credentialSignerCallback() {
return this._credentialSignerCallback;
}
get jwtVerifyCallback() {
return this._jwtVerifyCallback;
}
get credentialDataSupplier() {
return this._credentialDataSupplier;
}
get uris() {
return this._uris;
}
get cNonceExpiresIn() {
return this._cNonceExpiresIn;
}
get credentialOfferSessions() {
return this._credentialOfferSessions;
}
get cNonces() {
return this._cNonces;
}
get defaultCredentialOfferBaseUri() {
return this._defaultCredentialOfferBaseUri;
}
get issuerMetadata() {
return this._issuerMetadata;
}
get authorizationServerMetadata() {
return this._authorizationServerMetadata;
}
get asClientOpts() {
return this._asClientOpts;
}
}
exports.VcIssuer = VcIssuer;
//# sourceMappingURL=VcIssuer.js.map