@sphereon/oid4vc-common
Version:
OpenID 4 Verifiable Credentials Common
485 lines (476 loc) • 17.5 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// lib/index.ts
var index_exports = {};
__export(index_exports, {
BASE64_URL_REGEX: () => BASE64_URL_REGEX,
SigningAlgo: () => SigningAlgo,
VCI_LOGGERS: () => VCI_LOGGERS,
VCI_LOG_COMMON: () => VCI_LOG_COMMON,
calculateJwkThumbprint: () => calculateJwkThumbprint,
calculateJwkThumbprintUri: () => calculateJwkThumbprintUri,
checkExp: () => checkExp,
createDPoP: () => createDPoP,
decodeJwt: () => decodeJwt,
decodeProtectedHeader: () => decodeProtectedHeader,
defaultHasher: () => defaultHasher,
dpopTokenRequestNonceError: () => dpopTokenRequestNonceError,
epochTime: () => epochTime,
getCreateDPoPOptions: () => getCreateDPoPOptions,
getDidJwtVerifier: () => getDidJwtVerifier,
getDigestAlgorithmFromJwkThumbprintUri: () => getDigestAlgorithmFromJwkThumbprintUri,
getJwkVerifier: () => getJwkVerifier,
getJwtVerifierWithContext: () => getJwtVerifierWithContext,
getNowSkewed: () => getNowSkewed,
getX5cVerifier: () => getX5cVerifier,
isJwe: () => isJwe,
isJws: () => isJws,
parseJWT: () => parseJWT,
uuidv4: () => import_uuid2.v4,
verifyDPoP: () => verifyDPoP,
verifyResourceDPoP: () => verifyResourceDPoP
});
module.exports = __toCommonJS(index_exports);
var import_ssi_types = require("@sphereon/ssi-types");
// lib/jwt/Jwt.types.ts
var SigningAlgo = /* @__PURE__ */ function(SigningAlgo2) {
SigningAlgo2["EDDSA"] = "EdDSA";
SigningAlgo2["RS256"] = "RS256";
SigningAlgo2["PS256"] = "PS256";
SigningAlgo2["ES256"] = "ES256";
SigningAlgo2["ES256K"] = "ES256K";
return SigningAlgo2;
}({});
// lib/jwt/JwkThumbprint.ts
var u8a2 = __toESM(require("uint8arrays"), 1);
// lib/hasher.ts
var import_sha = __toESM(require("sha.js"), 1);
var u8a = __toESM(require("uint8arrays"), 1);
var supportedAlgorithms = [
"sha256",
"sha384",
"sha512"
];
var defaultHasher = /* @__PURE__ */ __name((data, algorithm) => {
const sanitizedAlgorithm = algorithm.toLowerCase().replace(/[-_]/g, "");
if (!supportedAlgorithms.includes(sanitizedAlgorithm)) {
throw new Error(`Unsupported hashing algorithm ${algorithm}`);
}
return new Uint8Array((0, import_sha.default)(sanitizedAlgorithm).update(typeof data === "string" ? u8a.fromString(data) : data).digest());
}, "defaultHasher");
// lib/jwt/JwkThumbprint.ts
var check = /* @__PURE__ */ __name((value, description) => {
if (typeof value !== "string" || !value) {
throw Error(`${description} missing or invalid`);
}
}, "check");
async function calculateJwkThumbprint(jwk, digestAlgorithm) {
if (!jwk || typeof jwk !== "object") {
throw new TypeError("JWK must be an object");
}
const algorithm = digestAlgorithm ?? "sha256";
if (algorithm !== "sha256" && algorithm !== "sha384" && algorithm !== "sha512") {
throw new TypeError('digestAlgorithm must one of "sha256", "sha384", or "sha512"');
}
let components;
switch (jwk.kty) {
case "EC":
check(jwk.crv, '"crv" (Curve) Parameter');
check(jwk.x, '"x" (X Coordinate) Parameter');
check(jwk.y, '"y" (Y Coordinate) Parameter');
components = {
crv: jwk.crv,
kty: jwk.kty,
x: jwk.x,
y: jwk.y
};
break;
case "OKP":
check(jwk.crv, '"crv" (Subtype of Key Pair) Parameter');
check(jwk.x, '"x" (Public Key) Parameter');
components = {
crv: jwk.crv,
kty: jwk.kty,
x: jwk.x
};
break;
case "RSA":
check(jwk.e, '"e" (Exponent) Parameter');
check(jwk.n, '"n" (Modulus) Parameter');
components = {
e: jwk.e,
kty: jwk.kty,
n: jwk.n
};
break;
case "oct":
check(jwk.k, '"k" (Key Value) Parameter');
components = {
k: jwk.k,
kty: jwk.kty
};
break;
default:
throw Error('"kty" (Key Type) Parameter missing or unsupported');
}
return u8a2.toString(defaultHasher(JSON.stringify(components), algorithm), "base64url");
}
__name(calculateJwkThumbprint, "calculateJwkThumbprint");
async function getDigestAlgorithmFromJwkThumbprintUri(uri) {
const match = uri.match(/^urn:ietf:params:oauth:jwk-thumbprint:sha-(\w+):/);
if (!match) {
throw new Error(`Invalid JWK thumbprint URI structure ${uri}`);
}
const algorithm = `sha${match[1]}`;
if (algorithm !== "sha256" && algorithm !== "sha384" && algorithm !== "sha512") {
throw new Error(`Invalid JWK thumbprint URI digest algorithm ${uri}`);
}
return algorithm;
}
__name(getDigestAlgorithmFromJwkThumbprintUri, "getDigestAlgorithmFromJwkThumbprintUri");
async function calculateJwkThumbprintUri(jwk, digestAlgorithm = "sha256") {
const thumbprint = await calculateJwkThumbprint(jwk, digestAlgorithm);
return `urn:ietf:params:oauth:jwk-thumbprint:sha-${digestAlgorithm.slice(-3)}:${thumbprint}`;
}
__name(calculateJwkThumbprintUri, "calculateJwkThumbprintUri");
// lib/jwt/JwtVerifier.ts
var getDidJwtVerifier = /* @__PURE__ */ __name((jwt, options) => {
const { type } = options;
if (!jwt.header.kid) throw new Error(`Received an invalid JWT. Missing kid header.`);
if (!jwt.header.alg) throw new Error(`Received an invalid JWT. Missing alg header.`);
if (!jwt.header.kid.includes("#")) {
throw new Error(`Received an invalid JWT.. '${type}' contains an invalid kid header.`);
}
return {
method: "did",
didUrl: jwt.header.kid,
type,
alg: jwt.header.alg
};
}, "getDidJwtVerifier");
var getIssuer = /* @__PURE__ */ __name((type, payload) => {
if (type === "request-object") {
if (!payload.client_id) {
throw new Error("Missing required field client_id in request object JWT");
}
return payload.client_id;
}
if (typeof payload.iss !== "string") {
throw new Error(`Received an invalid JWT. '${type}' contains an invalid iss claim or it is missing.`);
}
return payload.iss;
}, "getIssuer");
var getX5cVerifier = /* @__PURE__ */ __name((jwt, options) => {
const { type } = options;
if (!jwt.header.x5c) throw new Error(`Received an invalid JWT. Missing x5c header.`);
if (!jwt.header.alg) throw new Error(`Received an invalid JWT. Missing alg header.`);
if (!Array.isArray(jwt.header.x5c) || jwt.header.x5c.length === 0 || !jwt.header.x5c.every((cert) => typeof cert === "string")) {
throw new Error(`Received an invalid JWT.. '${type}' contains an invalid x5c header.`);
}
return {
method: "x5c",
x5c: jwt.header.x5c,
issuer: getIssuer(type, jwt.payload),
type,
alg: jwt.header.alg
};
}, "getX5cVerifier");
var getJwkVerifier = /* @__PURE__ */ __name(async (jwt, options) => {
const { type } = options;
if (!jwt.header.jwk) throw new Error(`Received an invalid JWT. Missing jwk header.`);
if (!jwt.header.alg) throw new Error(`Received an invalid JWT. Missing alg header.`);
if (typeof jwt.header.jwk !== "object") {
throw new Error(`Received an invalid JWT. '${type}' contains an invalid jwk header.`);
}
return {
method: "jwk",
type,
jwk: jwt.header.jwk,
alg: jwt.header.alg
};
}, "getJwkVerifier");
var getJwtVerifierWithContext = /* @__PURE__ */ __name(async (jwt, options) => {
const { header, payload } = jwt;
if (header.kid?.startsWith("did:")) return getDidJwtVerifier({
header,
payload
}, options);
else if (jwt.header.x5c) return getX5cVerifier({
header,
payload
}, options);
else if (jwt.header.jwk) return getJwkVerifier({
header,
payload
}, options);
return {
method: "custom",
type: options.type
};
}, "getJwtVerifierWithContext");
// lib/jwt/jwtUtils.ts
var import_jwt_decode = require("jwt-decode");
function parseJWT(jwt) {
const header = (0, import_jwt_decode.jwtDecode)(jwt, {
header: true
});
const payload = (0, import_jwt_decode.jwtDecode)(jwt, {
header: false
});
if (!payload || !header) {
throw new Error("Jwt Payload and/or Header could not be parsed");
}
return {
header,
payload
};
}
__name(parseJWT, "parseJWT");
var DEFAULT_SKEW_TIME = 60;
function getNowSkewed(now, skewTime) {
const _now = now ? now : epochTime();
const _skewTime = skewTime ? skewTime : DEFAULT_SKEW_TIME;
return {
nowSkewedPast: _now - _skewTime,
nowSkewedFuture: _now + _skewTime
};
}
__name(getNowSkewed, "getNowSkewed");
function epochTime() {
return Math.floor(Date.now() / 1e3);
}
__name(epochTime, "epochTime");
var BASE64_URL_REGEX = /^([0-9a-zA-Z-_]{4})*(([0-9a-zA-Z-_]{2}(==)?)|([0-9a-zA-Z-_]{3}(=)?))?$/;
var isJws = /* @__PURE__ */ __name((jws) => {
const jwsParts = jws.split(".");
return jwsParts.length === 3 && jwsParts.every((part) => BASE64_URL_REGEX.test(part));
}, "isJws");
var isJwe = /* @__PURE__ */ __name((jwe) => {
const jweParts = jwe.split(".");
return jweParts.length === 5 && jweParts.every((part) => BASE64_URL_REGEX.test(part));
}, "isJwe");
var decodeProtectedHeader = /* @__PURE__ */ __name((jwt) => {
return (0, import_jwt_decode.jwtDecode)(jwt, {
header: true
});
}, "decodeProtectedHeader");
var decodeJwt = /* @__PURE__ */ __name((jwt) => {
return (0, import_jwt_decode.jwtDecode)(jwt, {
header: false
});
}, "decodeJwt");
var checkExp = /* @__PURE__ */ __name((input) => {
const { exp, now, clockSkew } = input;
return exp < (now ?? Date.now() / 1e3) - (clockSkew ?? 120);
}, "checkExp");
// lib/dpop/DPoP.ts
var import_jwt_decode2 = require("jwt-decode");
var u8a3 = __toESM(require("uint8arrays"), 1);
var import_uuid = require("uuid");
var dpopTokenRequestNonceError = "use_dpop_nonce";
function getCreateDPoPOptions(createDPoPClientOpts, endPointUrl, resourceRequestOpts) {
const htu = endPointUrl.split("?")[0].split("#")[0];
return {
...createDPoPClientOpts,
jwtPayloadProps: {
...createDPoPClientOpts.jwtPayloadProps,
htu,
htm: "POST",
...resourceRequestOpts && {
accessToken: resourceRequestOpts.accessToken
}
}
};
}
__name(getCreateDPoPOptions, "getCreateDPoPOptions");
async function createDPoP(options) {
const { createJwtCallback, jwtIssuer, jwtPayloadProps, dPoPSigningAlgValuesSupported } = options;
if (jwtPayloadProps.accessToken && (jwtPayloadProps.accessToken?.startsWith("DPoP ") || jwtPayloadProps.accessToken?.startsWith("Bearer "))) {
throw new Error("expected access token without scheme");
}
const ath = jwtPayloadProps.accessToken ? u8a3.toString(defaultHasher(jwtPayloadProps.accessToken, "sha256"), "base64url") : void 0;
return createJwtCallback({
method: "jwk",
type: "dpop",
alg: jwtIssuer.alg,
jwk: jwtIssuer.jwk,
dPoPSigningAlgValuesSupported
}, {
header: {
...jwtIssuer,
typ: "dpop+jwt",
alg: jwtIssuer.alg,
jwk: jwtIssuer.jwk
},
payload: {
...jwtPayloadProps,
iat: epochTime(),
jti: (0, import_uuid.v4)(),
...ath && {
ath
}
}
});
}
__name(createDPoP, "createDPoP");
async function verifyDPoP(request, options) {
const dpop = request.headers["dpop"];
if (!dpop || typeof dpop !== "string") {
throw new Error("missing or invalid dpop header. Expected compact JWT");
}
const { header: dPoPHeader, payload: dPoPPayload } = parseJWT(dpop);
if (dPoPHeader.typ !== "dpop+jwt" || !dPoPHeader.alg || !dPoPHeader.jwk || typeof dPoPHeader.jwk !== "object" || dPoPHeader.jwk.d) {
throw new Error("invalid_dpop_proof. Invalid header claims");
}
if (!dPoPPayload.htm || !dPoPPayload.htu || !dPoPPayload.iat || !dPoPPayload.jti) {
throw new Error("invalid_dpop_proof. Missing required claims");
}
if (options?.acceptedAlgorithms && !options.acceptedAlgorithms.includes(dPoPHeader.alg)) {
throw new Error(`invalid_dpop_proof. Invalid 'alg' claim '${dPoPHeader.alg}'. Only ${options.acceptedAlgorithms.join(", ")} are supported.`);
}
if (options?.expectedNonce && !dPoPPayload.nonce || dPoPPayload.nonce !== options.expectedNonce) {
throw new Error("invalid_dpop_proof. Nonce mismatch");
}
try {
const verificationResult = await options.jwtVerifyCallback({
method: "jwk",
type: "dpop",
jwk: dPoPHeader.jwk,
alg: dPoPHeader.alg
}, {
header: dPoPHeader,
payload: dPoPPayload,
raw: dpop
});
if (!verificationResult) {
throw new Error("invalid_dpop_proof. Invalid JWT signature");
}
} catch (error) {
throw new Error("invalid_dpop_proof. Invalid JWT signature. " + (error instanceof Error ? error.message : "Unknown error"));
}
if (dPoPPayload.htm !== request.method) {
throw new Error(`invalid_dpop_proof. Invalid htm claim. Must match request method '${request.method}'`);
}
const currentUri = request.fullUrl.split("?")[0].split("#")[0];
if (dPoPPayload.htu !== currentUri) {
throw new Error("invalid_dpop_proof. Invalid htu claim");
}
if (options.expectedNonce && dPoPPayload.nonce !== options.expectedNonce || !options.expectedNonce && dPoPPayload.nonce) {
throw new Error("invalid_dpop_proof. Nonce mismatch");
}
const { nowSkewedPast, nowSkewedFuture } = getNowSkewed(options.now);
if (
// iat claim is too far in the future
nowSkewedPast - (options.maxIatAgeInSeconds ?? 60) > dPoPPayload.iat || // iat claim is too old
nowSkewedFuture + (options.maxIatAgeInSeconds ?? 60) < dPoPPayload.iat
) {
throw new Error("invalid_dpop_proof. Invalid iat claim");
}
const authorizationHeader = request.headers.authorization;
if (!options.expectAccessToken && authorizationHeader) {
throw new Error("invalid_dpop_proof. Received an unexpected authorization header.");
}
if (options.expectAccessToken) {
if (!dPoPPayload.ath) {
throw new Error("invalid_dpop_proof. Missing expected ath claim.");
}
if (!authorizationHeader || typeof authorizationHeader !== "string" || !authorizationHeader.startsWith("DPoP ")) {
throw new Error("invalid_dpop_proof. Invalid authorization header.");
}
const accessToken = authorizationHeader.replace("DPoP ", "");
const expectedAth = u8a3.toString(defaultHasher(accessToken, "sha256"), "base64url");
if (dPoPPayload.ath !== expectedAth) {
throw new Error("invalid_dpop_proof. Invalid ath claim");
}
const accessTokenPayload = (0, import_jwt_decode2.jwtDecode)(accessToken, {
header: false
});
if (!accessTokenPayload.cnf?.jkt) {
throw new Error("invalid_dpop_proof. Access token is missing the jkt claim");
}
const thumprint = await calculateJwkThumbprint(dPoPHeader.jwk, "sha256");
if (accessTokenPayload.cnf?.jkt !== thumprint) {
throw new Error("invalid_dpop_proof. JwkThumbprint mismatch");
}
}
return dPoPHeader.jwk;
}
__name(verifyDPoP, "verifyDPoP");
async function verifyResourceDPoP(request, options) {
if (!request.headers.authorization || typeof request.headers.authorization !== "string") {
throw new Error("Received an invalid resource request. Missing authorization header.");
}
const tokenPayload = (0, import_jwt_decode2.jwtDecode)(request.headers.authorization, {
header: false
});
const tokenType = tokenPayload.token_type;
if (tokenType !== "DPoP") {
return;
}
return verifyDPoP(request, {
...options,
expectAccessToken: true
});
}
__name(verifyResourceDPoP, "verifyResourceDPoP");
// lib/index.ts
var import_uuid2 = require("uuid");
var VCI_LOGGERS = import_ssi_types.Loggers.DEFAULT;
var VCI_LOG_COMMON = VCI_LOGGERS.get("sphereon:oid4vci:common");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
BASE64_URL_REGEX,
SigningAlgo,
VCI_LOGGERS,
VCI_LOG_COMMON,
calculateJwkThumbprint,
calculateJwkThumbprintUri,
checkExp,
createDPoP,
decodeJwt,
decodeProtectedHeader,
defaultHasher,
dpopTokenRequestNonceError,
epochTime,
getCreateDPoPOptions,
getDidJwtVerifier,
getDigestAlgorithmFromJwkThumbprintUri,
getJwkVerifier,
getJwtVerifierWithContext,
getNowSkewed,
getX5cVerifier,
isJwe,
isJws,
parseJWT,
uuidv4,
verifyDPoP,
verifyResourceDPoP
});
//# sourceMappingURL=index.cjs.map