Skip to content
Merged
28 changes: 24 additions & 4 deletions docs/usage/jose/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,35 @@ JOSE (JSON Object Signing and Encryption) is a set of standards for secure data
- **JWS** (RFC 7515) - JSON Web Signature - digital signature
- **JWE** (RFC 7516) - JSON Web Encryption - data encryption
- **JWK** (RFC 7517) - JSON Web Key - cryptographic key representation
- **RFC 7518** - JSON Web Algorithms - cryptographic algorithms
- **JWT** (RFC 7519) - JSON Web Token - compact token format

Jam provides a complete implementation of all JOSE standards with support for:

- Symmetric (HMAC) and asymmetric (RSA, ECDSA) signing algorithms
- Multiple key management algorithms (RSA, AES Key Wrap, ECDH-ES, PBES2)
- Symmetric (HMAC) and asymmetric (RSA, ECDSA, RSA-PSS) signing algorithms
- Multiple key management algorithms (RSA, AES Key Wrap, ECDH-ES, PBES2,
AES-GCM Key Wrap)
- Various content encryption modes (AES-CBC-HS, AES-GCM)
- Three token modes: JWS-only (signed), JWE-only (encrypted), JWS+JWE
(sign-then-encrypt)
- Automatic JWE key management algorithm selection based on key type
- HKDF key derivation for symmetric sign-then-encrypt scenarios
- JWT standard claims validation (exp, nbf)
- Token black/white lists
- Critical header (`crit`) validation per RFC 7515
- Token black/white lists with pluggable backends

## Exports

The `jam.jose` package exports the following:

**Classes:**

- `JWK` - JSON Web Key
- `JWKSet` - Set of JWK keys
- `JWKRSA`, `JWKEC`, `JWKOct` - TypedDicts for typed key definitions
- `JWS` - JSON Web Signature
- `JWE` - JSON Web Encryption
- `JWT` - JSON Web Token (supports all three token modes)

## Navigation

Expand All @@ -26,4 +46,4 @@ Jam provides a complete implementation of all JOSE standards with support for:
- [JWE](jwe.md) - data encryption and decryption
- [JWK](jwk.md) - cryptographic keys management
- [Lists](lists.md) - token black and white lists
- [Algorithms](algorithms.md) - supported algorithms reference
- [Algorithms](algorithms.md) - supported algorithms reference
43 changes: 17 additions & 26 deletions docs/usage/jose/jwe.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ Creates JWE Compact Serialization - encrypted data.

Args:

* `alg`: `str` - Key management algorithm. Available: `RSA-OAEP`, `RSA1_5`, `A128KW`, `A192KW`, `A256KW`, `ECDH-ES`, `ECDH-ES+A128KW`, `ECDH-ES+A192KW`, `ECDH-ES+A256KW`, `PBES2-HS256+A128KW`, `PBES2-HS384+A192KW`, `PBES2-HS512+A256KW`.
* `alg`: `str` - Key management algorithm. Available: `RSA-OAEP`,
`RSA-OAEP-256`, `RSA1_5`, `A128KW`, `A192KW`, `A256KW`, `ECDH-ES`,
`ECDH-ES+A128KW`, `ECDH-ES+A192KW`, `ECDH-ES+A256KW`, `A128GCMKW`,
`A256GCMKW`, `PBES2-HS256+A128KW`, `PBES2-HS384+A192KW`,
`PBES2-HS512+A256KW`.
* `enc`: `str` - Content encryption algorithm. Available: `A128CBC-HS256`, `A192CBC-HS384`, `A256CBC-HS512`, `A128GCM`, `A256GCM`.
* `payload`: `dict[str, Any] | str | bytes` - Data to encrypt.
* `header`: `dict[str, Any] | None = None` - Additional header fields.
Expand Down Expand Up @@ -87,6 +91,18 @@ jwe = JWE(
)
```

### Factory function

```python
from jam.jose import create_jwe_instance

