UNPKG

@gaonengwww/jose

Version:

JWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes

210 lines (203 loc) 6.37 kB
// src/lib/buffer_utils.ts var encoder = new TextEncoder(); var decoder = new TextDecoder(); var MAX_INT32 = 2 ** 32; function concat(...buffers) { const size = buffers.reduce((acc, { length }) => acc + length, 0); const buf = new Uint8Array(size); let i = 0; for (const buffer of buffers) { buf.set(buffer, i); i += buffer.length; } return buf; } // 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("")); } // src/util/base64url.ts function encode(input) { let unencoded = input; if (typeof unencoded === "string") { unencoded = encoder.encode(unencoded); } if (Uint8Array.prototype.toBase64) { return unencoded.toBase64({ alphabet: "base64url", omitPadding: true }); } return encodeBase64(unencoded).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_"); } // src/lib/crypto_key.ts function unusable(name, prop = "algorithm.name") { return new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`); } function isAlgorithm(algorithm, name) { return algorithm.name === name; } function getHashLength(hash) { return parseInt(hash.name.slice(4), 10); } function checkUsage(key, usage) { if (usage && !key.usages.includes(usage)) { throw new TypeError( `CryptoKey does not support this operation, its usages must include ${usage}.` ); } } function checkEncCryptoKey(key, alg, usage) { switch (alg) { case "A128GCM": case "A192GCM": case "A256GCM": { if (!isAlgorithm(key.algorithm, "AES-GCM")) throw unusable("AES-GCM"); const expected = parseInt(alg.slice(1, 4), 10); const actual = key.algorithm.length; if (actual !== expected) throw unusable(expected, "algorithm.length"); break; } case "A128KW": case "A192KW": case "A256KW": { if (!isAlgorithm(key.algorithm, "AES-KW")) throw unusable("AES-KW"); const expected = parseInt(alg.slice(1, 4), 10); const actual = key.algorithm.length; if (actual !== expected) throw unusable(expected, "algorithm.length"); break; } case "ECDH": { switch (key.algorithm.name) { case "ECDH": case "X25519": break; default: throw unusable("ECDH or X25519"); } break; } case "PBES2-HS256+A128KW": case "PBES2-HS384+A192KW": case "PBES2-HS512+A256KW": if (!isAlgorithm(key.algorithm, "PBKDF2")) throw unusable("PBKDF2"); break; case "RSA-OAEP": case "RSA-OAEP-256": case "RSA-OAEP-384": case "RSA-OAEP-512": { if (!isAlgorithm(key.algorithm, "RSA-OAEP")) throw unusable("RSA-OAEP"); const expected = parseInt(alg.slice(9), 10) || 1; const actual = getHashLength(key.algorithm.hash); if (actual !== expected) throw unusable(`SHA-${expected}`, "algorithm.hash"); break; } default: throw new TypeError("CryptoKey does not support this operation"); } checkUsage(key, usage); } // src/lib/aeskw.ts function checkKeySize(key, alg) { if (key.algorithm.length !== parseInt(alg.slice(1, 4), 10)) { throw new TypeError(`Invalid key size for alg: ${alg}`); } } function getCryptoKey(key, alg, usage) { if (key instanceof Uint8Array) { return crypto.subtle.importKey("raw", key, "AES-KW", true, [usage]); } checkEncCryptoKey(key, alg, usage); return key; } async function wrap(alg, key, cek) { const cryptoKey = await getCryptoKey(key, alg, "wrapKey"); checkKeySize(cryptoKey, alg); const cryptoKeyCek = await crypto.subtle.importKey( "raw", cek, { hash: "SHA-256", name: "HMAC" }, true, ["sign"] ); return new Uint8Array(await crypto.subtle.wrapKey("raw", cryptoKeyCek, cryptoKey, "AES-KW")); } async function unwrap(alg, key, encryptedKey) { const cryptoKey = await getCryptoKey(key, alg, "unwrapKey"); checkKeySize(cryptoKey, alg); const cryptoKeyCek = await crypto.subtle.unwrapKey( "raw", encryptedKey, cryptoKey, "AES-KW", { hash: "SHA-256", name: "HMAC" }, true, ["sign"] ); return new Uint8Array(await crypto.subtle.exportKey("raw", cryptoKeyCek)); } // 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 JWEInvalid = class extends JOSEError { /** @ignore */ static code = "ERR_JWE_INVALID"; /** A unique error code for {@link JWEInvalid}. */ code = "ERR_JWE_INVALID"; }; // src/lib/pbes2kw.ts function getCryptoKey2(key, alg) { if (key instanceof Uint8Array) { return crypto.subtle.importKey("raw", key, "PBKDF2", false, ["deriveBits"]); } checkEncCryptoKey(key, alg, "deriveBits"); return key; } var concatSalt = (alg, p2sInput) => concat(encoder.encode(alg), new Uint8Array([0]), p2sInput); async function deriveKey(p2s, alg, p2c, key) { if (!(p2s instanceof Uint8Array) || p2s.length < 8) { throw new JWEInvalid("PBES2 Salt Input must be 8 or more octets"); } const salt = concatSalt(alg, p2s); const keylen = parseInt(alg.slice(13, 16), 10); const subtleAlg = { hash: `SHA-${alg.slice(8, 11)}`, iterations: p2c, name: "PBKDF2", salt }; const cryptoKey = await getCryptoKey2(key, alg); return new Uint8Array(await crypto.subtle.deriveBits(subtleAlg, cryptoKey, keylen)); } async function wrap2(alg, key, cek, p2c = 2048, p2s = crypto.getRandomValues(new Uint8Array(16))) { const derived = await deriveKey(p2s, alg, p2c, key); const encryptedKey = await wrap(alg.slice(-6), derived, cek); return { encryptedKey, p2c, p2s: encode(p2s) }; } async function unwrap2(alg, key, encryptedKey, p2c, p2s) { const derived = await deriveKey(p2s, alg, p2c, key); return unwrap(alg.slice(-6), derived, encryptedKey); } export { unwrap2 as unwrap, wrap2 as wrap };