Files
2026-05-27 23:47:53 +01:00

3.4 KiB
Raw Permalink Blame History

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

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: 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.