jwe = create_jwe_instance(
alg="A256KW",
enc="A256GCM",
key="your-256-bit-key-here!!",
)
```

### Encrypt data

Method: `jwe.encrypt`
Expand Down Expand Up @@ -235,28 +251,3 @@ jwe_dec = JWE(
)
data = jwe_dec.decrypt(token)
```

## Error handling

```python
from jam.jose import JWE
from jam.exceptions.jose import (
JamJWEDecryptionError,
JamJWEEncryptionError,
JamJWEInvalidFormatError,
)

jwe = JWE(
alg="RSA-OAEP",
enc="A128CBC-HS256",
key=rsa_key
)

try:
data = jwe.decrypt(token)
except JamJWEDecryptionError as e:
print(f"Decryption failed: {e.message}")
print(f"Error code: {e.error_code}")
except JamJWEInvalidFormatError as e:
print(f"Invalid token format: {e.message}")
```
139 changes: 114 additions & 25 deletions docs/usage/jose/jwk.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,91 @@
title: JWK
---

## TypedDicts

### JWKCommon

Common JWK parameters shared across all key types.

```python
from jam.jose import JWKCommon

key: JWKCommon = {
"kty": "RSA", # Required. Key type: RSA, EC, oct
"use": "sig", # Public key use: "sig" or "enc"
"key_ops": ["sign"], # Intended key operations
"alg": "RS256", # Intended algorithm
"kid": "key-id", # Unique key identifier
"x5u": "https://...", # X.509 URL
"x5c": "MIID...", # X.509 certificate chain (base64)
"x5t": "abc...", # X.509 SHA-1 thumbprint
"x5t_S256": "xyz...", # X.509 SHA-256 thumbprint
}
```

### JWKRSA

RSA key parameters (extends `JWKCommon`).

```python
from jam.jose import JWKRSA

# Public key
rsa_pub: JWKRSA = {
"kty": "RSA",
"n": "0vx7agoebGcQSuu...", # Modulus (base64url)
"e": "AQAB", # Exponent (base64url)
}

# Private key (requires all CRT parameters per RFC 7518)
rsa_priv: JWKRSA = {
"kty": "RSA",
"n": "...", "e": "AQAB",
"d": "...", # Private exponent
"p": "...", # First prime factor
"q": "...", # Second prime factor
"dp": "...", # d mod (p-1)
"dq": "...", # d mod (q-1)
"qi": "...", # q^(-1) mod p
}
```

!!! warning "RSA private key validation"
When `d` is present, all CRT parameters (`p`, `q`, `dp`, `dq`, `qi`) are
required. Missing any of them raises `JamJWKValidationError` per RFC 7518
Section 6.3.2.

### JWKEC

Elliptic curve key parameters (extends `JWKCommon`).

```python
from jam.jose import JWKEC

ec_key: JWKEC = {
"kty": "EC",
"crv": "P-256", # Supported: P-256, P-384, P-521
"x": "f83OJ3D...", # X coordinate (base64url)
"y": "x_FEzRu...", # Y coordinate (base64url)
"d": "...", # Private key (optional, base64url)
}
```

### JWKOct

Symmetric (octet sequence) key parameters (extends `JWKCommon`).

```python
from jam.jose import JWKOct

oct_key: JWKOct = {
"kty": "oct",
"k": "c2VjcmV0LWtleS0zMi1ieXRlcy1sb25n", # Key value (base64url)
}
```

---

## Standalone (module)

### JWK - JSON Web Key
Expand Down Expand Up @@ -87,6 +172,20 @@ print(jwk.alg) # None
print(jwk.kid) # "key1"
```

All JWK parameters are accessible via `to_dict()`:

| Parameter | Type | Description |
|-----------|------|-------------|
| `kty` | `str` | Key Type (RSA, EC, oct) |
| `use` | `str` | Public key use (`sig`, `enc`) |
| `key_ops` | `list[str]` | Intended key operations |
| `alg` | `str` | Intended algorithm |
| `kid` | `str` | Key ID |
| `x5u` | `str` | X.509 URL |
| `x5c` | `str` | X.509 certificate chain |
| `x5t` | `str` | X.509 SHA-1 thumbprint |
| `x5t#S256` | `str` | X.509 SHA-256 thumbprint |

