UNPKG

cose-kit

Version:

**DEPRECATED:** Use [@auth0/cose](https://www.npmjs.com/package/@auth0/cose).

119 lines (118 loc) 4.08 kB
import { importJWK, } from 'jose'; import { JWKSInvalid, COSENotSupported, JWKSNoMatchingKey, JWKSMultipleMatchingKeys, } from '../util/errors.js'; import isObject from '../lib/is_object.js'; import { areEqual } from '../lib/buffer_utils.js'; function getKtyFromAlg(alg) { switch (typeof alg === 'string' && alg.slice(0, 2)) { case 'RS': case 'PS': return 'RSA'; case 'ES': return 'EC'; case 'Ed': return 'OKP'; default: throw new COSENotSupported('Unsupported "alg" value for a JSON Web Key Set'); } } export function isJWKSLike(jwks) { return (jwks && typeof jwks === 'object' && Array.isArray(jwks.keys) && jwks.keys.every(isJWKLike)); } function isJWKLike(key) { return isObject(key); } function clone(obj) { if (typeof structuredClone === 'function') { return structuredClone(obj); } return JSON.parse(JSON.stringify(obj)); } export class COSELocalJWKSet { constructor(jwks) { this._cached = new WeakMap(); if (!isJWKSLike(jwks)) { throw new JWKSInvalid('JSON Web Key Set malformed'); } this._jwks = clone(jwks); } async getKey(signature) { const { algName: alg, kid } = signature; const kty = getKtyFromAlg(alg); const candidates = this._jwks.keys.filter((jwk) => { let candidate = kty === jwk.kty; if (candidate && kid) { const jwkKidBytes = new TextEncoder().encode(jwk.kid); candidate = areEqual(jwkKidBytes, kid); } if (candidate && typeof jwk.alg === 'string') { 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 && alg === 'EdDSA') { candidate = jwk.crv === 'Ed25519' || jwk.crv === 'Ed448'; } if (candidate) { switch (alg) { case 'ES256': candidate = jwk.crv === 'P-256'; break; case 'ES256K': candidate = jwk.crv === 'secp256k1'; break; case 'ES384': candidate = jwk.crv === 'P-384'; break; case 'ES512': candidate = jwk.crv === 'P-521'; break; } } return candidate; }); const { 0: jwk, length } = candidates; if (length === 0) { throw new JWKSNoMatchingKey(); } else if (length !== 1) { const error = new JWKSMultipleMatchingKeys(); const { _cached } = this; error[Symbol.asyncIterator] = async function* () { for (const jwk of candidates) { try { yield await importWithAlgCache(_cached, jwk, alg); } catch { continue; } } }; throw error; } return importWithAlgCache(this._cached, jwk, alg); } } async function importWithAlgCache(cache, jwk, alg) { const cached = cache.get(jwk) || cache.set(jwk, {}).get(jwk); if (cached[alg] === undefined) { const key = await importJWK({ ...jwk, ext: true }, alg); if (key instanceof Uint8Array || key.type !== 'public') { throw new JWKSInvalid('JSON Web Key Set members must be public keys'); } cached[alg] = key; } return cached[alg]; } export function createLocalJWKSet(jwks) { const set = new COSELocalJWKSet(jwks); return async function (signature) { return set.getKey(signature); }; }