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
JavaScript
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);
}
}