🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

jose

Package Overview
Dependencies
Maintainers
1
Versions
241
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

jose - npm Package Compare versions

Comparing version
6.2.6
to
6.2.7
+2
-2
dist/webapi/jwe/compact/decrypt.js
import { prepareDecrypt, decryptCompact } from '../../lib/jwe_decrypt.js';
export async function compactDecrypt(jwe, key, options) {
const decrypted = await decryptCompact(jwe, prepareDecrypt(options), key);
const result = { plaintext: decrypted.plaintext, protectedHeader: decrypted.parsedProt };
const result = { plaintext: decrypted[0], protectedHeader: decrypted[1] };
if (typeof key === 'function') {
return { ...result, key: decrypted.key };
return { ...result, key: decrypted[2] };
}
return result;
}

@@ -59,15 +59,15 @@ import { unprotected, assertNotSet } from '../../lib/helpers.js';

validateCritDuplicates(JWEInvalid, this.#protectedHeader);
return createJWE({
plaintext: this.#plaintext,
protectedHeader: this.#protectedHeader,
unprotectedHeader: this.#unprotectedHeader,
sharedUnprotectedHeader: this.#sharedUnprotectedHeader,
aad: this.#aad,
cek: this.#cek,
iv: this.#iv,
keyManagementParameters: this.#keyManagementParameters,
crit: options?.crit,
unprotectedParameters: options ? unprotected in options : false,
}, key);
return createJWE([
this.#plaintext,
this.#protectedHeader,
this.#unprotectedHeader,
this.#sharedUnprotectedHeader,
this.#aad,
this.#cek,
this.#iv,
this.#keyManagementParameters,
options?.crit,
options ? unprotected in options : false,
], key);
}
}

@@ -13,19 +13,15 @@ import { FlattenedEncrypt } from '../flattened/encrypt.js';

