@gaonengwww/jose
Version:
JWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes
436 lines (428 loc) • 12.9 kB
JavaScript
// src/lib/buffer_utils.ts
var encoder = new TextEncoder();
var decoder = new TextDecoder();
var MAX_INT32 = 2 ** 32;
// src/lib/base64.ts
function encodeBase64(input) {
if (Uint8Array.prototype.toBase64) {
return input.toBase64();
}
const CHUNK_SIZE = 32768;
const arr = [];
for (let i = 0; i < input.length; i += CHUNK_SIZE) {
arr.push(String.fromCharCode.apply(null, input.subarray(i, i + CHUNK_SIZE)));
}
return btoa(arr.join(""));
}
function decodeBase64(encoded) {
if (Uint8Array.fromBase64) {
return Uint8Array.fromBase64(encoded);
}
const binary = atob(encoded);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes;
}
// src/util/base64url.ts
function decode(input) {
if (Uint8Array.fromBase64) {
return Uint8Array.fromBase64(typeof input === "string" ? input : decoder.decode(input), {
alphabet: "base64url"
});
}
let encoded = input;
if (encoded instanceof Uint8Array) {
encoded = decoder.decode(encoded);
}
encoded = encoded.replace(/-/g, "+").replace(/_/g, "/").replace(/\s/g, "");
try {
return decodeBase64(encoded);
} catch {
throw new TypeError("The input to be decoded is not correctly encoded.");
}
}
// src/util/errors.ts
var JOSEError = class extends Error {
/**
* A unique error code for the particular error subclass.
*
* @ignore
*/
static code = "ERR_JOSE_GENERIC";
/** A unique error code for {@link JOSEError}. */
code = "ERR_JOSE_GENERIC";
/** @ignore */
constructor(message, options) {
super(message, options);
this.name = this.constructor.name;
Error.captureStackTrace?.(this, this.constructor);
}
};
var JOSENotSupported = class extends JOSEError {
/** @ignore */
static code = "ERR_JOSE_NOT_SUPPORTED";
/** A unique error code for {@link JOSENotSupported}. */
code = "ERR_JOSE_NOT_SUPPORTED";
};
// src/lib/asn1.ts
var formatPEM = (b64, descriptor) => {
const newlined = (b64.match(/.{1,64}/g) || []).join("\n");
return `-----BEGIN ${descriptor}-----
${newlined}
-----END ${descriptor}-----`;
};
var findOid = (keyData, oid, from = 0) => {
if (from === 0) {
oid.unshift(oid.length);
oid.unshift(6);
}
const i = keyData.indexOf(oid[0], from);
if (i === -1) return false;
const sub = keyData.subarray(i, i + oid.length);
if (sub.length !== oid.length) return false;
return sub.every((value, index) => value === oid[index]) || findOid(keyData, oid, i + 1);
};
var getNamedCurve = (keyData) => {
switch (true) {
case findOid(keyData, [42, 134, 72, 206, 61, 3, 1, 7]):
return "P-256";
case findOid(keyData, [43, 129, 4, 0, 34]):
return "P-384";
case findOid(keyData, [43, 129, 4, 0, 35]):
return "P-521";
default:
return void 0;
}
};
var genericImport = async (replace, keyFormat, pem, alg, options) => {
let algorithm;
let keyUsages;
const keyData = new Uint8Array(
atob(pem.replace(replace, "")).split("").map((c) => c.charCodeAt(0))
);
const isPublic = keyFormat === "spki";
switch (alg) {
case "PS256":
case "PS384":
case "PS512":
algorithm = { name: "RSA-PSS", hash: `SHA-${alg.slice(-3)}` };
keyUsages = isPublic ? ["verify"] : ["sign"];
break;
case "RS256":
case "RS384":
case "RS512":
algorithm = { name: "RSASSA-PKCS1-v1_5", hash: `SHA-${alg.slice(-3)}` };
keyUsages = isPublic ? ["verify"] : ["sign"];
break;
case "RSA-OAEP":
case "RSA-OAEP-256":
case "RSA-OAEP-384":
case "RSA-OAEP-512":
algorithm = {
name: "RSA-OAEP",
hash: `SHA-${parseInt(alg.slice(-3), 10) || 1}`
};
keyUsages = isPublic ? ["encrypt", "wrapKey"] : ["decrypt", "unwrapKey"];
break;
case "ES256":
algorithm = { name: "ECDSA", namedCurve: "P-256" };
keyUsages = isPublic ? ["verify"] : ["sign"];
break;
case "ES384":
algorithm = { name: "ECDSA", namedCurve: "P-384" };
keyUsages = isPublic ? ["verify"] : ["sign"];
break;
case "ES512":
algorithm = { name: "ECDSA", namedCurve: "P-521" };
keyUsages = isPublic ? ["verify"] : ["sign"];
break;
case "ECDH-ES":
case "ECDH-ES+A128KW":
case "ECDH-ES+A192KW":
case "ECDH-ES+A256KW": {
const namedCurve = getNamedCurve(keyData);
algorithm = namedCurve?.startsWith("P-") ? { name: "ECDH", namedCurve } : { name: "X25519" };
keyUsages = isPublic ? [] : ["deriveBits"];
break;
}
case "Ed25519":
// Fall through
case "EdDSA":
algorithm = { name: "Ed25519" };
keyUsages = isPublic ? ["verify"] : ["sign"];
break;
default:
throw new JOSENotSupported('Invalid or unsupported "alg" (Algorithm) value');
}
return crypto.subtle.importKey(
keyFormat,
keyData,
algorithm,
options?.extractable ?? (isPublic ? true : false),
keyUsages
);
};
var fromPKCS8 = (pem, alg, options) => {
return genericImport(/(?:-----(?:BEGIN|END) PRIVATE KEY-----|\s)/g, "pkcs8", pem, alg, options);
};
var fromSPKI = (pem, alg, options) => {
return genericImport(/(?:-----(?:BEGIN|END) PUBLIC KEY-----|\s)/g, "spki", pem, alg, options);
};
function getElement(seq) {
const result = [];
let next = 0;
while (next < seq.length) {
const nextPart = parseElement(seq.subarray(next));
result.push(nextPart);
next += nextPart.byteLength;
}
return result;
}
function parseElement(bytes) {
let position = 0;
let tag = bytes[0] & 31;
position++;
if (tag === 31) {
tag = 0;
while (bytes[position] >= 128) {
tag = tag * 128 + bytes[position] - 128;
position++;
}
tag = tag * 128 + bytes[position] - 128;
position++;
}
let length = 0;
if (bytes[position] < 128) {
length = bytes[position];
position++;
} else if (length === 128) {
length = 0;
while (bytes[position + length] !== 0 || bytes[position + length + 1] !== 0) {
if (length > bytes.byteLength) {
throw new TypeError("invalid indefinite form length");
}
length++;
}
const byteLength2 = position + length + 2;
return {
byteLength: byteLength2,
contents: bytes.subarray(position, position + length),
raw: bytes.subarray(0, byteLength2)
};
} else {
const numberOfDigits = bytes[position] & 127;
position++;
length = 0;
for (let i = 0; i < numberOfDigits; i++) {
length = length * 256 + bytes[position];
position++;
}
}
const byteLength = position + length;
return {
byteLength,
contents: bytes.subarray(position, byteLength),
raw: bytes.subarray(0, byteLength)
};
}
function spkiFromX509(buf) {
const tbsCertificate = getElement(getElement(parseElement(buf).contents)[0].contents);
return encodeBase64(tbsCertificate[tbsCertificate[0].raw[0] === 160 ? 6 : 5].raw);
}
var createPublicKey;
function getSPKI(x509) {
try {
createPublicKey ??= globalThis.process?.getBuiltinModule?.("node:crypto")?.createPublicKey;
} catch {
createPublicKey = 0;
}
if (createPublicKey) {
try {
return new createPublicKey(x509).export({ format: "pem", type: "spki" });
} catch {
}
}
const pem = x509.replace(/(?:-----(?:BEGIN|END) CERTIFICATE-----|\s)/g, "");
const raw = decodeBase64(pem);
return formatPEM(spkiFromX509(raw), "PUBLIC KEY");
}
var fromX509 = (pem, alg, options) => {
let spki;
try {
spki = getSPKI(pem);
} catch (cause) {
throw new TypeError("Failed to parse the X.509 certificate", { cause });
}
return fromSPKI(spki, alg, options);
};
// src/lib/jwk_to_key.ts
function subtleMapping(jwk) {
let algorithm;
let keyUsages;
switch (jwk.kty) {
case "RSA": {
switch (jwk.alg) {
case "PS256":
case "PS384":
case "PS512":
algorithm = { name: "RSA-PSS", hash: `SHA-${jwk.alg.slice(-3)}` };
keyUsages = jwk.d ? ["sign"] : ["verify"];
break;
case "RS256":
case "RS384":
case "RS512":
algorithm = { name: "RSASSA-PKCS1-v1_5", hash: `SHA-${jwk.alg.slice(-3)}` };
keyUsages = jwk.d ? ["sign"] : ["verify"];
break;
case "RSA-OAEP":
case "RSA-OAEP-256":
case "RSA-OAEP-384":
case "RSA-OAEP-512":
algorithm = {
name: "RSA-OAEP",
hash: `SHA-${parseInt(jwk.alg.slice(-3), 10) || 1}`
};
keyUsages = jwk.d ? ["decrypt", "unwrapKey"] : ["encrypt", "wrapKey"];
break;
default:
throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value');
}
break;
}
case "EC": {
switch (jwk.alg) {
case "ES256":
algorithm = { name: "ECDSA", namedCurve: "P-256" };
keyUsages = jwk.d ? ["sign"] : ["verify"];
break;
case "ES384":
algorithm = { name: "ECDSA", namedCurve: "P-384" };
keyUsages = jwk.d ? ["sign"] : ["verify"];
break;
case "ES512":
algorithm = { name: "ECDSA", namedCurve: "P-521" };
keyUsages = jwk.d ? ["sign"] : ["verify"];
break;
case "ECDH-ES":
case "ECDH-ES+A128KW":
case "ECDH-ES+A192KW":
case "ECDH-ES+A256KW":
algorithm = { name: "ECDH", namedCurve: jwk.crv };
keyUsages = jwk.d ? ["deriveBits"] : [];
break;
default:
throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value');
}
break;
}
case "OKP": {
switch (jwk.alg) {
case "Ed25519":
// Fall through
case "EdDSA":
algorithm = { name: "Ed25519" };
keyUsages = jwk.d ? ["sign"] : ["verify"];
break;
case "ECDH-ES":
case "ECDH-ES+A128KW":
case "ECDH-ES+A192KW":
case "ECDH-ES+A256KW":
algorithm = { name: jwk.crv };
keyUsages = jwk.d ? ["deriveBits"] : [];
break;
default:
throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value');
}
break;
}
default:
throw new JOSENotSupported('Invalid or unsupported JWK "kty" (Key Type) Parameter value');
}
return { algorithm, keyUsages };
}
var jwk_to_key_default = async (jwk) => {
if (!jwk.alg) {
throw new TypeError('"alg" argument is required when "jwk.alg" is not present');
}
const { algorithm, keyUsages } = subtleMapping(jwk);
const keyData = { ...jwk };
delete keyData.alg;
delete keyData.use;
return crypto.subtle.importKey(
"jwk",
keyData,
algorithm,
jwk.ext ?? (jwk.d ? false : true),
jwk.key_ops ?? keyUsages
);
};
// src/lib/is_object.ts
function isObjectLike(value) {
return typeof value === "object" && value !== null;
}
var is_object_default = (input) => {
if (!isObjectLike(input) || Object.prototype.toString.call(input) !== "[object Object]") {
return false;
}
if (Object.getPrototypeOf(input) === null) {
return true;
}
let proto = input;
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}
return Object.getPrototypeOf(input) === proto;
};
// src/key/import.ts
async function importSPKI(spki, alg, options) {
if (typeof spki !== "string" || spki.indexOf("-----BEGIN PUBLIC KEY-----") !== 0) {
throw new TypeError('"spki" must be SPKI formatted string');
}
return fromSPKI(spki, alg, options);
}
async function importX509(x509, alg, options) {
if (typeof x509 !== "string" || x509.indexOf("-----BEGIN CERTIFICATE-----") !== 0) {
throw new TypeError('"x509" must be X.509 formatted string');
}
return fromX509(x509, alg, options);
}
async function importPKCS8(pkcs8, alg, options) {
if (typeof pkcs8 !== "string" || pkcs8.indexOf("-----BEGIN PRIVATE KEY-----") !== 0) {
throw new TypeError('"pkcs8" must be PKCS#8 formatted string');
}
return fromPKCS8(pkcs8, alg, options);
}
async function importJWK(jwk, alg, options) {
if (!is_object_default(jwk)) {
throw new TypeError("JWK must be an object");
}
let ext;
alg ??= jwk.alg;
ext ??= options?.extractable ?? jwk.ext;
switch (jwk.kty) {
case "oct":
if (typeof jwk.k !== "string" || !jwk.k) {
throw new TypeError('missing "k" (Key Value) Parameter value');
}
return decode(jwk.k);
case "RSA":
if ("oth" in jwk && jwk.oth !== void 0) {
throw new JOSENotSupported(
'RSA JWK "oth" (Other Primes Info) Parameter value is not supported'
);
}
case "EC":
case "OKP":
return jwk_to_key_default({ ...jwk, alg, ext });
default:
throw new JOSENotSupported('Unsupported "kty" (Key Type) Parameter value');
}
}
export {
importJWK,
importPKCS8,
importSPKI,
importX509
};