### Sign data

Method: `jwk.sign`
Expand Down Expand Up @@ -327,30 +426,20 @@ token = ec_jwk.sign(b"data", alg="ES256")
result = ec_jwk.verify(token)
```

## Error handling
## Key type-specific classes

Typed key classes are exported for static type checking:

```python
from jam.jose import JWK, JWKSet
from jam.exceptions.jose import (
JamJWKValidationError,
JamJWSVerificationError,
)

# JWK validation
try:
jwk = JWK.from_dict({"kty": "INVALID"})
except JamJWKValidationError as e:
print(f"Invalid JWK: {e.message}")

# JWKSet validation
try:
jwks = JWKSet.from_dict({"keys": "not a list"})
except JamJWKValidationError as e:
print(f"Invalid JWKSet: {e.message}")

# Signature verification
try:
result = jwk.verify(token)
except JamJWSVerificationError as e:
print(f"Verification failed: {e.error_code}")
```
from jam.jose import JWKRSA, JWKEC, JWKOct

# These are TypedDicts for type annotations
def process_rsa_key(key: JWKRSA) -> None:
...

def process_ec_key(key: JWKEC) -> None:
...

def process_symmetric_key(key: JWKOct) -> None:
...
```
62 changes: 51 additions & 11 deletions docs/usage/jose/jws.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,21 @@
title: JWS
---

## Instance (jam.Jam)
## Use in instance

### Config


* `alg`: `str` - Signing algorithm. Available: `HS256`, `HS384`, `HS512`, `RS256`, `RS384`, `RS512`, `ES256`, `ES384`, `ES512`, `PS256`, `PS384`, `PS512`.
* `key`: `str` - Secret key for signing/verify.
* `password`: `str | None` - Password for key derivation.

```toml
[jam.jose.jws]
alg = ""
key = "$JWS_SECRET_KEY"
password = "$JWS_PASSWORD"
```

### Sign data

Expand Down Expand Up @@ -90,6 +104,17 @@ jws = JWS(
)
```

### Factory function

```python
from jam.jose import create_jws_instance

jws = create_jws_instance(
alg="HS256",
key="your-secret-key",
)
```

### Sign data

Method: `jws.sign`
Expand Down Expand Up @@ -182,19 +207,34 @@ Raises:
data = jws.deserialize_compact(jws_token, validate=True)
```

## Error handling
### Critical header validation

JWS validates the `crit` (critical) header per RFC 7515. If a header name is
listed in `crit`, it must be a registered JOSE header name (`alg`, `typ`,
`kid`, `x5u`, `x5t`, `cty`, `crit`). Unknown critical headers cause
verification to fail.

```python
from jam.jose import JWS
from jam.exceptions.jose import JamJWSVerificationError
# This will fail - "unknown" is not a registered header
jws.deserialize_compact(
"eyJhbGciOiJIUzI1NiIsImNyaXQiOlsidW5rbm93biJ9...",
validate=True,
)
# Raises JamJWSVerificationError: unknown_critical_header
```

jws = JWS(alg="HS256", key="secret_key")
### Key auto-loading

try:
result = jws.verify(token, validate=True)
except JamJWSVerificationError as e:
print(f"Verification failed: {e.error_code}")
print(f"Details: {e.details}")
When a string is passed as `key`, JWS attempts to load it as a file path first.
If the file exists, its contents are used as the key. Otherwise, the string is
used directly as the key material.

```python
# Loads key from file if path exists
jws = JWS(alg="RS256", key="/path/to/private-key.pem")

# Uses string directly if not a valid file path
jws = JWS(alg="HS256", key="my-secret-string-key")
```

## Examples
Expand Down Expand Up @@ -249,4 +289,4 @@ from jam.jose import JWS
jws = JWS(alg="PS256", key=rsa_private_key)
token = jws.sign(header={}, data={"data": "value"})
result = jws.verify(token)
```
```
Loading
Loading