File size: 3,988 Bytes
10852fa 9592df2 10852fa 9592df2 10852fa 9592df2 10852fa 9592df2 10852fa 9592df2 10852fa 9592df2 10852fa |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
import { importJWK } from '../key/import.js';
import { JWKSInvalid, JOSENotSupported, JWKSNoMatchingKey, JWKSMultipleMatchingKeys, } from '../util/errors.js';
import isObject from '../lib/is_object.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 JOSENotSupported('Unsupported "alg" value for a JSON Web Key Set');
}
}
function isJWKSLike(jwks) {
return (jwks &&
typeof jwks === 'object' &&
Array.isArray(jwks.keys) &&
jwks.keys.every(isJWKLike));
}
function isJWKLike(key) {
return isObject(key);
}
class LocalJWKSet {
#jwks;
#cached = new WeakMap();
constructor(jwks) {
if (!isJWKSLike(jwks)) {
throw new JWKSInvalid('JSON Web Key Set malformed');
}
this.#jwks = structuredClone(jwks);
}
jwks() {
return this.#jwks;
}
async getKey(protectedHeader, token) {
const { alg, kid } = { ...protectedHeader, ...token?.header };
const kty = getKtyFromAlg(alg);
const candidates = this.#jwks.keys.filter((jwk) => {
let candidate = kty === jwk.kty;
if (candidate && typeof kid === 'string') {
candidate = kid === jwk.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) {
switch (alg) {
case 'ES256':
candidate = jwk.crv === 'P-256';
break;
case 'ES384':
candidate = jwk.crv === 'P-384';
break;
case 'ES512':
candidate = jwk.crv === 'P-521';
break;
case 'Ed25519':
case 'EdDSA':
candidate = jwk.crv === 'Ed25519';
break;
}
}
return candidate;
});
const { 0: jwk, length } = candidates;
if (length === 0) {
throw new JWKSNoMatchingKey();
}
if (length !== 1) {
const error = new JWKSMultipleMatchingKeys();
const _cached = this.#cached;
error[Symbol.asyncIterator] = async function* () {
for (const jwk of candidates) {
try {
yield await importWithAlgCache(_cached, jwk, alg);
}
catch { }
}
};
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 LocalJWKSet(jwks);
const localJWKSet = async (protectedHeader, token) => set.getKey(protectedHeader, token);
Object.defineProperties(localJWKSet, {
jwks: {
value: () => structuredClone(set.jwks()),
enumerable: false,
configurable: false,
writable: false,
},
});
return localJWKSet;
}
|