jose
Version:
JWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes
140 lines (139 loc) • 6.28 kB
JavaScript
import { verify } from './signing.js';
import { jwsAlgorithm } from './jws_algorithms.js';
import { JOSEAlgNotAllowed, JWSInvalid, JWSSignatureVerificationFailed } from '../util/errors.js';
import { concat, decoder, encoder, encode } from './buffer_utils.js';
import { decodeBase64url, encodeBase64url, parseJoseHeader } from './helpers.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) {
const [payload, parsedProt, , key, resolvedKey] = verified;
const result = { payload };
if (jws.protected !== undefined) {
result.protectedHeader = parsedProt;
}
if (jws.header !== undefined) {
result.unprotectedHeader = jws.header;
}
if (resolvedKey) {
return { ...result, key };
}
return result;
}
export function prepareVerify(options) {
return [options && validateAlgorithms('algorithms', options.algorithms), options?.crit];
}
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;
if (header !== undefined) {
if (!isDisjoint(parsedProt, header)) {
throw new JWSInvalid('JWS Protected and JWS Unprotected Header Parameter names must be disjoint');
}
joseHeader = { ...parsedProt, ...header };
}
else {
joseHeader = parsedProt;
}
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');
}
return encoder.encode(payload);
}
function encodeCompactUnencodedPayload(payload) {
try {
return encode(payload);
}
catch {
throw new JWSInvalid('JWS Compact Serialization payload must use only ASCII characters');
}
}
async function verifyPrepared(jws, shared, key, encodedProtected, parsedProt, alg, signingPayload) {
let resolvedKey = false;
if (typeof key === 'function') {
key = await key(parsedProt, jws);
resolvedKey = true;
}
const b64 = typeof signingPayload === 'string';
const entry = jwsAlgorithm(alg);
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');
if (!(await verify(entry, k, signature, data))) {
throw new JWSSignatureVerificationFailed();
}
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) {
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');
}
const signingPayload = b64 || typeof inputPayload !== 'string' ? inputPayload : encodeUnencodedPayload(inputPayload);
return verifyPrepared(jws, shared, key, encodedProtected, parsedProt, alg, signingPayload);
}
export async function verifyCompact(jws, shared, key) {
if (jws instanceof Uint8Array) {
jws = decoder.decode(jws);
}
if (typeof jws !== 'string') {
throw new JWSInvalid('Compact JWS must be a string or Uint8Array');
}
const { 0: protectedHeader, 1: payload, 2: signature, length } = jws.split('.');
if (length !== 3) {
throw new JWSInvalid('Invalid Compact JWS');
}
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);
}