#parent;
unprotectedHeader;
keyManagementParameters;
key;
options;
constructor(enc, key, options) {
state;
constructor(enc, key, crit) {
this.#parent = enc;
this.key = key;
this.options = options;
this.state = [undefined, undefined, key, crit];
}
setUnprotectedHeader(unprotectedHeader) {
assertNotSet(this.unprotectedHeader, 'setUnprotectedHeader');
this.unprotectedHeader = unprotectedHeader;
assertNotSet(this.state[0], 'setUnprotectedHeader');
this.state[0] = unprotectedHeader;
return this;
}
setKeyManagementParameters(parameters) {
assertNotSet(this.keyManagementParameters, 'setKeyManagementParameters');
this.keyManagementParameters = parameters;
assertNotSet(this.state[1], 'setKeyManagementParameters');
this.state[1] = parameters;
return this;

@@ -43,2 +39,13 @@ }

}
function copyOptionalMembers(flattened, jwe, recipient) {
const { aad, protected: protectedHeader, unprotected, header } = flattened;
if (aad)
jwe.aad = aad;
if (protectedHeader)
jwe.protected = protectedHeader;
if (unprotected)
jwe.unprotected = unprotected;
if (header)
recipient.header = header;
}
export class GeneralEncrypt {

@@ -54,3 +61,3 @@ #plaintext;

addRecipient(key, options) {
const recipient = new IndividualRecipient(this, key, { crit: options?.crit });
const recipient = new IndividualRecipient(this, key, options?.crit);
this.#recipients.push(recipient);

@@ -82,2 +89,3 @@ return recipient;

const [recipient] = this.#recipients;
const [unprotectedHeader, keyManagementParameters, key, crit] = recipient.state;
const flattened = await new FlattenedEncrypt(this.#plaintext)

@@ -87,5 +95,5 @@ .setAdditionalAuthenticatedData(this.#aad)

.setSharedUnprotectedHeader(this.#unprotectedHeader)
.setUnprotectedHeader(recipient.unprotectedHeader)
.setKeyManagementParameters(recipient.keyManagementParameters)
.encrypt(recipient.key, { ...recipient.options });
.setUnprotectedHeader(unprotectedHeader)
.setKeyManagementParameters(keyManagementParameters)
.encrypt(key, { crit });
const jwe = {

@@ -97,12 +105,5 @@ ciphertext: flattened.ciphertext,

};
if (flattened.aad)
jwe.aad = flattened.aad;
if (flattened.protected)
jwe.protected = flattened.protected;
if (flattened.unprotected)
jwe.unprotected = flattened.unprotected;
if (flattened.encrypted_key)
jwe.recipients[0].encrypted_key = flattened.encrypted_key;
if (flattened.header)
jwe.recipients[0].header = flattened.header;
copyOptionalMembers(flattened, jwe, jwe.recipients[0]);
return jwe;

@@ -116,26 +117,29 @@ }

const recipient = this.#recipients[i];
const input = {
plaintext: this.#plaintext,
protectedHeader: this.#protectedHeader,
unprotectedHeader: recipient.unprotectedHeader,
sharedUnprotectedHeader: this.#unprotectedHeader,
aad: this.#aad,
keyManagementParameters: recipient.keyManagementParameters,
crit: recipient.options.crit,
unprotectedParameters: true,
};
const [unprotectedHeader, keyManagementParameters, , crit] = recipient.state;
const input = [
this.#plaintext,
this.#protectedHeader,
unprotectedHeader,
this.#unprotectedHeader,
this.#aad,
undefined,
undefined,
keyManagementParameters,
crit,
true,
];
const headers = checkEncryptHeaders(input);
inputs.push(input);
checked.push(headers);
if (headers.alg === 'dir' || headers.alg === 'ECDH-ES') {
if (headers[1] === 'dir' || headers[1] === 'ECDH-ES') {
throw new JWEInvalid('"dir" and "ECDH-ES" alg may only be used with a single recipient');
}
if (!enc) {
enc = headers.enc;
enc = headers[2];
}
else if (enc !== headers.enc) {
else if (enc !== headers[2]) {
throw new JWEInvalid('JWE "enc" (Encryption Algorithm) Header Parameter must be the same for all recipients');
}
}
const cek = generateCek(checked[0].encEntry);
const cek = generateCek(checked[0][3]);
const jwe = {

@@ -147,26 +151,21 @@ ciphertext: '',

const recipient = this.#recipients[i];
const [unprotectedHeader, keyManagementParameters, key] = recipient.state;
const target = {};
jwe.recipients.push(target);
if (i === 0) {
const flattened = await encryptJWE({ ...inputs[0], cek }, checked[0], recipient.key);
inputs[0][5] = cek;
const flattened = await encryptJWE(inputs[0], checked[0], key);
jwe.ciphertext = flattened.ciphertext;
jwe.iv = flattened.iv;
jwe.tag = flattened.tag;
if (flattened.aad)
jwe.aad = flattened.aad;
if (flattened.protected)
jwe.protected = flattened.protected;
if (flattened.unprotected)
jwe.unprotected = flattened.unprotected;
target.encrypted_key = flattened.encrypted_key;
if (flattened.header)
target.header = flattened.header;
copyOptionalMembers(flattened, jwe, target);
continue;
}
const { alg } = checked[i];
const k = await prepareKey(jweAlgorithm(alg), recipient.key, 'encrypt');
const { encryptedKey, parameters } = await encryptKeyManagement(alg, checked[i].encEntry, k, cek, recipient.keyManagementParameters);
const [, alg, , encEntry] = checked[i];
const k = await prepareKey(jweAlgorithm(alg), key, 'encrypt');
const [, encryptedKey, parameters] = await encryptKeyManagement(alg, encEntry, k, cek, keyManagementParameters);
target.encrypted_key = b64u(encryptedKey);
if (recipient.unprotectedHeader || parameters)
target.header = { ...recipient.unprotectedHeader, ...parameters };
if (unprotectedHeader || parameters)
target.header = { ...unprotectedHeader, ...parameters };
}

@@ -173,0 +172,0 @@ return jwe;

@@ -7,3 +7,3 @@ import { jwkToKey } from '../lib/jwk_to_key.js';

const entry = typeof alg === 'string' ? maybeJWSAlgorithm(alg) : undefined;
if (!entry || entry.symmetric) {
if (!entry || entry.secret) {
throw new JOSENotSupported('Unsupported "alg" value for a JSON Web Key Set');

@@ -18,7 +18,4 @@ }

const { keys } = jwks;
return Array.isArray(keys) && keys.every(isJWKLike);
return Array.isArray(keys) && keys.every((isObject));
}
function isJWKLike(key) {
return isObject(key);
}
class LocalJWKSetImpl {

@@ -39,21 +36,8 @@ #jwks;

const entry = signatureAlgorithm(alg);
const candidates = this.#jwks.keys.filter((jwk) => {
let candidate = entry.kty.includes(jwk.kty);
if (candidate && typeof kid === 'string') {
candidate = kid === jwk.kid;
}
if (candidate && (typeof jwk.alg === 'string' || jwk.kty === 'AKP')) {
candidate = alg === jwk.alg;
}
if (candidate && typeof jwk.use === 'string') {
candidate = jwk.use === 'sig';
}
if (candidate && Array.isArray(jwk.key_ops)) {
candidate = jwk.key_ops.includes('verify');
}
if (candidate && entry.crv) {
candidate = jwk.crv === entry.crv;
}
return candidate;
});
const candidates = this.#jwks.keys.filter((jwk) => entry.kty.includes(jwk.kty) &&
(typeof kid !== 'string' || kid === jwk.kid) &&
(!(typeof jwk.alg === 'string' || jwk.kty === 'AKP') || alg === jwk.alg) &&
(typeof jwk.use !== 'string' || jwk.use === 'sig') &&
(!Array.isArray(jwk.key_ops) || jwk.key_ops.includes('verify')) &&
(!entry.crv || jwk.crv === entry.crv));
const { 0: jwk, length } = candidates;

@@ -93,11 +77,6 @@ if (length === 0) {

const localJWKSet = async (protectedHeader, token) => set.getKey(protectedHeader, token);
Object.defineProperties(localJWKSet, {
jwks: {
value: () => structuredClone(set.jwks()),
enumerable: false,
configurable: false,
writable: false,
},
Object.defineProperty(localJWKSet, 'jwks', {
value: () => structuredClone(set.jwks()),
});
return localJWKSet;
}

@@ -12,3 +12,3 @@ import { JOSEError, JWKSNoMatchingKey, JWKSTimeout } from '../util/errors.js';

const NAME = 'jose';
const VERSION = 'v6.2.6';
const VERSION = 'v6.2.7';
USER_AGENT = `${NAME}/${VERSION}`;

@@ -71,8 +71,8 @@ }

this.#url = new URL(url.href);
this.#timeoutDuration =
typeof options?.timeoutDuration === 'number' ? options?.timeoutDuration : 5000;
const opts = options ?? {};
this.#timeoutDuration = typeof opts.timeoutDuration === 'number' ? opts.timeoutDuration : 5000;
this.#cooldownDuration =
typeof options?.cooldownDuration === 'number' ? options?.cooldownDuration : 30000;
this.#cacheMaxAge = typeof options?.cacheMaxAge === 'number' ? options?.cacheMaxAge : 600000;
this.#headers = new Headers(options?.headers);
typeof opts.cooldownDuration === 'number' ? opts.cooldownDuration : 30000;
this.#cacheMaxAge = typeof opts.cacheMaxAge === 'number' ? opts.cacheMaxAge : 600000;
this.#headers = new Headers(opts.headers);
if (USER_AGENT && !this.#headers.has('User-Agent')) {

@@ -85,6 +85,7 @@ this.#headers.set('User-Agent', USER_AGENT);

}
this.#customFetch = options?.[customFetch];
if (options?.[jwksCache] !== undefined) {
this.#cache = options?.[jwksCache];
if (isFreshJwksCache(options?.[jwksCache], this.#cacheMaxAge)) {
this.#customFetch = opts[customFetch];
const cache = opts[jwksCache];
if (cache !== undefined) {
this.#cache = cache;
if (isFreshJwksCache(cache, this.#cacheMaxAge)) {
this.#jwksTimestamp = this.#cache.uat;

@@ -98,11 +99,10 @@ this.#local = createLocalJWKSet(this.#cache.jwks);

}
#validFor(duration) {
return typeof this.#jwksTimestamp === 'number' && Date.now() < this.#jwksTimestamp + duration;
}
coolingDown() {
return typeof this.#jwksTimestamp === 'number'
? Date.now() < this.#jwksTimestamp + this.#cooldownDuration
: false;
return this.#validFor(this.#cooldownDuration);
}
fresh() {
return typeof this.#jwksTimestamp === 'number'
? Date.now() < this.#jwksTimestamp + this.#cacheMaxAge
: false;
return this.#validFor(this.#cacheMaxAge);
}

@@ -141,7 +141,5 @@ jwks() {

this.#jwksTimestamp = Date.now();
this.#pendingFetch = undefined;
})
.catch((err) => {
.finally(() => {
this.#pendingFetch = undefined;
throw err;
});

@@ -158,3 +156,2 @@ await this.#pendingFetch;

enumerable: true,
configurable: false,
},

@@ -164,3 +161,2 @@ fresh: {

enumerable: true,
configurable: false,
},

@@ -170,4 +166,2 @@ reload: {

enumerable: true,
configurable: false,
writable: false,
},

@@ -177,3 +171,2 @@ reloading: {

enumerable: true,
configurable: false,
},

@@ -183,4 +176,2 @@ jwks: {

enumerable: true,
configurable: false,
writable: false,
},

@@ -187,0 +178,0 @@ });

import { prepareVerify, verifyCompact } from '../../lib/jws_verify.js';
export async function compactVerify(jws, key, options) {
const verified = await verifyCompact(jws, prepareVerify(options), key);
const result = { payload: verified.payload, protectedHeader: verified.parsedProt };
const result = { payload: verified[0], protectedHeader: verified[1] };
if (typeof key === 'function') {
return { ...result, key: verified.key };
return { ...result, key: verified[3] };
}
return result;
}

@@ -1,2 +0,1 @@

import { JWSInvalid } from '../../util/errors.js';
import { createSignature } from '../../lib/jws_sign.js';

@@ -25,6 +24,3 @@ import { assertNotSet } from '../../lib/helpers.js';

async sign(key, options) {
if (!this.#protectedHeader && !this.#unprotectedHeader) {
throw new JWSInvalid('either setProtectedHeader or setUnprotectedHeader must be called before #sign()');
}
const jws = await createSignature({
return createSignature({
payload: this.#payload,

@@ -35,7 +31,3 @@ protectedHeader: this.#protectedHeader,

}, key);
if (this.#unprotectedHeader) {
jws.header = this.#unprotectedHeader;
}
return jws;
}
}

@@ -6,19 +6,15 @@ import { createSignature } from '../../lib/jws_sign.js';

#parent;
protectedHeader;
unprotectedHeader;
options;
key;
state;
constructor(sig, key, options) {
this.#parent = sig;
this.key = key;
this.options = options;
this.state = [undefined, undefined, key, options?.crit];
}
setProtectedHeader(protectedHeader) {
assertNotSet(this.protectedHeader, 'setProtectedHeader');
this.protectedHeader = protectedHeader;
assertNotSet(this.state[0], 'setProtectedHeader');
this.state[0] = protectedHeader;
return this;
}
setUnprotectedHeader(unprotectedHeader) {
assertNotSet(this.unprotectedHeader, 'setUnprotectedHeader');
this.unprotectedHeader = unprotectedHeader;
assertNotSet(this.state[1], 'setUnprotectedHeader');
this.state[1] = unprotectedHeader;
return this;

@@ -58,18 +54,13 @@ }

};
const encoded = {};
const encoded = [];
for (let i = 0; i < this.#signatures.length; i++) {
const signature = this.#signatures[i];
if (!signature.protectedHeader && !signature.unprotectedHeader) {
throw new JWSInvalid('either setProtectedHeader or setUnprotectedHeader must be called before #sign()');
}
const [protectedHeader, unprotectedHeader, key, crit] = signature.state;
const { payload, ...rest } = await createSignature({
payload: this.#payload,
protectedHeader: signature.protectedHeader,
unprotectedHeader: signature.unprotectedHeader,
crit: signature.options?.crit,
protectedHeader,
unprotectedHeader,
crit,
encoded,
}, signature.key);
if (signature.unprotectedHeader) {
rest.header = signature.unprotectedHeader;
}
}, key);
if (i === 0) {

@@ -76,0 +67,0 @@ jws.payload = payload;

@@ -8,3 +8,4 @@ import { prepareVerify, verifySignature, verifyResult } from '../../lib/jws_verify.js';

}
if (!Array.isArray(jws.signatures) || !jws.signatures.every(isObject)) {
const { signatures, payload } = jws;
if (!Array.isArray(signatures) || !signatures.every(isObject)) {
throw new JWSInvalid('JWS Signatures missing or incorrect type');

@@ -14,3 +15,3 @@ }

try {
if (jws.payload === undefined)
if (payload === undefined)
throw new Error();

@@ -22,18 +23,19 @@ shared = prepareVerify(options);

}
for (const signature of jws.signatures) {
for (const signature of signatures) {
try {
if (signature.protected === undefined && signature.header === undefined)
const { protected: encodedProtected, header, signature: encodedSignature } = signature;
if (encodedProtected === undefined && header === undefined)
throw new Error();
if (signature.protected !== undefined && typeof signature.protected !== 'string') {
if (encodedProtected !== undefined && typeof encodedProtected !== 'string') {
throw new Error();
}
if (typeof signature.signature !== 'string')
if (typeof encodedSignature !== 'string')
throw new Error();
if (signature.header !== undefined && !isObject(signature.header))
if (header !== undefined && !isObject(header))
throw new Error();
return verifyResult(signature, await verifySignature({
header: signature.header,
payload: jws.payload,
protected: signature.protected,
signature: signature.signature,
header,
payload,
protected: encodedProtected,
signature: encodedSignature,
}, shared, key));

@@ -40,0 +42,0 @@ }

@@ -6,4 +6,4 @@ import { prepareDecrypt, decryptCompact } from '../lib/jwe_decrypt.js';

const decrypted = await decryptCompact(jwt, prepareDecrypt(options), key);
const protectedHeader = decrypted.parsedProt;
const payload = validateClaimsSet(protectedHeader, decrypted.plaintext, options);
const protectedHeader = decrypted[1];
const payload = validateClaimsSet(protectedHeader, decrypted[0], options);
if (protectedHeader.iss !== undefined && protectedHeader.iss !== payload.iss) {

@@ -21,5 +21,5 @@ throw new JWTClaimValidationFailed('replicated "iss" claim header parameter mismatch', payload, 'iss', 'mismatch');

if (typeof key === 'function') {
return { ...result, key: decrypted.key };
return { ...result, key: decrypted[2] };
}
return result;
}

@@ -6,11 +6,11 @@ import { prepareVerify, verifyCompact } from '../lib/jws_verify.js';

const verified = await verifyCompact(jwt, prepareVerify(options), key);
if (!verified.b64) {
if (!verified[2]) {
throw new JWTInvalid('JWTs MUST NOT use unencoded payload');
}
const payload = validateClaimsSet(verified.parsedProt, verified.payload, options);
const result = { payload, protectedHeader: verified.parsedProt };
const payload = validateClaimsSet(verified[1], verified[0], options);
const result = { payload, protectedHeader: verified[1] };
if (typeof key === 'function') {
return { ...result, key: verified.key };
return { ...result, key: verified[3] };
}
return result;
}

@@ -36,10 +36,10 @@ import { toSPKI as exportPublic, toPKCS8 as exportPrivate } from '../lib/asn1.js';

}
export async function exportSPKI(key) {
export function exportSPKI(key) {
return exportPublic(key);
}
export async function exportPKCS8(key) {
export function exportPKCS8(key) {
return exportPrivate(key);
}
export async function exportJWK(key) {
export function exportJWK(key) {
return keyToJWK(key);
}

@@ -12,12 +12,13 @@ import { JOSENotSupported } from '../util/errors.js';

const entry = keyAlgorithm(alg);
if (entry.symmetric) {
if (entry.secret) {
throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value');
}
let algorithm;
if (entry.subtleFor) {
switch (options?.crv ?? 'P-256') {
if (entry.resolve) {
const crv = options?.crv ?? 'P-256';
switch (crv) {
case 'P-256':
case 'P-384':
case 'P-521':
algorithm = { name: 'ECDH', namedCurve: options?.crv ?? 'P-256' };
algorithm = { name: 'ECDH', namedCurve: crv };
break;

@@ -45,5 +46,5 @@ case 'X25519':

return crypto.subtle.generateKey(algorithm, options?.extractable ?? false, [
...entry.usages.private,
...entry.usages.public,
...entry.usages[1],
...entry.usages[0],
]);
}

@@ -60,9 +60,6 @@ import { invalidKeyInput } from './invalid_key_input.js';

const skipElement = (state, count = 1) => {
if (count <= 0)
return;
state.pos++;
const length = parseLength(state);
state.pos += length;
if (count > 1) {
skipElement(state, count - 1);
while (count-- > 0) {
state.pos++;
const length = parseLength(state);
state.pos += length;
}

@@ -88,20 +85,12 @@ };

};
function parsePKCS8Header(state) {
expectTag(state, 0x30, 'Invalid PKCS#8 structure');
function parseKeyHeader(state, keyFormat) {
expectTag(state, 0x30, `Invalid ${keyFormat === 'spki' ? 'SPKI' : 'PKCS#8'} structure`);
parseLength(state);
expectTag(state, 0x02, 'Expected version field');
const verLen = parseLength(state);
state.pos += verLen;
if (keyFormat === 'pkcs8') {
expectTag(state, 0x02, 'Expected version field');
const length = parseLength(state);
state.pos += length;
}
expectTag(state, 0x30, 'Expected algorithm identifier');
const algIdLen = parseLength(state);
const algIdStart = state.pos;
return { algIdStart, algIdLength: algIdLen };
}
function parseSPKIHeader(state) {
expectTag(state, 0x30, 'Invalid SPKI structure');
parseLength(state);
expectTag(state, 0x30, 'Expected algorithm identifier');
const algIdLen = parseLength(state);
const algIdStart = state.pos;
return { algIdStart, algIdLength: algIdLen };
}

@@ -119,11 +108,8 @@ const parseECAlgorithmIdentifier = (state) => {

const curveOid = getSubarray(state, curveOidLen);
for (const { name, oid } of [
{ name: 'P-256', oid: [0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07] },
{ name: 'P-384', oid: [0x2b, 0x81, 0x04, 0x00, 0x22] },
{ name: 'P-521', oid: [0x2b, 0x81, 0x04, 0x00, 0x23] },
]) {
if (bytesEqual(curveOid, oid)) {
return name;
}
}
if (bytesEqual(curveOid, [0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07]))
return 'P-256';
if (bytesEqual(curveOid, [0x2b, 0x81, 0x04, 0x00, 0x22]))
return 'P-384';
if (bytesEqual(curveOid, [0x2b, 0x81, 0x04, 0x00, 0x23]))
return 'P-521';
throw new Error('Unsupported named curve');

@@ -133,3 +119,3 @@ };

const entry = keyAlgorithm(alg);
if (entry.symmetric) {
if (entry.secret) {
throw new JOSENotSupported('Invalid or unsupported "alg" (Algorithm) value');

@@ -139,7 +125,9 @@ }

let algorithm;
if (entry.subtleFor) {
if (entry.resolve) {
try {
algorithm = entry.subtleFor({ crv: options.getNamedCurve(keyData) });
const state = createASN1State(keyData);
parseKeyHeader(state, keyFormat);
algorithm = entry.resolve({ crv: parseECAlgorithmIdentifier(state) });
}
catch (cause) {
catch {
throw new JOSENotSupported('Invalid or unsupported key format');

@@ -151,3 +139,3 @@ }

}
return crypto.subtle.importKey(keyFormat, keyData, algorithm, options?.extractable ?? (isPublic ? true : false), isPublic ? entry.usages.public : entry.usages.private);
return crypto.subtle.importKey(keyFormat, keyData, algorithm, options?.extractable ?? isPublic, entry.usages[isPublic ? 0 : 1]);
};

@@ -159,25 +147,7 @@ const processPEMData = (pem, pattern) => {

const keyData = processPEMData(pem, /(?:-----(?:BEGIN|END) PRIVATE KEY-----|\s)/g);
let opts = options;
if (alg?.startsWith?.('ECDH-ES')) {
opts ||= {};
opts.getNamedCurve = (keyData) => {
const state = createASN1State(keyData);
parsePKCS8Header(state);
return parseECAlgorithmIdentifier(state);
};
}
return genericImport('pkcs8', keyData, alg, opts);
return genericImport('pkcs8', keyData, alg, options);
};
export const fromSPKI = (pem, alg, options) => {
const keyData = processPEMData(pem, /(?:-----(?:BEGIN|END) PUBLIC KEY-----|\s)/g);
let opts = options;
if (alg?.startsWith?.('ECDH-ES')) {
opts ||= {};
opts.getNamedCurve = (keyData) => {
const state = createASN1State(keyData);
parseSPKIHeader(state);
return parseECAlgorithmIdentifier(state);
};
}
return genericImport('spki', keyData, alg, opts);
return genericImport('spki', keyData, alg, options);
};

@@ -201,10 +171,7 @@ function spkiFromX509(buf) {

}
function extractX509SPKI(x509) {
const derBytes = processPEMData(x509, /(?:-----(?:BEGIN|END) CERTIFICATE-----|\s)/g);
return spkiFromX509(derBytes);
}
export const fromX509 = (pem, alg, options) => {
let spki;
try {
spki = extractX509SPKI(pem);
const certificate = processPEMData(pem, /(?:-----(?:BEGIN|END) CERTIFICATE-----|\s)/g);
spki = spkiFromX509(certificate);
}

@@ -214,3 +181,3 @@ catch (cause) {

}
return fromSPKI(formatPEM(encodeBase64(spki), 'PUBLIC KEY'), alg, options);
return genericImport('spki', spki, alg, options);
};

@@ -29,3 +29,3 @@ import { concat, uint64be } from './buffer_utils.js';

}, false, ['sign']);
return { encKey, macKey, keySize };
return [encKey, macKey, keySize];
}

@@ -36,3 +36,3 @@ async function cbcHmacTag(macKey, macData, keySize) {

async function cbcEncrypt(enc, plaintext, cek, iv, aad) {
const { encKey, macKey, keySize } = await cbcKeySetup(enc, cek, 'encrypt');
const [encKey, macKey, keySize] = await cbcKeySetup(enc, cek, 'encrypt');
const ciphertext = new Uint8Array(await crypto.subtle.encrypt({

@@ -47,21 +47,9 @@ iv: iv,

async function timingSafeEqual(a, b) {
if (!(a instanceof Uint8Array)) {
throw new TypeError('First argument must be a buffer');
}
if (!(b instanceof Uint8Array)) {
throw new TypeError('Second argument must be a buffer');
}
const algorithm = { name: 'HMAC', hash: 'SHA-256' };
const key = (await crypto.subtle.generateKey(algorithm, false, ['sign']));
const aHmac = new Uint8Array(await crypto.subtle.sign(algorithm, key, a));
const bHmac = new Uint8Array(await crypto.subtle.sign(algorithm, key, b));
let out = 0;
let i = -1;
while (++i < 32) {
out |= aHmac[i] ^ bHmac[i];
}
return out === 0;
const key = (await crypto.subtle.generateKey(algorithm, false, ['sign', 'verify']));
const aHmac = await crypto.subtle.sign(algorithm, key, a);
return crypto.subtle.verify(algorithm, key, aHmac, b);
}
async function cbcDecrypt(enc, cek, ciphertext, iv, tag, aad) {
const { encKey, macKey, keySize } = await cbcKeySetup(enc, cek, 'decrypt');
const [encKey, macKey, keySize] = await cbcKeySetup(enc, cek, 'decrypt');
const macData = concat(aad, iv, ciphertext, uint64be(aad.length * 8));

@@ -90,10 +78,7 @@ const expectedTag = await cbcHmacTag(macKey, macData, keySize);

async function gcmEncrypt(enc, plaintext, cek, iv, aad) {
let encKey;
if (cek instanceof Uint8Array) {
encKey = await crypto.subtle.importKey('raw', cek, 'AES-GCM', false, ['encrypt']);
}
else {
checkCryptoKey(cek, enc.subtle, 'encrypt');
encKey = cek;
}
const encKey = cek instanceof Uint8Array
? await crypto.subtle.importKey('raw', cek, 'AES-GCM', false, [
'encrypt',
])
: (checkCryptoKey(cek, enc.subtle, 'encrypt'), cek);
const encrypted = new Uint8Array(await crypto.subtle.encrypt({

@@ -110,10 +95,7 @@ additionalData: aad,

async function gcmDecrypt(enc, cek, ciphertext, iv, tag, aad) {
let encKey;
if (cek instanceof Uint8Array) {
encKey = await crypto.subtle.importKey('raw', cek, 'AES-GCM', false, ['decrypt']);
}
else {
checkCryptoKey(cek, enc.subtle, 'decrypt');
encKey = cek;
}
const encKey = cek instanceof Uint8Array
? await crypto.subtle.importKey('raw', cek, 'AES-GCM', false, [
'decrypt',
])
: (checkCryptoKey(cek, enc.subtle, 'decrypt'), cek);
try {

@@ -120,0 +102,0 @@ return new Uint8Array(await crypto.subtle.decrypt({

@@ -7,2 +7,8 @@ const unusable = (name, prop = 'algorithm.name') => new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`);

}
export function checkModulusLength(alg, key) {
const { modulusLength } = key.algorithm;
if (typeof modulusLength !== 'number' || modulusLength < 2048) {
throw new TypeError(`${alg} requires key modulusLength to be 2048 bits or larger`);
}
}
export function checkCryptoKey(key, expected, usage) {

@@ -9,0 +15,0 @@ const algorithm = key.algorithm;

function message(msg, actual, ...types) {
types = types.filter(Boolean);
if (types.length > 2) {

@@ -4,0 +3,0 @@ const last = types.pop();

import { JOSENotSupported } from '../util/errors.js';
import { table } from './key_descriptor.js';
const wrap = {
public: ['encrypt', 'wrapKey'],
private: ['decrypt', 'unwrapKey'],
};
const derive = { public: [], private: ['deriveBits'] };
const none = { public: [], private: [] };
const wrap = [
['encrypt', 'wrapKey'],
['decrypt', 'unwrapKey'],
];
const derive = [[], ['deriveBits']];
const none = [[], []];
function rsaes(bits) {

@@ -14,11 +14,10 @@ return {

usages: wrap,
minModulusLength: 2048,
keyOps: { encrypt: 'wrapKey', decrypt: 'unwrapKey' },
ops: ['wrapKey', 'unwrapKey'],
};
}
function ecdh(kwBits) {
function ecdh() {
return {
kty: ['EC', 'OKP'],
subtle: { name: 'ECDH' },
subtleFor: ({ kty, crv, asymmetricKeyType }) => {
resolve: ({ kty, crv, asymmetricKeyType }) => {
if (crv === 'X25519' || asymmetricKeyType === 'x25519') {

@@ -33,34 +32,21 @@ return { name: 'X25519' };

usages: derive,
kwBits,
keyOps: { decrypt: 'deriveBits' },
ops: [undefined, 'deriveBits'],
};
}
function aeskw(bits) {
function aeskw(bits, gcm = false) {
return {
kty: ['oct'],
symmetric: true,
subtle: { name: 'AES-KW', length: bits },
secret: true,
subtle: { name: gcm ? 'AES-GCM' : 'AES-KW', length: bits },
usages: none,
keyOps: { encrypt: 'wrapKey', decrypt: 'unwrapKey' },
ops: gcm ? ['encrypt', 'decrypt'] : ['wrapKey', 'unwrapKey'],
};
}
function aesgcmkw(bits) {
function pbes2() {
return {
kty: ['oct'],
symmetric: true,
subtle: { name: 'AES-GCM', length: bits },
usages: none,
gcmkw: `A${bits}GCM`,
keyOps: { encrypt: 'encrypt', decrypt: 'decrypt' },
};
}
function pbes2(bits, kwBits) {
return {
kty: ['oct'],
symmetric: true,
secret: true,
subtle: { name: 'PBKDF2' },
usages: none,
pbes2Hash: `SHA-${bits}`,
kwBits,
keyOps: { encrypt: 'deriveBits', decrypt: 'deriveBits' },
ops: ['deriveBits', 'deriveBits'],
};

@@ -71,6 +57,6 @@ }

kty: ['oct'],
symmetric: true,
secret: true,
subtle: { name: 'AES-GCM' },
usages: none,
keyOps: { encrypt: 'encrypt', decrypt: 'decrypt' },
ops: ['encrypt', 'decrypt'],
},

@@ -82,54 +68,41 @@ 'RSA-OAEP': rsaes(1),

'ECDH-ES': ecdh(),
'ECDH-ES+A128KW': ecdh(128),
'ECDH-ES+A192KW': ecdh(192),
'ECDH-ES+A256KW': ecdh(256),
'ECDH-ES+A128KW': ecdh(),
'ECDH-ES+A192KW': ecdh(),
'ECDH-ES+A256KW': ecdh(),
A128KW: aeskw(128),
A192KW: aeskw(192),
A256KW: aeskw(256),
A128GCMKW: aesgcmkw(128),
A192GCMKW: aesgcmkw(192),
A256GCMKW: aesgcmkw(256),
'PBES2-HS256+A128KW': pbes2(256, 128),
'PBES2-HS384+A192KW': pbes2(384, 192),
'PBES2-HS512+A256KW': pbes2(512, 256),
A128GCMKW: aeskw(128, true),
A192GCMKW: aeskw(192, true),
A256GCMKW: aeskw(256, true),
'PBES2-HS256+A128KW': pbes2(),
'PBES2-HS384+A192KW': pbes2(),
'PBES2-HS512+A256KW': pbes2(),
});
const content = { public: [], private: [] };
const contentOps = { encrypt: 'encrypt', decrypt: 'decrypt' };
function gcm(bits) {
const contentOps = ['encrypt', 'decrypt'];
function contentEncryption(bits, cbc = false) {
return {
kty: ['oct'],
symmetric: true,
subtle: { name: 'AES-GCM', length: bits },
usages: content,
keyOps: contentOps,
secret: true,
subtle: { name: cbc ? 'AES-CBC' : 'AES-GCM', length: bits },
usages: none,
ops: contentOps,
cekBits: bits,
ivBits: 96,
cbc: false,
ivBits: cbc ? 128 : 96,
cbc,
};
}
function cbc(bits) {
return {
kty: ['oct'],
symmetric: true,
subtle: { name: 'AES-CBC', length: bits },
usages: content,
keyOps: contentOps,
cekBits: bits,
ivBits: 128,
cbc: true,
};
}
const ENC = table({
A128GCM: gcm(128),
A192GCM: gcm(192),
A256GCM: gcm(256),
'A128CBC-HS256': cbc(256),
'A192CBC-HS384': cbc(384),
'A256CBC-HS512': cbc(512),
A128GCM: contentEncryption(128),
A192GCM: contentEncryption(192),
A256GCM: contentEncryption(256),
'A128CBC-HS256': contentEncryption(256, true),
'A192CBC-HS384': contentEncryption(384, true),
'A256CBC-HS512': contentEncryption(512, true),
});
const unsupportedAlgHeader = 'Invalid or unsupported "alg" (JWE Algorithm) header value';
const unsupportedAlg = 'Invalid or unsupported "alg" (JWE Algorithm) header value';
export function jweAlgorithm(alg) {
const entry = JWE[alg];
if (!entry) {
throw new JOSENotSupported(unsupportedAlgHeader);
throw new JOSENotSupported(unsupportedAlg);
}

@@ -136,0 +109,0 @@ return entry;

@@ -12,6 +12,7 @@ import { decrypt, generateCek } from './content_encryption.js';

export function checkShared(jwe) {
const { ciphertext, protected: encodedProtected, unprotected } = jwe;
if (jwe.iv !== undefined && typeof jwe.iv !== 'string') {
throw new JWEInvalid('JWE Initialization Vector incorrect type');
}
if (typeof jwe.ciphertext !== 'string') {
if (typeof ciphertext !== 'string') {
throw new JWEInvalid('JWE Ciphertext missing or incorrect type');

@@ -22,3 +23,3 @@ }

}
if (jwe.protected !== undefined && typeof jwe.protected !== 'string') {
if (encodedProtected !== undefined && typeof encodedProtected !== 'string') {
throw new JWEInvalid('JWE Protected Header incorrect type');

@@ -29,3 +30,3 @@ }

}
if (jwe.unprotected !== undefined && !isObject(jwe.unprotected)) {
if (unprotected !== undefined && !isObject(unprotected)) {
throw new JWEInvalid('JWE Shared Unprotected Header incorrect type');

@@ -35,9 +36,10 @@ }

export function checkRecipient(jwe) {
if (jwe.encrypted_key !== undefined && typeof jwe.encrypted_key !== 'string') {
const { encrypted_key: encryptedKey, header } = jwe;
if (encryptedKey !== undefined && typeof encryptedKey !== 'string') {
throw new JWEInvalid('JWE Encrypted Key incorrect type');
}
if (jwe.header !== undefined && !isObject(jwe.header)) {
if (header !== undefined && !isObject(header)) {
throw new JWEInvalid('JWE Per-Recipient Unprotected Header incorrect type');
}
if (jwe.protected === undefined && jwe.header === undefined && jwe.unprotected === undefined) {
if (jwe.protected === undefined && header === undefined && jwe.unprotected === undefined) {
throw new JWEInvalid('JOSE Header missing');

@@ -47,33 +49,36 @@ }

export function shareJWE(jwe) {
const { protected: encodedProtected, ciphertext, iv, tag, aad } = jwe;
let parsedProt;
if (jwe.protected) {
parsedProt = parseJoseHeader(jwe.protected, JWEInvalid, 'JWE Protected Header is invalid');
if (encodedProtected) {
parsedProt = parseJoseHeader(encodedProtected, JWEInvalid, 'JWE Protected Header is invalid');
}
const protectedHeader = jwe.protected !== undefined ? encode(jwe.protected) : new Uint8Array();
return {
const protectedHeader = encodedProtected !== undefined ? encode(encodedProtected) : new Uint8Array();
return [
parsedProt,
ciphertext: decodeBase64url(jwe.ciphertext, 'ciphertext', JWEInvalid),
iv: jwe.iv !== undefined ? decodeBase64url(jwe.iv, 'iv', JWEInvalid) : undefined,
tag: jwe.tag !== undefined ? decodeBase64url(jwe.tag, 'tag', JWEInvalid) : undefined,
additionalData: jwe.aad !== undefined
? concat(protectedHeader, encode('.'), encodeBase64url(jwe.aad, 'aad', JWEInvalid))
decodeBase64url(ciphertext, 'ciphertext', JWEInvalid),
iv !== undefined ? decodeBase64url(iv, 'iv', JWEInvalid) : undefined,
tag !== undefined ? decodeBase64url(tag, 'tag', JWEInvalid) : undefined,
aad !== undefined
? concat(protectedHeader, encode('.'), encodeBase64url(aad, 'aad', JWEInvalid))
: protectedHeader,
};
];
}
export function decryptResult(jwe, decrypted) {
const result = { plaintext: decrypted.plaintext };
if (jwe.protected !== undefined) {
result.protectedHeader = decrypted.parsedProt;
const [plaintext, parsedProt, key, resolvedKey] = decrypted;
const { protected: encodedProtected, aad, unprotected, header } = jwe;
const result = { plaintext };
if (encodedProtected !== undefined) {
result.protectedHeader = parsedProt;
}
if (jwe.aad !== undefined) {
result.additionalAuthenticatedData = decodeBase64url(jwe.aad, 'aad', JWEInvalid);
if (aad !== undefined) {
result.additionalAuthenticatedData = decodeBase64url(aad, 'aad', JWEInvalid);
}
if (jwe.unprotected !== undefined) {
result.sharedUnprotectedHeader = jwe.unprotected;
if (unprotected !== undefined) {
result.sharedUnprotectedHeader = unprotected;
}
if (jwe.header !== undefined) {
result.unprotectedHeader = jwe.header;
if (header !== undefined) {
result.unprotectedHeader = header;
}
if (decrypted.resolvedKey) {
return { ...result, key: decrypted.key };
if (resolvedKey) {
return { ...result, key };
}

@@ -83,18 +88,19 @@ return result;

export function prepareDecrypt(options) {
return {
keyManagementAlgorithms: options && validateAlgorithms('keyManagementAlgorithms', options.keyManagementAlgorithms),
contentEncryptionAlgorithms: options &&
return [
options && validateAlgorithms('keyManagementAlgorithms', options.keyManagementAlgorithms),
options &&
validateAlgorithms('contentEncryptionAlgorithms', options.contentEncryptionAlgorithms),
options,
};
];
}
export async function decryptRecipient(jwe, token, shared, key) {
const { options } = shared;
const { parsedProt } = token;
const [keyManagementAlgorithms, contentEncryptionAlgorithms, options] = shared;
const [parsedProt, ciphertext, iv, tag, additionalData] = token;
const { encrypted_key: encodedKey, header, unprotected } = jwe;
let joseHeader;
if (jwe.header !== undefined || jwe.unprotected !== undefined) {
if (!isDisjoint(parsedProt, jwe.header, jwe.unprotected)) {
if (header !== undefined || unprotected !== undefined) {
if (!isDisjoint(parsedProt, header, unprotected)) {
throw new JWEInvalid('JWE Protected, JWE Unprotected Header, and JWE Per-Recipient Unprotected Header Parameter names must be disjoint');
}
joseHeader = { ...parsedProt, ...jwe.header, ...jwe.unprotected };
joseHeader = { ...parsedProt, ...header, ...unprotected };
}

@@ -118,3 +124,2 @@ else {

}
const { keyManagementAlgorithms, contentEncryptionAlgorithms } = shared;
if ((keyManagementAlgorithms && !keyManagementAlgorithms.has(alg)) ||

@@ -129,4 +134,4 @@ (!keyManagementAlgorithms && alg.startsWith('PBES2'))) {

let encryptedKey;
if (jwe.encrypted_key !== undefined) {
encryptedKey = decodeBase64url(jwe.encrypted_key, 'encrypted_key', JWEInvalid);
if (encodedKey !== undefined) {
encryptedKey = decodeBase64url(encodedKey, 'encrypted_key', JWEInvalid);
}

@@ -150,3 +155,3 @@ let resolvedKey = false;

}
let plaintext = await decrypt(encEntry, cek, token.ciphertext, token.iv, token.tag, token.additionalData);
let plaintext = await decrypt(encEntry, cek, ciphertext, iv, tag, additionalData);
if (joseHeader.zip === 'DEF') {

@@ -167,3 +172,3 @@ const maxDecompressedLength = options?.maxDecompressedLength ?? 250_000;

}
return { plaintext, parsedProt, key: k, resolvedKey };
return [plaintext, parsedProt, k, resolvedKey];
}

@@ -170,0 +175,0 @@ export async function decryptJWE(jwe, shared, key) {

@@ -12,3 +12,3 @@ import { encode as b64u } from '../util/base64url.js';

export function checkEncryptHeaders(input) {
const { protectedHeader, unprotectedHeader, sharedUnprotectedHeader } = input;
const [, protectedHeader, unprotectedHeader, sharedUnprotectedHeader, , , , , crit] = input;
if (!isDisjoint(protectedHeader, unprotectedHeader, sharedUnprotectedHeader)) {

@@ -22,3 +22,3 @@ throw new JWEInvalid('JWE Protected, JWE Shared Unprotected and JWE Per-Recipient Header Parameter names must be disjoint');

};
validateCrit(JWEInvalid, JWE_RECOGNIZED, input.crit, protectedHeader, joseHeader);
validateCrit(JWEInvalid, JWE_RECOGNIZED, crit, protectedHeader, joseHeader);
if (joseHeader.zip !== undefined && joseHeader.zip !== 'DEF') {

@@ -37,9 +37,10 @@ throw new JOSENotSupported('Unsupported JWE "zip" (Compression Algorithm) Header Parameter value.');

}
return { joseHeader, alg, enc, encEntry: jweEncryption(enc) };
return [joseHeader, alg, enc, jweEncryption(enc)];
}
export async function encryptJWE(input, checked, key) {
const { joseHeader, alg, encEntry } = checked;
let { protectedHeader, unprotectedHeader } = input;
const { sharedUnprotectedHeader } = input;
if (input.cek && (alg === 'dir' || alg === 'ECDH-ES')) {
const [joseHeader, alg, , encEntry] = checked;
const [inputPlaintext, inputProtectedHeader, inputUnprotectedHeader, sharedUnprotectedHeader, aad, providedCek, inputIv, keyManagementParameters, , unprotectedParameters,] = input;
let protectedHeader = inputProtectedHeader;
let unprotectedHeader = inputUnprotectedHeader;
if (providedCek && (alg === 'dir' || alg === 'ECDH-ES')) {
throw new TypeError(`setContentEncryptionKey cannot be called with JWE "alg" (Algorithm) Header ${alg}`);

@@ -49,5 +50,5 @@ }

const k = await prepareKey(alg === 'dir' ? encEntry : algEntry, key, 'encrypt');
const { cek, encryptedKey, parameters } = await encryptKeyManagement(alg, encEntry, k, input.cek, input.keyManagementParameters);
const [cek, encryptedKey, parameters] = await encryptKeyManagement(alg, encEntry, k, providedCek, keyManagementParameters);
if (parameters) {
if (input.unprotectedParameters) {
if (unprotectedParameters) {
unprotectedHeader = unprotectedHeader ? { ...unprotectedHeader, ...parameters } : parameters;

@@ -71,4 +72,4 @@ }

let aadMember;
if (input.aad?.byteLength) {
aadMember = b64u(input.aad);
if (aad?.byteLength) {
aadMember = b64u(aad);
additionalData = concat(protectedHeaderB, encode('.'), encode(aadMember));

@@ -79,3 +80,3 @@ }

}
let plaintext = input.plaintext;
let plaintext = inputPlaintext;
if (joseHeader.zip === 'DEF') {

@@ -86,3 +87,3 @@ plaintext = await compress(plaintext).catch((cause) => {

}
const { ciphertext, tag, iv } = await encrypt(encEntry, plaintext, cek, input.iv, additionalData);
const { ciphertext, tag, iv } = await encrypt(encEntry, plaintext, cek, inputIv, additionalData);
const jwe = {

@@ -89,0 +90,0 @@ ciphertext: b64u(ciphertext),

import { JOSENotSupported } from '../util/errors.js';
const unsupportedAlg = 'Invalid or unsupported JWK "alg" (Algorithm) Parameter value';
function subtleParams(entry, jwk) {
if (!entry.kty.includes(jwk.kty)) {
throw new JOSENotSupported(unsupportedAlg);
}
return entry.subtleFor?.({ kty: jwk.kty, crv: jwk.crv }) ?? entry.subtle;
}
export async function jwkToKey(entry, jwk) {

@@ -13,5 +6,7 @@ if (jwk.kty === 'RSA' && 'oth' in jwk && jwk.oth !== undefined) {

}
const algorithm = subtleParams(entry, jwk);
if (!entry.kty.includes(jwk.kty)) {
throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value');
}
const algorithm = entry.resolve?.({ kty: jwk.kty, crv: jwk.crv }) ?? entry.subtle;
const isPrivate = !!(jwk.d || jwk.priv);
const keyUsages = isPrivate ? entry.usages.private : entry.usages.public;
const keyData = { ...jwk };

@@ -22,3 +17,3 @@ if (keyData.kty !== 'AKP') {

delete keyData.use;
return crypto.subtle.importKey('jwk', keyData, algorithm, jwk.ext ?? (isPrivate ? false : true), jwk.key_ops ?? keyUsages);
return crypto.subtle.importKey('jwk', keyData, algorithm, jwk.ext ?? !isPrivate, jwk.key_ops ?? entry.usages[isPrivate ? 1 : 0]);
}
import { JOSENotSupported } from '../util/errors.js';
import { table } from './key_descriptor.js';
const sig = { public: ['verify'], private: ['sign'] };
const sig = [['verify'], ['sign']];
function hmac(bits) {
const subtle = { name: 'HMAC', hash: `SHA-${bits}` };
return { kty: ['oct'], symmetric: true, subtle, operation: subtle, usages: sig };
return { kty: ['oct'], secret: true, subtle, signing: subtle, usages: sig };
}
function rsa(name, bits, saltLength) {
function rsa(bits, saltLength) {
const name = saltLength ? 'RSA-PSS' : 'RSASSA-PKCS1-v1_5';
const subtle = { name, hash: `SHA-${bits}` };

@@ -13,5 +14,5 @@ return {

subtle,
operation: saltLength ? { ...subtle, saltLength } : subtle,
signing: saltLength ? { ...subtle, saltLength } : subtle,
usages: sig,
minModulusLength: 2048,
minRsaBits: 2048,
};

@@ -24,3 +25,3 @@ }

subtle: { name: 'ECDSA', namedCurve: crv },
operation: { name: 'ECDSA', hash: `SHA-${bits}` },
signing: { name: 'ECDSA', hash: `SHA-${bits}` },
usages: sig,

@@ -35,7 +36,8 @@ };

subtle,
operation: subtle,
signing: subtle,
usages: sig,
};
}
function mldsa(name) {
function mldsa(bits) {
const name = `ML-DSA-${bits}`;
const subtle = { name };

@@ -45,3 +47,3 @@ return {

subtle,
operation: subtle,
signing: subtle,
usages: sig,

@@ -54,8 +56,8 @@ };

HS512: hmac(512),
RS256: rsa('RSASSA-PKCS1-v1_5', 256),
RS384: rsa('RSASSA-PKCS1-v1_5', 384),
RS512: rsa('RSASSA-PKCS1-v1_5', 512),
PS256: rsa('RSA-PSS', 256, 32),
PS384: rsa('RSA-PSS', 384, 48),
PS512: rsa('RSA-PSS', 512, 64),
RS256: rsa(256),
RS384: rsa(384),
RS512: rsa(512),
PS256: rsa(256, 32),
PS384: rsa(384, 48),
PS512: rsa(512, 64),
ES256: ecdsa('P-256', 256),

@@ -66,5 +68,5 @@ ES384: ecdsa('P-384', 384),

Ed25519: eddsa(),
'ML-DSA-44': mldsa('ML-DSA-44'),
'ML-DSA-65': mldsa('ML-DSA-65'),
'ML-DSA-87': mldsa('ML-DSA-87'),
'ML-DSA-44': mldsa(44),
'ML-DSA-65': mldsa(65),
'ML-DSA-87': mldsa(87),
});

@@ -71,0 +73,0 @@ export function jwsAlgorithm(alg) {

@@ -16,2 +16,5 @@ import { encode as b64u } from '../util/base64url.js';

const { protectedHeader, unprotectedHeader } = input;
if (!protectedHeader && !unprotectedHeader) {
throw new JWSInvalid('either setProtectedHeader or setUnprotectedHeader must be called before #sign()');
}
if (!isDisjoint(protectedHeader, unprotectedHeader)) {

@@ -24,3 +27,3 @@ throw new JWSInvalid('JWS Protected and JWS Unprotected Header Parameter names must be disjoint');

let b64 = true;
if (extensions.has('b64')) {
if (extensions.includes('b64')) {
b64 = protectedHeader.b64;

@@ -39,7 +42,7 @@ if (typeof b64 !== 'boolean') {

if (b64) {
const encoded = (input.encoded ??= {});
encoded.b64 ??= b64u(input.payload);
encoded.raw ??= encode(encoded.b64);
payloadS = encoded.b64;
payloadB = encoded.raw;
const encoded = (input.encoded ??= []);
encoded[0] ??= b64u(input.payload);
encoded[1] ??= encode(encoded[0]);
payloadS = encoded[0];
payloadB = encoded[1];
}

@@ -70,3 +73,6 @@ else {

}
if (unprotectedHeader) {
jws.header = unprotectedHeader;
}
return jws;
}

@@ -10,5 +10,6 @@ import { verify } from './signing.js';

export function verifyResult(jws, verified) {
const result = { payload: verified.payload };
const [payload, parsedProt, , key, resolvedKey] = verified;
const result = { payload };
if (jws.protected !== undefined) {
result.protectedHeader = verified.parsedProt;
result.protectedHeader = parsedProt;
}

@@ -18,4 +19,4 @@ if (jws.header !== undefined) {

}
if (verified.resolvedKey) {
return { ...result, key: verified.key };
if (resolvedKey) {
return { ...result, key };
}

@@ -25,18 +26,16 @@ return result;

export function prepareVerify(options) {
return {
algorithms: options && validateAlgorithms('algorithms', options.algorithms),
crit: options?.crit,
};
return [options && validateAlgorithms('algorithms', options.algorithms), options?.crit];
}
export async function verifySignature(jws, shared, key) {
const { protected: encodedProtected, header, payload: inputPayload } = jws;
let parsedProt = {};
if (jws.protected) {
parsedProt = parseJoseHeader(jws.protected, JWSInvalid, 'JWS Protected Header is invalid');
if (encodedProtected) {
parsedProt = parseJoseHeader(encodedProtected, JWSInvalid, 'JWS Protected Header is invalid');
}
let joseHeader;
if (jws.header !== undefined) {
if (!isDisjoint(parsedProt, jws.header)) {
if (header !== undefined) {
if (!isDisjoint(parsedProt, header)) {
throw new JWSInvalid('JWS Protected and JWS Unprotected Header Parameter names must be disjoint');
}
joseHeader = { ...parsedProt, ...jws.header };
joseHeader = { ...parsedProt, ...header };
}

@@ -46,5 +45,5 @@ else {

}
const extensions = validateCrit(JWSInvalid, JWS_RECOGNIZED, shared.crit, parsedProt, joseHeader);
const extensions = validateCrit(JWSInvalid, JWS_RECOGNIZED, shared[1], parsedProt, joseHeader);
let b64 = true;
if (extensions.has('b64')) {
if (extensions.includes('b64')) {
b64 = parsedProt.b64;

@@ -59,11 +58,11 @@ if (typeof b64 !== 'boolean') {

}
if (shared.algorithms && !shared.algorithms.has(alg)) {
if (shared[0] && !shared[0].has(alg)) {
throw new JOSEAlgNotAllowed('"alg" (Algorithm) Header Parameter value not allowed');
}
if (b64) {
if (typeof jws.payload !== 'string') {
if (typeof inputPayload !== 'string') {
throw new JWSInvalid('JWS Payload must be a string');
}
}
else if (typeof jws.payload !== 'string' && !(jws.payload instanceof Uint8Array)) {
else if (typeof inputPayload !== 'string' && !(inputPayload instanceof Uint8Array)) {
throw new JWSInvalid('JWS Payload must be a string or an Uint8Array instance');

@@ -77,8 +76,8 @@ }

const entry = jwsAlgorithm(alg);
const data = concat(jws.protected !== undefined ? encode(jws.protected) : new Uint8Array(), encode('.'), typeof jws.payload === 'string'
const data = concat(encodedProtected !== undefined ? encode(encodedProtected) : new Uint8Array(), encode('.'), typeof inputPayload === 'string'
? b64
?
(shared.b64p ??= encodeBase64url(jws.payload, 'payload', JWSInvalid))
: encoder.encode(jws.payload)
: jws.payload);
(shared[2] ??= encodeBase64url(inputPayload, 'payload', JWSInvalid))
: encoder.encode(inputPayload)
: inputPayload);
const signature = decodeBase64url(jws.signature, 'signature', JWSInvalid);

@@ -92,11 +91,11 @@ const k = await prepareKey(entry, key, 'verify');

if (b64) {
payload = decodeBase64url(jws.payload, 'payload', JWSInvalid);
payload = decodeBase64url(inputPayload, 'payload', JWSInvalid);
}
else if (typeof jws.payload === 'string') {
payload = encoder.encode(jws.payload);
else if (typeof inputPayload === 'string') {
payload = encoder.encode(inputPayload);
}
else {
payload = jws.payload;
payload = inputPayload;
}
return { payload, parsedProt, b64, key: k, resolvedKey };
return [payload, parsedProt, b64, k, resolvedKey];
}

@@ -103,0 +102,0 @@ export async function verifyCompact(jws, shared, key) {

@@ -5,8 +5,12 @@ import { JWTClaimValidationFailed, JWTExpired, JWTInvalid } from '../util/errors.js';

const epoch = (date) => Math.floor(date.getTime() / 1000);
const minute = 60;
const hour = minute * 60;
const day = hour * 24;
const week = day * 7;
const year = day * 365.25;
const multipliers = {
s: 1,
m: 60,
h: 3600,
d: 86400,
w: 604800,
y: 31557600,
};
const REGEX = /^(\+|\-)? ?(\d+|\d+\.\d+) ?(seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)(?: (ago|from now))?$/i;
const checkFailed = 'check_failed';
export function secs(str) {

@@ -18,40 +22,3 @@ const matched = REGEX.exec(str);

const value = parseFloat(matched[2]);
const unit = matched[3].toLowerCase();
let numericDate;
switch (unit) {
case 'sec':
case 'secs':
case 'second':
case 'seconds':
case 's':
numericDate = Math.round(value);
break;
case 'minute':
case 'minutes':
case 'min':
case 'mins':
case 'm':
numericDate = Math.round(value * minute);
break;
case 'hour':
case 'hours':
case 'hr':
case 'hrs':
case 'h':
numericDate = Math.round(value * hour);
break;
case 'day':
case 'days':
case 'd':
numericDate = Math.round(value * day);
break;
case 'week':
case 'weeks':
case 'w':
numericDate = Math.round(value * week);
break;
default:
numericDate = Math.round(value * year);
break;
}
const numericDate = Math.round(value * multipliers[matched[3][0].toLowerCase()]);
if (matched[1] === '-' || matched[4] === 'ago') {

@@ -68,2 +35,9 @@ return -numericDate;

}
function numericDate(value, label) {
if (typeof value === 'number')
return validateInput(label, value);
if (value instanceof Date)
return validateInput(label, epoch(value));
return epoch(new Date()) + secs(value);
}
const normalizeTyp = (value) => {

@@ -80,6 +54,18 @@ if (value.includes('/')) {

if (Array.isArray(audPayload)) {
return audOption.some(Set.prototype.has.bind(new Set(audPayload)));
return audOption.some((aud) => audPayload.includes(aud));
}
return false;
};
function validateNumericDate(payload, claim, required = false) {
const value = payload[claim];
if (value === undefined && !required)
return undefined;
if (typeof value !== 'number') {
throw new JWTClaimValidationFailed(`"${claim}" claim must be a number`, payload, claim, 'invalid');
}
return value;
}
function unexpectedClaim(payload, claim) {
throw new JWTClaimValidationFailed(`unexpected "${claim}" claim value`, payload, claim, checkFailed);
}
export function validateClaimsSet(protectedHeader, encodedPayload, options = {}) {

@@ -99,3 +85,3 @@ let payload;

normalizeTyp(protectedHeader.typ) !== normalizeTyp(typ))) {
throw new JWTClaimValidationFailed('unexpected "typ" JWT header value', payload, 'typ', 'check_failed');
throw new JWTClaimValidationFailed('unexpected "typ" JWT header value', payload, 'typ', checkFailed);
}

@@ -113,3 +99,3 @@ const { requiredClaims = [], issuer, subject, audience, maxTokenAge } = options;

for (const claim of new Set(presenceCheck.reverse())) {
if (!(claim in payload)) {
if (!Object.hasOwn(payload, claim)) {
throw new JWTClaimValidationFailed(`missing required "${claim}" claim`, payload, claim, 'missing');

@@ -120,24 +106,21 @@ }

!(Array.isArray(issuer) ? issuer : [issuer]).includes(payload.iss)) {
throw new JWTClaimValidationFailed('unexpected "iss" claim value', payload, 'iss', 'check_failed');
unexpectedClaim(payload, 'iss');
}
if (subject !== undefined && payload.sub !== subject) {
throw new JWTClaimValidationFailed('unexpected "sub" claim value', payload, 'sub', 'check_failed');
unexpectedClaim(payload, 'sub');
}
if (audience !== undefined &&
!checkAudiencePresence(payload.aud, typeof audience === 'string' ? [audience] : audience)) {
throw new JWTClaimValidationFailed('unexpected "aud" claim value', payload, 'aud', 'check_failed');
unexpectedClaim(payload, 'aud');
}
let tolerance;
switch (typeof options.clockTolerance) {
case 'string':
tolerance = secs(options.clockTolerance);
break;
case 'number':
tolerance = options.clockTolerance;
break;
case 'undefined':
tolerance = 0;
break;
default:
const { clockTolerance } = options;
let tolerance = 0;
if (typeof clockTolerance === 'string') {
tolerance = secs(clockTolerance);
}
else if (clockTolerance !== undefined) {
if (typeof clockTolerance !== 'number') {
throw new TypeError('Invalid clockTolerance option type');
}
tolerance = clockTolerance;
}

@@ -147,29 +130,23 @@ validateInput('clockTolerance option', tolerance);

const now = validateInput('currentDate option', epoch(currentDate || new Date()));
if ((payload.iat !== undefined || maxTokenAge !== undefined) && typeof payload.iat !== 'number') {
throw new JWTClaimValidationFailed('"iat" claim must be a number', payload, 'iat', 'invalid');
}
if (payload.nbf !== undefined) {
if (typeof payload.nbf !== 'number') {
throw new JWTClaimValidationFailed('"nbf" claim must be a number', payload, 'nbf', 'invalid');
const iat = validateNumericDate(payload, 'iat', maxTokenAge !== undefined);
const nbf = validateNumericDate(payload, 'nbf');
if (nbf !== undefined) {
if (nbf > now + tolerance) {
throw new JWTClaimValidationFailed('"nbf" claim timestamp check failed', payload, 'nbf', checkFailed);
}
if (payload.nbf > now + tolerance) {
throw new JWTClaimValidationFailed('"nbf" claim timestamp check failed', payload, 'nbf', 'check_failed');
}
}
if (payload.exp !== undefined) {
if (typeof payload.exp !== 'number') {
throw new JWTClaimValidationFailed('"exp" claim must be a number', payload, 'exp', 'invalid');
const exp = validateNumericDate(payload, 'exp');
if (exp !== undefined) {
if (exp <= now - tolerance) {
throw new JWTExpired('"exp" claim timestamp check failed', payload, 'exp', checkFailed);
}
if (payload.exp <= now - tolerance) {
throw new JWTExpired('"exp" claim timestamp check failed', payload, 'exp', 'check_failed');
}
}
if (maxTokenAge !== undefined) {
const age = now - payload.iat;
const age = now - iat;
const max = typeof maxTokenAge === 'number' ? maxTokenAge : secs(maxTokenAge);
if (age - tolerance > max) {
throw new JWTExpired('"iat" claim timestamp check failed (too far in the past)', payload, 'iat', 'check_failed');
throw new JWTExpired('"iat" claim timestamp check failed (too far in the past)', payload, 'iat', checkFailed);
}
if (age < 0 - tolerance) {
throw new JWTClaimValidationFailed('"iat" claim timestamp check failed (it should be in the past)', payload, 'iat', 'check_failed');
throw new JWTClaimValidationFailed('"iat" claim timestamp check failed (it should be in the past)', payload, 'iat', checkFailed);
}

@@ -212,22 +189,6 @@ }

set nbf(value) {
if (typeof value === 'number') {
this.#payload.nbf = validateInput('setNotBefore', value);
}
else if (value instanceof Date) {
this.#payload.nbf = validateInput('setNotBefore', epoch(value));
}
else {
this.#payload.nbf = epoch(new Date()) + secs(value);
}
this.#payload.nbf = numericDate(value, 'setNotBefore');
}
set exp(value) {
if (typeof value === 'number') {
this.#payload.exp = validateInput('setExpirationTime', value);
}
else if (value instanceof Date) {
this.#payload.exp = validateInput('setExpirationTime', epoch(value));
}
else {
this.#payload.exp = epoch(new Date()) + secs(value);
}
this.#payload.exp = numericDate(value, 'setExpirationTime');
}

@@ -238,5 +199,2 @@ set iat(value) {

}
else if (value instanceof Date) {
this.#payload.iat = validateInput('setIssuedAt', epoch(value));
}
else if (typeof value === 'string') {

@@ -246,5 +204,5 @@ this.#payload.iat = validateInput('setIssuedAt', epoch(new Date()) + secs(value));

else {
this.#payload.iat = validateInput('setIssuedAt', value);
this.#payload.iat = numericDate(value, 'setIssuedAt');
}
}
}
import { JOSENotSupported } from '../util/errors.js';
import { maybeJWSAlgorithm } from './jws_algorithms.js';
import { maybeJWEAlgorithm } from './jwe_algorithms.js';
function unsupportedAlgorithm() {
return new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value');
}
export function keyAlgorithm(alg) {
if (typeof alg !== 'string') {
throw unsupportedAlgorithm();
}
const entry = maybeJWSAlgorithm(alg) ?? maybeJWEAlgorithm(alg);
const entry = typeof alg === 'string' ? (maybeJWSAlgorithm(alg) ?? maybeJWEAlgorithm(alg)) : undefined;
if (!entry) {
throw unsupportedAlgorithm();
throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value');
}
return entry;
}
export function table(entries) {
const out = { __proto__: null };
for (const alg of Object.keys(entries)) {
for (const alg in entries) {
out[alg] = { ...entries[alg], alg };

@@ -5,0 +5,0 @@ }

@@ -9,31 +9,23 @@ import { encode as b64u } from '../util/base64url.js';

import { isObject } from './type_checks.js';
import { checkCryptoKey, checkUsage } from './crypto_key.js';
import { checkModulusLength } from './signing.js';
import { checkCryptoKey, checkModulusLength, checkUsage } from './crypto_key.js';
import { concat, encode, uint32be } from './buffer_utils.js';
import { assertCryptoKey } from './is_key_like.js';
function checkEcdhCryptoKey(key, usage) {
switch (key.algorithm.name) {
case 'ECDH':
case 'X25519':
break;
default:
throw new TypeError('CryptoKey does not support this operation, its algorithm.name must be ECDH or X25519');
if (key.algorithm.name !== 'ECDH' && key.algorithm.name !== 'X25519') {
throw new TypeError('CryptoKey does not support this operation, its algorithm.name must be ECDH or X25519');
}
checkUsage(key, usage);
}
function checkKeySize(key, alg) {
if (key.algorithm.length !== parseInt(alg.slice(1, 4), 10)) {
throw new TypeError(`Invalid key size for alg: ${alg}`);
}
async function aeskwCryptoKey(key, alg, usage) {
const expected = jweAlgorithm(alg).subtle;
const cryptoKey = key instanceof Uint8Array
? await crypto.subtle.importKey('raw', key, 'AES-KW', true, [
usage,
])
: key;
checkCryptoKey(cryptoKey, expected, usage);
return cryptoKey;
}
function aeskwCryptoKey(key, alg, usage) {
if (key instanceof Uint8Array) {
return crypto.subtle.importKey('raw', key, 'AES-KW', true, [usage]);
}
checkCryptoKey(key, jweAlgorithm(alg).subtle, usage);
return key;
}
async function aeskwWrap(alg, key, cek) {
const cryptoKey = await aeskwCryptoKey(key, alg, 'wrapKey');
checkKeySize(cryptoKey, alg);
const cryptoKeyCek = await crypto.subtle.importKey('raw', cek, { hash: 'SHA-256', name: 'HMAC' }, true, ['sign']);

@@ -44,38 +36,9 @@ return new Uint8Array(await crypto.subtle.wrapKey('raw', cryptoKeyCek, cryptoKey, 'AES-KW'));

const cryptoKey = await aeskwCryptoKey(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));
}
async function aesGcmKwWrap(gcm, key, cek, iv) {
const wrapped = await encrypt(gcm, cek, key, iv, new Uint8Array());
return {
encryptedKey: wrapped.ciphertext,
iv: b64u(wrapped.iv),
tag: b64u(wrapped.tag),
};
}
async function aesGcmKwUnwrap(gcm, key, encryptedKey, iv, tag) {
return decrypt(gcm, key, encryptedKey, iv, tag, new Uint8Array());
}
const subtleAlgorithm = (alg) => {
switch (alg) {
case 'RSA-OAEP':
case 'RSA-OAEP-256':
case 'RSA-OAEP-384':
case 'RSA-OAEP-512':
return 'RSA-OAEP';
default:
throw new JOSENotSupported(`alg ${alg} is not supported either by JOSE or your javascript runtime`);
}
};
async function rsaesEncrypt(alg, key, cek) {
checkCryptoKey(key, jweAlgorithm(alg).subtle, 'encrypt');
function checkRsaKey(alg, key, usage) {
checkCryptoKey(key, jweAlgorithm(alg).subtle, usage);
checkModulusLength(alg, key);
return new Uint8Array(await crypto.subtle.encrypt(subtleAlgorithm(alg), key, cek));
}
async function rsaesDecrypt(alg, key, encryptedKey) {
checkCryptoKey(key, jweAlgorithm(alg).subtle, 'decrypt');
checkModulusLength(alg, key);
return new Uint8Array(await crypto.subtle.decrypt(subtleAlgorithm(alg), key, encryptedKey));
}
function pbes2CryptoKey(key, alg) {

@@ -90,3 +53,2 @@ if (key instanceof Uint8Array) {

}
const concatSalt = (alg, p2sInput) => concat(encode(alg), Uint8Array.of(0x00), p2sInput);
async function deriveKey(p2s, alg, p2c, key) {

@@ -99,3 +61,3 @@ if (!(p2s instanceof Uint8Array) || p2s.length < 8) {

}
const salt = concatSalt(alg, p2s);
const salt = concat(encode(alg), Uint8Array.of(0), p2s);
const keylen = parseInt(alg.slice(13, 16), 10);

@@ -111,11 +73,2 @@ const subtleAlg = {

}
async function pbes2kwWrap(alg, key, cek, p2c = 2048, p2s = crypto.getRandomValues(new Uint8Array(16))) {
const derived = await deriveKey(p2s, alg, p2c, key);
const encryptedKey = await aeskwWrap(alg.slice(-6), derived, cek);
return { encryptedKey, p2c, p2s: b64u(p2s) };
}
async function pbes2kwUnwrap(alg, key, encryptedKey, p2c, p2s) {
const derived = await deriveKey(p2s, alg, p2c, key);
return aeskwUnwrap(alg.slice(-6), derived, encryptedKey);
}
function lengthAndInput(input) {

@@ -130,7 +83,3 @@ return concat(uint32be(input.length), input);

for (let i = 1; i <= reps; i++) {
const hashInput = new Uint8Array(4 + Z.length + OtherInfo.length);
hashInput.set(uint32be(i), 0);
hashInput.set(Z, 4);
hashInput.set(OtherInfo, 4 + Z.length);
const hashResult = await digest('sha256', hashInput);
const hashResult = await digest('sha256', concat(uint32be(i), Z, OtherInfo));
dk.set(hashResult, (i - 1) * hashLen);

@@ -143,31 +92,21 @@ }

checkEcdhCryptoKey(privateKey, 'deriveBits');
const algorithmID = lengthAndInput(encode(algorithm));
const partyUInfo = lengthAndInput(apu);
const partyVInfo = lengthAndInput(apv);
const suppPubInfo = uint32be(keyLength);
const suppPrivInfo = new Uint8Array();
const otherInfo = concat(algorithmID, partyUInfo, partyVInfo, suppPubInfo, suppPrivInfo);
const otherInfo = concat(lengthAndInput(encode(algorithm)), lengthAndInput(apu), lengthAndInput(apv), uint32be(keyLength));
const Z = new Uint8Array(await crypto.subtle.deriveBits({
name: publicKey.algorithm.name,
public: publicKey,
}, privateKey, getEcdhBitLength(publicKey)));
}, privateKey, publicKey.algorithm.name === 'X25519'
? 256
: Math.ceil(parseInt(publicKey.algorithm.namedCurve.slice(-3), 10) / 8) << 3));
return concatKdf(Z, keyLength, otherInfo);
}
function getEcdhBitLength(publicKey) {
if (publicKey.algorithm.name === 'X25519') {
return 256;
function assertEcdhKey(key) {
assertCryptoKey(key);
const curve = key.algorithm.namedCurve;
if (curve !== 'P-256' &&
curve !== 'P-384' &&
curve !== 'P-521' &&
key.algorithm.name !== 'X25519') {
throw new JOSENotSupported('ECDH with the provided key is not allowed or not supported by your javascript runtime');
}
return (Math.ceil(parseInt(publicKey.algorithm.namedCurve.slice(-3), 10) / 8) << 3);
}
function ecdhesAllowed(key) {
switch (key.algorithm.namedCurve) {
case 'P-256':
case 'P-384':
case 'P-521':
return true;
default:
return key.algorithm.name === 'X25519';
}
}
const unsupportedAlgHeader = 'Invalid or unsupported "alg" (JWE Algorithm) header value';
function assertEncryptedKey(encryptedKey) {

@@ -177,21 +116,20 @@ if (encryptedKey === undefined)

}
function assertNoEncryptedKey(encryptedKey) {
if (encryptedKey !== undefined)
throw new JWEInvalid('Encountered unexpected JWE Encrypted Key');
}
export async function decryptKeyManagement(alg, enc, key, encryptedKey, joseHeader, options) {
switch (alg) {
case 'dir': {
if (encryptedKey !== undefined)
throw new JWEInvalid('Encountered unexpected JWE Encrypted Key');
return key;
}
case 'ECDH-ES':
if (encryptedKey !== undefined)
throw new JWEInvalid('Encountered unexpected JWE Encrypted Key');
case 'ECDH-ES+A128KW':
case 'ECDH-ES+A192KW':
case 'ECDH-ES+A256KW': {
const entry = jweAlgorithm(alg);
if (alg === 'dir') {
assertNoEncryptedKey(encryptedKey);
return key;
}
switch (entry.subtle.name) {
case 'ECDH': {
if (alg === 'ECDH-ES')
assertNoEncryptedKey(encryptedKey);
if (!isObject(joseHeader.epk))
throw new JWEInvalid(`JOSE Header "epk" (Ephemeral Public Key) missing or invalid`);
assertCryptoKey(key);
if (!ecdhesAllowed(key))
throw new JOSENotSupported('ECDH with the provided key is not allowed or not supported by your javascript runtime');
const epk = await jwkToKey(jweAlgorithm(alg), joseHeader.epk);
assertEcdhKey(key);
const epk = await jwkToKey(entry, joseHeader.epk);
let partyUInfo;

@@ -215,13 +153,9 @@ let partyVInfo;

}
case 'RSA-OAEP':
case 'RSA-OAEP-256':
case 'RSA-OAEP-384':
case 'RSA-OAEP-512': {
case 'RSA-OAEP': {
assertEncryptedKey(encryptedKey);
assertCryptoKey(key);
return rsaesDecrypt(alg, key, encryptedKey);
checkRsaKey(alg, key, 'decrypt');
return new Uint8Array(await crypto.subtle.decrypt('RSA-OAEP', key, encryptedKey));
}
case 'PBES2-HS256+A128KW':
case 'PBES2-HS384+A192KW':
case 'PBES2-HS512+A256KW': {
case 'PBKDF2': {
assertEncryptedKey(encryptedKey);

@@ -235,15 +169,11 @@ if (typeof joseHeader.p2c !== 'number')

throw new JWEInvalid(`JOSE Header "p2s" (PBES2 Salt) missing or invalid`);
let p2s;
p2s = decodeBase64url(joseHeader.p2s, 'p2s', JWEInvalid);
return pbes2kwUnwrap(alg, key, encryptedKey, joseHeader.p2c, p2s);
const p2s = decodeBase64url(joseHeader.p2s, 'p2s', JWEInvalid);
const derived = await deriveKey(p2s, alg, joseHeader.p2c, key);
return aeskwUnwrap(alg.slice(-6), derived, encryptedKey);
}
case 'A128KW':
case 'A192KW':
case 'A256KW': {
case 'AES-KW': {
assertEncryptedKey(encryptedKey);
return aeskwUnwrap(alg, key, encryptedKey);
}
case 'A128GCMKW':
case 'A192GCMKW':
case 'A256GCMKW': {
case 'AES-GCM': {
assertEncryptedKey(encryptedKey);

@@ -258,7 +188,4 @@ if (typeof joseHeader.iv !== 'string')

tag = decodeBase64url(joseHeader.tag, 'tag', JWEInvalid);
return aesGcmKwUnwrap(jweEncryption(jweAlgorithm(alg).gcmkw), key, encryptedKey, iv, tag);
return decrypt(jweEncryption(alg.slice(0, -2)), key, encryptedKey, iv, tag, new Uint8Array());
}
default: {
throw new JOSENotSupported(unsupportedAlgHeader);
}
}

@@ -270,19 +197,12 @@ }

let cek;
switch (alg) {
case 'dir': {
cek = key;
break;
}
case 'ECDH-ES':
case 'ECDH-ES+A128KW':
case 'ECDH-ES+A192KW':
case 'ECDH-ES+A256KW': {
assertCryptoKey(key);
if (!ecdhesAllowed(key)) {
throw new JOSENotSupported('ECDH with the provided key is not allowed or not supported by your javascript runtime');
}
const entry = jweAlgorithm(alg);
if (alg === 'dir')
return [key, undefined, undefined];
switch (entry.subtle.name) {
case 'ECDH': {
assertEcdhKey(key);
const { apu, apv } = providedParameters;
let ephemeralKey;
if (providedParameters.epk) {
ephemeralKey = (await prepareKey(jweAlgorithm(alg), providedParameters.epk, 'decrypt'));
ephemeralKey = (await prepareKey(entry, providedParameters.epk, 'decrypt'));
}

@@ -318,22 +238,18 @@ else {

}
case 'RSA-OAEP':
case 'RSA-OAEP-256':
case 'RSA-OAEP-384':
case 'RSA-OAEP-512': {
case 'RSA-OAEP': {
cek = providedCek || generateCek(enc);
assertCryptoKey(key);
encryptedKey = await rsaesEncrypt(alg, key, cek);
checkRsaKey(alg, key, 'encrypt');
encryptedKey = new Uint8Array(await crypto.subtle.encrypt('RSA-OAEP', key, cek));
break;
}
case 'PBES2-HS256+A128KW':
case 'PBES2-HS384+A192KW':
case 'PBES2-HS512+A256KW': {
case 'PBKDF2': {
cek = providedCek || generateCek(enc);
const { p2c, p2s } = providedParameters;
({ encryptedKey, ...parameters } = await pbes2kwWrap(alg, key, cek, p2c, p2s));
const { p2c = 2048, p2s = crypto.getRandomValues(new Uint8Array(16)) } = providedParameters;
const derived = await deriveKey(p2s, alg, p2c, key);
encryptedKey = await aeskwWrap(alg.slice(-6), derived, cek);
parameters = { p2c, p2s: b64u(p2s) };
break;
}
case 'A128KW':
case 'A192KW':
case 'A256KW': {
case 'AES-KW': {
cek = providedCek || generateCek(enc);

@@ -343,15 +259,12 @@ encryptedKey = await aeskwWrap(alg, key, cek);

}
case 'A128GCMKW':
case 'A192GCMKW':
case 'A256GCMKW': {
case 'AES-GCM': {
cek = providedCek || generateCek(enc);
const { iv } = providedParameters;
({ encryptedKey, ...parameters } = await aesGcmKwWrap(jweEncryption(jweAlgorithm(alg).gcmkw), key, cek, iv));
const wrapped = await encrypt(jweEncryption(alg.slice(0, -2)), cek, key, iv, new Uint8Array());
encryptedKey = wrapped.ciphertext;
parameters = { iv: b64u(wrapped.iv), tag: b64u(wrapped.tag) };
break;
}
default: {
throw new JOSENotSupported(unsupportedAlgHeader);
}
}
return { cek, encryptedKey, parameters };
return [cek, encryptedKey, parameters];
}

@@ -10,13 +10,3 @@ import { withAlg as invalidKeyInput } from './invalid_key_input.js';

if (key.use !== undefined) {
let expected;
switch (usage) {
case 'sign':
case 'verify':
expected = 'sig';
break;
case 'encrypt':
case 'decrypt':
expected = 'enc';
break;
}
const expected = usage === 'sign' || usage === 'verify' ? 'sig' : 'enc';
if (key.use !== expected) {

@@ -30,75 +20,52 @@ throw new TypeError(`Invalid key for this operation, its "use" must be "${expected}" when present`);

if (Array.isArray(key.key_ops)) {
const expectedKeyOp = usage === 'encrypt' || usage === 'decrypt' ? entry.keyOps?.[usage] : usage;
if (expectedKeyOp && key.key_ops?.includes?.(expectedKeyOp) === false) {
const expectedKeyOp = usage === 'encrypt' || usage === 'decrypt' ? entry.ops?.[usage === 'encrypt' ? 0 : 1] : usage;
if (expectedKeyOp && !key.key_ops.includes(expectedKeyOp)) {
throw new TypeError(`Invalid key for this operation, its "key_ops" must include "${expectedKeyOp}" when present`);
}
}
return true;
};
const symmetricTypeCheck = (entry, key, usage) => {
const { alg } = entry;
if (key instanceof Uint8Array)
return { kind: BYTES, key };
export function checkKeyType(entry, key, usage) {
const { alg, secret } = entry;
const privateKey = usage === 'decrypt' || usage === 'sign';
if (secret && key instanceof Uint8Array)
return [BYTES, key];
if (jwk.isJWK(key)) {
if (jwk.isSecretJWK(key) && jwkMatchesOp(entry, key, usage))
return { kind: JWK, key };
throw new TypeError(`JSON Web Key for symmetric algorithms must have JWK "kty" (Key Type) equal to "oct" and the JWK "k" (Key Value) present`);
}
if (!isKeyLike(key)) {
throw new TypeError(invalidKeyInput(alg, key, 'CryptoKey', 'KeyObject', 'JSON Web Key', 'Uint8Array'));
}
if (key.type !== 'secret') {
throw new TypeError(`${tag(key)} instances for symmetric algorithms must be of type "secret"`);
}
return isCryptoKey(key) ? { kind: CRYPTO, key } : { kind: KEYOBJECT, key };
};
const asymmetricTypeCheck = (entry, key, usage) => {
const { alg } = entry;
if (jwk.isJWK(key)) {
switch (usage) {
case 'decrypt':
case 'sign':
if (jwk.isPrivateJWK(key) && jwkMatchesOp(entry, key, usage))
return { kind: JWK, key };
throw new TypeError(`JSON Web Key for this operation must be a private JWK`);
case 'encrypt':
case 'verify':
if (jwk.isPublicJWK(key) && jwkMatchesOp(entry, key, usage))
return { kind: JWK, key };
throw new TypeError(`JSON Web Key for this operation must be a public JWK`);
if (secret ? !jwk.isSecretJWK(key) : !(privateKey ? jwk.isPrivateJWK(key) : jwk.isPublicJWK(key))) {
throw new TypeError(secret
? `JSON Web Key for symmetric algorithms must have JWK "kty" (Key Type) equal to "oct" and the JWK "k" (Key Value) present`
: `JSON Web Key for this operation must be a ${privateKey ? 'private' : 'public'} JWK`);
}
jwkMatchesOp(entry, key, usage);
return [JWK, key];
}
if (!isKeyLike(key)) {
throw new TypeError(invalidKeyInput(alg, key, 'CryptoKey', 'KeyObject', 'JSON Web Key'));
throw new TypeError(secret
? invalidKeyInput(alg, key, 'CryptoKey', 'KeyObject', 'JSON Web Key', 'Uint8Array')
: invalidKeyInput(alg, key, 'CryptoKey', 'KeyObject', 'JSON Web Key'));
}
if (key.type === 'secret') {
throw new TypeError(`${tag(key)} instances for asymmetric algorithms must not be of type "secret"`);
}
if (key.type === 'public') {
switch (usage) {
case 'sign':
throw new TypeError(`${tag(key)} instances for asymmetric algorithm signing must be of type "private"`);
case 'decrypt':
throw new TypeError(`${tag(key)} instances for asymmetric algorithm decryption must be of type "private"`);
if (secret) {
if (key.type !== 'secret') {
throw new TypeError(`${tag(key)} instances for symmetric algorithms must be of type "secret"`);
}
}
if (key.type === 'private') {
switch (usage) {
case 'verify':
throw new TypeError(`${tag(key)} instances for asymmetric algorithm verifying must be of type "public"`);
case 'encrypt':
throw new TypeError(`${tag(key)} instances for asymmetric algorithm encryption must be of type "public"`);
else {
if (key.type === 'secret') {
throw new TypeError(`${tag(key)} instances for asymmetric algorithms must not be of type "secret"`);
}
const expectedType = privateKey ? 'private' : 'public';
if ((key.type === 'public' || key.type === 'private') && key.type !== expectedType) {
const operation = usage === 'sign'
? 'signing'
: usage === 'verify'
? 'verifying'
: `${usage.slice(0, -1)}tion`;
throw new TypeError(`${tag(key)} instances for asymmetric algorithm ${operation} must be of type "${expectedType}"`);
}
}
return isCryptoKey(key) ? { kind: CRYPTO, key } : { kind: KEYOBJECT, key };
};
const BYTES = Symbol();
const CRYPTO = Symbol();
const KEYOBJECT = Symbol();
const JWK = Symbol();
export function checkKeyType(entry, key, usage) {
return entry.symmetric
? symmetricTypeCheck(entry, key, usage)
: asymmetricTypeCheck(entry, key, usage);
return isCryptoKey(key) ? [CRYPTO, key] : [KEYOBJECT, key];
}
const BYTES = 0;
const CRYPTO = 1;
const KEYOBJECT = 2;
const JWK = 3;
let cache;

@@ -111,23 +78,17 @@ const nist = {

};
function cached(key, alg) {
function cached(key, alg, value) {
cache ||= new WeakMap();
return cache.get(key)?.[alg];
}
function store(key, alg, cryptoKey) {
const entry = cache.get(key);
if (entry) {
entry[alg] = cryptoKey;
if (value) {
if (entry) {
entry[alg] = value;
}
else {
cache.set(key, { [alg]: value });
}
}
else {
cache.set(key, { [alg]: cryptoKey });
}
return cryptoKey;
return value ?? entry?.[alg];
}
const handleJWK = async (key, jwk, entry) => {
const hit = cached(key, entry.alg);
if (hit)
return hit;
const cryptoKey = await jwkToKey(entry, { ...jwk, alg: entry.alg });
return store(key, entry.alg, cryptoKey);
};
const handleJWK = async (key, jwk, entry) => cached(key, entry.alg) ??
cached(key, entry.alg, await jwkToKey(entry, { ...jwk, alg: entry.alg }));
const handleKeyObject = (keyObject, entry) => {

@@ -138,28 +99,29 @@ const hit = cached(keyObject, entry.alg);

const isPublic = keyObject.type === 'public';
const usages = isPublic ? entry.usages.public : entry.usages.private;
const usages = entry.usages[isPublic ? 0 : 1];
const { asymmetricKeyType } = keyObject;
const crv = nist[keyObject.asymmetricKeyDetails?.namedCurve];
const params = entry.subtleFor?.({ crv, asymmetricKeyType }) ?? entry.subtle;
return store(keyObject, entry.alg, keyObject.toCryptoKey(params, isPublic, usages));
const params = entry.resolve?.({ crv, asymmetricKeyType }) ?? entry.subtle;
return cached(keyObject, entry.alg, keyObject.toCryptoKey(params, isPublic, usages));
};
export async function prepareKey(entry, key, usage) {
const tagged = checkKeyType(entry, key, usage);
switch (tagged.kind) {
switch (tagged[0]) {
case BYTES:
case CRYPTO:
return tagged.key;
return tagged[1];
case JWK: {
if (tagged.key.k) {
return decode(tagged.key.k);
const key = tagged[1];
if (key.k) {
return decode(key.k);
}
if (!Object.isFrozen(tagged.key)) {
const { key_ops } = tagged.key;
if (!Object.isFrozen(key)) {
const { key_ops } = key;
if (Array.isArray(key_ops))
Object.freeze(key_ops);
Object.freeze(tagged.key);
Object.freeze(key);
}
return handleJWK(tagged.key, tagged.key, entry);
return handleJWK(key, key, entry);
}
case KEYOBJECT: {
const keyObject = tagged.key;
const keyObject = tagged[1];
if (keyObject.type === 'secret') {

@@ -166,0 +128,0 @@ return keyObject.export();

import { JOSENotSupported, JWEInvalid, JWSInvalid } from '../util/errors.js';
export const JWS_RECOGNIZED = new Map([['b64', true]]);
export const JWE_RECOGNIZED = new Map();
export const JWS_RECOGNIZED = { __proto__: null, b64: true };
export const JWE_RECOGNIZED = { __proto__: null };
export function validateAlgorithms(option, algorithms) {

@@ -25,3 +25,3 @@ if (algorithms !== undefined &&

if (!protectedHeader || protectedHeader.crit === undefined) {
return new Set();
return [];
}

@@ -33,21 +33,18 @@ if (!Array.isArray(protectedHeader.crit) ||

}
let recognized;
if (recognizedOption !== undefined) {
recognized = new Map([...Object.entries(recognizedOption), ...recognizedDefault.entries()]);
}
else {
recognized = recognizedDefault;
}
const recognized = recognizedOption === undefined
? recognizedDefault
: { __proto__: null, ...recognizedOption, ...recognizedDefault };
for (const parameter of protectedHeader.crit) {
if (!recognized.has(parameter)) {
if (!(parameter in recognized)) {
throw new JOSENotSupported(`Extension Header Parameter "${parameter}" is not recognized`);
}
if (joseHeader[parameter] === undefined) {
if (!Object.hasOwn(joseHeader, parameter) || joseHeader[parameter] === undefined) {
throw new Err(`Extension Header Parameter "${parameter}" is missing`);
}
if (recognized.get(parameter) && protectedHeader[parameter] === undefined) {
if (recognized[parameter] &&
(!Object.hasOwn(protectedHeader, parameter) || protectedHeader[parameter] === undefined)) {
throw new Err(`Extension Header Parameter "${parameter}" MUST be integrity protected`);
}
}
return new Set(protectedHeader.crit);
return protectedHeader.crit;
}

@@ -1,14 +0,2 @@

import { checkCryptoKey } from './crypto_key.js';
export function checkModulusLength(alg, key) {
const { modulusLength } = key.algorithm;
if (typeof modulusLength !== 'number' || modulusLength < 2048) {
throw new TypeError(`${alg} requires key modulusLength to be 2048 bits or larger`);
}
}
function checkSigCryptoKey(entry, key, usage) {
checkCryptoKey(key, entry.subtle, usage);
if (entry.minModulusLength) {
checkModulusLength(entry.alg, key);
}
}
import { checkCryptoKey, checkModulusLength } from './crypto_key.js';
async function getSigKey(entry, key, usage) {

@@ -20,3 +8,5 @@ if (key instanceof Uint8Array) {

}
checkSigCryptoKey(entry, key, usage);
checkCryptoKey(key, entry.subtle, usage);
if (entry.minRsaBits)
checkModulusLength(entry.alg, key);
return key;

@@ -26,3 +16,3 @@ }

const cryptoKey = await getSigKey(entry, key, 'sign');
const signature = await crypto.subtle.sign(entry.operation, cryptoKey, data);
const signature = await crypto.subtle.sign(entry.signing, cryptoKey, data);
return new Uint8Array(signature);

@@ -33,3 +23,3 @@ }

try {
return await crypto.subtle.verify(entry.operation, cryptoKey, signature, data);
return await crypto.subtle.verify(entry.signing, cryptoKey, signature, data);
}

@@ -36,0 +26,0 @@ catch {

@@ -1,32 +0,27 @@

const isObjectLike = (value) => typeof value === 'object' && value !== null;
export function isObject(input) {
if (!isObjectLike(input) || Object.prototype.toString.call(input) !== '[object Object]') {
if (typeof input !== 'object' ||
input === null ||
Object.prototype.toString.call(input) !== '[object Object]') {
return false;
}
if (Object.getPrototypeOf(input) === null) {
const prototype = Object.getPrototypeOf(input);
if (prototype === null) {
return true;
}
let proto = input;
let proto = prototype;
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}
return Object.getPrototypeOf(input) === proto;
return prototype === proto;
}
export function isDisjoint(...headers) {
const sources = headers.filter(Boolean);
if (sources.length === 0 || sources.length === 1) {
return true;
}
let acc;
for (const header of sources) {
const parameters = Object.keys(header);
if (!acc || acc.size === 0) {
acc = new Set(parameters);
const parameters = new Set();
for (const header of headers) {
if (!header)
continue;
}
for (const parameter of parameters) {
if (acc.has(parameter)) {
for (const parameter of Object.keys(header)) {
if (parameters.has(parameter)) {
return false;
}
acc.add(parameter);
parameters.add(parameter);
}

@@ -33,0 +28,0 @@ }

import { encoder, decoder } from '../lib/buffer_utils.js';
import { encodeBase64, decodeBase64 } from '../lib/base64.js';
const invalid = 'The input to be decoded is not correctly encoded.';
export function decode(input) {

@@ -11,3 +12,3 @@ if (Uint8Array.fromBase64) {

catch (cause) {
throw new TypeError('The input to be decoded is not correctly encoded.', { cause });
throw new TypeError(invalid, { cause });
}

@@ -20,3 +21,3 @@ }

if (encoded.includes('+') || encoded.includes('/')) {
throw new TypeError('The input to be decoded is not correctly encoded.');
throw new TypeError(invalid);
}

@@ -28,3 +29,3 @@ encoded = encoded.replace(/-/g, '+').replace(/_/g, '/');

catch {
throw new TypeError('The input to be decoded is not correctly encoded.');
throw new TypeError(invalid);
}

@@ -31,0 +32,0 @@ }

{
"name": "jose",
"version": "6.2.6",
"version": "6.2.7",
"description": "JWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes",

@@ -5,0 +5,0 @@ "keywords": [