3.4 KiB
3.4 KiB
keys/jwk-policy
Purpose
Cross-check a JWK's key_ops, use, and alg members against the
requested operation per RFC 7517 §4.2 / §4.3 / §4.4 + RFC 7518 §6.3.
Prevents the common operational mistake of using a JWK marked
use: "sig" for encryption (or vice-versa), which leaks both the key
purpose and the operator intent into the wrong code path.
Features
- Validates
key_ops⊃ requested operation (with the standard encrypt → wrapKey, decrypt → unwrapKey aliases) - Validates
use === "sig"permits only sign/verify anduse === "enc"permits only encrypt/decrypt - Validates
algis one of the RSA-compatible JWA registry values (RS{256,384,512},PS{256,384,512},RSA-OAEP{,−256,−384,−512},RSA1_5) AND is appropriate for the requested operation class - Each violation throws
CRYPTO_RSA.KEY_OP_NOT_PERMITTEDwith areasonattribute ("key_ops"/"use"/"alg") plus context identifying the offending member - Skips checks when the JWK omits the corresponding member (RFC 7517 semantics: absence ≠ denial)
Usage: Basic Example
import { enforceJwkPolicy } from "@suaveplan/crypto-rsa/keys/jwk-policy";
const jwk = {
kty: "RSA",
n: "...",
e: "AQAB",
use: "sig",
alg: "RS256",
key_ops: ["sign"],
};
enforceJwkPolicy(jwk, "sign"); // no-op
enforceJwkPolicy(jwk, "encrypt"); // throws KEY_OP_NOT_PERMITTED
Usage: Advanced Example
Wire policy enforcement into a higher-level signer that accepts both
canonical RsaPrivateKey and JWK inputs; only run the policy check on
the JWK path because canonical keys carry no policy fields:
import { enforceJwkPolicy, parseRsaPrivateKey } from "@suaveplan/crypto-rsa";
function loadSigningKey(input: RsaJwk | Uint8Array): RsaPrivateKey {
if (typeof input === "object" && "kty" in input) {
enforceJwkPolicy(input, "sign");
return parseRsaPrivateKey(input, { format: "jwk" });
}
return parseRsaPrivateKey(input, { format: "pkcs8" });
}
API Reference
enforceJwkPolicy(jwk, operation): void
jwk: RsaJwkoperation: "sign" | "verify" | "encrypt" | "decrypt"- throws
CryptoRsaError(CRYPTO_RSA.KEY_OP_NOT_PERMITTED)on any cross-policy violation; the error context carriesreason(which member triggered the rejection) plus the member value
JwkOperation
"sign" | "verify" | "encrypt" | "decrypt".
Implementation Notes
- The canonical
RsaPublicKey/RsaPrivateKeyshapes have nokey_ops/use/algfields, so direct callers using the canonical types bypass policy enforcement entirely. Wrap the policy check at the JWK ingestion boundary if you need it; the package's built-in sign / encrypt surfaces accept canonical keys precisely so they don't impose JWK-specific semantics. - The encrypt → wrapKey / decrypt → unwrapKey aliases follow RFC 7517
§4.3 (
key_opsvalue definitions); a JWK markedkey_ops: ["wrapKey"]is permitted to encrypt because the cryptographic operation is the same (RSA public encryption). - When
useandalgboth appear they MUST be consistent (RFC 7517 §4.4); this implementation does not cross-check them against each other because the JWA registry ties algorithm IDs to a single use, so the per-member checks already cover the cross-product. key_opsandusetogether are RFC-permitted but discouraged (RFC 7517 §4.3 last paragraph); both checks fire independently when both are present.