Skip to content

Commit

Permalink
more convenience methods (#22)
Browse files Browse the repository at this point in the history
* BREAKING CHANGE: make more args kw-only
* BREAKING CHANGE: JwtSigner now takes a `key` param instead of `jwk`. `issuer` becomes an optional kwarg.
* BREAKING CHANGE: `InvalidSignature` exception is now defined in `jws` submodule and accepts any `SupportsBytes` as data.
* add `JwsCompact.verify()`
  • Loading branch information
guillp committed Jan 19, 2024
1 parent ecd2aac commit 6a73468
Show file tree
Hide file tree
Showing 58 changed files with 1,144 additions and 1,056 deletions.
13 changes: 6 additions & 7 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.5.0
hooks:
- id: trailing-whitespace
args: [--markdown-linebreak-ext=md]
Expand All @@ -21,20 +21,18 @@ repos:
- --in-place
- --wrap-summaries=100
- --wrap-descriptions=100
- repo: https://github.com/psf/black
rev: 23.9.1
hooks:
- id: black
- repo: https://github.com/asottile/blacken-docs
rev: 1.16.0
hooks:
- id: blacken-docs
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.292
rev: v0.1.11
hooks:
- id: ruff
args: [ --fix ]
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.5.1
rev: v1.8.0
hooks:
- id: mypy
args:
Expand All @@ -48,3 +46,4 @@ repos:
- pytest-mypy==0.10.3
- binapy==0.7.0
- freezegun==1.2.2
- jwcrypto==1.5.0
52 changes: 29 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,21 @@ from jwskate import Jwk

# Let's generate a random private key, to use with alg 'RS256'.
# Based on that alg, jwskate knows it must be an RSA key.
# RSA keys can be of variable size, so let's pass the requested key size as parameter
# RSA keys can be of any size, so let's pass the requested key size as parameter
rsa_private_jwk = Jwk.generate(alg="RS256", key_size=2048)

data = b"Signing is easy!" # we will sign this
signature = rsa_private_jwk.sign(data) # done!

print(signature)
# b'-\xe89\x81\xc4\xb9.G\x11\xa6\x93/dm\xf0\xc8\x0f\xd....'

# now extract the public key, and verify the signature with it
rsa_public_jwk = rsa_private_jwk.public_jwk()
assert rsa_public_jwk.verify(data, signature)

# let's see what a Jwk looks like:
assert isinstance(rsa_private_jwk, dict) # Jwk are dict
# let's see what a `Jwk` looks like:
assert isinstance(rsa_private_jwk, dict) # Jwk are dict subclasses

print(rsa_private_jwk.with_usage_parameters())
```
Expand Down Expand Up @@ -98,13 +101,13 @@ assert jwt.verify_signature(private_jwk.public_jwk(), alg="ES256")
# or with `alg` or `algs` params, and will ignore the 'alg' that is set in the JWT, for security reasons.
```

Now let's sign a JWT with the standardised lifetime, subject, audience and ID claims, plus arbitrary custom claims:
Now let's sign a JWT with the standardized lifetime, subject, audience and ID claims, plus arbitrary custom claims:

```python
from jwskate import Jwk, JwtSigner

private_jwk = Jwk.generate(alg="ES256")
signer = JwtSigner(issuer="https://myissuer.com", jwk=private_jwk)
signer = JwtSigner(issuer="https://myissuer.com", key=private_jwk)
jwt = signer.sign(
subject="some_sub",
audience="some_aud",
Expand All @@ -114,7 +117,7 @@ jwt = signer.sign(
print(jwt.claims)
```

The generated JWT will include the standardised claims (`iss`, `aud`, `sub`, `iat`, `exp` and `jti`),
The generated JWT will include the standardized claims (`iss`, `aud`, `sub`, `iat`, `exp` and `jti`),
together with the `extra_claims` provided to `.sign()`:

```
Expand Down Expand Up @@ -163,18 +166,31 @@ together with the `extra_claims` provided to `.sign()`:
| `RS256` | RSASSA-PKCS1-v1_5 using SHA-256 | `RSA` | [RFC7518, Section 3.3] | |
| `RS384` | RSASSA-PKCS1-v1_5 using SHA-384 | `RSA` | [RFC7518, Section 3.3] | |
| `RS512` | RSASSA-PKCS1-v1_5 using SHA-512 | `RSA` | [RFC7518, Section 3.3] | |
| `ES256` | ECDSA using P-256 and SHA-256 | `EC` | [RFC7518, Section 3.4] | |
| `ES384` | ECDSA using P-384 and SHA-384 | `EC` | [RFC7518, Section 3.4] | |
| `ES512` | ECDSA using P-521 and SHA-512 | `EC` | [RFC7518, Section 3.4] | |
| `PS256` | RSASSA-PSS using SHA-256 and MGF1 with SHA-256 | `RSA` | [RFC7518, Section 3.5] | |
| `PS384` | RSASSA-PSS using SHA-384 and MGF1 with SHA-384 | `RSA` | [RFC7518, Section 3.5] | |
| `PS512` | RSASSA-PSS using SHA-512 and MGF1 with SHA-512 | `RSA` | [RFC7518, Section 3.5] | |
| `EdDSA` | EdDSA signature algorithms | `OKP` | [RFC8037, Section 3.1] | Ed2219 and Ed448 are supported |
| `ES256` | ECDSA using P-256 and SHA-256 | `EC` | [RFC7518, Section 3.4] | |
| `ES384` | ECDSA using P-384 and SHA-384 | `EC` | [RFC7518, Section 3.4] | |
| `ES512` | ECDSA using P-521 and SHA-512 | `EC` | [RFC7518, Section 3.4] | |
| `ES256K` | ECDSA using secp256k1 curve and SHA-256 | `EC` | [RFC8812, Section 3.2] | |
| `EdDSA` | EdDSA signature algorithms | `OKP` | [RFC8037, Section 3.1] | Ed2219 and Ed448 are supported |
| `HS1` | HMAC using SHA-1 | `oct` | https://www.w3.org/TR/WebCryptoAPI | Validation Only |
| `RS1` | RSASSA-PKCS1-v1_5 with SHA-1 | `oct` | https://www.w3.org/TR/WebCryptoAPI | Validation Only |
| `RS1` | RSASSA-PKCS1-v1_5 with SHA-1 | `RSA` | https://www.w3.org/TR/WebCryptoAPI | Validation Only |
| `none` | No digital signature or MAC performed | | [RFC7518, Section 3.6] | Not usable by mistake |

### Supported Encryption algorithms


| Signature Alg | Description | Reference |
|-----------------|-------------------------------------------------------------|--------------------------|
| `A128CBC-HS256` | AES_128_CBC_HMAC_SHA_256 authenticated encryption algorithm | [RFC7518, Section 5.2.3] |
| `A192CBC-HS384` | AES_192_CBC_HMAC_SHA_384 authenticated encryption algorithm | [RFC7518, Section 5.2.4] |
| `A256CBC-HS512` | AES_256_CBC_HMAC_SHA_512 authenticated encryption algorithm | [RFC7518, Section 5.2.5] |
| `A128GCM` | AES GCM using 128-bit key | [RFC7518, Section 5.3] |
| `A192GCM` | AES GCM using 192-bit key | [RFC7518, Section 5.3] |
| `A256GCM` | AES GCM using 256-bit key | [RFC7518, Section 5.3] |


### Supported Key Management algorithms


Expand All @@ -200,18 +216,8 @@ together with the `extra_claims` provided to `.sign()`:
| `PBES2-HS384+A192KW` | PBES2 with HMAC SHA-384 and "A192KW" wrapping | `password` | [RFC7518, Section 4.8] | |
| `PBES2-HS512+A256KW` | PBES2 with HMAC SHA-512 and "A256KW" wrapping | `password` | [RFC7518, Section 4.8] | |

### Supported Encryption algorithms


| Signature Alg | Description | Reference |
| ------------- | ----------------------------------------------------------- | ------------------------ |
| `A128CBC-HS256` | AES_128_CBC_HMAC_SHA_256 authenticated encryption algorithm | [RFC7518, Section 5.2.3] |
| `A192CBC-HS384` | AES_192_CBC_HMAC_SHA_384 authenticated encryption algorithm | [RFC7518, Section 5.2.4] |
| `A256CBC-HS512` | AES_256_CBC_HMAC_SHA_512 authenticated encryption algorithm | [RFC7518, Section 5.2.5] |
| `A128GCM` | AES GCM using 128-bit key | [RFC7518, Section 5.3] |
| `A192GCM` | AES GCM using 192-bit key | [RFC7518, Section 5.3] |
| `A256GCM` | AES GCM using 256-bit key | [RFC7518, Section 5.3] |

### Supported Elliptic Curves


Expand All @@ -220,13 +226,13 @@ together with the `extra_claims` provided to `.sign()`:
| `P-256` | P-256 Curve | `EC` | signature, encryption | [RFC7518, Section 6.2.1.1] |
| `P-384` | P-384 Curve | `EC` | signature, encryption | [RFC7518, Section 6.2.1.1] |
| `P-521` | P-521 Curve | `EC` | signature, encryption | [RFC7518, Section 6.2.1.1] |
| `secp256k1` | SECG secp256k1 curve | `EC` | signature, encryption | [RFC8812, Section 3.1] |
| `Ed25519` | Ed25519 signature algorithm key pairs | `OKP` | signature | [RFC8037, Section 3.1] |
| `Ed448` | Ed448 signature algorithm key pairs | `OKP` | signature | [RFC8037, Section 3.1] |
| `X25519` | X25519 function key pairs | `OKP` | encryption | [RFC8037, Section 3.2] |
| `X448` | X448 function key pairs | `OKP` | encryption | [RFC8037, Section 3.2] |
| `secp256k1` | SECG secp256k1 curve | `EC` | signature, encryption | [RFC8812, Section 3.1] |

## Why a new lib ?
## Why a new lib?

There are already multiple modules implementing JOSE and Json Web Crypto related specifications in Python. However, I
have been dissatisfied by all of them so far, so I decided to come up with my own module.
Expand Down
4 changes: 2 additions & 2 deletions jwskate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
provides a set of convenient wrappers around the `cryptography` module.
"""

from __future__ import annotations

__author__ = """Guillaume Pujol"""
Expand Down Expand Up @@ -97,12 +98,11 @@
select_alg_classes,
to_jwk,
)
from .jws import InvalidJws, JwsCompact, JwsJsonFlat, JwsJsonGeneral, JwsSignature
from .jws import InvalidJws, InvalidSignature, JwsCompact, JwsJsonFlat, JwsJsonGeneral, JwsSignature
from .jwt import (
ExpiredJwt,
InvalidClaim,
InvalidJwt,
InvalidSignature,
Jwt,
JwtSigner,
JwtVerifier,
Expand Down
1 change: 1 addition & 0 deletions jwskate/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
See [IANA JOSE](https://www.iana.org/assignments/jose/jose.xhtml).
"""

from __future__ import annotations


Expand Down
1 change: 1 addition & 0 deletions jwskate/jwa/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
[RFC7518]: https://www.rfc-editor.org/rfc/rfc7518
"""

from __future__ import annotations

from .base import (
Expand Down
1 change: 1 addition & 0 deletions jwskate/jwa/base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""This module implement base classes for the algorithms defined in JWA."""

from __future__ import annotations

from contextlib import contextmanager
Expand Down
1 change: 1 addition & 0 deletions jwskate/jwa/ec.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""This module contains classes that describe Elliptic Curves as described in RFC7518."""

from __future__ import annotations

from dataclasses import dataclass
Expand Down
1 change: 1 addition & 0 deletions jwskate/jwa/encryption/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""This module exposes the Encryption algorithms that are available in `jwskate`."""

from __future__ import annotations

from .aescbchmac import A128CBC_HS256, A192CBC_HS384, A256CBC_HS512
Expand Down
1 change: 1 addition & 0 deletions jwskate/jwa/encryption/aescbchmac.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""This module implements AES-CBC with HMAC-SHA based Encryption algorithms."""

from __future__ import annotations

from typing import SupportsBytes
Expand Down
1 change: 1 addition & 0 deletions jwskate/jwa/encryption/aesgcm.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""This module implements AES-GCM based encryption algorithms."""

from __future__ import annotations

from typing import SupportsBytes
Expand Down
1 change: 1 addition & 0 deletions jwskate/jwa/key_mgmt/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""This module exposes all Key Management algorithms available in `jwskate`."""

from __future__ import annotations

from .aesgcmkw import A128GCMKW, A192GCMKW, A256GCMKW, BaseAesGcmKeyWrap
Expand Down
1 change: 1 addition & 0 deletions jwskate/jwa/key_mgmt/aesgcmkw.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""This module implements AES-GCM based Key Management algorithms."""

from __future__ import annotations

from typing import SupportsBytes
Expand Down
1 change: 1 addition & 0 deletions jwskate/jwa/key_mgmt/aeskw.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""This module implements AES based Key Management algorithms."""

from __future__ import annotations

from typing import SupportsBytes
Expand Down
1 change: 1 addition & 0 deletions jwskate/jwa/key_mgmt/dir.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""This module implements direct use of a shared symmetric key as Key Management algorithm."""

from __future__ import annotations

from binapy import BinaPy
Expand Down
17 changes: 9 additions & 8 deletions jwskate/jwa/key_mgmt/ecdh.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""This module implements Elliptic Curve Diffie-Hellman based Key Management algorithms."""

from __future__ import annotations

from typing import Any, SupportsBytes, Union
Expand Down Expand Up @@ -65,8 +66,8 @@ def otherinfo(cls, alg: str, apu: bytes, apv: bytes, key_size: int) -> BinaPy:
@classmethod
def ecdh(
cls,
private_key: (ec.EllipticCurvePrivateKey | x25519.X25519PrivateKey | x448.X448PrivateKey),
public_key: (ec.EllipticCurvePublicKey | x25519.X25519PublicKey | x448.X448PublicKey),
private_key: ec.EllipticCurvePrivateKey | x25519.X25519PrivateKey | x448.X448PrivateKey,
public_key: ec.EllipticCurvePublicKey | x25519.X25519PublicKey | x448.X448PublicKey,
) -> BinaPy:
"""Perform an Elliptic Curve Diffie-Hellman key exchange.
Expand Down Expand Up @@ -104,8 +105,8 @@ def ecdh(
def derive(
cls,
*,
private_key: (ec.EllipticCurvePrivateKey | x25519.X25519PrivateKey | x448.X448PrivateKey),
public_key: (ec.EllipticCurvePublicKey | x25519.X25519PublicKey | x448.X448PublicKey),
private_key: ec.EllipticCurvePrivateKey | x25519.X25519PrivateKey | x448.X448PrivateKey,
public_key: ec.EllipticCurvePublicKey | x25519.X25519PublicKey | x448.X448PublicKey,
otherinfo: bytes,
key_size: int,
) -> BinaPy:
Expand Down Expand Up @@ -143,7 +144,7 @@ def generate_ephemeral_key(

def sender_key(
self,
ephemeral_private_key: (ec.EllipticCurvePrivateKey | x25519.X25519PrivateKey | x448.X448PrivateKey),
ephemeral_private_key: ec.EllipticCurvePrivateKey | x25519.X25519PrivateKey | x448.X448PrivateKey,
*,
alg: str,
key_size: int,
Expand Down Expand Up @@ -175,7 +176,7 @@ def sender_key(

def recipient_key(
self,
ephemeral_public_key: (ec.EllipticCurvePublicKey | x25519.X25519PublicKey | x448.X448PublicKey),
ephemeral_public_key: ec.EllipticCurvePublicKey | x25519.X25519PublicKey | x448.X448PublicKey,
*,
alg: str,
key_size: int,
Expand Down Expand Up @@ -214,7 +215,7 @@ class BaseEcdhEs_AesKw(EcdhEs): # noqa: N801
def wrap_key_with_epk(
self,
plainkey: bytes,
ephemeral_private_key: (ec.EllipticCurvePrivateKey | x25519.X25519PrivateKey | x448.X448PrivateKey),
ephemeral_private_key: ec.EllipticCurvePrivateKey | x25519.X25519PrivateKey | x448.X448PrivateKey,
**headers: Any,
) -> BinaPy:
"""Wrap a key for content encryption.
Expand All @@ -234,7 +235,7 @@ def wrap_key_with_epk(
def unwrap_key_with_epk(
self,
cipherkey: bytes | SupportsBytes,
ephemeral_public_key: (ec.EllipticCurvePublicKey | x25519.X25519PublicKey | x448.X448PublicKey),
ephemeral_public_key: ec.EllipticCurvePublicKey | x25519.X25519PublicKey | x448.X448PublicKey,
**headers: Any,
) -> BinaPy:
"""Unwrap a key for content decryption.
Expand Down
1 change: 1 addition & 0 deletions jwskate/jwa/key_mgmt/pbes2.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""This module implements password-based Key Management Algorithms relying on PBES2."""

from __future__ import annotations

from typing import SupportsBytes
Expand Down
1 change: 1 addition & 0 deletions jwskate/jwa/key_mgmt/rsa.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""This module implements RSA based Key Management algorithms."""

from __future__ import annotations

from typing import Any, SupportsBytes
Expand Down
7 changes: 5 additions & 2 deletions jwskate/jwa/okp.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
: https: //www.rfc-editor.org/rfc/rfc8037.html
"""

from __future__ import annotations

from dataclasses import dataclass
Expand All @@ -22,7 +23,8 @@ def public_bytes( # noqa: D102
self,
encoding: serialization.Encoding,
format: serialization.PublicFormat, # noqa: A002
) -> bytes: ...
) -> bytes:
...


@runtime_checkable
Expand All @@ -34,7 +36,8 @@ def private_bytes( # noqa: D102
encoding: serialization.Encoding,
format: serialization.PrivateFormat, # noqa: A002
encryption_algorithm: serialization.KeySerializationEncryption,
) -> bytes: ...
) -> bytes:
...

def public_key(self) -> PublicKeyProtocol: # noqa: D102
...
Expand Down
1 change: 1 addition & 0 deletions jwskate/jwa/signature/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""This module exposes all the Signature algorithms available from `jwskate`."""

from __future__ import annotations

from .ec import ES256, ES256K, ES384, ES512, BaseECSignatureAlg
Expand Down
1 change: 1 addition & 0 deletions jwskate/jwa/signature/ec.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""This module implement Elliptic Curve signature algorithms."""

from __future__ import annotations

from typing import SupportsBytes
Expand Down
1 change: 1 addition & 0 deletions jwskate/jwa/signature/eddsa.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""This module implements the Edwards-curve Digital Signature Algorithm (EdDSA)."""

from __future__ import annotations

from typing import SupportsBytes, Union
Expand Down
1 change: 1 addition & 0 deletions jwskate/jwa/signature/hmac.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""This module implements HMAC based signature algorithms."""

from __future__ import annotations

from typing import SupportsBytes
Expand Down
1 change: 1 addition & 0 deletions jwskate/jwa/signature/rsa.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""This module implements RSA signature algorithms."""

from __future__ import annotations

from typing import SupportsBytes
Expand Down
1 change: 1 addition & 0 deletions jwskate/jwe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
: https: //www.rfc-editor.org/rfc/rfc7516
"""

from __future__ import annotations

from .compact import InvalidJwe, JweCompact
Expand Down
Loading

0 comments on commit 6a73468

Please sign in to comment.