New:Socket for Asana Is Now Available.Learn more
Get Started

jose

Package Overview
Dependencies
Maintainers
1
Versions
243
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.9
to
6.2.10
+20
dist/webapi/lib/jwk_metadata.js
export function snapshotJwk(jwk) {
return { __proto__: null, ...jwk };
}
export function normalizeJwk(jwk) {
const normalized = snapshotJwk(jwk);
if (normalized.ext !== undefined && typeof normalized.ext !== 'boolean') {
throw new TypeError('"ext" (Extractable) Parameter must be a boolean');
}
if (normalized.key_ops !== undefined) {
const value = normalized.key_ops;
const keyOps = Array.isArray(value) ? [...value] : undefined;
if (!keyOps ||
keyOps.some((operation) => typeof operation !== 'string') ||
new Set(keyOps).size !== keyOps.length) {
throw new TypeError('"key_ops" (Key Operations) Parameter must be an array of unique strings');
}
normalized.key_ops = keyOps;
}
return normalized;
}
export function validateExtractableOption(extractable) {
if (extractable !== undefined && typeof extractable !== 'boolean') {
throw new TypeError('"extractable" option must be a boolean');
}
return extractable;
}
+4
-4

@@ -39,3 +39,3 @@ import type * as types from '../types.d.ts';

* Timeout (in milliseconds) for the HTTP request. When reached the request will be aborted and
* the verification will fail. Default is 5000 (5 seconds).
* the verification will fail. Must be a non-negative integer. Default is 5000 (5 seconds).
*/

@@ -45,3 +45,3 @@ timeoutDuration?: number;

* Duration (in milliseconds) for which no more HTTP requests will be triggered after a previous
* successful fetch. Default is 30000 (30 seconds).
* successful fetch. Must not be `NaN`. Default is 30000 (30 seconds).
*/

@@ -51,3 +51,3 @@ cooldownDuration?: number;

* Maximum time (in milliseconds) between successful HTTP requests. Default is 600000 (10
* minutes).
* minutes). Must not be `NaN`.
*/

@@ -66,3 +66,3 @@ cacheMaxAge?: number | typeof Infinity;

jwks: types.JSONWebKeySet;
/** Last updated at timestamp (seconds since epoch) */
/** Last updated at timestamp (milliseconds since epoch) */
uat: number;

@@ -69,0 +69,0 @@ }

@@ -14,5 +14,5 @@ import type * as types from '../../types.d.ts';

* > the payload, protected header, and unprotected header of that successfully verified signature
* > entry. Other signature entries in the General JWS are not validated, and their headers are not
* > included in the returned result. Recipients of a General JWS should only rely on the returned
* > (verified) data.
* > entry. Other signature entries' headers may be inspected solely to reject inconsistent use of the
* > JWS Unencoded Payload Option, and their headers are not included in the returned result.
* > Recipients of a General JWS should only rely on the returned (verified) data.
*

@@ -19,0 +19,0 @@ * @param jws General JWS.

