@sphereon/oid4vci-common
Version:
OpenID 4 Verifiable Credential Issuance Common Types
440 lines • 21.7 kB
JavaScript
;
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.getTypesFromOfferV1_0_11 = exports.determineGrantTypes = exports.getCredentialOfferPayload = exports.determineFlowType = exports.toUniformCredentialOfferPayload = exports.resolveCredentialOfferURI = exports.assertedUniformCredentialOffer = exports.isPreAuthCode = exports.toUniformCredentialOfferRequest = exports.isCredentialOfferVersion = exports.determineSpecVersionFromOffer = exports.getStateFromCredentialOfferPayload = exports.getClientIdFromCredentialOfferPayload = exports.getIssuerFromCredentialOfferPayload = exports.getScheme = exports.determineSpecVersionFromScheme = exports.determineSpecVersionFromURI = void 0;
const debug_1 = __importDefault(require("debug"));
const jwt_decode_1 = require("jwt-decode");
const index_1 = require("../index");
const types_1 = require("../types");
const HttpUtils_1 = require("./HttpUtils");
const debug = (0, debug_1.default)('sphereon:oid4vci:offer');
function determineSpecVersionFromURI(uri) {
var _a;
let version = (_a = determineSpecVersionFromScheme(uri, types_1.OpenId4VCIVersion.VER_UNKNOWN)) !== null && _a !== void 0 ? _a : types_1.OpenId4VCIVersion.VER_UNKNOWN;
version = getVersionFromURIParam(uri, version, [types_1.OpenId4VCIVersion.VER_1_0_08], 'initiate_issuance');
version = getVersionFromURIParam(uri, version, [types_1.OpenId4VCIVersion.VER_1_0_08], 'credential_type');
version = getVersionFromURIParam(uri, version, [types_1.OpenId4VCIVersion.VER_1_0_08], 'op_state');
// version = getVersionFromURIParam(uri, version, OpenId4VCIVersion.VER_1_0_09, 'credentials');
// version = getVersionFromURIParam(uri, version, OpenId4VCIVersion.VER_1_0_09, 'initiate_issuance_uri')
// version = getVersionFromURIParam(uri, version, OpenId4VCIVersion.VER_1_0_11, 'credential_offer=');
version = getVersionFromURIParam(uri, version, [types_1.OpenId4VCIVersion.VER_1_0_11], 'credentials');
version = getVersionFromURIParam(uri, version, [types_1.OpenId4VCIVersion.VER_1_0_11], 'grants.user_pin_required');
version = getVersionFromURIParam(uri, version, [types_1.OpenId4VCIVersion.VER_1_0_13], 'credential_configuration_ids');
version = getVersionFromURIParam(uri, version, [types_1.OpenId4VCIVersion.VER_1_0_13], 'tx_code');
if (version === types_1.OpenId4VCIVersion.VER_UNKNOWN) {
version = types_1.OpenId4VCIVersion.VER_1_0_13;
}
return version;
}
exports.determineSpecVersionFromURI = determineSpecVersionFromURI;
function determineSpecVersionFromScheme(credentialOfferURI, openId4VCIVersion) {
const scheme = getScheme(credentialOfferURI);
if (credentialOfferURI.includes(types_1.DefaultURISchemes.INITIATE_ISSUANCE)) {
return recordVersion(openId4VCIVersion, [types_1.OpenId4VCIVersion.VER_1_0_08], scheme);
}
if (credentialOfferURI.includes('credential_offer_uri')) {
return undefined;
}
// todo: drop support for v1_0_8. version 11 and version 13 have the same scheme 'openid-credential-offer'
else if (credentialOfferURI.includes(types_1.DefaultURISchemes.CREDENTIAL_OFFER)) {
if (credentialOfferURI.includes('credentials:') || credentialOfferURI.includes('credentials%22')) {
return recordVersion(openId4VCIVersion, [types_1.OpenId4VCIVersion.VER_1_0_11], scheme);
}
return recordVersion(openId4VCIVersion, [types_1.OpenId4VCIVersion.VER_1_0_13], scheme);
}
else {
return recordVersion(openId4VCIVersion, [types_1.OpenId4VCIVersion.VER_UNKNOWN], scheme);
}
}
exports.determineSpecVersionFromScheme = determineSpecVersionFromScheme;
function getScheme(credentialOfferURI) {
if (!credentialOfferURI || !credentialOfferURI.includes('://')) {
throw Error('Invalid credential offer URI');
}
return credentialOfferURI.split('://')[0];
}
exports.getScheme = getScheme;
function getIssuerFromCredentialOfferPayload(request) {
if (!request || (!('issuer' in request) && !('credential_issuer' in request))) {
return undefined;
}
return 'issuer' in request ? request.issuer : request['credential_issuer'];
}
exports.getIssuerFromCredentialOfferPayload = getIssuerFromCredentialOfferPayload;
const getClientIdFromCredentialOfferPayload = (credentialOffer) => {
if (!credentialOffer) {
return;
}
if ('client_id' in credentialOffer) {
return credentialOffer.client_id;
}
const state = (0, exports.getStateFromCredentialOfferPayload)(credentialOffer);
if (state && isJWT(state)) {
const decoded = (0, jwt_decode_1.jwtDecode)(state, { header: false });
if ('client_id' in decoded && typeof decoded.client_id === 'string') {
return decoded.client_id;
}
}
return;
};
exports.getClientIdFromCredentialOfferPayload = getClientIdFromCredentialOfferPayload;
const isJWT = (input) => {
if (!input) {
return false;
}
const noParts = input === null || input === void 0 ? void 0 : input.split('.').length;
return (input === null || input === void 0 ? void 0 : input.startsWith('ey')) && noParts === 3;
};
const getStateFromCredentialOfferPayload = (credentialOffer) => {
var _a, _b, _c, _d;
if ('grants' in credentialOffer) {
if ((_a = credentialOffer.grants) === null || _a === void 0 ? void 0 : _a.authorization_code) {
return credentialOffer.grants.authorization_code.issuer_state;
}
else if ((_b = credentialOffer.grants) === null || _b === void 0 ? void 0 : _b[index_1.PRE_AUTH_GRANT_LITERAL]) {
return (_d = (_c = credentialOffer.grants) === null || _c === void 0 ? void 0 : _c[index_1.PRE_AUTH_GRANT_LITERAL]) === null || _d === void 0 ? void 0 : _d[index_1.PRE_AUTH_CODE_LITERAL];
}
}
if ('op_state' in credentialOffer) {
// older spec versions
return credentialOffer.op_state;
}
else if (index_1.PRE_AUTH_CODE_LITERAL in credentialOffer) {
return credentialOffer[index_1.PRE_AUTH_CODE_LITERAL];
}
return;
};
exports.getStateFromCredentialOfferPayload = getStateFromCredentialOfferPayload;
function determineSpecVersionFromOffer(offer) {
if (isCredentialOfferV1_0_13(offer)) {
return types_1.OpenId4VCIVersion.VER_1_0_13;
// We don't have full support for V12, so let's skip for now
/*} else if (isCredentialOfferV1_0_12(offer)) {
return OpenId4VCIVersion.VER_1_0_12;*/
}
else if (isCredentialOfferV1_0_11(offer)) {
return types_1.OpenId4VCIVersion.VER_1_0_11;
}
else if (isCredentialOfferV1_0_09(offer)) {
return types_1.OpenId4VCIVersion.VER_1_0_09;
}
else if (isCredentialOfferV1_0_08(offer)) {
return types_1.OpenId4VCIVersion.VER_1_0_08;
}
return types_1.OpenId4VCIVersion.VER_UNKNOWN;
}
exports.determineSpecVersionFromOffer = determineSpecVersionFromOffer;
function isCredentialOfferVersion(offer, min, max) {
if (max && max.valueOf() < min.valueOf()) {
throw Error(`Cannot have a max ${max.valueOf()} version smaller than the min version ${min.valueOf()}`);
}
const version = determineSpecVersionFromOffer(offer);
if (version.valueOf() < min.valueOf()) {
debug(`Credential offer version (${version.valueOf()}) is lower than minimum required version (${min.valueOf()})`);
return false;
}
else if (max && version.valueOf() > max.valueOf()) {
debug(`Credential offer version (${version.valueOf()}) is higher than maximum required version (${max.valueOf()})`);
return false;
}
return true;
}
exports.isCredentialOfferVersion = isCredentialOfferVersion;
function isCredentialOfferV1_0_08(offer) {
if (!offer) {
return false;
}
if ('issuer' in offer && 'credential_type' in offer) {
// payload
return true;
}
if ('credential_offer' in offer && offer['credential_offer']) {
// offer, so check payload
return isCredentialOfferV1_0_08(offer['credential_offer']);
}
return false;
}
function isCredentialOfferV1_0_09(offer) {
if (!offer) {
return false;
}
if ('issuer' in offer && 'credentials' in offer) {
// payload
return true;
}
if ('credential_offer' in offer && offer['credential_offer']) {
// offer, so check payload
return isCredentialOfferV1_0_09(offer['credential_offer']);
}
return false;
}
function isCredentialOfferV1_0_11(offer) {
if (!offer) {
return false;
}
if ('credential_issuer' in offer && 'credentials' in offer) {
// payload
return true;
}
if ('credential_offer' in offer && offer['credential_offer']) {
// offer, so check payload
return isCredentialOfferV1_0_11(offer['credential_offer']);
}
return 'credential_offer_uri' in offer;
}
/*
function isCredentialOfferV1_0_12(offer: CredentialOfferPayload | CredentialOffer): boolean {
if (!offer) {
return false;
}
if ('credential_issuer' in offer && 'credentials' in offer) {
// payload
return true;
}
if ('credential_offer' in offer && offer['credential_offer']) {
// offer, so check payload
return isCredentialOfferV1_0_12(offer['credential_offer']);
}
return 'credential_offer_uri' in offer;
}
*/
function isCredentialOfferV1_0_13(offer) {
if (!offer) {
return false;
}
else if (typeof offer === 'string' && offer.startsWith('{')) {
offer = JSON.parse(offer);
}
if ('credential_issuer' in offer && 'credential_configuration_ids' in offer) {
// payload
return true;
}
if ('credential_offer' in offer && offer['credential_offer']) {
// offer, so check payload
return isCredentialOfferV1_0_13(offer['credential_offer']);
}
return 'credential_offer_uri' in offer;
}
function toUniformCredentialOfferRequest(offer, opts) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
let version = (_a = opts === null || opts === void 0 ? void 0 : opts.version) !== null && _a !== void 0 ? _a : determineSpecVersionFromOffer(offer);
let originalCredentialOffer = offer.credential_offer;
let credentialOfferURI;
if ('credential_offer_uri' in offer && (offer === null || offer === void 0 ? void 0 : offer.credential_offer_uri) !== undefined) {
credentialOfferURI = offer.credential_offer_uri;
if ((opts === null || opts === void 0 ? void 0 : opts.resolve) || (opts === null || opts === void 0 ? void 0 : opts.resolve) === undefined) {
index_1.VCI_LOG_COMMON.log(`Credential offer contained a URI. Will use that to get the credential offer payload: ${credentialOfferURI}`);
originalCredentialOffer = (yield resolveCredentialOfferURI(credentialOfferURI));
}
else if (!originalCredentialOffer) {
throw Error(`Credential offer uri (${credentialOfferURI}) found, but resolution was explicitly disabled and credential_offer was supplied`);
}
// We need to redetermine the version of the offer, as we only had the offer_uri until now
version = determineSpecVersionFromOffer(originalCredentialOffer);
index_1.VCI_LOG_COMMON.log(`Offer URI payload determined to be of version ${version}`);
}
if (!originalCredentialOffer) {
throw Error('No credential offer available');
}
const payload = toUniformCredentialOfferPayload(originalCredentialOffer, Object.assign(Object.assign({}, opts), { version }));
const supportedFlows = determineFlowType(payload, version);
return Object.assign(Object.assign({ credential_offer: payload, original_credential_offer: originalCredentialOffer }, (credentialOfferURI && { credential_offer_uri: credentialOfferURI })), { supportedFlows,
version });
});
}
exports.toUniformCredentialOfferRequest = toUniformCredentialOfferRequest;
function isPreAuthCode(request) {
var _a, _b;
const payload = 'credential_offer' in request ? request.credential_offer : request;
return ((_b = (_a = payload === null || payload === void 0 ? void 0 : payload.grants) === null || _a === void 0 ? void 0 : _a[index_1.PRE_AUTH_GRANT_LITERAL]) === null || _b === void 0 ? void 0 : _b[index_1.PRE_AUTH_CODE_LITERAL]) !== undefined;
}
exports.isPreAuthCode = isPreAuthCode;
function assertedUniformCredentialOffer(origCredentialOffer, opts) {
return __awaiter(this, void 0, void 0, function* () {
const credentialOffer = JSON.parse(JSON.stringify(origCredentialOffer));
if (credentialOffer.credential_offer_uri && !credentialOffer.credential_offer) {
if ((opts === null || opts === void 0 ? void 0 : opts.resolve) === undefined || opts.resolve) {
credentialOffer.credential_offer = yield resolveCredentialOfferURI(credentialOffer.credential_offer_uri);
}
else {
throw Error(`No credential_offer present, but we did get a URI, but resolution was explicitly disabled`);
}
}
if (!credentialOffer.credential_offer) {
throw Error(`No credential_offer present`);
}
credentialOffer.credential_offer = yield toUniformCredentialOfferPayload(credentialOffer.credential_offer, { version: credentialOffer.version });
return credentialOffer;
});
}
exports.assertedUniformCredentialOffer = assertedUniformCredentialOffer;
function resolveCredentialOfferURI(uri) {
return __awaiter(this, void 0, void 0, function* () {
if (!uri) {
return undefined;
}
const response = (yield (0, HttpUtils_1.getJson)(uri));
if (!response || !response.successBody) {
throw Error(`Could not get credential offer from uri: ${uri}: ${JSON.stringify(response === null || response === void 0 ? void 0 : response.errorBody)}`);
}
return response.successBody;
});
}
exports.resolveCredentialOfferURI = resolveCredentialOfferURI;
function toUniformCredentialOfferPayload(offer, opts) {
var _a;
// todo: create test to check idempotence once a payload is already been made uniform.
const version = (_a = opts === null || opts === void 0 ? void 0 : opts.version) !== null && _a !== void 0 ? _a : determineSpecVersionFromOffer(offer);
if (version >= types_1.OpenId4VCIVersion.VER_1_0_11) {
const orig = offer;
return Object.assign({}, orig);
}
const grants = 'grants' in offer ? offer.grants : {};
let offerPayloadAsV8V9 = offer;
if (isCredentialOfferVersion(offer, types_1.OpenId4VCIVersion.VER_1_0_08, types_1.OpenId4VCIVersion.VER_1_0_09)) {
if (offerPayloadAsV8V9.op_state) {
grants.authorization_code = Object.assign(Object.assign({}, grants.authorization_code), { issuer_state: offerPayloadAsV8V9.op_state });
}
let user_pin_required = false;
if (typeof offerPayloadAsV8V9.user_pin_required === 'string') {
user_pin_required = offerPayloadAsV8V9.user_pin_required === 'true' || offerPayloadAsV8V9.user_pin_required === 'yes';
}
else if (offerPayloadAsV8V9.user_pin_required !== undefined) {
user_pin_required = offerPayloadAsV8V9.user_pin_required;
}
if (offerPayloadAsV8V9[index_1.PRE_AUTH_CODE_LITERAL]) {
grants[index_1.PRE_AUTH_GRANT_LITERAL] = {
'pre-authorized_code': offerPayloadAsV8V9[index_1.PRE_AUTH_CODE_LITERAL],
user_pin_required,
};
}
}
const issuer = getIssuerFromCredentialOfferPayload(offer);
if (version === types_1.OpenId4VCIVersion.VER_1_0_09) {
offerPayloadAsV8V9 = offer;
return {
// credential_definition: getCredentialsSupported(never, offerPayloadAsV8V9.credentials).map(sup => {credentialSubject: sup.credentialSubject})[0],
credential_issuer: issuer !== null && issuer !== void 0 ? issuer : offerPayloadAsV8V9.issuer,
credentials: offerPayloadAsV8V9.credentials,
grants,
};
}
if (version === types_1.OpenId4VCIVersion.VER_1_0_08) {
offerPayloadAsV8V9 = offer;
return {
credential_issuer: issuer !== null && issuer !== void 0 ? issuer : offerPayloadAsV8V9.issuer,
credentials: Array.isArray(offerPayloadAsV8V9.credential_type) ? offerPayloadAsV8V9.credential_type : [offerPayloadAsV8V9.credential_type],
grants,
};
}
throw Error(`Could not create uniform payload for version ${version}`);
}
exports.toUniformCredentialOfferPayload = toUniformCredentialOfferPayload;
function determineFlowType(suppliedOffer, version) {
var _a, _b, _c;
const payload = getCredentialOfferPayload(suppliedOffer);
const supportedFlows = [];
if ((_a = payload.grants) === null || _a === void 0 ? void 0 : _a.authorization_code) {
supportedFlows.push(types_1.AuthzFlowType.AUTHORIZATION_CODE_FLOW);
}
if ((_c = (_b = payload.grants) === null || _b === void 0 ? void 0 : _b[index_1.PRE_AUTH_GRANT_LITERAL]) === null || _c === void 0 ? void 0 : _c[index_1.PRE_AUTH_CODE_LITERAL]) {
supportedFlows.push(types_1.AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW);
}
if (supportedFlows.length === 0 && version < types_1.OpenId4VCIVersion.VER_1_0_09) {
// auth flow without op_state was possible in v08. The only way to know is that the detections would result in finding nothing.
supportedFlows.push(types_1.AuthzFlowType.AUTHORIZATION_CODE_FLOW);
}
return supportedFlows;
}
exports.determineFlowType = determineFlowType;
function getCredentialOfferPayload(offer) {
let payload;
if ('credential_offer' in offer && offer['credential_offer']) {
payload = offer.credential_offer;
}
else {
payload = offer;
}
return payload;
}
exports.getCredentialOfferPayload = getCredentialOfferPayload;
function determineGrantTypes(offer) {
let grants;
if ('grants' in offer && offer.grants) {
grants = offer.grants;
}
else {
grants = getCredentialOfferPayload(offer).grants;
}
const types = [];
if (grants) {
if ('authorization_code' in grants) {
types.push(types_1.GrantTypes.AUTHORIZATION_CODE);
}
if (index_1.PRE_AUTH_GRANT_LITERAL in grants) {
types.push(types_1.GrantTypes.PRE_AUTHORIZED_CODE);
}
}
return types;
}
exports.determineGrantTypes = determineGrantTypes;
function getVersionFromURIParam(credentialOfferURI, currentVersion, matchingVersion, param, allowUpgrade = true) {
if (credentialOfferURI.includes(param)) {
return recordVersion(currentVersion, matchingVersion, param, allowUpgrade);
}
return currentVersion;
}
function recordVersion(currentVersion, matchingVersion, key, allowUpgrade = true) {
matchingVersion = matchingVersion.sort().reverse();
if (currentVersion === types_1.OpenId4VCIVersion.VER_UNKNOWN) {
return matchingVersion[0];
}
else if (matchingVersion.includes(currentVersion)) {
if (!allowUpgrade) {
return currentVersion;
}
return matchingVersion[0];
}
throw new Error(`Invalid param. Some keys have been used from version: ${currentVersion} version while '${key}' is used from version: ${JSON.stringify(matchingVersion)}`);
}
function getTypesFromOfferV1_0_11(credentialOffer, opts) {
const types = credentialOffer.credentials.reduce((prev, curr) => {
// FIXME returning the string value is wrong (as it's an id), but just matching the current behavior of this library
// The credential_type (from draft 8) and the actual 'type' value in a VC (from draft 11) are mixed up
// Fix for this here: https://github.com/Sphereon-Opensource/OID4VCI/pull/54
if (typeof curr === 'string') {
return [...prev, curr];
}
else if (curr.format === 'jwt_vc_json-ld' || curr.format === 'ldp_vc') {
return [...prev, ...curr.credential_definition.types];
}
else if (curr.format === 'jwt_vc_json' || curr.format === 'jwt_vc') {
return [...prev, ...curr.types];
}
else if (curr.format === 'vc+sd-jwt') {
return [...prev, curr.vct];
}
return prev;
}, []);
if (!types || types.length === 0) {
throw Error('Could not deduce types from credential offer');
}
if (opts === null || opts === void 0 ? void 0 : opts.filterVerifiableCredential) {
return types.filter((type) => type !== 'VerifiableCredential');
}
return types;
}
exports.getTypesFromOfferV1_0_11 = getTypesFromOfferV1_0_11;
//# sourceMappingURL=CredentialOfferUtil.js.map