@gaonengwww/jose
Version:
JWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes
308 lines (303 loc) • 8.92 kB
JavaScript
// src/lib/invalid_key_input.ts
function message(msg, actual, ...types) {
types = types.filter(Boolean);
if (types.length > 2) {
const last = types.pop();
msg += `one of type ${types.join(", ")}, or ${last}.`;
} else if (types.length === 2) {
msg += `one of type ${types[0]} or ${types[1]}.`;
} else {
msg += `of type ${types[0]}.`;
}
if (actual == null) {
msg += ` Received ${actual}`;
} else if (typeof actual === "function" && actual.name) {
msg += ` Received function ${actual.name}`;
} else if (typeof actual === "object" && actual != null) {
if (actual.constructor?.name) {
msg += ` Received an instance of ${actual.constructor.name}`;
}
}
return msg;
}
var invalid_key_input_default = (actual, ...types) => {
return message("Key must be ", actual, ...types);
};
// 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/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(message2, options) {
super(message2, 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/is_key_like.ts
function isCryptoKey(key) {
return key?.[Symbol.toStringTag] === "CryptoKey";
}
function isKeyObject(key) {
return key?.[Symbol.toStringTag] === "KeyObject";
}
// src/lib/asn1.ts
var formatPEM = (b64, descriptor) => {
const newlined = (b64.match(/.{1,64}/g) || []).join("\n");
return `-----BEGIN ${descriptor}-----
${newlined}
-----END ${descriptor}-----`;
};
var genericExport = async (keyType, keyFormat, key) => {
if (isKeyObject(key)) {
if (key.type !== keyType) {
throw new TypeError(`key is not a ${keyType} key`);
}
return key.export({ format: "pem", type: keyFormat });
}
if (!isCryptoKey(key)) {
throw new TypeError(invalid_key_input_default(key, "CryptoKey", "KeyObject"));
}
if (!key.extractable) {
throw new TypeError("CryptoKey is not extractable");
}
if (key.type !== keyType) {
throw new TypeError(`key is not a ${keyType} key`);
}
return formatPEM(
encodeBase64(new Uint8Array(await crypto.subtle.exportKey(keyFormat, key))),
`${keyType.toUpperCase()} KEY`
);
};
var toSPKI = (key) => {
return genericExport("public", "spki", key);
};
var toPKCS8 = (key) => {
return genericExport("private", "pkcs8", key);
};
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);
};
export {
fromPKCS8,
fromSPKI,
fromX509,
toPKCS8,
toSPKI
};