Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
# 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.