import { withAlg as invalidKeyInput } from './invalid_key_input.js';
|
import { isKeyLike } from './is_key_like.js';
|
import * as jwk from './type_checks.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);
|
}
|
}
|