@sphereon/oid4vc-common
Version:
OpenID 4 Verifiable Credentials Common
444 lines (434 loc) • 15.1 kB
JavaScript
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
// lib/index.ts
import { Loggers } from "@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
import * as u8a from "uint8arrays";
// lib/hasher.ts
import { shaHasher } from "@sphereon/ssi-types";
var defaultHasher = /* @__PURE__ */ __name((data, algorithm) => {
return shaHasher(data, algorithm);
}, "defaultHasher");
// lib/jwt/JwkThumbprint.ts
var { toString } = u8a;
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 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
import { jwtDecode } from "jwt-decode";
function parseJWT(jwt) {
const header = jwtDecode(jwt, {
header: true
});
const payload = 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 jwtDecode(jwt, {
header: true
});
}, "decodeProtectedHeader");
var decodeJwt = /* @__PURE__ */ __name((jwt) => {
return 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
import { jwtDecode as jwtDecode2 } from "jwt-decode";
import * as u8a2 from "uint8arrays";
import { v4 as uuidv4 } from "uuid";
var { toString: toString2 } = u8a2;
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 ? toString2(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: uuidv4(),
...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 = toString2(defaultHasher(accessToken, "sha256"), "base64url");
if (dPoPPayload.ath !== expectedAth) {
throw new Error("invalid_dpop_proof. Invalid ath claim");
}
const accessTokenPayload = jwtDecode2(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 = jwtDecode2(request.headers.authorization, {
header: false
});
const tokenType = tokenPayload.token_type;
if (tokenType !== "DPoP") {
return;
}
return verifyDPoP(request, {
...options,
expectAccessToken: true
});
}
__name(verifyResourceDPoP, "verifyResourceDPoP");
// lib/helpers/Encodings.ts
import * as u8a3 from "uint8arrays";
var { fromString, toString: toString3 } = u8a3;
function base64ToHexString(input, encoding) {
return toString3(fromString(input, encoding ?? "base64url"), "base16");
}
__name(base64ToHexString, "base64ToHexString");
function fromBase64(base64) {
return base64.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
}
__name(fromBase64, "fromBase64");
function base64urlEncodeBuffer(buf) {
return fromBase64(buf.toString("base64"));
}
__name(base64urlEncodeBuffer, "base64urlEncodeBuffer");
function base64urlToString(base64url) {
const uint8array = fromString(base64url, "base64url");
return toString3(uint8array, "ascii");
}
__name(base64urlToString, "base64urlToString");
// lib/index.ts
import { v4 } from "uuid";
var VCI_LOGGERS = Loggers.DEFAULT;
var VCI_LOG_COMMON = VCI_LOGGERS.get("sphereon:oid4vci:common");
export {
BASE64_URL_REGEX,
SigningAlgo,
VCI_LOGGERS,
VCI_LOG_COMMON,
base64ToHexString,
base64urlEncodeBuffer,
base64urlToString,
calculateJwkThumbprint,
calculateJwkThumbprintUri,
checkExp,
createDPoP,
decodeJwt,
decodeProtectedHeader,
defaultHasher,
dpopTokenRequestNonceError,
epochTime,
fromBase64,
getCreateDPoPOptions,
getDidJwtVerifier,
getDigestAlgorithmFromJwkThumbprintUri,
getJwkVerifier,
getJwtVerifierWithContext,
getNowSkewed,
getX5cVerifier,
isJwe,
isJws,
parseJWT,
v4 as uuidv4,
verifyDPoP,
verifyResourceDPoP
};
//# sourceMappingURL=index.js.map