UNPKG

@sd-jwt/sd-jwt-vc

Version:
326 lines (324 loc) 11.3 kB
var __getProtoOf = Object.getPrototypeOf; var __reflectGet = Reflect.get; var __superGet = (cls, obj, key) => __reflectGet(__getProtoOf(cls), key, obj); var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // src/sd-jwt-vc-instance.ts import { Jwt, SDJwt, SDJwtInstance } from "@sd-jwt/core"; import { base64urlDecode, SDJWTException } from "@sd-jwt/utils"; import { getListFromStatusListJWT } from "@sd-jwt/jwt-status-list"; import Ajv from "ajv"; import addFormats from "ajv-formats"; var SDJwtVcInstance = class _SDJwtVcInstance extends SDJwtInstance { constructor(userConfig) { super(userConfig); /** * The type of the SD-JWT-VC set in the header.typ field. */ this.type = "dc+sd-jwt"; this.userConfig = {}; if (userConfig) { this.userConfig = userConfig; } } /** * Validates if the disclosureFrame contains any reserved fields. If so it will throw an error. * @param disclosureFrame */ validateReservedFields(disclosureFrame) { if ((disclosureFrame == null ? void 0 : disclosureFrame._sd) && Array.isArray(disclosureFrame._sd) && disclosureFrame._sd.length > 0) { const reservedNames = ["iss", "nbf", "exp", "cnf", "vct", "status"]; const reservedNamesInDisclosureFrame = disclosureFrame._sd.filter((key) => reservedNames.includes(key)); if (reservedNamesInDisclosureFrame.length > 0) { throw new SDJWTException("Cannot disclose protected field"); } } } /** * Fetches the status list from the uri with a timeout of 10 seconds. * @param uri The URI to fetch from. * @returns A promise that resolves to a compact JWT. */ statusListFetcher(uri) { return __async(this, null, function* () { var _a; const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 1e4); try { const response = yield fetch(uri, { signal: controller.signal, headers: { Accept: "application/statuslist+jwt" } }); if (!response.ok) { throw new Error( `Error fetching status list: ${response.status} ${yield response.text()}` ); } if (!((_a = response.headers.get("content-type")) == null ? void 0 : _a.includes("application/statuslist+jwt"))) { throw new Error("Invalid content type"); } return response.text(); } finally { clearTimeout(timeoutId); } }); } /** * Validates the status, throws an error if the status is not 0. * @param status * @returns */ statusValidator(status) { return __async(this, null, function* () { if (status !== 0) throw new SDJWTException("Status is not valid"); return Promise.resolve(); }); } /** * Verifies the SD-JWT-VC. It will validate the signature, the keybindings when required, the status, and the VCT. * @param currentDate current time in seconds */ verify(encodedSDJwt, options) { return __async(this, null, function* () { const result = yield __superGet(_SDJwtVcInstance.prototype, this, "verify").call(this, encodedSDJwt, options).then((res) => { return { payload: res.payload, header: res.header, kb: res.kb }; }); yield this.verifyStatus(result, options); if (this.userConfig.loadTypeMetadataFormat) { yield this.verifyVct(result); } return result; }); } /** * Gets VCT Metadata of the raw SD-JWT-VC. Returns the type metadata format. If the SD-JWT-VC is invalid or does not contain a vct claim, an error is thrown. * @param encodedSDJwt * @returns */ getVct(encodedSDJwt) { return __async(this, null, function* () { const { payload, header } = yield SDJwt.extractJwt(encodedSDJwt); if (!payload) { throw new SDJWTException("JWT payload is missing"); } const result = { payload, header, kb: void 0 }; return this.fetchVct(result); }); } /** * Validates the integrity of the response if the integrity is passed. If the integrity does not match, an error is thrown. * @param integrity * @param response */ validateIntegrity(response, url, integrity) { return __async(this, null, function* () { if (integrity) { const arrayBuffer = yield response.arrayBuffer(); const alg = integrity.split("-")[0]; const hashBuffer = yield this.userConfig.hasher( arrayBuffer, alg ); const integrityHash = integrity.split("-")[1]; const hash = Array.from(new Uint8Array(hashBuffer)).map((byte) => byte.toString(16).padStart(2, "0")).join(""); if (hash !== integrityHash) { throw new Error( `Integrity check for ${url} failed: is ${hash}, but expected ${integrityHash}` ); } } }); } /** * Fetches the content from the url with a timeout of 10 seconds. * @param url * @returns */ fetch(url, integrity) { return __async(this, null, function* () { var _a; try { const response = yield fetch(url, { signal: AbortSignal.timeout((_a = this.userConfig.timeout) != null ? _a : 1e4) }); if (!response.ok) { const errorText = yield response.text(); throw new Error( `Error fetching ${url}: ${response.status} ${response.statusText} - ${errorText}` ); } yield this.validateIntegrity(response.clone(), url, integrity); return response.json(); } catch (error) { if (error.name === "TimeoutError") { throw new Error(`Request to ${url} timed out`); } throw error; } }); } /** * Loads the schema either from the object or as fallback from the uri. * @param typeMetadataFormat * @returns */ loadSchema(typeMetadataFormat) { return __async(this, null, function* () { if (typeMetadataFormat.schema) return typeMetadataFormat.schema; if (typeMetadataFormat.schema_uri) { const schema = yield this.fetch( typeMetadataFormat.schema_uri, typeMetadataFormat["schema_uri#Integrity"] ); return schema; } throw new Error("No schema or schema_uri found"); }); } /** * Verifies the VCT of the SD-JWT-VC. Returns the type metadata format. If the schema does not match, an error is thrown. If it matches, it will return the type metadata format. * @param result * @returns */ verifyVct(result) { return __async(this, null, function* () { const typeMetadataFormat = yield this.fetchVct(result); if (typeMetadataFormat.extends) { } const schema = yield this.loadSchema(typeMetadataFormat); const loadedSchemas = /* @__PURE__ */ new Set(); const ajv = new Ajv({ loadSchema: (uri) => __async(this, null, function* () { if (loadedSchemas.has(uri)) { return {}; } const response = yield fetch(uri); if (!response.ok) { throw new Error( `Error fetching schema: ${response.status} ${yield response.text()}` ); } loadedSchemas.add(uri); return response.json(); }) }); addFormats(ajv); const validate = yield ajv.compileAsync(schema); const valid = validate(result.payload); if (!valid) { throw new SDJWTException( `Payload does not match the schema: ${JSON.stringify(validate.errors)}` ); } return typeMetadataFormat; }); } /** * Fetches VCT Metadata of the SD-JWT-VC. Returns the type metadata format. If the SD-JWT-VC does not contain a vct claim, an error is thrown. * @param result * @returns */ fetchVct(result) { return __async(this, null, function* () { var _a, _b; if (!result.payload.vct) { throw new SDJWTException("vct claim is required"); } if ((_a = result.header) == null ? void 0 : _a.vctm) { return this.fetchVctFromHeader(result.payload.vct, result); } const fetcher = (_b = this.userConfig.vctFetcher) != null ? _b : (uri, integrity) => this.fetch(uri, integrity); return fetcher(result.payload.vct, result.payload["vct#Integrity"]); }); } /** * Fetches VCT Metadata from the header of the SD-JWT-VC. Returns the type metadata format. If the SD-JWT-VC does not contain a vct claim, an error is thrown. * @param result * @param */ fetchVctFromHeader(vct, result) { return __async(this, null, function* () { var _a; const vctmHeader = (_a = result.header) == null ? void 0 : _a.vctm; if (!vctmHeader || !Array.isArray(vctmHeader)) { throw new Error("vctm claim in SD JWT header is invalid"); } const typeMetadataFormat = vctmHeader.map((vctm) => { if (!(typeof vctm === "string")) { throw new Error("vctm claim in SD JWT header is invalid"); } return JSON.parse(base64urlDecode(vctm)); }).find((typeMetadataFormat2) => { return typeMetadataFormat2.vct === vct; }); if (!typeMetadataFormat) { throw new Error("could not find VCT Metadata in JWT header"); } return typeMetadataFormat; }); } /** * Verifies the status of the SD-JWT-VC. * @param result * @param options */ verifyStatus(result, options) { return __async(this, null, function* () { var _a, _b, _c, _d, _e; if (result.payload.status) { if (result.payload.status.status_list) { const fetcher = (_a = this.userConfig.statusListFetcher) != null ? _a : this.statusListFetcher.bind(this); const statusListJWT = yield fetcher( result.payload.status.status_list.uri ); const slJWT = Jwt.fromEncode(statusListJWT); yield slJWT.verify( (_b = this.userConfig.statusVerifier) != null ? _b : this.userConfig.verifier, options ); const currentDate = (_c = options == null ? void 0 : options.currentDate) != null ? _c : Math.floor(Date.now() / 1e3); if (((_d = slJWT.payload) == null ? void 0 : _d.exp) && slJWT.payload.exp < currentDate) { throw new SDJWTException("Status list is expired"); } const statusList = getListFromStatusListJWT(statusListJWT); const status = statusList.getStatus( result.payload.status.status_list.idx ); const statusValidator = (_e = this.userConfig.statusValidator) != null ? _e : this.statusValidator.bind(this); yield statusValidator(status); } } }); } }; export { SDJwtVcInstance };