UNPKG

jose

Version:

JWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes

123 lines (122 loc) 4.76 kB
import { withAlg as invalidKeyInput } from './invalid_key_input.js'; import { isKeyLike } from './is_key_like.js'; import * as jwk from './is_jwk.js'; const tag = (key) => key?.[Symbol.toStringTag]; const jwkMatchesOp = (alg, key, usage) => { if (key.use !== undefined) { let expected; switch (usage) { case 'sign': case 'verify': expected = 'sig'; break; case 'encrypt': case 'decrypt': expected = 'enc'; break; } if (key.use !== expected) { throw new TypeError(`Invalid key for this operation, its "use" must be "${expected}" when present`); } } if (key.alg !== undefined && key.alg !== alg) { throw new TypeError(`Invalid key for this operation, its "alg" must be "${alg}" when present`); } if (Array.isArray(key.key_ops)) { let expectedKeyOp; switch (true) { case usage === 'sign' || usage === 'verify': case alg === 'dir': case alg.includes('CBC-HS'): expectedKeyOp = usage; break; case alg.startsWith('PBES2'): expectedKeyOp = 'deriveBits'; break; case /^A\d{3}(?:GCM)?(?:KW)?$/.test(alg): if (!alg.includes('GCM') && alg.endsWith('KW')) { expectedKeyOp = usage === 'encrypt' ? 'wrapKey' : 'unwrapKey'; } else { expectedKeyOp = usage; } break; case usage === 'encrypt' && alg.startsWith('RSA'): expectedKeyOp = 'wrapKey'; break; case usage === 'decrypt': expectedKeyOp = alg.startsWith('RSA') ? 'unwrapKey' : 'deriveBits'; break; } if (expectedKeyOp && key.key_ops?.includes?.(expectedKeyOp) === false) { throw new TypeError(`Invalid key for this operation, its "key_ops" must include "${expectedKeyOp}" when present`); } } return true; }; const symmetricTypeCheck = (alg, key, usage) => { if (key instanceof Uint8Array) return; if (jwk.isJWK(key)) { if (jwk.isSecretJWK(key) && jwkMatchesOp(alg, key, usage)) return; throw new TypeError(`JSON Web Key for symmetric algorithms must have JWK "kty" (Key Type) equal to "oct" and the JWK "k" (Key Value) present`); } if (!isKeyLike(key)) { throw new TypeError(invalidKeyInput(alg, key, 'CryptoKey', 'KeyObject', 'JSON Web Key', 'Uint8Array')); } if (key.type !== 'secret') { throw new TypeError(`${tag(key)} instances for symmetric algorithms must be of type "secret"`); } }; const asymmetricTypeCheck = (alg, key, usage) => { if (jwk.isJWK(key)) { switch (usage) { case 'decrypt': case 'sign': if (jwk.isPrivateJWK(key) && jwkMatchesOp(alg, key, usage)) return; throw new TypeError(`JSON Web Key for this operation must be a private JWK`); case 'encrypt': case 'verify': if (jwk.isPublicJWK(key) && jwkMatchesOp(alg, key, usage)) return; throw new TypeError(`JSON Web Key for this operation must be a public JWK`); } } if (!isKeyLike(key)) { throw new TypeError(invalidKeyInput(alg, key, 'CryptoKey', 'KeyObject', 'JSON Web Key')); } if (key.type === 'secret') { throw new TypeError(`${tag(key)} instances for asymmetric algorithms must not be of type "secret"`); } if (key.type === 'public') { switch (usage) { case 'sign': throw new TypeError(`${tag(key)} instances for asymmetric algorithm signing must be of type "private"`); case 'decrypt': throw new TypeError(`${tag(key)} instances for asymmetric algorithm decryption must be of type "private"`); } } if (key.type === 'private') { switch (usage) { case 'verify': throw new TypeError(`${tag(key)} instances for asymmetric algorithm verifying must be of type "public"`); case 'encrypt': throw new TypeError(`${tag(key)} instances for asymmetric algorithm encryption must be of type "public"`); } } }; export function checkKeyType(alg, key, usage) { switch (alg.substring(0, 2)) { case 'A1': case 'A2': case 'di': case 'HS': case 'PB': symmetricTypeCheck(alg, key, usage); break; default: asymmetricTypeCheck(alg, key, usage); } }