# 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 and `use === "enc"` permits only encrypt/decrypt - Validates `alg` is 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_PERMITTED` with a `reason` attribute (`"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 ```ts 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: ```ts 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: RsaJwk` - `operation: "sign" | "verify" | "encrypt" | "decrypt"` - **throws** `CryptoRsaError(CRYPTO_RSA.KEY_OP_NOT_PERMITTED)` on any cross-policy violation; the error context carries `reason` (which member triggered the rejection) plus the member value ### `JwkOperation` `"sign" | "verify" | "encrypt" | "decrypt"`. ## Implementation Notes - The canonical `RsaPublicKey` / `RsaPrivateKey` shapes have no `key_ops` / `use` / `alg` fields, 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_ops` value definitions); a JWK marked `key_ops: ["wrapKey"]` is permitted to encrypt because the cryptographic operation is the same (RSA public encryption). - When `use` and `alg` both 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_ops` and `use` together are RFC-permitted but discouraged (RFC 7517 §4.3 last paragraph); both checks fire independently when both are present.