@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
JavaScript
// 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
};