import type * as types from '../types.d.ts';
/**
* EncryptJWT constructor
*
* @param payload The JWT Claims Set object. Defaults to an empty object.
*/
declare const EncryptJWT_base: new (payload?: types.JWTPayload) => types.ProduceJWT;
/** The EncryptJWT class is used to build and encrypt Compact JWE formatted JSON Web Tokens. */
export declare class EncryptJWT implements types.ProduceJWT {
export declare class EncryptJWT extends EncryptJWT_base {
#private;
/**
* {@link EncryptJWT} constructor
*
* @param payload The JWT Claims Set object. Defaults to an empty object.
*/
constructor(payload?: types.JWTPayload);
setIssuer(issuer: string): this;
setSubject(subject: string): this;
setAudience(audience: string | string[]): this;
setJti(jwtId: string): this;
setNotBefore(input: number | string | Date): this;
setExpirationTime(input: number | string | Date): this;
setIssuedAt(input?: number | string | Date): this;
/**
* Sets the JWE Protected Header on the EncryptJWT object.

@@ -69,1 +62,2 @@ *

}
export {};
import type * as types from '../types.d.ts';
/**
* SignJWT constructor
*
* @param payload The JWT Claims Set object. Defaults to an empty object.
*/
declare const SignJWT_base: new (payload?: types.JWTPayload) => types.ProduceJWT;
/** The SignJWT class is used to build and sign Compact JWS formatted JSON Web Tokens. */
export declare class SignJWT implements types.ProduceJWT {
export declare class SignJWT extends SignJWT_base {
#private;
/**
* {@link SignJWT} constructor
*
* @param payload The JWT Claims Set object. Defaults to an empty object.
*/
constructor(payload?: types.JWTPayload);
setIssuer(issuer: string): this;
setSubject(subject: string): this;
setAudience(audience: string | string[]): this;
setJti(jwtId: string): this;
setNotBefore(input: number | string | Date): this;
setExpirationTime(input: number | string | Date): this;
setIssuedAt(input?: number | string | Date): this;
/**
* Sets the JWS Protected Header on the SignJWT object.

@@ -33,1 +26,2 @@ *

}
export {};

@@ -9,20 +9,13 @@ import type * as types from '../types.d.ts';

}
/**
* UnsecuredJWT constructor
*
* @param payload The JWT Claims Set object. Defaults to an empty object.
*/
declare const UnsecuredJWT_base: new (payload?: types.JWTPayload) => types.ProduceJWT;
/** The UnsecuredJWT class is a utility for dealing with `{ "alg": "none" }` Unsecured JWTs. */
export declare class UnsecuredJWT implements types.ProduceJWT {
#private;
/**
* {@link UnsecuredJWT} constructor
*
* @param payload The JWT Claims Set object. Defaults to an empty object.
*/
constructor(payload?: types.JWTPayload);
export declare class UnsecuredJWT extends UnsecuredJWT_base {
private jwt;
/** Encodes the Unsecured JWT. */
encode(): string;
setIssuer(issuer: string): this;
setSubject(subject: string): this;
setAudience(audience: string | string[]): this;
setJti(jwtId: string): this;
setNotBefore(input: number | string | Date): this;
setExpirationTime(input: number | string | Date): this;
setIssuedAt(input?: number | string | Date): this;
/**

@@ -36,1 +29,2 @@ * Decodes an unsecured JWT.

}
export {};

@@ -1,27 +0,49 @@

import { FlattenedEncrypt } from '../flattened/encrypt.js';
import { assertNotSet } from '../../lib/helpers.js';
import { assertUint8Array } from '../../lib/type_checks.js';
import { createJWE } from '../../lib/jwe_encrypt.js';
export class CompactEncrypt {
#flattened;
#plaintext;
#protectedHeader;
#cek;
#iv;
#keyManagementParameters;
constructor(plaintext) {
this.#flattened = new FlattenedEncrypt(plaintext);
assertUint8Array(plaintext, 'plaintext');
this.#plaintext = plaintext;
}
setContentEncryptionKey(cek) {
this.#flattened.setContentEncryptionKey(cek);
assertNotSet(this.#cek, 'setContentEncryptionKey');
this.#cek = cek;
return this;
}
setInitializationVector(iv) {
this.#flattened.setInitializationVector(iv);
assertNotSet(this.#iv, 'setInitializationVector');
this.#iv = iv;
return this;
}
setProtectedHeader(protectedHeader) {
this.#flattened.setProtectedHeader(protectedHeader);
assertNotSet(this.#protectedHeader, 'setProtectedHeader');
this.#protectedHeader = protectedHeader;
return this;
}
setKeyManagementParameters(parameters) {
this.#flattened.setKeyManagementParameters(parameters);
assertNotSet(this.#keyManagementParameters, 'setKeyManagementParameters');
this.#keyManagementParameters = parameters;
return this;
}
async encrypt(key, options) {
const jwe = await this.#flattened.encrypt(key, options);
const jwe = await createJWE([
this.#plaintext,
this.#protectedHeader,
undefined,
undefined,
undefined,
this.#cek,
this.#iv,
this.#keyManagementParameters,
undefined,
false,
], key, options);
return [jwe.protected, jwe.encrypted_key, jwe.iv, jwe.ciphertext, jwe.tag].join('.');
}
}
import { JWEInvalid } from '../../util/errors.js';
import { isObject } from '../../lib/type_checks.js';
import { prepareDecrypt, decryptJWE, decryptResult, checkShared, checkRecipient, } from '../../lib/jwe_decrypt.js';
import { prepareDecrypt, decryptJWE, decryptResult, checkRecipient, snapshotSharedJWE, snapshotRecipientJWE, } from '../../lib/jwe_decrypt.js';
export async function flattenedDecrypt(jwe, key, options) {

@@ -8,5 +8,9 @@ if (!isObject(jwe)) {

}
checkShared(jwe);
checkRecipient(jwe);
return decryptResult(jwe, await decryptJWE(jwe, prepareDecrypt(options), key));
const shared = snapshotSharedJWE(jwe);
const [recipient, , error] = snapshotRecipientJWE(jwe);
if (!recipient)
throw error;
const snapshot = { ...shared, ...recipient };
checkRecipient(snapshot);
return decryptResult(snapshot, await decryptJWE(snapshot, prepareDecrypt(options), key));
}

@@ -1,5 +0,4 @@

import { unprotected, assertNotSet } from '../../lib/helpers.js';
import { JWEInvalid } from '../../util/errors.js';
import { assertNotSet } from '../../lib/helpers.js';
import { createJWE } from '../../lib/jwe_encrypt.js';
import { validateCritDuplicates } from '../../lib/options.js';
import { assertUint8Array } from '../../lib/type_checks.js';
export class FlattenedEncrypt {

@@ -15,5 +14,3 @@ #plaintext;

constructor(plaintext) {
if (!(plaintext instanceof Uint8Array)) {
throw new TypeError('plaintext must be an instance of Uint8Array');
}
assertUint8Array(plaintext, 'plaintext');
this.#plaintext = plaintext;

@@ -56,6 +53,2 @@ }

async encrypt(key, options) {
if (!this.#protectedHeader && !this.#unprotectedHeader && !this.#sharedUnprotectedHeader) {
throw new JWEInvalid('either setProtectedHeader, setUnprotectedHeader, or sharedUnprotectedHeader must be called before #encrypt()');
}
validateCritDuplicates(JWEInvalid, this.#protectedHeader);
return createJWE([

@@ -70,6 +63,6 @@ this.#plaintext,

this.#keyManagementParameters,
options?.crit,
options ? unprotected in options : false,
], key);
undefined,
false,
], key, options);
}
}

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

import { prepareDecrypt, shareJWE, decryptRecipient, decryptResult, checkShared, checkRecipient, } from '../../lib/jwe_decrypt.js';
import { prepareDecrypt, shareJWE, decryptRecipient, decryptResult, checkRecipient, snapshotSharedJWE, snapshotRecipientJWE, } from '../../lib/jwe_decrypt.js';
import { JWEDecryptionFailed, JWEInvalid } from '../../util/errors.js';

@@ -8,14 +8,20 @@ import { isObject } from '../../lib/type_checks.js';

}
if (!Array.isArray(jwe.recipients) || !jwe.recipients.every(isObject)) {
const inputRecipients = jwe.recipients;
if (!Array.isArray(inputRecipients)) {
throw new JWEInvalid('JWE Recipients missing or incorrect type');
}
if (!jwe.recipients.length) {
const recipients = Array.from(inputRecipients);
if (!recipients.every(isObject)) {
throw new JWEInvalid('JWE Recipients missing or incorrect type');
}
if (!recipients.length) {
throw new JWEInvalid('JWE Recipients has no members');
}
let shared;
let sharedJwe;
let token;
try {
checkShared(jwe);
shared = prepareDecrypt(options);
token = shareJWE(jwe);
sharedJwe = snapshotSharedJWE(jwe);
token = shareJWE(sharedJwe);
}

@@ -25,5 +31,6 @@ catch {

}
if (jwe.recipients.length > 1) {
for (const { header } of jwe.recipients) {
const alg = token[0]?.alg ?? header?.alg ?? jwe.unprotected?.alg;
const recipientSnapshots = recipients.map((recipient) => snapshotRecipientJWE(recipient));
if (recipients.length > 1) {
for (const [, headerAlg] of recipientSnapshots) {
const alg = token[0]?.alg ?? headerAlg ?? sharedJwe.unprotected?.alg;
if (alg === 'dir' || alg === 'ECDH-ES') {

@@ -34,14 +41,7 @@ throw new JWEInvalid(`"${alg}" alg may only have a single recipient`);

}
for (const recipient of jwe.recipients) {
for (const [recipient] of recipientSnapshots) {
if (!recipient)
continue;
try {
const flattened = {
aad: jwe.aad,
ciphertext: jwe.ciphertext,
encrypted_key: recipient.encrypted_key,
header: recipient.header,
iv: jwe.iv,
protected: jwe.protected,
tag: jwe.tag,
unprotected: jwe.unprotected,
};
const flattened = { ...sharedJwe, ...recipient };
checkRecipient(flattened);

@@ -48,0 +48,0 @@ return decryptResult(flattened, await decryptRecipient(flattened, token, shared, key));

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

import { FlattenedEncrypt } from '../flattened/encrypt.js';
import { assertNotSet } from '../../lib/helpers.js';

@@ -7,6 +6,6 @@ import { JWEInvalid } from '../../util/errors.js';

import { encode as b64u } from '../../util/base64url.js';
import { validateCritDuplicates } from '../../lib/options.js';
import { checkDisjoint, checkEncryptHeaders, encryptJWE } from '../../lib/jwe_encrypt.js';
import { checkDisjoint, checkEncryptHeaders, createJWE, encryptJWE } from '../../lib/jwe_encrypt.js';
import { prepareKey } from '../../lib/key.js';
import { jweAlgorithm } from '../../lib/jwe_algorithms.js';
import { assertUint8Array } from '../../lib/type_checks.js';
class IndividualRecipient {

@@ -82,15 +81,17 @@ #parent;

}
if (!(this.#plaintext instanceof Uint8Array)) {
throw new TypeError('plaintext must be an instance of Uint8Array');
}
assertUint8Array(this.#plaintext, 'plaintext');
if (this.#recipients.length === 1) {
const [recipient] = this.#recipients;
const [unprotectedHeader, keyManagementParameters, key, crit] = recipient.state;
const flattened = await new FlattenedEncrypt(this.#plaintext)
.setAdditionalAuthenticatedData(this.#aad)
.setProtectedHeader(this.#protectedHeader)
.setSharedUnprotectedHeader(this.#unprotectedHeader)
.setUnprotectedHeader(unprotectedHeader)
.setKeyManagementParameters(keyManagementParameters)
.encrypt(key, { crit });
const [unprotectedHeader, keyManagementParameters, key, crit] = this.#recipients[0].state;
const flattened = await createJWE([
this.#plaintext,
this.#protectedHeader,
unprotectedHeader,
this.#unprotectedHeader,
this.#aad,
undefined,
undefined,
keyManagementParameters,
crit,
false,
], key);
const jwe = {

@@ -107,4 +108,5 @@ ciphertext: flattened.ciphertext,

}
validateCritDuplicates(JWEInvalid, this.#protectedHeader);
let enc;
let protectedHeader = this.#protectedHeader;
let sharedUnprotectedHeader = this.#unprotectedHeader;
const inputs = [];

@@ -117,5 +119,5 @@ const checked = [];

this.#plaintext,
this.#protectedHeader,
protectedHeader,
unprotectedHeader,
this.#unprotectedHeader,
sharedUnprotectedHeader,
this.#aad,

@@ -131,2 +133,6 @@ undefined,

checked.push(headers);
if (i === 0) {
protectedHeader = input[1];
sharedUnprotectedHeader = input[3];
}
if (headers[1] === 'dir' || headers[1] === 'ECDH-ES') {

@@ -149,3 +155,3 @@ throw new JWEInvalid(`"${headers[1]}" alg may only have a single recipient`);

const recipient = this.#recipients[i];
const [unprotectedHeader, keyManagementParameters, key] = recipient.state;
const [, keyManagementParameters, key] = recipient.state;
const target = {};

@@ -164,2 +170,3 @@ jwe.recipients.push(target);

const [, alg, , encEntry] = checked[i];
const unprotectedHeader = inputs[i][2];
const k = await prepareKey(jweAlgorithm(alg), key, 'encrypt');

@@ -171,3 +178,3 @@ const [, encryptedKey, parameters] = await encryptKeyManagement(alg, encEntry, k, cek, keyManagementParameters);

if (parameters)
checkDisjoint(this.#protectedHeader, header, this.#unprotectedHeader);
checkDisjoint(inputs[i][1], header, inputs[i][3]);
target.header = header;

@@ -174,0 +181,0 @@ }

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

import { JWSInvalid } from '../util/errors.js';
import { normalizeJwk } from '../lib/jwk_metadata.js';
export async function EmbeddedJWK(protectedHeader, token) {

@@ -14,4 +15,17 @@ const joseHeader = {

}
let jwk;
try {
jwk = normalizeJwk(joseHeader.jwk);
}
catch (cause) {
throw new JWSInvalid('Invalid Embedded JWK', { cause });
}
const entry = jwsAlgorithm(joseHeader.alg);
const key = await jwkToKey(entry, { ...joseHeader.jwk, ext: true });
if (jwk.use !== undefined && jwk.use !== 'sig') {
throw new JWSInvalid('Invalid Embedded JWK, its "use" must be "sig" when present');
}
if (jwk.alg !== undefined && jwk.alg !== entry.alg) {
throw new JWSInvalid(`Invalid Embedded JWK, its "alg" must be "${entry.alg}" when present`);
}
const key = await jwkToKey(entry, { ...jwk, ext: true });
if (key.type !== 'public') {

@@ -18,0 +32,0 @@ throw new JWSInvalid('"jwk" (JSON Web Key) Header Parameter must be a public key');

@@ -6,5 +6,6 @@ import { digest } from '../lib/helpers.js';

import { isKeyLike } from '../lib/is_key_like.js';
import { isJWK } from '../lib/type_checks.js';
import { isObject } from '../lib/type_checks.js';
import { exportJWK } from '../key/export.js';
import { invalidKeyInput } from '../lib/invalid_key_input.js';
import { snapshotJwk } from '../lib/jwk_metadata.js';
const check = (value, description) => {

@@ -17,7 +18,10 @@ if (typeof value !== 'string' || !value) {

let jwk;
if (isJWK(key)) {
jwk = key;
if (isObject(key)) {
jwk = snapshotJwk(key);
if (typeof jwk.kty !== 'string') {
throw new TypeError(invalidKeyInput(key, 'CryptoKey', 'KeyObject', 'JSON Web Key'));
}
}
else if (isKeyLike(key)) {
jwk = await exportJWK(key);
jwk = snapshotJwk(await exportJWK(key));
}

@@ -57,3 +61,5 @@ else {

case 'oct':
check(jwk.k, '"k" (Key Value) Parameter');
if (typeof jwk.k !== 'string') {
throw new JWKInvalid('"k" (Key Value) Parameter missing or invalid');
}
components = { k: jwk.k, kty: jwk.kty };

@@ -60,0 +66,0 @@ break;

import { jwkToKey } from '../lib/jwk_to_key.js';
import { JWS } from '../lib/jws_algorithms.js';
import { JWKSInvalid, JOSENotSupported, JWKSNoMatchingKey, JWKSMultipleMatchingKeys, } from '../util/errors.js';
import { isObject } from '../lib/type_checks.js';
function signatureAlgorithm(alg) {
const entry = typeof alg === 'string' ? JWS[alg] : undefined;
if (!entry || entry.secret) {
throw new JOSENotSupported('Unsupported "alg" value for a JSON Web Key Set');
}
return entry;
import { isJwkSet } from '../lib/type_checks.js';
import { snapshotJwk } from '../lib/jwk_metadata.js';
function isUsableJWK(jwk, entry, alg, kid) {
const { kty, key_ops, ext, kid: jwkKid, alg: jwkAlg, use, crv } = snapshotJwk(jwk);
const keyOps = Array.isArray(key_ops) ? [...key_ops] : key_ops;
return ((ext === undefined || typeof ext === 'boolean') &&
(keyOps === undefined ||
(Array.isArray(keyOps) &&
keyOps.every((operation, index) => typeof operation === 'string' && keyOps.indexOf(operation) === index) &&
keyOps.includes('verify'))) &&
entry.kty.includes(kty) &&
(kid === undefined || (typeof kid === 'string' && kid === jwkKid)) &&
(jwkAlg === undefined ? kty !== 'AKP' : alg === jwkAlg) &&
(use === undefined || use === 'sig') &&
(!entry.crv || crv === entry.crv));
}
function isJWKSLike(jwks) {
if (!jwks || typeof jwks !== 'object') {
return false;
async function importWithAlgCache(cache, jwk, entry) {
const cached = cache.get(jwk) || cache.set(jwk, {}).get(jwk);
const { alg } = entry;
if (cached[alg] === undefined) {
const key = await jwkToKey(entry, { ...jwk, alg, ext: true });
if (key.type !== 'public') {
throw new JWKSInvalid('JSON Web Key Set members must be public keys');
}
cached[alg] = key;
}
const { keys } = jwks;
return Array.isArray(keys) && keys.every((isObject));
return cached[alg];
}
class LocalJWKSetImpl {
#jwks;
#cached = new WeakMap();
constructor(jwks) {
if (!isJWKSLike(jwks)) {
throw new JWKSInvalid('JSON Web Key Set malformed');
}
this.#jwks = structuredClone(jwks);
export function createLocalJWKSet(jwks) {
let snapshot;
try {
snapshot = structuredClone(jwks);
}
jwks() {
return this.#jwks;
catch { }
if (!isJwkSet(snapshot)) {
throw new JWKSInvalid('JSON Web Key Set malformed');
}
async getKey(protectedHeader, token) {
const cached = new WeakMap();
const localJWKSet = async (protectedHeader, token) => {
const { alg, kid } = { ...protectedHeader, ...token?.header };
const entry = signatureAlgorithm(alg);
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 entry = typeof alg === 'string' ? JWS[alg] : undefined;
if (!entry || entry.secret) {
throw new JOSENotSupported('Unsupported "alg" value for a JSON Web Key Set');
}
const candidates = snapshot.keys.filter((jwk) => isUsableJWK(jwk, entry, alg, kid));
const { 0: jwk, length } = candidates;
if (length === 0) {
if (!length) {
throw new JWKSNoMatchingKey();

@@ -46,7 +55,6 @@ }

const error = new JWKSMultipleMatchingKeys();
const _cached = this.#cached;
error[Symbol.asyncIterator] = async function* () {
for (const jwk of candidates) {
try {
yield await importWithAlgCache(_cached, jwk, entry);
yield await importWithAlgCache(cached, jwk, entry);
}

@@ -58,23 +66,7 @@ catch { }

}
return importWithAlgCache(this.#cached, jwk, entry);
}
}
async function importWithAlgCache(cache, jwk, entry) {
const cached = cache.get(jwk) || cache.set(jwk, { __proto__: null }).get(jwk);
if (cached[entry.alg] === undefined) {
const key = await jwkToKey(entry, { ...jwk, alg: entry.alg, ext: true });
if (key.type !== 'public') {
throw new JWKSInvalid('JSON Web Key Set members must be public keys');
}
cached[entry.alg] = key;
}
return cached[entry.alg];
}
export function createLocalJWKSet(jwks) {
const set = new LocalJWKSetImpl(jwks);
const localJWKSet = async (protectedHeader, token) => set.getKey(protectedHeader, token);
Object.defineProperty(localJWKSet, 'jwks', {
value: () => structuredClone(set.jwks()),
return importWithAlgCache(cached, jwk, entry);
};
return Object.defineProperty(localJWKSet, 'jwks', {
value: () => structuredClone(snapshot),
});
return localJWKSet;
}
import { JOSEError, JWKSNoMatchingKey, JWKSTimeout } from '../util/errors.js';
import { createLocalJWKSet } from './local.js';
import { isObject } from '../lib/type_checks.js';
import { isJwkSet } from '../lib/type_checks.js';
function isCloudflareWorkers() {

@@ -12,3 +12,3 @@ return (typeof WebSocketPair !== 'undefined' ||

const NAME = 'jose';
const VERSION = 'v6.2.9';
const VERSION = 'v6.2.10';
USER_AGENT = `${NAME}/${VERSION}`;

@@ -40,133 +40,112 @@ }

export const jwksCache = Symbol();
function isFreshJwksCache(input, cacheMaxAge) {
if (typeof input !== 'object' || input === null) {
return false;
function isFreshFor(timestamp, duration) {
return Number.isFinite(timestamp) && Date.now() < timestamp + duration;
}
function validateDuration(value, fallback, option) {
if (Number.isNaN(value)) {
throw new TypeError(`"${option}" option must not be NaN`);
}
if (!('uat' in input) || typeof input.uat !== 'number' || Date.now() - input.uat >= cacheMaxAge) {
return false;
}
if (!('jwks' in input) ||
!isObject(input.jwks) ||
!Array.isArray(input.jwks.keys) ||
!Array.prototype.every.call(input.jwks.keys, isObject)) {
return false;
}
return true;
return typeof value === 'number' ? value : fallback;
}
class RemoteJWKSetImpl {
#url;
#timeoutDuration;
#cooldownDuration;
#cacheMaxAge;
#jwksTimestamp;
#pendingFetch;
#headers;
#customFetch;
#local;
#cache;
constructor(url, options) {
if (!(url instanceof URL)) {
throw new TypeError('url must be an instance of URL');
}
this.#url = new URL(url.href);
const opts = options ?? {};
this.#timeoutDuration = typeof opts.timeoutDuration === 'number' ? opts.timeoutDuration : 5000;
this.#cooldownDuration =
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')) {
this.#headers.set('User-Agent', USER_AGENT);
}
if (!this.#headers.has('accept')) {
this.#headers.set('accept', 'application/json');
this.#headers.append('accept', 'application/jwk-set+json');
}
this.#customFetch = opts[customFetch];
const cache = opts[jwksCache];
if (cache !== undefined) {
this.#cache = cache;
if (isFreshJwksCache(cache, this.#cacheMaxAge)) {
this.#jwksTimestamp = this.#cache.uat;
this.#local = createLocalJWKSet(this.#cache.jwks);
}
}
export function createRemoteJWKSet(url, options) {
if (!(url instanceof URL)) {
throw new TypeError('url must be an instance of URL');
}
pendingFetch() {
return !!this.#pendingFetch;
const href = new URL(url.href).href;
const opts = options ?? {};
const timeoutOption = opts.timeoutDuration;
if (typeof timeoutOption === 'number' &&
(!Number.isInteger(timeoutOption) || timeoutOption < 0)) {
throw new TypeError('"timeoutDuration" option must be a non-negative integer');
}
#validFor(duration) {
return typeof this.#jwksTimestamp === 'number' && Date.now() < this.#jwksTimestamp + duration;
const timeoutDuration = typeof timeoutOption === 'number' ? timeoutOption : 5000;
const cooldownDuration = validateDuration(opts.cooldownDuration, 30000, 'cooldownDuration');
const cacheMaxAge = validateDuration(opts.cacheMaxAge, 600000, 'cacheMaxAge');
const headers = new Headers(opts.headers);
if (USER_AGENT && !headers.has('User-Agent')) {
headers.set('User-Agent', USER_AGENT);
}
coolingDown() {
return this.#validFor(this.#cooldownDuration);
if (!headers.has('accept')) {
headers.set('accept', 'application/json, application/jwk-set+json');
}
fresh() {
return this.#validFor(this.#cacheMaxAge);
const fetchImpl = opts[customFetch];
const cache = opts[jwksCache];
let jwksTimestamp;
let pendingFetch;
let reloadSequence = 0;
let appliedSequence = 0;
let local;
if (cache && typeof cache === 'object') {
const { uat, jwks } = cache;
if (isFreshFor(uat, cacheMaxAge) && isJwkSet(jwks)) {
jwksTimestamp = uat;
local = createLocalJWKSet(jwks);
}
}
jwks() {
return this.#local?.jwks();
}
async getKey(protectedHeader, token) {
if (!this.#local || !this.fresh()) {
await this.reload();
const reload = async () => {
if (pendingFetch && isCloudflareWorkers()) {
pendingFetch = undefined;
}
if (!pendingFetch) {
const sequence = ++reloadSequence;
const current = (pendingFetch = fetchJwks(href, headers, AbortSignal.timeout(timeoutDuration), fetchImpl)
.then((json) => {
const next = createLocalJWKSet(json);
if (sequence <= appliedSequence) {
return;
}
local = next;
const updatedAt = Date.now();
if (cache) {
cache.uat = updatedAt;
cache.jwks = json;
}
jwksTimestamp = updatedAt;
appliedSequence = sequence;
})
.finally(() => {
if (pendingFetch === current) {
pendingFetch = undefined;
}
}));
}
await pendingFetch;
};
const remoteJWKSet = async (protectedHeader, token) => {
if (!local || !isFreshFor(jwksTimestamp, cacheMaxAge)) {
await reload();
}
try {
return await this.#local(protectedHeader, token);
return await local(protectedHeader, token);
}
catch (err) {
if (err instanceof JWKSNoMatchingKey) {
if (this.coolingDown() === false) {
await this.reload();
return this.#local(protectedHeader, token);
}
if (err instanceof JWKSNoMatchingKey && !isFreshFor(jwksTimestamp, cooldownDuration)) {
await reload();
return local(protectedHeader, token);
}
throw err;
}
}
async reload() {
if (this.#pendingFetch && isCloudflareWorkers()) {
this.#pendingFetch = undefined;
}
this.#pendingFetch ||= fetchJwks(this.#url.href, this.#headers, AbortSignal.timeout(this.#timeoutDuration), this.#customFetch)
.then((json) => {
this.#local = createLocalJWKSet(json);
if (this.#cache) {
this.#cache.uat = Date.now();
this.#cache.jwks = json;
}
this.#jwksTimestamp = Date.now();
})
.finally(() => {
this.#pendingFetch = undefined;
});
await this.#pendingFetch;
}
}
export function createRemoteJWKSet(url, options) {
const set = new RemoteJWKSetImpl(url, options);
const remoteJWKSet = async (protectedHeader, token) => set.getKey(protectedHeader, token);
Object.defineProperties(remoteJWKSet, {
};
return Object.defineProperties(remoteJWKSet, {
coolingDown: {
get: () => set.coolingDown(),
get: () => isFreshFor(jwksTimestamp, cooldownDuration),
enumerable: true,
},
fresh: {
get: () => set.fresh(),
get: () => isFreshFor(jwksTimestamp, cacheMaxAge),
enumerable: true,
},
reload: {
value: () => set.reload(),
value: reload,
enumerable: true,
},
reloading: {
get: () => set.pendingFetch(),
get: () => !!pendingFetch,
enumerable: true,
},
jwks: {
value: () => set.jwks(),
value: () => local?.jwks(),
enumerable: true,
},
});
return remoteJWKSet;
}

@@ -1,11 +0,14 @@

import { FlattenedSign } from '../flattened/sign.js';
import { unencodedPayload } from '../../lib/jws_sign.js';
import { createCompactSignature } from '../../lib/jws_sign.js';
import { assertNotSet } from '../../lib/helpers.js';
export class CompactSign {
#flattened;
#payload;
#protectedHeader;
constructor(payload) {
this.#flattened = new FlattenedSign(payload);
if (!(payload instanceof Uint8Array)) {
throw new TypeError('payload must be an instance of Uint8Array');
}
this.#payload = payload;
}
setProtectedHeader(protectedHeader) {
this.#flattened.setProtectedHeader(protectedHeader);
assertNotSet(this.#protectedHeader, 'setProtectedHeader');
this.#protectedHeader = protectedHeader;

@@ -15,8 +18,6 @@ return this;

async sign(key, options) {
if (unencodedPayload(this.#protectedHeader)) {
return createCompactSignature(this.#payload, this.#protectedHeader, options?.crit, key, () => {
throw new TypeError('use the flattened module for creating JWS with b64: false');
}
const jws = await this.#flattened.sign(key, options);
return `${jws.protected}.${jws.payload}.${jws.signature}`;
});
}
}

@@ -24,3 +24,3 @@ import { createSignature } from '../../lib/jws_sign.js';

async sign(key, options) {
return createSignature({
const [jws] = await createSignature({
payload: this.#payload,

@@ -31,3 +31,4 @@ protectedHeader: this.#protectedHeader,

}, key);
return jws;
}
}
import { JWSInvalid } from '../../util/errors.js';
import { isObject } from '../../lib/type_checks.js';
import { prepareVerify, verifySignature, verifyResult } from '../../lib/jws_verify.js';
import { encodeJsonUnencodedPayload, prepareVerify, snapshotJws, verifySignature, verifyResult, } from '../../lib/jws_verify.js';
export async function flattenedVerify(jws, key, options) {

@@ -8,18 +8,19 @@ if (!isObject(jws)) {

}
if (jws.protected === undefined && jws.header === undefined) {
const snapshot = snapshotJws(jws);
if (snapshot.protected === undefined && snapshot.header === undefined) {
throw new JWSInvalid('Flattened JWS must have either of the "protected" or "header" members');
}
if (jws.protected !== undefined && typeof jws.protected !== 'string') {
if (snapshot.protected !== undefined && typeof snapshot.protected !== 'string') {
throw new JWSInvalid('JWS Protected Header incorrect type');
}
if (jws.payload === undefined) {
if (snapshot.payload === undefined) {
throw new JWSInvalid('JWS Payload missing');
}
if (typeof jws.signature !== 'string') {
if (typeof snapshot.signature !== 'string') {
throw new JWSInvalid('JWS Signature missing or incorrect type');
}
if (jws.header !== undefined && !isObject(jws.header)) {
if (snapshot.header !== undefined && !isObject(snapshot.header)) {
throw new JWSInvalid('JWS Unprotected Header incorrect type');
}
return verifyResult(jws, await verifySignature(jws, prepareVerify(options), key));
return verifyResult(snapshot, await verifySignature(snapshot, prepareVerify(options), key, encodeJsonUnencodedPayload));
}

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

const encoded = [];
let b64;
for (let i = 0; i < this.#signatures.length; i++) {
const signature = this.#signatures[i];
const [protectedHeader, unprotectedHeader, key, crit] = signature.state;
const { payload, ...rest } = await createSignature({
const [{ payload, ...rest }, signatureB64] = await createSignature({
payload: this.#payload,

@@ -65,6 +66,7 @@ protectedHeader,

}, key);
if (i === 0) {
if (b64 === undefined) {
b64 = signatureB64;
jws.payload = payload;
}
else if (jws.payload !== payload) {
else if (b64 !== signatureB64) {
throw new JWSInvalid('inconsistent use of JWS Unencoded Payload (RFC7797)');

@@ -71,0 +73,0 @@ }

@@ -1,4 +0,34 @@

import { prepareVerify, verifySignature, verifyResult } from '../../lib/jws_verify.js';
import { encodeJsonUnencodedPayload, parseProtectedHeader, prepareVerify, snapshotJws, verifySignature, verifyResult, } from '../../lib/jws_verify.js';
import { JWSInvalid, JWSSignatureVerificationFailed } from '../../util/errors.js';
import { isObject } from '../../lib/type_checks.js';
function snapshotSignature(signature, payload) {
try {
const jws = snapshotJws(signature, [payload]);
const { protected: encodedProtected, header, signature: encodedSignature } = jws;
if (encodedProtected === undefined && header === undefined)
return undefined;
if (encodedProtected !== undefined && typeof encodedProtected !== 'string')
return undefined;
if (typeof encodedSignature !== 'string')
return undefined;
if (header !== undefined && !isObject(header))
return undefined;
const protectedHeader = parseProtectedHeader(encodedProtected);
const { b64, crit } = protectedHeader;
return [
jws,
protectedHeader,
Array.isArray(crit) && crit.includes('b64')
? typeof b64 === 'boolean'
? b64
? 1
: 2
: 0
: 1,
];
}
catch {
return undefined;
}
}
export async function generalVerify(jws, key, options) {

@@ -8,9 +38,13 @@ if (!isObject(jws)) {

}
const { signatures, payload } = jws;
if (!Array.isArray(signatures) || !signatures.every(isObject)) {
const { signatures, payload: inputPayload } = jws;
if (!Array.isArray(signatures)) {
throw new JWSInvalid('JWS Signatures missing or incorrect type');
}
const signatureEntries = Array.from(signatures);
if (!signatureEntries.every(isObject)) {
throw new JWSInvalid('JWS Signatures missing or incorrect type');
}
let shared;
try {
if (payload === undefined)
if (inputPayload === undefined)
throw new Error();

@@ -22,20 +56,16 @@ shared = prepareVerify(options);

}
for (const signature of signatures) {
const payload = inputPayload instanceof Uint8Array ? new Uint8Array(inputPayload) : inputPayload;
const candidates = signatureEntries
.map((signature) => snapshotSignature(signature, payload))
.filter((candidate) => candidate !== undefined);
let modes = 0;
for (const [, , mode] of candidates) {
modes |= mode;
if (modes === 3) {
throw new JWSInvalid('inconsistent use of JWS Unencoded Payload (RFC7797)');
}
}
for (const candidate of candidates) {
try {
const { protected: encodedProtected, header, signature: encodedSignature } = signature;
if (encodedProtected === undefined && header === undefined)
throw new Error();
if (encodedProtected !== undefined && typeof encodedProtected !== 'string') {
throw new Error();
}
if (typeof encodedSignature !== 'string')
throw new Error();
if (header !== undefined && !isObject(header))
throw new Error();
return verifyResult(signature, await verifySignature({
header,
payload,
protected: encodedProtected,
signature: encodedSignature,
}, shared, key));
return verifyResult(candidate[0], await verifySignature(candidate[0], shared, key, encodeJsonUnencodedPayload, candidate[1]));
}

@@ -42,0 +72,0 @@ catch {

@@ -8,12 +8,10 @@ import { prepareDecrypt, decryptCompact } from '../lib/jwe_decrypt.js';

const payload = validateClaimsSet(protectedHeader, decrypted[0], options);
if (protectedHeader.iss !== undefined && protectedHeader.iss !== payload.iss) {
throw new JWTClaimValidationFailed('replicated "iss" claim header parameter mismatch', payload, 'iss', 'mismatch');
for (const claim of ['iss', 'sub', 'aud']) {
if (protectedHeader[claim] !== undefined &&
(claim === 'aud'
? JSON.stringify(protectedHeader.aud) !== JSON.stringify(payload.aud)
: protectedHeader[claim] !== payload[claim])) {
throw new JWTClaimValidationFailed(`replicated "${claim}" claim header parameter mismatch`, payload, claim, 'mismatch');
}
}
if (protectedHeader.sub !== undefined && protectedHeader.sub !== payload.sub) {
throw new JWTClaimValidationFailed('replicated "sub" claim header parameter mismatch', payload, 'sub', 'mismatch');
}
if (protectedHeader.aud !== undefined &&
JSON.stringify(protectedHeader.aud) !== JSON.stringify(payload.aud)) {
throw new JWTClaimValidationFailed('replicated "aud" claim header parameter mismatch', payload, 'aud', 'mismatch');
}
const result = { payload, protectedHeader };

@@ -20,0 +18,0 @@ if (typeof key === 'function') {

@@ -1,5 +0,6 @@

import { CompactEncrypt } from '../jwe/compact/encrypt.js';
import { JWTClaimsBuilder } from '../lib/jwt_claims_set.js';
import { createJWE } from '../lib/jwe_encrypt.js';
import { JWTClaimsBuilder, jwtClaim, jwtData } from '../lib/jwt_claims_set.js';
import { assertNotSet } from '../lib/helpers.js';
export class EncryptJWT {
const EncryptJWT_base = JWTClaimsBuilder;
export class EncryptJWT extends EncryptJWT_base {
#cek;

@@ -12,34 +13,2 @@ #iv;

#replicateAudienceAsHeader;
#jwt;
constructor(payload = {}) {
this.#jwt = new JWTClaimsBuilder(payload);
}
setIssuer(issuer) {
this.#jwt.iss = issuer;
return this;
}
setSubject(subject) {
this.#jwt.sub = subject;
return this;
}
setAudience(audience) {
this.#jwt.aud = audience;
return this;
}
setJti(jwtId) {
this.#jwt.jti = jwtId;
return this;
}
setNotBefore(input) {
this.#jwt.nbf = input;
return this;
}
setExpirationTime(input) {
this.#jwt.exp = input;
return this;
}
setIssuedAt(input) {
this.#jwt.iat = input;
return this;
}
setProtectedHeader(protectedHeader) {

@@ -78,3 +47,3 @@ assertNotSet(this.#protectedHeader, 'setProtectedHeader');

async encrypt(key, options) {
const enc = new CompactEncrypt(this.#jwt.data());
const plaintext = jwtData(this);
if (this.#protectedHeader &&

@@ -86,19 +55,21 @@ (this.#replicateIssuerAsHeader ||

...this.#protectedHeader,
iss: this.#replicateIssuerAsHeader ? this.#jwt.iss : undefined,
sub: this.#replicateSubjectAsHeader ? this.#jwt.sub : undefined,
aud: this.#replicateAudienceAsHeader ? this.#jwt.aud : undefined,
iss: this.#replicateIssuerAsHeader ? jwtClaim(this, 'iss') : undefined,
sub: this.#replicateSubjectAsHeader ? jwtClaim(this, 'sub') : undefined,
aud: this.#replicateAudienceAsHeader ? jwtClaim(this, 'aud') : undefined,
};
}
enc.setProtectedHeader(this.#protectedHeader);
if (this.#iv) {
enc.setInitializationVector(this.#iv);
}
if (this.#cek) {
enc.setContentEncryptionKey(this.#cek);
}
if (this.#keyManagementParameters) {
enc.setKeyManagementParameters(this.#keyManagementParameters);
}
return enc.encrypt(key, options);
const jwe = await createJWE([
plaintext,
this.#protectedHeader,
undefined,
undefined,
undefined,
this.#cek,
this.#iv,
this.#keyManagementParameters,
undefined,
false,
], key, options);
return [jwe.protected, jwe.encrypted_key, jwe.iv, jwe.ciphertext, jwe.tag].join('.');
}
}

@@ -1,40 +0,10 @@

import { CompactSign } from '../jws/compact/sign.js';
import { unencodedPayload } from '../lib/jws_sign.js';
import { createCompactSignature } from '../lib/jws_sign.js';
import { JWTInvalid } from '../util/errors.js';
import { JWTClaimsBuilder } from '../lib/jwt_claims_set.js';
export class SignJWT {
import { JWTClaimsBuilder, jwtData } from '../lib/jwt_claims_set.js';
import { assertNotSet } from '../lib/helpers.js';
const SignJWT_base = JWTClaimsBuilder;
export class SignJWT extends SignJWT_base {
#protectedHeader;
#jwt;
constructor(payload = {}) {
this.#jwt = new JWTClaimsBuilder(payload);
}
setIssuer(issuer) {
this.#jwt.iss = issuer;
return this;
}
setSubject(subject) {
this.#jwt.sub = subject;
return this;
}
setAudience(audience) {
this.#jwt.aud = audience;
return this;
}
setJti(jwtId) {
this.#jwt.jti = jwtId;
return this;
}
setNotBefore(input) {
this.#jwt.nbf = input;
return this;
}
setExpirationTime(input) {
this.#jwt.exp = input;
return this;
}
setIssuedAt(input) {
this.#jwt.iat = input;
return this;
}
setProtectedHeader(protectedHeader) {
assertNotSet(this.#protectedHeader, 'setProtectedHeader');
this.#protectedHeader = protectedHeader;

@@ -44,9 +14,6 @@ return this;

async sign(key, options) {
const sig = new CompactSign(this.#jwt.data());
sig.setProtectedHeader(this.#protectedHeader);
if (unencodedPayload(this.#protectedHeader)) {
return createCompactSignature(jwtData(this), this.#protectedHeader, options?.crit, key, () => {
throw new JWTInvalid('JWTs MUST NOT use unencoded payload');
}
return sig.sign(key, options);
});
}
}
import * as b64u from '../util/base64url.js';
import { strictDecoder } from '../lib/buffer_utils.js';
import { decodeBase64url } from '../lib/helpers.js';
import { JWTInvalid } from '../util/errors.js';
import { validateClaimsSet, JWTClaimsBuilder } from '../lib/jwt_claims_set.js';
export class UnsecuredJWT {
#jwt;
constructor(payload = {}) {
this.#jwt = new JWTClaimsBuilder(payload);
}
import { decodeBase64url, parseJoseHeader } from '../lib/helpers.js';
import { JWSInvalid, JWTInvalid } from '../util/errors.js';
import { validateClaimsSet, JWTClaimsBuilder, jwtData } from '../lib/jwt_claims_set.js';
import { JWS_RECOGNIZED, validateB64, validateCrit } from '../lib/options.js';
const UnsecuredJWT_base = JWTClaimsBuilder;
export class UnsecuredJWT extends UnsecuredJWT_base {
encode() {
const header = b64u.encode(JSON.stringify({ alg: 'none' }));
const payload = b64u.encode(this.#jwt.data());
const payload = b64u.encode(jwtData(this));
return `${header}.${payload}.`;
}
setIssuer(issuer) {
this.#jwt.iss = issuer;
return this;
}
setSubject(subject) {
this.#jwt.sub = subject;
return this;
}
setAudience(audience) {
this.#jwt.aud = audience;
return this;
}
setJti(jwtId) {
this.#jwt.jti = jwtId;
return this;
}
setNotBefore(input) {
this.#jwt.nbf = input;
return this;
}
setExpirationTime(input) {
this.#jwt.exp = input;
return this;
}
setIssuedAt(input) {
this.#jwt.iat = input;
return this;
}
static decode(jwt, options) {

@@ -53,10 +22,20 @@ if (typeof jwt !== 'string') {

let header;
let b64;
try {
header = JSON.parse(strictDecoder.decode(b64u.decode(encodedHeader)));
if (header.alg !== 'none')
throw new Error();
header = parseJoseHeader(encodedHeader, JWSInvalid, 'JWS Protected Header is invalid');
const extensions = validateCrit(JWSInvalid, JWS_RECOGNIZED, undefined, header, header);
b64 = validateB64(header, extensions);
}
catch {
catch (cause) {
if (!(cause instanceof JWSInvalid)) {
throw cause;
}
throw new JWTInvalid('Invalid Unsecured JWT', { cause });
}
if (header.alg !== 'none') {
throw new JWTInvalid('Invalid Unsecured JWT');
}
if (!b64) {
throw new JWTInvalid('JWTs MUST NOT use unencoded payload');
}
const payload = validateClaimsSet(header, decodeBase64url(encodedPayload, 'payload', JWTInvalid), options);

@@ -63,0 +42,0 @@ return { payload, header };

import { JOSENotSupported } from '../util/errors.js';
import { validateExtractableOption } from '../lib/key_options.js';
import { keyAlgorithm, unsupportedAlg, algArgument } from '../lib/key_algorithm.js';
function getModulusLengthOption(options) {
const modulusLength = options?.modulusLength ?? 2048;
if (typeof modulusLength !== 'number' || modulusLength < 2048) {
if (typeof modulusLength !== 'number' ||
!Number.isInteger(modulusLength) ||
modulusLength < 2048) {
throw new JOSENotSupported('Invalid or unsupported modulusLength option provided, 2048 bits or larger keys must be used');

@@ -11,2 +14,3 @@ }

export async function generateKeyPair(alg, options) {
const extractable = validateExtractableOption(options?.extractable);
const entry = keyAlgorithm(alg, algArgument);

@@ -45,3 +49,3 @@ if (entry.secret) {

}
return crypto.subtle.generateKey(algorithm, options?.extractable ?? false, [
return crypto.subtle.generateKey(algorithm, extractable ?? false, [
...entry.usages[1],

@@ -48,0 +52,0 @@ ...entry.usages[0],

import { unsupportedAlg, algArgument } from '../lib/key_algorithm.js';
import { validateExtractableOption } from '../lib/key_options.js';
export async function generateSecret(alg, options) {
const extractable = validateExtractableOption(options?.extractable);
let length;

@@ -10,3 +12,3 @@ let algorithm;

case 'HS512':
length = parseInt(alg.slice(-3), 10);
length = +alg.slice(-3);
algorithm = { name: 'HMAC', hash: `SHA-${length}`, length };

@@ -18,8 +20,7 @@ keyUsages = ['sign', 'verify'];

case 'A256CBC-HS512':
length = parseInt(alg.slice(-3), 10);
return crypto.getRandomValues(new Uint8Array(length >> 3));
return crypto.getRandomValues(new Uint8Array(+alg.slice(-3) >> 3));
case 'A128KW':
case 'A192KW':
case 'A256KW':
length = parseInt(alg.slice(1, 4), 10);
length = +alg.slice(1, 4);
algorithm = { name: 'AES-KW', length };

@@ -34,3 +35,3 @@ keyUsages = ['wrapKey', 'unwrapKey'];

case 'A256GCM':
length = parseInt(alg.slice(1, 4), 10);
length = +alg.slice(1, 4);
algorithm = { name: 'AES-GCM', length };

@@ -42,3 +43,3 @@ keyUsages = ['encrypt', 'decrypt'];

}
return crypto.subtle.generateKey(algorithm, options?.extractable ?? false, keyUsages);
return crypto.subtle.generateKey(algorithm, extractable ?? false, keyUsages);
}

@@ -7,2 +7,4 @@ import { decode as decodeBase64URL } from '../util/base64url.js';

import { isObject } from '../lib/type_checks.js';
import { validateExtractableOption } from '../lib/key_options.js';
import { normalizeJwk } from '../lib/jwk_metadata.js';
export async function importSPKI(spki, alg, options) {

@@ -30,27 +32,29 @@ if (typeof spki !== 'string' || spki.indexOf('-----BEGIN PUBLIC KEY-----') !== 0) {

}
alg ??= jwk.alg;
const ext = options?.extractable ?? jwk.ext;
if (jwk.kty !== 'oct' && !alg) {
const normalized = normalizeJwk(jwk);
const extractable = validateExtractableOption(options?.extractable);
const { alg: jwkAlg } = normalized;
alg ??= jwkAlg;
const ext = extractable ?? normalized.ext;
if (normalized.kty !== 'oct' && !alg) {
throw new TypeError('"alg" argument is required when "jwk.alg" is not present');
}
switch (jwk.kty) {
switch (normalized.kty) {
case 'oct':
if (typeof jwk.k !== 'string' || !jwk.k) {
if (typeof normalized.k !== 'string') {
throw new TypeError('missing "k" (Key Value) Parameter value');
}
return decodeBase64URL(jwk.k);
case 'RSA':
return jwkToKey(keyAlgorithm(alg), { ...jwk, alg, ext });
return decodeBase64URL(normalized.k);
case 'AKP': {
if (typeof jwk.alg !== 'string' || !jwk.alg) {
if (typeof jwkAlg !== 'string' || !jwkAlg) {
throw new TypeError('missing "alg" (Algorithm) Parameter value');
}
if (alg !== undefined && alg !== jwk.alg) {
if (alg !== jwkAlg) {
throw new TypeError('JWK alg and alg option value mismatch');
}
return jwkToKey(keyAlgorithm(jwk.alg), { ...jwk, ext });
return jwkToKey(keyAlgorithm(alg), { ...normalized, ext });
}
case 'RSA':
case 'EC':
case 'OKP':
return jwkToKey(keyAlgorithm(alg), { ...jwk, alg, ext });
return jwkToKey(keyAlgorithm(alg), { ...normalized, alg, ext });
default:

@@ -57,0 +61,0 @@ throw new JOSENotSupported('Unsupported "kty" (Key Type) Parameter value');

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

import { isCryptoKey, isKeyObject } from './is_key_like.js';
import { validateExtractableOption } from './key_options.js';
const formatPEM = (b64, descriptor) => {

@@ -116,2 +117,3 @@ const newlined = (b64.match(/.{1,64}/g) || []).join('\n');

const genericImport = async (keyFormat, keyData, alg, options) => {
const extractable = validateExtractableOption(options?.extractable);
const entry = keyAlgorithm(alg, algArgument);

@@ -136,3 +138,3 @@ if (entry.secret) {

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

@@ -153,3 +155,6 @@ const processPEMData = (pem, pattern) => {

expectTag(state, 0x30, 'Invalid certificate structure');
parseLength(state);
const certificateLength = parseLength(state);
if (certificateLength < 0 || state.pos + certificateLength > state.data.length) {
throw new Error('Unexpected end of ASN.1 input');
}
expectTag(state, 0x30, 'Invalid tbsCertificate structure');

@@ -156,0 +161,0 @@ parseLength(state);

@@ -54,21 +54,10 @@ import { concat, uint64be } from './buffer_utils.js';

const expectedTag = await cbcHmacTag(macKey, macData, keySize);
let macCheckPassed;
try {
macCheckPassed = await timingSafeEqual(tag, expectedTag);
if (await timingSafeEqual(tag, expectedTag)) {
return new Uint8Array(await crypto.subtle.decrypt({ iv: iv, name: 'AES-CBC' }, encKey, ciphertext));
}
}
catch {
}
if (!macCheckPassed) {
throw new JWEDecryptionFailed();
}
let plaintext;
try {
plaintext = new Uint8Array(await crypto.subtle.decrypt({ iv: iv, name: 'AES-CBC' }, encKey, ciphertext));
}
catch {
}
if (!plaintext) {
throw new JWEDecryptionFailed();
}
return plaintext;
throw new JWEDecryptionFailed();
}

@@ -136,2 +125,5 @@ async function gcmEncrypt(enc, plaintext, cek, iv, aad) {

}
if (!enc.cbc && tag.length !== 16) {
throw new JWEInvalid('Invalid Authentication Tag length');
}
checkIvLength(enc, iv);

@@ -138,0 +130,0 @@ if (cek instanceof Uint8Array) {

import { JOSENotSupported, JWEInvalid } from '../util/errors.js';
import { concat } from './buffer_utils.js';
export function validateZip(joseHeader, protectedHeader) {
if (joseHeader.zip !== undefined && joseHeader.zip !== 'DEF') {
throw new JOSENotSupported('Unsupported JWE "zip" (Compression Algorithm) Header Parameter value.');
}
if (joseHeader.zip !== undefined && !protectedHeader?.zip) {
throw new JWEInvalid('JWE "zip" (Compression Algorithm) Header Parameter MUST be in a protected header.');
}
}
function supported(name) {

@@ -4,0 +12,0 @@ if (typeof globalThis[name] === 'undefined') {

@@ -6,3 +6,3 @@ import { decode } from '../util/base64url.js';

export function assertNotSet(value, name) {
if (value) {
if (value !== undefined) {
throw new TypeError(`${name} can only be called once`);

@@ -9,0 +9,0 @@ }

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

import { jweAlgorithm, jweEncryption } from './jwe_algorithms.js';
import { decompress } from './deflate.js';
export function checkShared(jwe) {
const { ciphertext, protected: encodedProtected, unprotected } = jwe;
if (jwe.iv !== undefined && typeof jwe.iv !== 'string') {
import { decompress, validateZip } from './deflate.js';
export function snapshotSharedJWE(jwe) {
const { aad, ciphertext, iv, protected: encodedProtected, tag, unprotected } = jwe;
if (iv !== undefined && typeof iv !== 'string') {
throw new JWEInvalid('JWE Initialization Vector incorrect type');

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

}
if (jwe.tag !== undefined && typeof jwe.tag !== 'string') {
if (tag !== undefined && typeof tag !== 'string') {
throw new JWEInvalid('JWE Authentication Tag incorrect type');

@@ -27,3 +27,3 @@ }

}
if (jwe.aad !== undefined && typeof jwe.aad !== 'string') {
if (aad !== undefined && (typeof aad !== 'string' || !aad)) {
throw new JWEInvalid('JWE AAD incorrect type');

@@ -34,3 +34,41 @@ }

}
return {
aad,
ciphertext,
iv,
protected: encodedProtected,
tag,
unprotected: unprotected === undefined ? undefined : { ...unprotected },
};
}
export function snapshotRecipientJWE(recipient) {
let header;
let headerAlg;
try {
const { header: inputHeader } = recipient;
if (isObject(inputHeader)) {
headerAlg = inputHeader.alg;
const parameters = Object.keys(inputHeader);
if (!parameters.includes('alg'))
headerAlg = undefined;
header = Object.fromEntries(parameters.map((parameter) => [
parameter,
parameter === 'alg' ? headerAlg : inputHeader[parameter],
]));
}
else {
header = inputHeader;
}
}
catch (error) {
return [undefined, headerAlg, error];
}
try {
const { encrypted_key: encryptedKey } = recipient;
return [{ encrypted_key: encryptedKey, header }, headerAlg];
}
catch (error) {
return [undefined, headerAlg, error];
}
}
export function checkRecipient(jwe) {

@@ -41,4 +79,6 @@ const { encrypted_key: encryptedKey, header } = jwe;

}
if (header !== undefined && !isObject(header)) {
throw new JWEInvalid('JWE Per-Recipient Unprotected Header incorrect type');
if (header !== undefined) {
if (!isObject(header)) {
throw new JWEInvalid('JWE Per-Recipient Unprotected Header incorrect type');
}
}

@@ -52,3 +92,3 @@ if (jwe.protected === undefined && header === undefined && jwe.unprotected === undefined) {

let parsedProt;
if (encodedProtected) {
if (encodedProtected !== undefined) {
parsedProt = parseJoseHeader(encodedProtected, JWEInvalid, 'JWE Protected Header is invalid');

@@ -93,9 +133,10 @@ }

validateAlgorithms('contentEncryptionAlgorithms', options.contentEncryptionAlgorithms),
options,
options?.crit,
options?.maxPBES2Count,
options?.maxDecompressedLength,
];
}
export async function decryptRecipient(jwe, token, shared, key) {
const [keyManagementAlgorithms, contentEncryptionAlgorithms, options] = shared;
const [parsedProt, ciphertext, iv, tag, additionalData] = token;
const { encrypted_key: encodedKey, header, unprotected } = jwe;
const [parsedProt] = token;
const { header, unprotected } = jwe;
let joseHeader;

@@ -111,9 +152,10 @@ if (header !== undefined || unprotected !== undefined) {

}
validateCrit(JWEInvalid, JWE_RECOGNIZED, options?.crit, parsedProt, joseHeader);
if (joseHeader.zip !== undefined && joseHeader.zip !== 'DEF') {
throw new JOSENotSupported('Unsupported JWE "zip" (Compression Algorithm) Header Parameter value.');
}
if (joseHeader.zip !== undefined && !parsedProt?.zip) {
throw new JWEInvalid('JWE "zip" (Compression Algorithm) Header Parameter MUST be in a protected header.');
}
return decryptRecipientCore(jwe, token, shared, key, joseHeader);
}
async function decryptRecipientCore(jwe, token, shared, key, joseHeader) {
const [keyManagementAlgorithms, contentEncryptionAlgorithms, crit, maxPBES2Count, maxDecompressedLength,] = shared;
const [parsedProt, ciphertext, iv, tag, additionalData] = token;
const { encrypted_key: encodedKey } = jwe;
validateCrit(JWEInvalid, JWE_RECOGNIZED, crit, parsedProt, joseHeader);
validateZip(joseHeader, parsedProt);
const { alg, enc } = joseHeader;

@@ -147,3 +189,8 @@ if (typeof alg !== 'string' || !alg) {

try {
cek = await decryptKeyManagement(alg, encEntry, k, encryptedKey, joseHeader, options);
cek = await decryptKeyManagement(alg, encEntry, k, encryptedKey, joseHeader, maxPBES2Count);
if (encodedKey !== undefined &&
cek instanceof Uint8Array &&
cek.byteLength << 3 !== encEntry.cekBits) {
cek = generateCek(encEntry);
}
}

@@ -158,11 +205,11 @@ catch (err) {

if (joseHeader.zip === 'DEF') {
const maxDecompressedLength = options?.maxDecompressedLength ?? 250_000;
if (maxDecompressedLength === 0) {
const decompressionLimit = maxDecompressedLength ?? 250_000;
if (decompressionLimit === 0) {
throw new JOSENotSupported('JWE "zip" (Compression Algorithm) Header Parameter is not supported.');
}
if (maxDecompressedLength !== Infinity &&
(!Number.isSafeInteger(maxDecompressedLength) || maxDecompressedLength < 1)) {
if (decompressionLimit !== Infinity &&
(!Number.isSafeInteger(decompressionLimit) || decompressionLimit < 1)) {
throw new TypeError('maxDecompressedLength must be 0, a positive safe integer, or Infinity');
}
plaintext = await decompress(plaintext, maxDecompressedLength).catch((cause) => {
plaintext = await decompress(plaintext, decompressionLimit).catch((cause) => {
if (cause instanceof JWEInvalid)

@@ -189,3 +236,3 @@ throw cause;

}
return decryptJWE({
const flattened = {
ciphertext,

@@ -196,3 +243,13 @@ iv: iv || undefined,

encrypted_key: encryptedKey || undefined,
}, shared, key);
};
const parsedProt = parseJoseHeader(protectedHeader, JWEInvalid, 'JWE Protected Header is invalid');
const protectedBytes = encode(protectedHeader);
const token = [
parsedProt,
decodeBase64url(ciphertext, 'ciphertext', JWEInvalid),
iv ? decodeBase64url(iv, 'iv', JWEInvalid) : undefined,
tag ? decodeBase64url(tag, 'tag', JWEInvalid) : undefined,
protectedBytes,
];
return decryptRecipientCore(flattened, token, shared, key, parsedProt);
}
import { encode as b64u } from '../util/base64url.js';
import { encrypt } from './content_encryption.js';
import { encryptKeyManagement } from './key_management.js';
import { JOSENotSupported, JWEInvalid } from '../util/errors.js';
import { isDisjoint } from './type_checks.js';
import { JWEInvalid } from '../util/errors.js';
import { assertUint8Array, isDisjoint, isObject } from './type_checks.js';
import { concat, encode } from './buffer_utils.js';
import { validateCrit, JWE_RECOGNIZED } from './options.js';
import { serializeJoseHeader, validateCrit, validateCritDuplicates, JWE_RECOGNIZED, } from './options.js';
import { prepareKey } from './key.js';
import { jweAlgorithm, jweEncryption } from './jwe_algorithms.js';
import { compress } from './deflate.js';
import { compress, validateZip } from './deflate.js';
import { unprotected } from './helpers.js';
export function checkDisjoint(protectedHeader, unprotectedHeader, sharedUnprotectedHeader) {

@@ -17,3 +18,27 @@ if (!isDisjoint(protectedHeader, unprotectedHeader, sharedUnprotectedHeader)) {

export function checkEncryptHeaders(input) {
const [, protectedHeader, unprotectedHeader, sharedUnprotectedHeader, , , , , crit] = input;
let [, protectedHeader, unprotectedHeader, sharedUnprotectedHeader, aad, cek, iv, keyManagementParameters, crit,] = input;
if (aad !== undefined) {
assertUint8Array(aad, 'JWE Additional Authenticated Data');
}
if (cek !== undefined) {
assertUint8Array(cek, 'JWE Content Encryption Key');
}
if (iv !== undefined) {
assertUint8Array(iv, 'JWE Initialization Vector');
}
if (protectedHeader !== undefined) {
protectedHeader = serializeJoseHeader(JWEInvalid, protectedHeader)[0];
input[1] = protectedHeader;
}
if (unprotectedHeader !== undefined) {
unprotectedHeader = serializeJoseHeader(JWEInvalid, unprotectedHeader)[0];
input[2] = unprotectedHeader;
}
if (sharedUnprotectedHeader !== undefined) {
sharedUnprotectedHeader = serializeJoseHeader(JWEInvalid, sharedUnprotectedHeader)[0];
input[3] = sharedUnprotectedHeader;
}
if (keyManagementParameters !== undefined && !isObject(keyManagementParameters)) {
throw new TypeError('JWE Key Management Parameters must be an object');
}
checkDisjoint(protectedHeader, unprotectedHeader, sharedUnprotectedHeader);

@@ -25,9 +50,5 @@ const joseHeader = {

};
validateCritDuplicates(JWEInvalid, protectedHeader);
validateCrit(JWEInvalid, JWE_RECOGNIZED, crit, protectedHeader, joseHeader);
if (joseHeader.zip !== undefined && joseHeader.zip !== 'DEF') {
throw new JOSENotSupported('Unsupported JWE "zip" (Compression Algorithm) Header Parameter value.');
}
if (joseHeader.zip !== undefined && !protectedHeader?.zip) {
throw new JWEInvalid('JWE "zip" (Compression Algorithm) Header Parameter MUST be in a protected header.');
}
validateZip(joseHeader, protectedHeader);
const { alg, enc } = joseHeader;

@@ -114,4 +135,11 @@ if (typeof alg !== 'string' || !alg) {

}
export async function createJWE(input, key) {
export async function createJWE(input, key, options) {
if (!input[1] && !input[2] && !input[3]) {
throw new JWEInvalid('either setProtectedHeader, setUnprotectedHeader, or sharedUnprotectedHeader must be called before #encrypt()');
}
if (options !== undefined) {
input[8] = options?.crit;
input[9] = options ? unprotected in options : false;
}
return encryptJWE(input, checkEncryptHeaders(input), key);
}

@@ -7,11 +7,33 @@ import { encode as b64u } from '../util/base64url.js';

import { concat, encode } from './buffer_utils.js';
import { validateCrit, validateCritDuplicates, JWS_RECOGNIZED } from './options.js';
import { serializeJoseHeader, validateB64, validateCrit, validateCritDuplicates, JWS_RECOGNIZED, } from './options.js';
import { prepareKey } from './key.js';
export function unencodedPayload(protectedHeader) {
return (protectedHeader?.b64 === false &&
Array.isArray(protectedHeader.crit) &&
protectedHeader.crit.includes('b64'));
function serializeProtectedHeader(protectedHeader) {
if (protectedHeader === undefined)
return [undefined, ''];
const normalized = serializeJoseHeader(JWSInvalid, protectedHeader);
return [normalized[0], b64u(normalized[1])];
}
export async function createSignature(input, key) {
const { protectedHeader, unprotectedHeader } = input;
function validateSignatureHeader(protectedHeader, joseHeader, crit) {
validateCritDuplicates(JWSInvalid, protectedHeader);
return validateB64(protectedHeader, validateCrit(JWSInvalid, JWS_RECOGNIZED, crit, protectedHeader, joseHeader));
}
function signatureAlgorithm(joseHeader) {
const alg = joseHeader.alg;
if (typeof alg !== 'string' || !alg) {
throw new JWSInvalid('JWS "alg" (Algorithm) Header Parameter missing or invalid');
}
return jwsAlgorithm(alg);
}
async function signSignature(protectedHeader, payload, entry, key) {
const data = concat(encode(protectedHeader), encode('.'), payload);
const k = await prepareKey(entry, key, 'sign');
return b64u(await sign(entry, k, data));
}
export async function createSignature(input, key, assertB64) {
let { protectedHeader, unprotectedHeader } = input;
let protectedHeaderString;
[protectedHeader, protectedHeaderString] = serializeProtectedHeader(protectedHeader);
if (unprotectedHeader !== undefined) {
unprotectedHeader = serializeJoseHeader(JWSInvalid, unprotectedHeader)[0];
}
if (!protectedHeader && !unprotectedHeader) {

@@ -24,16 +46,5 @@ throw new JWSInvalid('either setProtectedHeader or setUnprotectedHeader must be called before #sign()');

const joseHeader = { ...protectedHeader, ...unprotectedHeader };
validateCritDuplicates(JWSInvalid, protectedHeader);
const extensions = validateCrit(JWSInvalid, JWS_RECOGNIZED, input.crit, protectedHeader, joseHeader);
let b64 = true;
if (extensions.includes('b64')) {
b64 = protectedHeader.b64;
if (typeof b64 !== 'boolean') {
throw new JWSInvalid('The "b64" (base64url-encode payload) Header Parameter must be a boolean');
}
}
const { alg } = joseHeader;
if (typeof alg !== 'string' || !alg) {
throw new JWSInvalid('JWS "alg" (Algorithm) Header Parameter missing or invalid');
}
const entry = jwsAlgorithm(alg);
const b64 = validateSignatureHeader(protectedHeader, joseHeader, input.crit);
assertB64?.(b64);
const entry = signatureAlgorithm(joseHeader);
let payloadS;

@@ -52,17 +63,4 @@ let payloadB;

}
let protectedHeaderString;
let protectedHeaderBytes;
if (protectedHeader) {
protectedHeaderString = b64u(JSON.stringify(protectedHeader));
protectedHeaderBytes = encode(protectedHeaderString);
}
else {
protectedHeaderString = '';
protectedHeaderBytes = new Uint8Array();
}
const data = concat(protectedHeaderBytes, encode('.'), payloadB);
const k = await prepareKey(entry, key, 'sign');
const signature = await sign(entry, k, data);
const jws = {
signature: b64u(signature),
signature: await signSignature(protectedHeaderString, payloadB, entry, key),
payload: payloadS,

@@ -76,3 +74,16 @@ };

}
return jws;
return [jws, b64];
}
export async function createCompactSignature(payload, inputProtectedHeader, inputCrit, key, rejectUnencoded) {
const [protectedHeader, protectedHeaderString] = serializeProtectedHeader(inputProtectedHeader);
if (!protectedHeader) {
throw new JWSInvalid('either setProtectedHeader or setUnprotectedHeader must be called before #sign()');
}
const b64 = validateSignatureHeader(protectedHeader, protectedHeader, inputCrit);
if (!b64)
rejectUnencoded();
const entry = signatureAlgorithm(protectedHeader);
const encodedPayload = b64u(payload);
const signature = await signSignature(protectedHeaderString, encode(encodedPayload), entry, key);
return `${protectedHeaderString}.${encodedPayload}.${signature}`;
}

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

import { decodeBase64url, encodeBase64url, parseJoseHeader } from './helpers.js';
import { isDisjoint } from './type_checks.js';
import { validateCrit, validateAlgorithms, JWS_RECOGNIZED } from './options.js';
import { isDisjoint, isObject } from './type_checks.js';
import { validateB64, validateCrit, validateAlgorithms, JWS_RECOGNIZED } from './options.js';
import { prepareKey } from './key.js';
export function snapshotJws(jws, sharedPayload) {
const encodedProtected = jws.protected;
const inputHeader = jws.header;
const header = isObject(inputHeader) ? { ...inputHeader } : inputHeader;
let payload = sharedPayload ? sharedPayload[0] : jws.payload;
if (!sharedPayload && payload instanceof Uint8Array) {
payload = new Uint8Array(payload);
}
const signature = jws.signature;
const snapshot = { payload, signature };
if (encodedProtected !== undefined)
snapshot.protected = encodedProtected;
if (inputHeader !== undefined)
snapshot.header = header;
return snapshot;
}
export function verifyResult(jws, verified) {

@@ -27,8 +43,20 @@ const [payload, parsedProt, , key, resolvedKey] = verified;

}
export async function verifySignature(jws, shared, key) {
const { protected: encodedProtected, header, payload: inputPayload } = jws;
let parsedProt = {};
if (encodedProtected) {
parsedProt = parseJoseHeader(encodedProtected, JWSInvalid, 'JWS Protected Header is invalid');
export function parseProtectedHeader(encodedProtected, parsedProtected = encodedProtected === undefined
? {}
: parseJoseHeader(encodedProtected, JWSInvalid, 'JWS Protected Header is invalid')) {
return parsedProtected;
}
function validateJwsHeaders(parsedProt, joseHeader, shared) {
const b64 = validateB64(parsedProt, validateCrit(JWSInvalid, JWS_RECOGNIZED, shared[1], parsedProt, joseHeader));
const alg = joseHeader.alg;
if (typeof alg !== 'string' || !alg) {
throw new JWSInvalid('JWS "alg" (Algorithm) Header Parameter missing or invalid');
}
if (shared[0] && !shared[0].has(alg)) {
throw new JOSEAlgNotAllowed('"alg" (Algorithm) Header Parameter value not allowed');
}
return [b64, alg];
}
export function parseJwsHeaders(encodedProtected, header, shared, parsedProtected) {
const parsedProt = parseProtectedHeader(encodedProtected, parsedProtected);
let joseHeader;

@@ -44,25 +72,22 @@ if (header !== undefined) {

}
const extensions = validateCrit(JWSInvalid, JWS_RECOGNIZED, shared[1], parsedProt, joseHeader);
let b64 = true;
if (extensions.includes('b64')) {
b64 = parsedProt.b64;
if (typeof b64 !== 'boolean') {
throw new JWSInvalid('The "b64" (base64url-encode payload) Header Parameter must be a boolean');
}
return [parsedProt, joseHeader, ...validateJwsHeaders(parsedProt, joseHeader, shared)];
}
export function encodeJsonUnencodedPayload(payload) {
const invalid = /[\p{Cs}\p{Cn}]/u.exec(payload)?.[0];
if (invalid !== undefined) {
throw new JWSInvalid(/\p{Cs}/u.test(invalid)
? 'JWS Payload must be a well-formed Unicode string'
: 'JWS Payload must not contain unassigned Unicode code points');
}
const { alg } = joseHeader;
if (typeof alg !== 'string' || !alg) {
throw new JWSInvalid('JWS "alg" (Algorithm) Header Parameter missing or invalid');
return encoder.encode(payload);
}
function encodeCompactUnencodedPayload(payload) {
try {
return encode(payload);
}
if (shared[0] && !shared[0].has(alg)) {
throw new JOSEAlgNotAllowed('"alg" (Algorithm) Header Parameter value not allowed');
catch {
throw new JWSInvalid('JWS Compact Serialization payload must use only ASCII characters');
}
if (b64) {
if (typeof inputPayload !== 'string') {
throw new JWSInvalid('JWS Payload must be a string');
}
}
else if (typeof inputPayload !== 'string' && !(inputPayload instanceof Uint8Array)) {
throw new JWSInvalid('JWS Payload must be a string or an Uint8Array instance');
}
}
async function verifyPrepared(jws, shared, key, encodedProtected, parsedProt, alg, signingPayload) {
let resolvedKey = false;

@@ -73,26 +98,29 @@ if (typeof key === 'function') {

}
const b64 = typeof signingPayload === 'string';
const entry = jwsAlgorithm(alg);
const data = concat(encodedProtected !== undefined ? encode(encodedProtected) : new Uint8Array(), encode('.'), typeof inputPayload === 'string'
? b64
?
(shared[2] ??= encodeBase64url(inputPayload, 'payload', JWSInvalid))
: encoder.encode(inputPayload)
: inputPayload);
const data = concat(encodedProtected !== undefined ? encode(encodedProtected) : new Uint8Array(), encode('.'), b64
?
(shared[2] ??= encodeBase64url(signingPayload, 'payload', JWSInvalid))
: signingPayload);
const signature = decodeBase64url(jws.signature, 'signature', JWSInvalid);
const k = await prepareKey(entry, key, 'verify');
const verified = await verify(entry, k, signature, data);
if (!verified) {
if (!(await verify(entry, k, signature, data))) {
throw new JWSSignatureVerificationFailed();
}
let payload;
const payload = b64 ? decodeBase64url(signingPayload, 'payload', JWSInvalid) : signingPayload;
return [payload, parsedProt, b64, k, resolvedKey];
}
export async function verifySignature(jws, shared, key, encodeUnencodedPayload, parsedProtected) {
const { protected: encodedProtected, header, payload: inputPayload } = jws;
const [parsedProt, , b64, alg] = parseJwsHeaders(encodedProtected, header, shared, parsedProtected);
if (b64) {
payload = decodeBase64url(inputPayload, 'payload', JWSInvalid);
if (typeof inputPayload !== 'string') {
throw new JWSInvalid('JWS Payload must be a string');
}
}
else if (typeof inputPayload === 'string') {
payload = encoder.encode(inputPayload);
else if (typeof inputPayload !== 'string' && !(inputPayload instanceof Uint8Array)) {
throw new JWSInvalid('JWS Payload must be a string or an Uint8Array instance');
}
else {
payload = inputPayload;
}
return [payload, parsedProt, b64, k, resolvedKey];
const signingPayload = b64 || typeof inputPayload !== 'string' ? inputPayload : encodeUnencodedPayload(inputPayload);
return verifyPrepared(jws, shared, key, encodedProtected, parsedProt, alg, signingPayload);
}

@@ -110,3 +138,7 @@ export async function verifyCompact(jws, shared, key) {

}
return verifySignature({ payload, protected: protectedHeader, signature }, shared, key);
const compactJws = { payload, protected: protectedHeader, signature };
const parsedProt = parseProtectedHeader(protectedHeader);
const [b64, alg] = validateJwsHeaders(parsedProt, parsedProt, shared);
const signingPayload = b64 ? payload : encodeCompactUnencodedPayload(payload);
return verifyPrepared(compactJws, shared, key, protectedHeader, parsedProt, alg, signingPayload);
}

@@ -15,9 +15,18 @@ import { JWTClaimValidationFailed, JWTExpired, JWTInvalid } from '../util/errors.js';

const checkFailed = 'check_failed';
function invalidDuration() {
throw new TypeError('Invalid time period format');
}
export function secs(str) {
if (typeof str !== 'string') {
invalidDuration();
}
const matched = REGEX.exec(str);
if (!matched || (matched[4] && matched[1])) {
throw new TypeError('Invalid time period format');
invalidDuration();
}
const value = parseFloat(matched[2]);
const numericDate = Math.round(value * multipliers[matched[3][0].toLowerCase()]);
if (!Number.isFinite(numericDate)) {
invalidDuration();
}
if (matched[1] === '-' || matched[4] === 'ago') {

@@ -34,2 +43,13 @@ return -numericDate;

}
function validateStringClaim(claim, value) {
if (typeof value !== 'string') {
throw new TypeError(`"${claim}" claim must be a string`);
}
}
function validateAudienceClaim(value) {
if (typeof value !== 'string' &&
(!Array.isArray(value) || Array.from(value).some((member) => typeof member !== 'string'))) {
throw new TypeError('"aud" claim must be a string or an array of strings');
}
}
function numericDate(value, label) {

@@ -43,6 +63,4 @@ if (typeof value === 'number')

const normalizeTyp = (value) => {
if (value.includes('/')) {
return value.toLowerCase();
}
return `application/${value.toLowerCase()}`;
const normalized = value.toLowerCase();
return value.includes('/') ? normalized : `application/${normalized}`;
};

@@ -81,3 +99,3 @@ const checkAudiencePresence = (audPayload, audOption) => {

const { typ } = options;
if (typ &&
if (typ !== undefined &&
(typeof protectedHeader.typ !== 'string' ||

@@ -126,3 +144,3 @@ normalizeTyp(protectedHeader.typ) !== normalizeTyp(typ))) {

const { currentDate } = options;
const now = validateInput('currentDate option', epoch(currentDate || new Date()));
const now = validateInput('currentDate option', epoch(currentDate === undefined ? new Date() : currentDate));
const iat = validateNumericDate(payload, 'iat', maxTokenAge !== undefined);

@@ -143,7 +161,7 @@ const nbf = validateNumericDate(payload, 'nbf');

const age = now - iat;
const max = typeof maxTokenAge === 'number' ? maxTokenAge : secs(maxTokenAge);
const max = validateInput('maxTokenAge option', 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', checkFailed);
}
if (age < 0 - tolerance) {
if (age < -tolerance) {
throw new JWTClaimValidationFailed('"iat" claim timestamp check failed (it should be in the past)', payload, 'iat', checkFailed);

@@ -154,51 +172,68 @@ }

}
let producerPayloads;
function producerPayload(producer) {
return producerPayloads.get(producer);
}
export function jwtData(producer) {
const payload = producerPayload(producer);
for (const claim of ['iat', 'nbf', 'exp']) {
const value = payload[claim];
if (typeof value === 'number' && !Number.isFinite(value)) {
throw new TypeError(`"${claim}" claim must be a finite number`);
}
}
return encoder.encode(JSON.stringify(payload));
}
export function jwtClaim(producer, claim) {
return producerPayload(producer)[claim];
}
export class JWTClaimsBuilder {
#payload;
constructor(payload) {
constructor(payload = {}) {
if (!isObject(payload)) {
throw new TypeError('JWT Claims Set MUST be an object');
}
this.#payload = structuredClone(payload);
;
(producerPayloads ||= new WeakMap()).set(this, structuredClone(payload));
}
data() {
return encoder.encode(JSON.stringify(this.#payload));
setIssuer(value) {
validateStringClaim('iss', value);
producerPayload(this).iss = value;
return this;
}
get iss() {
return this.#payload.iss;
setSubject(value) {
validateStringClaim('sub', value);
producerPayload(this).sub = value;
return this;
}
set iss(value) {
this.#payload.iss = value;
setAudience(value) {
validateAudienceClaim(value);
producerPayload(this).aud = value;
return this;
}
get sub() {
return this.#payload.sub;
setJti(value) {
validateStringClaim('jti', value);
producerPayload(this).jti = value;
return this;
}
set sub(value) {
this.#payload.sub = value;
setNotBefore(value) {
producerPayload(this).nbf = numericDate(value, 'setNotBefore');
return this;
}
get aud() {
return this.#payload.aud;
setExpirationTime(value) {
producerPayload(this).exp = numericDate(value, 'setExpirationTime');
return this;
}
set aud(value) {
this.#payload.aud = value;
}
set jti(value) {
this.#payload.jti = value;
}
set nbf(value) {
this.#payload.nbf = numericDate(value, 'setNotBefore');
}
set exp(value) {
this.#payload.exp = numericDate(value, 'setExpirationTime');
}
set iat(value) {
setIssuedAt(value) {
const payload = producerPayload(this);
if (value === undefined) {
this.#payload.iat = epoch(new Date());
payload.iat = epoch(new Date());
}
else if (typeof value === 'string') {
this.#payload.iat = validateInput('setIssuedAt', epoch(new Date()) + secs(value));
payload.iat = validateInput('setIssuedAt', epoch(new Date()) + secs(value));
}
else {
this.#payload.iat = numericDate(value, 'setIssuedAt');
payload.iat = numericDate(value, 'setIssuedAt');
}
return this;
}
}

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

import { generateCek, encrypt, decrypt } from './content_encryption.js';
import { isObject } from './type_checks.js';
import { assertUint8Array, isObject } from './type_checks.js';
import { checkCryptoKey, checkModulusLength, checkUsage } from './crypto_key.js';

@@ -114,3 +114,3 @@ import { concat, encode, uint32be } from './buffer_utils.js';

}
export async function decryptKeyManagement(alg, enc, key, encryptedKey, joseHeader, options) {
export async function decryptKeyManagement(alg, enc, key, encryptedKey, joseHeader, maxPBES2Count) {
const entry = jweAlgorithm(alg);

@@ -157,3 +157,3 @@ if (alg === 'dir') {

throw new JWEInvalid(`JOSE Header "p2c" (PBES2 Count) missing or invalid`);
const p2cLimit = options?.maxPBES2Count || 10_000;
const p2cLimit = maxPBES2Count || 10_000;
if (joseHeader.p2c > p2cLimit)

@@ -196,4 +196,10 @@ throw new JWEInvalid(`JOSE Header "p2c" (PBES2 Count) out is of acceptable bounds`);

const { apu, apv } = providedParameters;
if (apu !== undefined) {
assertUint8Array(apu, '"apu"');
}
if (apv !== undefined) {
assertUint8Array(apv, '"apv"');
}
let ephemeralKey;
if (providedParameters.epk) {
if (providedParameters.epk !== undefined) {
ephemeralKey = (await prepareKey(entry, providedParameters.epk, 'decrypt'));

@@ -200,0 +206,0 @@ }

import { withAlg as invalidKeyInput } from './invalid_key_input.js';
import { isKeyLike, isCryptoKey } from './is_key_like.js';
import * as jwk from './type_checks.js';
import { isObject } from './type_checks.js';
import { decode } from '../util/base64url.js';
import { jwkToKey } from './jwk_to_key.js';
import { normalizeJwk } from './jwk_metadata.js';
const tag = (key) => key[Symbol.toStringTag];

@@ -30,10 +31,23 @@ const jwkMatchesOp = (entry, key, usage) => {

return [BYTES, key];
if (jwk.isJWK(key)) {
if (secret ? !jwk.isSecretJWK(key) : !(privateKey ? jwk.isPrivateJWK(key) : jwk.isPublicJWK(key))) {
if (isObject(key)) {
const normalized = normalizeJwk(key);
if (typeof normalized.kty !== 'string') {
throw new TypeError(secret
? invalidKeyInput(alg, key, 'CryptoKey', 'KeyObject', 'JSON Web Key', 'Uint8Array')
: invalidKeyInput(alg, key, 'CryptoKey', 'KeyObject', 'JSON Web Key'));
}
const valid = secret
? normalized.kty === 'oct' && typeof normalized.k === 'string'
: normalized.kty !== 'oct' &&
(privateKey
? (normalized.kty === 'AKP' && typeof normalized.priv === 'string') ||
typeof normalized.d === 'string'
: normalized.d === undefined && normalized.priv === undefined);
if (!valid) {
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];
jwkMatchesOp(entry, normalized, usage);
return [JWK, key, normalized];
}

@@ -85,3 +99,3 @@ if (!isKeyLike(key)) {

else {
cache.set(key, { __proto__: null, [alg]: value });
cache.set(key, { [alg]: value });
}

@@ -112,4 +126,5 @@ }

const key = tagged[1];
if (key.k) {
return decode(key.k);
const normalized = tagged[2];
if (normalized.kty === 'oct') {
return decode(normalized.k);
}

@@ -122,3 +137,3 @@ if (!Object.isFrozen(key)) {

}
return handleJWK(key, key, entry);
return handleJWK(key, normalized, entry);
}

@@ -125,0 +140,0 @@ case KEYOBJECT: {

import { JOSENotSupported, JWEInvalid, JWSInvalid } from '../util/errors.js';
import { isObject } from './type_checks.js';
export const JWS_RECOGNIZED = { __proto__: null, b64: true };

@@ -49,1 +50,26 @@ export const JWE_RECOGNIZED = { __proto__: null };

}
export function validateB64(protectedHeader, extensions) {
if (extensions.includes('b64')) {
const b64 = protectedHeader.b64;
if (typeof b64 !== 'boolean') {
throw new JWSInvalid('The "b64" (base64url-encode payload) Header Parameter must be a boolean');
}
return b64;
}
return true;
}
export function serializeJoseHeader(Err, header) {
let serialized;
let parsed;
try {
serialized = JSON.stringify(header);
parsed = JSON.parse(serialized);
}
catch (cause) {
throw new Err('JOSE Header is not valid JSON', { cause });
}
if (!isObject(parsed)) {
throw new Err('JOSE Header is not a JSON object');
}
return [parsed, serialized];
}

@@ -0,1 +1,6 @@

export function assertUint8Array(input, label) {
if (!(input instanceof Uint8Array)) {
throw new TypeError(`${label} must be an instance of Uint8Array`);
}
}
export function isObject(input) {

@@ -8,11 +13,9 @@ if (typeof input !== 'object' ||

const prototype = Object.getPrototypeOf(input);
if (prototype === null) {
return true;
}
let proto = prototype;
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}
return prototype === proto;
return prototype === null || Object.getPrototypeOf(prototype) === null;
}
export function isJwkSet(input) {
return (isObject(input) &&
Array.isArray(input.keys) &&
Array.from(input.keys).every(isObject));
}
export function isDisjoint(...headers) {

@@ -32,6 +35,1 @@ const parameters = new Set();

}
export const isJWK = (key) => isObject(key) && typeof key.kty === 'string';
export const isPrivateJWK = (key) => key.kty !== 'oct' &&
((key.kty === 'AKP' && typeof key.priv === 'string') || typeof key.d === 'string');
export const isPublicJWK = (key) => key.kty !== 'oct' && key.d === undefined && key.priv === undefined;
export const isSecretJWK = (key) => key.kty === 'oct' && typeof key.k === 'string';
{
"name": "jose",
"version": "6.2.9",
"version": "6.2.10",
"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": [