Skip to content

Commit 87f4ac5

Browse files
authored
Cosmetics (#18)
* remove Python<3.12 blocker, bump version to 0.9.0 * add `from __future__ import annotations` everywhere * use `pyupgrade` * update deps
1 parent 6fa76b6 commit 87f4ac5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+965
-853
lines changed

.pre-commit-config.yaml

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,15 @@ repos:
1010
rev: v4.4.0
1111
hooks:
1212
- id: trailing-whitespace
13+
args: [--markdown-linebreak-ext=md]
1314
- id: end-of-file-fixer
1415
- id: check-merge-conflict
1516
- id: check-yaml
1617
args: [--unsafe]
18+
- id: debug-statements
19+
- id: mixed-line-ending
20+
args: [--fix=lf]
21+
- id: no-commit-to-branch
1722
- repo: https://github.com/pre-commit/pygrep-hooks
1823
rev: v1.10.0
1924
hooks:
@@ -22,16 +27,21 @@ repos:
2227
- id: python-no-eval
2328
- id: python-use-type-annotations
2429
- id: text-unicode-replacement-char
30+
- repo: https://github.com/asottile/pyupgrade
31+
rev: v3.9.0
32+
hooks:
33+
- id: pyupgrade
34+
args: [--py38-plus]
2535
- repo: https://github.com/myint/docformatter
26-
rev: v1.7.2
36+
rev: v1.7.5
2737
hooks:
2838
- id: docformatter
2939
args:
3040
- --in-place
3141
- --wrap-summaries=100
3242
- --wrap-descriptions=100
3343
- repo: https://github.com/hadialqattan/pycln
34-
rev: v2.1.5
44+
rev: v2.1.6
3545
hooks:
3646
- id: pycln
3747
args: [--config=pyproject.toml]
@@ -40,7 +50,7 @@ repos:
4050
hooks:
4151
- id: isort
4252
- repo: https://github.com/psf/black
43-
rev: 23.3.0
53+
rev: 23.7.0
4454
hooks:
4555
- id: black
4656
- repo: https://github.com/pycqa/flake8
@@ -50,7 +60,7 @@ repos:
5060
additional_dependencies:
5161
- flake8-typing-imports==1.14.0
5262
- repo: https://github.com/asottile/blacken-docs
53-
rev: 1.14.0
63+
rev: 1.15.0
5464
hooks:
5565
- id: blacken-docs
5666
- repo: https://github.com/pycqa/pydocstyle
@@ -62,7 +72,7 @@ repos:
6272
args:
6373
- --add-ignore=D107
6474
- repo: https://github.com/pre-commit/mirrors-mypy
65-
rev: v1.4.0
75+
rev: v1.4.1
6676
hooks:
6777
- id: mypy
6878
args:
@@ -74,5 +84,5 @@ repos:
7484
additional_dependencies:
7585
- types-cryptography==3.3.23.2
7686
- pytest-mypy==0.10.3
77-
- binapy==0.6.0
87+
- binapy==0.7.0
7888
- freezegun==1.2.2

HISTORY.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# History
2+
3+
## 0.9.0 (2023-07-24)
4+
- Include `"typ": "JWT"` header by default when signing JWT tokens
5+
- Added `RSAJwk.from_prime_numbers()` to generate a RSA private key from 2 prime numbers
6+
- Code cleanups, packaging fixes & docs review
7+
28
## 0.8.0 (2023-06-21)
9+
310
- BREAKING CHANGE: all method parameters `jwk`, `sig_jwk`, `enc_jwk`, or `jwk_or_password`, accepting a `Jwk` instance
411
have been renamed to `key` or `sig_key`,`enc_key` or `key_or_password` respectively.
512
They now accept either a `Jwk` instance, or a dict containing a JWK, or a `cryptography` key instance directly.

README.md

Lines changed: 91 additions & 77 deletions
Large diffs are not rendered by default.

docs/usage.md

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,46 +10,53 @@ from jwskate import *
1010

1111
## Generating new keys
1212

13+
1314
| Usage | Method |
14-
|--------------------------|-----------------------------|
15+
| ------------------------ | --------------------------- |
1516
| For a specific algorithm | `Jwk.generate(alg='ES256')` |
1617
| For a specific key type | `Jwk.generate(kty='RSA')` |
1718

1819
## Loading keys
1920

20-
| From | Method |
21-
|--------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
22-
| A JWK in Python dict | `jwk = Jwk({'kty': 'RSA', 'n': '...', ...})` |
23-
| A JWK in JSON formatted string | `jwk = Jwk('{"kty": "RSA", "n": "...", ...}')` |
24-
| A `cryptography` key | `jwk = Jwk(cryptography_key, password='mypassword')` |
25-
| A PEM formatted string | `jwk = Jwk.from_pem(`<br/>`'''-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp0VYc2zc/6yNzQUSFprv`<br/>`... 3QIDAQAB`<br/>`-----END PUBLIC KEY----- '''`<br/>`)` |
21+
22+
| From | Method |
23+
|-----------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
24+
| A JWK in Python dict | `jwk = Jwk({'kty': 'RSA', 'n': '...', ...})` |
25+
| A JWK in JSON formatted string | `jwk = Jwk('{"kty": "RSA", "n": "...", ...}')` |
26+
| A`cryptography` key | `jwk = Jwk(cryptography_key)` |
27+
| A public key in a PEM formatted string | `jwk = Jwk.from_pem(`<br/>`'''-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp0VYc2zc/6yNzQUSFprv`<br/>`... 3QIDAQAB`<br/>`-----END PUBLIC KEY----- '''`<br/>`)` |
28+
| A private key in a PEM formatted string | `jwk = Jwk.from_pem(`<br/>`'''-----BEGIN PRIVATE KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp0VYc2zc/6yNzQUSFprv`<br/>`... 3QIDAQAB`<br/>`-----END PRIVATE KEY----- ''',`<br/> `password=b'password_if_any'`<br/> `)` |
29+
| A private key in a DER binary | `jwk = Jwk.from_der(b'der_formatted_binary')` |
30+
2631

2732
## Saving keys
2833

2934
From an instance of a `Jwk` named `jwk`:
3035

31-
| To | Method | Note |
32-
|--------------------------------|-------------------------------------|----------------------------------|
33-
| A JWK in Python dict | `jwk` # Jwk is a dict subclass | you may also do `dict(jwk)` |
34-
| A JWK in JSON formatted string | `jwk.to_json()` | |
35-
| A cryptography key | `jwk.cryptography_key` | |
36-
| A PEM formatted string | `jwk.to_pem(password="mypassword")` | password is optional |
37-
| A symmetric key, as bytes | `jwk.key` | only works with kty=oct |
38-
| A JWKS | `jwk.as_jwks()` | will contain `jwk` as single key |
36+
37+
| To | Method | Note |
38+
| ------------------------------ | ----------------------------------- | ------------------------------- |
39+
| A JWK in Python dict | `jwk` # Jwk is a dict subclass | you may also do`dict(jwk)` |
40+
| A JWK in JSON formatted string | `jwk.to_json()` | |
41+
| A cryptography key | `jwk.cryptography_key` | |
42+
| A PEM formatted string | `jwk.to_pem(password="mypassword")` | password is optional |
43+
| A symmetric key, as bytes | `jwk.key` | only works with kty=oct |
44+
| A JWKS | `jwk.as_jwks()` | will contain`jwk` as single key |
3945

4046
## Inspecting keys
4147

42-
| Usage | Method | Returns |
43-
|--------------------------|------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
44-
| Check privateness | `jwk.is_private()` | `bool` |
45-
| Check symmetricness | `jwk.is_symmetric()` | `bool` |
46-
| Get Key Type | `jwk.kty` | key type, as `str` |
47-
| Get Alg (if present) | `jwk.alg` | intended algorithm identifier, as `str` |
48-
| Get Use | `jwk.use` | intended key use, if present, or deduced from `alg` |
49-
| Get Key Ops | `jwk.key_ops` | intended key operations, if present,<br>or deduced from `use`, `kty`, privateness and symmetricness |
50-
| Get attributes | `jwk['attribute']`<br>`jwk.attribute` | attribute value |
51-
| Get thumbprint | `jwk.thumbprint()`<br>`jwk.thumbprint_uri()` | Computed thumbprint or thumbprint URI |
52-
| Get supported algorithms | `jwk.supported_signing_algorithms()`<br>`jwk.supported_key_management_algorithms()`<br>`jwk.supported_encryption_algorithms()` | List of supported algorithms identifiers, as `str`. |
48+
49+
| Usage | Method | Returns |
50+
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- |
51+
| Check privateness | `jwk.is_private()` | `bool` |
52+
| Check symmetricness | `jwk.is_symmetric()` | `bool` |
53+
| Get Key Type | `jwk.kty` | key type, as`str` |
54+
| Get Alg (if present) | `jwk.alg` | intended algorithm identifier, as`str` |
55+
| Get Use | `jwk.use` | intended key use, if present, or deduced from`alg` |
56+
| Get Key Ops | `jwk.key_ops` | intended key operations, if present,<br>or deduced from `use`, `kty`, privateness and symmetricness |
57+
| Get attributes | `jwk['attribute']`<br>`jwk.attribute` | attribute value |
58+
| Get thumbprint | `jwk.thumbprint()`<br>`jwk.thumbprint_uri()` | Computed thumbprint or thumbprint URI |
59+
| Get supported algorithms | `jwk.supported_signing_algorithms()`<br>`jwk.supported_key_management_algorithms()`<br>`jwk.supported_encryption_algorithms()` | List of supported algorithms identifiers, as`str`. |
5360

5461
# JWK
5562

@@ -125,7 +132,7 @@ daBAqhoDEr4SoKju8pagw6lqm65XeARyWkxqFqAZbb2K3bWY3x9qZT6oubLrCDGD
125132

126133
## Getting key parameters
127134

128-
Once you have a `Jwk` instance, you can get its parameters either with subscription or attribute access:
135+
Once you have a `Jwk` instance, you can access its parameters either by using subscription or attributes:
129136

130137
```python
131138
from jwskate import Jwk
@@ -145,8 +152,8 @@ assert jwk.x == "WtjnvHG9b_IKBLn4QYTHz-AdoAiO_ork5LH1BL_5tyI"
145152
assert jwk["x"] == jwk.x
146153
```
147154

148-
Those will return the exact (usually base64url-encoded) value exactly as expressed in the JWK. You can also get the
149-
real, decoded parameters with some special attributes:
155+
Those will return the exact (usually base64url-encoded) value, exactly as expressed in the JWK. You can also get the
156+
real, decoded parameters with some special attributes, which depend on the key type (thus on the `Jwk` subclass):
150157

151158
```python
152159
from jwskate import Jwk
@@ -180,8 +187,8 @@ The available special attributes vary depending on the key type.
180187

181188
`jwskate` can generate private keys from any of it supported key types. To generate a key, use `Jwk.generate()`. It just
182189
needs some hints to know what kind of key to generate, either an identifier for the algorithm that will be used with
183-
that key (`alg`), or a key type (`kty`). An `alg` is preferred, since it gives more hints, like the Elliptic Curve to
184-
use, or the key size to generate. The specified `alg` will be part of the generated key, and will avoid having to
190+
that key (`alg`), or a key type (`kty`). An `alg` is preferred, since it gives more hints to generate a key that is suitable
191+
for its purpose. Those hints include the Elliptic Curve to use, or the key size to generate. The specified `alg` will be part of the generated key, and will avoid having to
185192
specify the alg for every cryptographic operation you will perform with that key.
186193

187194
```python
@@ -201,8 +208,9 @@ oct_jwk = Jwk.generate(kty="oct")
201208

202209
# you may combine both if needed:
203210
rsa_jwk = Jwk.generate(kty="RSA", alg="RS256")
204-
# alg takes precedence if it is inconsistent with kty:
211+
# a ValueError is raised if kty and alg are inconsistent:
205212
rsa_jwk = Jwk.generate(kty="EC", alg="RS256")
213+
# ValueError: Incompatible `alg='RS256'` and `kty='EC'` parameters. `alg='RS256'` points to `kty='RSA'`.
206214
```
207215

208216
You can include additional parameters such as "use" or "key_ops", or custom parameters which will be included in the

jwskate/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
88
`jwskate` doesn't implement any actual cryptographic operation, it just
99
provides a set of convenient wrappers around the `cryptography` module.
10+
1011
"""
12+
from __future__ import annotations
1113

1214
__author__ = """Guillaume Pujol"""
1315
__email__ = "[email protected]"

jwskate/enums.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
See [IANA JOSE](https://www.iana.org/assignments/jose/jose.xhtml).
44
55
"""
6+
from __future__ import annotations
67

78

89
class SignatureAlgs:

jwskate/jwa/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
[RFC7518]: https://www.rfc-editor.org/rfc/rfc7518
88
99
"""
10+
from __future__ import annotations
1011

1112
from .base import (
1213
BaseAESEncryptionAlg,

jwskate/jwa/base.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
"""This module implement base classes for the algorithms defined in JWA."""
2-
32
from __future__ import annotations
43

54
from contextlib import contextmanager
6-
from typing import Generic, Iterator, SupportsBytes, Tuple, Type, TypeVar, Union
5+
from typing import Generic, Iterator, SupportsBytes, TypeVar
76

87
import cryptography.exceptions
98
from binapy import BinaPy
@@ -88,6 +87,7 @@ def supports_key(cls, key: bytes) -> bool:
8887
8988
Returns:
9089
`True` if the key is suitable for this alg class, `False` otherwise
90+
9191
"""
9292
try:
9393
cls.check_key(key)
@@ -111,15 +111,15 @@ class BaseAsymmetricAlg(Generic[Kpriv, Kpub], BaseAlg):
111111
112112
"""
113113

114-
private_key_class: Union[Type[Kpriv], Tuple[Type[Kpriv], ...]]
115-
public_key_class: Union[Type[Kpub], Tuple[Type[Kpub], ...]]
114+
private_key_class: type[Kpriv] | tuple[type[Kpriv], ...]
115+
public_key_class: type[Kpub] | tuple[type[Kpub], ...]
116116

117-
def __init__(self, key: Union[Kpriv, Kpub]):
117+
def __init__(self, key: Kpriv | Kpub):
118118
self.check_key(key)
119119
self.key = key
120120

121121
@classmethod
122-
def check_key(cls, key: Union[Kpriv, Kpub]) -> None:
122+
def check_key(cls, key: Kpriv | Kpub) -> None:
123123
"""Check that a given key is suitable for this alg class.
124124
125125
This must be implemented by subclasses as required.
@@ -183,7 +183,7 @@ class BaseSignatureAlg(BaseAlg):
183183
use = "sig"
184184
hashing_alg: hashes.HashAlgorithm
185185

186-
def sign(self, data: Union[bytes, SupportsBytes]) -> BinaPy:
186+
def sign(self, data: bytes | SupportsBytes) -> BinaPy:
187187
"""Sign arbitrary data, return the signature.
188188
189189
Args:
@@ -196,7 +196,7 @@ def sign(self, data: Union[bytes, SupportsBytes]) -> BinaPy:
196196
raise NotImplementedError
197197

198198
def verify(
199-
self, data: Union[bytes, SupportsBytes], signature: Union[bytes, SupportsBytes]
199+
self, data: bytes | SupportsBytes, signature: bytes | SupportsBytes
200200
) -> bool:
201201
"""Verify a signature against some data.
202202
@@ -258,11 +258,11 @@ def generate_iv(cls) -> BinaPy:
258258

259259
def encrypt(
260260
self,
261-
plaintext: Union[bytes, SupportsBytes],
261+
plaintext: bytes | SupportsBytes,
262262
*,
263-
iv: Union[bytes, SupportsBytes],
264-
aad: Union[bytes, SupportsBytes, None] = None,
265-
) -> Tuple[BinaPy, BinaPy]:
263+
iv: bytes | SupportsBytes,
264+
aad: bytes | SupportsBytes | None = None,
265+
) -> tuple[BinaPy, BinaPy]:
266266
"""Encrypt arbitrary data, with optional Authenticated Encryption.
267267
268268
This needs as parameters:
@@ -286,11 +286,11 @@ def encrypt(
286286

287287
def decrypt(
288288
self,
289-
ciphertext: Union[bytes, SupportsBytes],
289+
ciphertext: bytes | SupportsBytes,
290290
*,
291-
iv: Union[bytes, SupportsBytes],
292-
auth_tag: Union[bytes, SupportsBytes],
293-
aad: Union[bytes, SupportsBytes, None] = None,
291+
iv: bytes | SupportsBytes,
292+
auth_tag: bytes | SupportsBytes,
293+
aad: bytes | SupportsBytes | None = None,
294294
) -> BinaPy:
295295
"""Decrypt and verify a ciphertext with Authenticated Encryption.
296296

0 commit comments

Comments
 (0)