From 87f4ac539b78032d81507bfcc91a6a7745c47849 Mon Sep 17 00:00:00 2001 From: Guillaume Pujol Date: Mon, 24 Jul 2023 16:08:53 +0200 Subject: [PATCH] Cosmetics (#18) * remove Python<3.12 blocker, bump version to 0.9.0 * add `from __future__ import annotations` everywhere * use `pyupgrade` * update deps --- .pre-commit-config.yaml | 22 +- HISTORY.md | 7 + README.md | 168 ++++---- docs/usage.md | 72 ++-- jwskate/__init__.py | 2 + jwskate/enums.py | 1 + jwskate/jwa/__init__.py | 1 + jwskate/jwa/base.py | 32 +- jwskate/jwa/ec.py | 17 +- jwskate/jwa/encryption/__init__.py | 1 + jwskate/jwa/encryption/aescbchmac.py | 27 +- jwskate/jwa/encryption/aesgcm.py | 19 +- jwskate/jwa/key_mgmt/__init__.py | 1 + jwskate/jwa/key_mgmt/aesgcmkw.py | 13 +- jwskate/jwa/key_mgmt/aeskw.py | 6 +- jwskate/jwa/key_mgmt/dir.py | 5 +- jwskate/jwa/key_mgmt/ecdh.py | 59 ++- jwskate/jwa/key_mgmt/pbes2.py | 7 +- jwskate/jwa/key_mgmt/rsa.py | 5 +- jwskate/jwa/okp.py | 16 +- jwskate/jwa/signature/__init__.py | 1 + jwskate/jwa/signature/ec.py | 10 +- jwskate/jwa/signature/eddsa.py | 5 +- jwskate/jwa/signature/hmac.py | 9 +- jwskate/jwa/signature/rsa.py | 8 +- jwskate/jwe/__init__.py | 1 + jwskate/jwe/compact.py | 54 +-- jwskate/jwk/__init__.py | 1 + jwskate/jwk/alg.py | 21 +- jwskate/jwk/base.py | 153 ++++---- jwskate/jwk/ec.py | 19 +- jwskate/jwk/jwks.py | 29 +- jwskate/jwk/oct.py | 34 +- jwskate/jwk/okp.py | 15 +- jwskate/jwk/rsa.py | 24 +- jwskate/jws/__init__.py | 1 + jwskate/jws/compact.py | 23 +- jwskate/jws/json.py | 71 ++-- jwskate/jws/signature.py | 27 +- jwskate/jwt/__init__.py | 1 + jwskate/jwt/base.py | 62 +-- jwskate/jwt/signed.py | 47 ++- jwskate/jwt/signer.py | 24 +- jwskate/jwt/verifier.py | 18 +- jwskate/token.py | 10 +- mkdocs.yml | 1 - poetry.lock | 552 +++++++++++++-------------- pyproject.toml | 25 +- tests/__init__.py | 1 + tests/conftest.py | 1 + tests/test_jwa/test_base.py | 4 +- tests/test_jwa/test_encryption.py | 1 + tests/test_jwa/test_examples.py | 2 + tests/test_jwa/test_key_mgmt.py | 12 +- tests/test_jwa/test_signature.py | 1 + tests/test_jwe.py | 19 +- tests/test_jwk/test_alg.py | 2 + tests/test_jwk/test_ec.py | 2 + tests/test_jwk/test_generate.py | 5 +- tests/test_jwk/test_jwk.py | 7 +- tests/test_jwk/test_jwks.py | 6 +- tests/test_jwk/test_okp.py | 8 +- tests/test_jwk/test_rsa.py | 2 + tests/test_jwk/test_symmetric.py | 2 + tests/test_jws.py | 5 + tests/test_jwt.py | 11 +- 66 files changed, 965 insertions(+), 853 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b56b9f8..7348dde 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,10 +10,15 @@ repos: rev: v4.4.0 hooks: - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] - id: end-of-file-fixer - id: check-merge-conflict - id: check-yaml args: [--unsafe] + - id: debug-statements + - id: mixed-line-ending + args: [--fix=lf] + - id: no-commit-to-branch - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.10.0 hooks: @@ -22,8 +27,13 @@ repos: - id: python-no-eval - id: python-use-type-annotations - id: text-unicode-replacement-char +- repo: https://github.com/asottile/pyupgrade + rev: v3.9.0 + hooks: + - id: pyupgrade + args: [--py38-plus] - repo: https://github.com/myint/docformatter - rev: v1.7.2 + rev: v1.7.5 hooks: - id: docformatter args: @@ -31,7 +41,7 @@ repos: - --wrap-summaries=100 - --wrap-descriptions=100 - repo: https://github.com/hadialqattan/pycln - rev: v2.1.5 + rev: v2.1.6 hooks: - id: pycln args: [--config=pyproject.toml] @@ -40,7 +50,7 @@ repos: hooks: - id: isort - repo: https://github.com/psf/black - rev: 23.3.0 + rev: 23.7.0 hooks: - id: black - repo: https://github.com/pycqa/flake8 @@ -50,7 +60,7 @@ repos: additional_dependencies: - flake8-typing-imports==1.14.0 - repo: https://github.com/asottile/blacken-docs - rev: 1.14.0 + rev: 1.15.0 hooks: - id: blacken-docs - repo: https://github.com/pycqa/pydocstyle @@ -62,7 +72,7 @@ repos: args: - --add-ignore=D107 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.4.0 + rev: v1.4.1 hooks: - id: mypy args: @@ -74,5 +84,5 @@ repos: additional_dependencies: - types-cryptography==3.3.23.2 - pytest-mypy==0.10.3 - - binapy==0.6.0 + - binapy==0.7.0 - freezegun==1.2.2 diff --git a/HISTORY.md b/HISTORY.md index 574f49b..45c256c 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,12 @@ # History + +## 0.9.0 (2023-07-24) +- Include `"typ": "JWT"` header by default when signing JWT tokens +- Added `RSAJwk.from_prime_numbers()` to generate a RSA private key from 2 prime numbers +- Code cleanups, packaging fixes & docs review + ## 0.8.0 (2023-06-21) + - BREAKING CHANGE: all method parameters `jwk`, `sig_jwk`, `enc_jwk`, or `jwk_or_password`, accepting a `Jwk` instance have been renamed to `key` or `sig_key`,`enc_key` or `key_or_password` respectively. They now accept either a `Jwk` instance, or a dict containing a JWK, or a `cryptography` key instance directly. diff --git a/README.md b/README.md index 72cdf54..72dc3c5 100644 --- a/README.md +++ b/README.md @@ -6,24 +6,25 @@ A Pythonic implementation of the JOSE set of IETF specifications: [Json Web Signature][rfc7515], [Keys][rfc7517], [Algorithms][rfc7518], [Tokens][rfc7519] and [Encryption][rfc7516] (RFC7515 to 7519), and their extensions [ECDH Signatures][rfc8037] (RFC8037), [JWK Thumbprints][rfc7638] (RFC7638), and [JWK Thumbprint URI][rfc9278] (RFC9278), -and with respect to [JWT Best Current Practices][rfc8725]. +and with respects to [JWT Best Current Practices][rfc8725] (RFC8725). - Free software: MIT - Documentation: [https://guillp.github.io/jwskate/](https://guillp.github.io/jwskate/) -A quick usage example, generating an RSA private key, signing some data, then validating that signature: +Here is a quick usage example: generating a private RSA key, signing some data, then validating that signature with the matching public key: ```python 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_private_jwk = Jwk.generate(alg="RS256", key_size=2048) -data = b"Signing is easy!" -signature = rsa_private_jwk.sign(data) +data = b"Signing is easy!" # we will sign this +signature = rsa_private_jwk.sign(data) # done! -# extract the public key, and verify the signature with it +# 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) @@ -51,20 +52,34 @@ The result of this print will look like this (with the random parts abbreviated 'key_ops': ['sign']} ``` -Now let's sign a JWT containing arbitrary claims, this time using an EC key: +Now let's sign a JWT containing arbitrary claims, this time using an Elliptic Curve (`EC`) key: ```python from jwskate import Jwk, Jwt # This time let's try an EC key, based on `alg` parameter, -# and let's specigy an arbitrary Key ID (kid). +# and let's specify an arbitrary Key ID (kid). +# additional args are either options (like 'key_size' above for RSA keys) +# or additional parameters to include in the JWK private_jwk = Jwk.generate(alg="ES256", kid="my_key") - -# here are claims to sign in a JWT: +# note that based only on the `alg` value, the appropriate key type and curve +# are automatically deduced and included in the JWK +print(private_jwk) +# {'kty': 'EC', 'crv': 'P-256', 'x': 'Ppe...', 'y': '9Si...', 'd': 'g09...', 'alg': 'ES256'} +assert private_jwk.kty == "EC" +assert private_jwk.crv == "P-256" +assert private_jwk.alg == "ES256" +# this is a private key and 'ES256' is a signature alg, so 'use' and 'key_ops' can also be deduced: +assert private_jwk.use == "sig" +assert private_jwk.key_ops == ("sign",) + +# here are the claims to sign in a JWT: claims = {"sub": "some_sub", "claim1": "value1"} jwt = Jwt.sign(claims, private_jwk) -# that's it! we have a signed JWT +# that's it! we have a signed JWT. +print(jwt) +# eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzb21lX3N1YiIsImNsYWltMSI6InZhbHVlMSJ9.SBQIlGlFdwoEMViWUFsBmCsXShtOq4lnp3Im5ZVh1PFCGJFdW-dTG9qJjlFSAA_BkM5PF9u38PL7Ai9cC2_DJw assert isinstance(jwt, Jwt) # Jwt are objects assert jwt.claims == claims # claims can be accessed as a dict assert jwt.headers == {"typ": "JWT", "alg": "ES256", "kid": "my_key"} # headers too @@ -72,20 +87,18 @@ assert jwt.sub == "some_sub" # individual claims can be accessed as attributes assert jwt["claim1"] == "value1" # or as dict items (with "subscription") assert jwt.alg == "ES256" # alg and kid headers are also accessible as attributes assert jwt.kid == private_jwk.kid -# notice that alg and kid are automatically set with appropriate values +# notice that alg and kid are automatically set with appropriate values taken from our private jwk assert isinstance(jwt.signature, bytes) # signature is accessible too # verifying the jwt signature is as easy as: assert jwt.verify_signature(private_jwk.public_jwk()) # since our jwk contains an 'alg' parameter (here 'ES256'), the signature is automatically verified using that alg # you could also specify an alg manually, useful for keys with no "alg" hint: assert jwt.verify_signature(private_jwk.public_jwk(), alg="ES256") - -print(jwt) -# eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Im15X2tleSJ9.eyJzdWIiOiJzb21lX3N1YiIsImNsYWltMSI6InZhbHVlMSJ9.3VVLoaMVvdsieKKXqKenR62-7FqzUx4mhHV_AXYkNaaEbuEhdI6ej7PTkxZGtdm8pIQljhajo8NOk7eA-Z1klQ -# This will output the full JWT compact representation. You can inspect it for example at +# note that jwskate will only trust the alg(s) you provide as parameter, either part of the JWK +# or with `alg` or `algs` params, and will ignore the 'alg' that is set in the JWT, for security reasons. ``` -Or 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 standardised lifetime, subject, audience and ID claims, plus arbitrary custom claims: ```python from jwskate import Jwk, JwtSigner @@ -101,7 +114,8 @@ jwt = signer.sign( print(jwt.claims) ``` -The generated JWT claims will include the standardised claims: +The generated JWT will include the standardised claims (`iss`, `aud`, `sub`, `iat`, `exp` and `jti`), +together with the `extra_claims` provided to `.sign()`: ``` {'custom_claim1': 'value1', @@ -141,76 +155,76 @@ The generated JWT claims will include the standardised claims: ### Supported Signature algorithms -| Signature Alg | Description | Key Type | Reference | Note | -| ------------- | ---------------------------------------------- | -------- | ---------------------------------- | ------------------------------ | -| HS256 | HMAC using SHA-256 | oct | [RFC7518, Section 3.2] | | -| HS384 | HMAC using SHA-384 | oct | [RFC7518, Section 3.2] | | -| HS512 | HMAC using SHA-512 | oct | [RFC7518, Section 3.2] | | -| 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 | -| ES256K | ECDSA using secp256k1 curve and SHA-256 | EC | [RFC8812, Section 3.2] | | -| 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 | -| none | No digital signature or MAC performed | | [RFC7518, Section 3.6] | Not usable by mistake | +| Signature Alg | Description | Key Type | Reference | Note | +|-----------------|--------------------------------------------------|----------| ---------------------------------- | ------------------------------ | +| `HS256` | HMAC using SHA-256 | `oct` | [RFC7518, Section 3.2] | | +| `HS384` | HMAC using SHA-384 | `oct` | [RFC7518, Section 3.2] | | +| `HS512` | HMAC using SHA-512 | `oct` | [RFC7518, Section 3.2] | | +| `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 | +| `ES256K` | ECDSA using secp256k1 curve and SHA-256 | `EC` | [RFC8812, Section 3.2] | | +| `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 | +| `none` | No digital signature or MAC performed | | [RFC7518, Section 3.6] | Not usable by mistake | ### Supported Key Management algorithms -| Signature Alg | Description | Key Type | Reference | Note | -| ------------------ | ---------------------------------------------- | -------- | ---------------------------------- | ----------- | -| RSA1_5 | RSAES-PKCS1-v1_5 | RSA | [RFC7518, Section 4.2] | Unwrap Only | -| RSA-OAEP | RSAES OAEP using default parameters | RSA | [RFC7518, Section 4.3] | | -| RSA-OAEP-256 | RSAES OAEP using SHA-256 and MGF1 with SHA-256 | RSA | [RFC7518, Section 4.3] | | -| RSA-OAEP-384 | RSA-OAEP using SHA-384 and MGF1 with SHA-384 | RSA | https://www.w3.org/TR/WebCryptoAPI | | -| RSA-OAEP-512 | RSA-OAEP using SHA-512 and MGF1 with SHA-512 | RSA | https://www.w3.org/TR/WebCryptoAPI | | -| A128KW | AES Key Wrap using 128-bit key | oct | [RFC7518, Section 4.4] | | -| A192KW | AES Key Wrap using 192-bit key | oct | [RFC7518, Section 4.4] | | -| A256KW | AES Key Wrap using 256-bit key | oct | [RFC7518, Section 4.4] | | -| dir | Direct use of a shared symmetric key | oct | [RFC7518, Section 4.5] | | -| ECDH-ES | ECDH-ES using Concat KDF | EC | [RFC7518, Section 4.6] | | -| ECDH-ES+A128KW | ECDH-ES using Concat KDF and "A128KW" wrapping | EC | [RFC7518, Section 4.6] | | -| ECDH-ES+A192KW | ECDH-ES using Concat KDF and "A192KW" wrapping | EC | [RFC7518, Section 4.6] | | -| ECDH-ES+A256KW | ECDH-ES using Concat KDF and "A256KW" wrapping | EC | [RFC7518, Section 4.6] | | -| A128GCMKW | Key wrapping with AES GCM using 128-bit key | oct | [RFC7518, Section 4.7] | | -| A192GCMKW | Key wrapping with AES GCM using 192-bit key | oct | [RFC7518, Section 4.7] | | -| A256GCMKW | Key wrapping with AES GCM using 256-bit key | oct | [RFC7518, Section 4.7] | | -| PBES2-HS256+A128KW | PBES2 with HMAC SHA-256 and "A128KW" wrapping | password | [RFC7518, Section 4.8] | | -| 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] | | +| Signature Alg | Description | Key Type | Reference | Note | +| ------------------ | ---------------------------------------------- |---------------| ---------------------------------- | ----------- | +| `RSA1_5` | RSAES-PKCS1-v1_5 | `RSA` | [RFC7518, Section 4.2] | Unwrap Only | +| `RSA-OAEP` | RSAES OAEP using default parameters | `RSA` | [RFC7518, Section 4.3] | | +| `RSA-OAEP-256` | RSAES OAEP using SHA-256 and MGF1 with SHA-256 | `RSA` | [RFC7518, Section 4.3] | | +| `RSA-OAEP-384` | RSA-OAEP using SHA-384 and MGF1 with SHA-384 | `RSA` | https://www.w3.org/TR/WebCryptoAPI | | +| `RSA-OAEP-512` | RSA-OAEP using SHA-512 and MGF1 with SHA-512 | `RSA` | https://www.w3.org/TR/WebCryptoAPI | | +| `A128KW` | AES Key Wrap using 128-bit key | `oct` | [RFC7518, Section 4.4] | | +| `A192KW` | AES Key Wrap using 192-bit key | `oct` | [RFC7518, Section 4.4] | | +| `A256KW` | AES Key Wrap using 256-bit key | `oct` | [RFC7518, Section 4.4] | | +| `A128GCMKW` | Key wrapping with AES GCM using 128-bit key | `oct` | [RFC7518, Section 4.7] | | +| `A192GCMKW` | Key wrapping with AES GCM using 192-bit key | `oct` | [RFC7518, Section 4.7] | | +| `A256GCMKW` | Key wrapping with AES GCM using 256-bit key | `oct` | [RFC7518, Section 4.7] | | +| `dir` | Direct use of a shared symmetric key | `oct` | [RFC7518, Section 4.5] | | +| `ECDH-ES` | ECDH-ES using Concat KDF | `EC` | [RFC7518, Section 4.6] | | +| `ECDH-ES+A128KW` | ECDH-ES using Concat KDF and "A128KW" wrapping | `EC` | [RFC7518, Section 4.6] | | +| `ECDH-ES+A192KW` | ECDH-ES using Concat KDF and "A192KW" wrapping | `EC` | [RFC7518, Section 4.6] | | +| `ECDH-ES+A256KW` | ECDH-ES using Concat KDF and "A256KW" wrapping | `EC` | [RFC7518, Section 4.6] | | +| `PBES2-HS256+A128KW` | PBES2 with HMAC SHA-256 and "A128KW" wrapping | `password` | [RFC7518, Section 4.8] | | +| `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] | +| `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 -| Curve | Description | Key Type | Usage | Reference | -| --------- | ------------------------------------- | -------- | --------------------- | -------------------------- | -| 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] | -| 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] | +| Curve | Description | Key Type | Usage | Reference | +|-------------|---------------------------------------|----------| --------------------- | -------------------------- | +| `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] | +| `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 ? @@ -223,7 +237,7 @@ have been dissatisfied by all of them so far, so I decided to come up with my ow - [AuthLib](https://docs.authlib.org/en/latest/jose/) Not to say that those are _bad_ libs (I actually use `jwcrypto` myself for `jwskate` unit tests), but they either don't -support some important features, lack documentation, or generally have APIs that don't feel easy-enough, Pythonic-enough +support some important features, lack documentation, or more generally have APIs that don't feel easy-enough, Pythonic-enough to use. ## Design @@ -231,9 +245,9 @@ to use. ### JWK are dicts JWK are specified as JSON objects, which are parsed as `dict` in Python. The `Jwk` class in `jwskate` is actually a -`dict` subclass, so you can use it exactly like you would use a dict: you can access its members, dump it back as JSON, -etc. The same is true for Signed or Encrypted Json Web tokens in JSON format. You cannot change the key cryptographic -material, however, since that would lead to unusable keys. +`dict` subclass, so you can use it exactly like you would use a `dict`: you can access its members, dump it back as JSON, +etc. The same is true for Signed or Encrypted Json Web tokens in JSON format. However, you cannot change the key cryptographic +materials, since that would lead to unusable keys. ### JWA Wrappers diff --git a/docs/usage.md b/docs/usage.md index 98b235f..b36ecba 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -10,46 +10,53 @@ from jwskate import * ## Generating new keys + | Usage | Method | -|--------------------------|-----------------------------| +| ------------------------ | --------------------------- | | For a specific algorithm | `Jwk.generate(alg='ES256')` | | For a specific key type | `Jwk.generate(kty='RSA')` | ## Loading keys -| From | Method | -|--------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| A JWK in Python dict | `jwk = Jwk({'kty': 'RSA', 'n': '...', ...})` | -| A JWK in JSON formatted string | `jwk = Jwk('{"kty": "RSA", "n": "...", ...}')` | -| A `cryptography` key | `jwk = Jwk(cryptography_key, password='mypassword')` | -| A PEM formatted string | `jwk = Jwk.from_pem(`
`'''-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp0VYc2zc/6yNzQUSFprv`
`... 3QIDAQAB`
`-----END PUBLIC KEY----- '''`
`)` | + +| From | Method | +|-----------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| A JWK in Python dict | `jwk = Jwk({'kty': 'RSA', 'n': '...', ...})` | +| A JWK in JSON formatted string | `jwk = Jwk('{"kty": "RSA", "n": "...", ...}')` | +| A`cryptography` key | `jwk = Jwk(cryptography_key)` | +| A public key in a PEM formatted string | `jwk = Jwk.from_pem(`
`'''-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp0VYc2zc/6yNzQUSFprv`
`... 3QIDAQAB`
`-----END PUBLIC KEY----- '''`
`)` | +| A private key in a PEM formatted string | `jwk = Jwk.from_pem(`
`'''-----BEGIN PRIVATE KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp0VYc2zc/6yNzQUSFprv`
`... 3QIDAQAB`
`-----END PRIVATE KEY----- ''',`
`password=b'password_if_any'`
`)` | +| A private key in a DER binary | `jwk = Jwk.from_der(b'der_formatted_binary')` | + ## Saving keys From an instance of a `Jwk` named `jwk`: -| To | Method | Note | -|--------------------------------|-------------------------------------|----------------------------------| -| A JWK in Python dict | `jwk` # Jwk is a dict subclass | you may also do `dict(jwk)` | -| A JWK in JSON formatted string | `jwk.to_json()` | | -| A cryptography key | `jwk.cryptography_key` | | -| A PEM formatted string | `jwk.to_pem(password="mypassword")` | password is optional | -| A symmetric key, as bytes | `jwk.key` | only works with kty=oct | -| A JWKS | `jwk.as_jwks()` | will contain `jwk` as single key | + +| To | Method | Note | +| ------------------------------ | ----------------------------------- | ------------------------------- | +| A JWK in Python dict | `jwk` # Jwk is a dict subclass | you may also do`dict(jwk)` | +| A JWK in JSON formatted string | `jwk.to_json()` | | +| A cryptography key | `jwk.cryptography_key` | | +| A PEM formatted string | `jwk.to_pem(password="mypassword")` | password is optional | +| A symmetric key, as bytes | `jwk.key` | only works with kty=oct | +| A JWKS | `jwk.as_jwks()` | will contain`jwk` as single key | ## Inspecting keys -| Usage | Method | Returns | -|--------------------------|------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------| -| Check privateness | `jwk.is_private()` | `bool` | -| Check symmetricness | `jwk.is_symmetric()` | `bool` | -| Get Key Type | `jwk.kty` | key type, as `str` | -| Get Alg (if present) | `jwk.alg` | intended algorithm identifier, as `str` | -| Get Use | `jwk.use` | intended key use, if present, or deduced from `alg` | -| Get Key Ops | `jwk.key_ops` | intended key operations, if present,
or deduced from `use`, `kty`, privateness and symmetricness | -| Get attributes | `jwk['attribute']`
`jwk.attribute` | attribute value | -| Get thumbprint | `jwk.thumbprint()`
`jwk.thumbprint_uri()` | Computed thumbprint or thumbprint URI | -| Get supported algorithms | `jwk.supported_signing_algorithms()`
`jwk.supported_key_management_algorithms()`
`jwk.supported_encryption_algorithms()` | List of supported algorithms identifiers, as `str`. | + +| Usage | Method | Returns | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- | +| Check privateness | `jwk.is_private()` | `bool` | +| Check symmetricness | `jwk.is_symmetric()` | `bool` | +| Get Key Type | `jwk.kty` | key type, as`str` | +| Get Alg (if present) | `jwk.alg` | intended algorithm identifier, as`str` | +| Get Use | `jwk.use` | intended key use, if present, or deduced from`alg` | +| Get Key Ops | `jwk.key_ops` | intended key operations, if present,
or deduced from `use`, `kty`, privateness and symmetricness | +| Get attributes | `jwk['attribute']`
`jwk.attribute` | attribute value | +| Get thumbprint | `jwk.thumbprint()`
`jwk.thumbprint_uri()` | Computed thumbprint or thumbprint URI | +| Get supported algorithms | `jwk.supported_signing_algorithms()`
`jwk.supported_key_management_algorithms()`
`jwk.supported_encryption_algorithms()` | List of supported algorithms identifiers, as`str`. | # JWK @@ -125,7 +132,7 @@ daBAqhoDEr4SoKju8pagw6lqm65XeARyWkxqFqAZbb2K3bWY3x9qZT6oubLrCDGD ## Getting key parameters -Once you have a `Jwk` instance, you can get its parameters either with subscription or attribute access: +Once you have a `Jwk` instance, you can access its parameters either by using subscription or attributes: ```python from jwskate import Jwk @@ -145,8 +152,8 @@ assert jwk.x == "WtjnvHG9b_IKBLn4QYTHz-AdoAiO_ork5LH1BL_5tyI" assert jwk["x"] == jwk.x ``` -Those will return the exact (usually base64url-encoded) value exactly as expressed in the JWK. You can also get the -real, decoded parameters with some special attributes: +Those will return the exact (usually base64url-encoded) value, exactly as expressed in the JWK. You can also get the +real, decoded parameters with some special attributes, which depend on the key type (thus on the `Jwk` subclass): ```python from jwskate import Jwk @@ -180,8 +187,8 @@ The available special attributes vary depending on the key type. `jwskate` can generate private keys from any of it supported key types. To generate a key, use `Jwk.generate()`. It just needs some hints to know what kind of key to generate, either an identifier for the algorithm that will be used with -that key (`alg`), or a key type (`kty`). An `alg` is preferred, since it gives more hints, like 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 +that key (`alg`), or a key type (`kty`). An `alg` is preferred, since it gives more hints to generate a key that is suitable +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 specify the alg for every cryptographic operation you will perform with that key. ```python @@ -201,8 +208,9 @@ oct_jwk = Jwk.generate(kty="oct") # you may combine both if needed: rsa_jwk = Jwk.generate(kty="RSA", alg="RS256") -# alg takes precedence if it is inconsistent with kty: +# a ValueError is raised if kty and alg are inconsistent: rsa_jwk = Jwk.generate(kty="EC", alg="RS256") +# ValueError: Incompatible `alg='RS256'` and `kty='EC'` parameters. `alg='RS256'` points to `kty='RSA'`. ``` You can include additional parameters such as "use" or "key_ops", or custom parameters which will be included in the diff --git a/jwskate/__init__.py b/jwskate/__init__.py index 01c5d32..f4dca40 100644 --- a/jwskate/__init__.py +++ b/jwskate/__init__.py @@ -7,7 +7,9 @@ `jwskate` doesn't implement any actual cryptographic operation, it just provides a set of convenient wrappers around the `cryptography` module. + """ +from __future__ import annotations __author__ = """Guillaume Pujol""" __email__ = "guill.p.linux@gmail.com" diff --git a/jwskate/enums.py b/jwskate/enums.py index df72e7b..de99b11 100644 --- a/jwskate/enums.py +++ b/jwskate/enums.py @@ -3,6 +3,7 @@ See [IANA JOSE](https://www.iana.org/assignments/jose/jose.xhtml). """ +from __future__ import annotations class SignatureAlgs: diff --git a/jwskate/jwa/__init__.py b/jwskate/jwa/__init__.py index 503bb86..6da0b33 100644 --- a/jwskate/jwa/__init__.py +++ b/jwskate/jwa/__init__.py @@ -7,6 +7,7 @@ [RFC7518]: https://www.rfc-editor.org/rfc/rfc7518 """ +from __future__ import annotations from .base import ( BaseAESEncryptionAlg, diff --git a/jwskate/jwa/base.py b/jwskate/jwa/base.py index eb87742..967b372 100644 --- a/jwskate/jwa/base.py +++ b/jwskate/jwa/base.py @@ -1,9 +1,8 @@ """This module implement base classes for the algorithms defined in JWA.""" - from __future__ import annotations from contextlib import contextmanager -from typing import Generic, Iterator, SupportsBytes, Tuple, Type, TypeVar, Union +from typing import Generic, Iterator, SupportsBytes, TypeVar import cryptography.exceptions from binapy import BinaPy @@ -88,6 +87,7 @@ def supports_key(cls, key: bytes) -> bool: Returns: `True` if the key is suitable for this alg class, `False` otherwise + """ try: cls.check_key(key) @@ -111,15 +111,15 @@ class BaseAsymmetricAlg(Generic[Kpriv, Kpub], BaseAlg): """ - private_key_class: Union[Type[Kpriv], Tuple[Type[Kpriv], ...]] - public_key_class: Union[Type[Kpub], Tuple[Type[Kpub], ...]] + private_key_class: type[Kpriv] | tuple[type[Kpriv], ...] + public_key_class: type[Kpub] | tuple[type[Kpub], ...] - def __init__(self, key: Union[Kpriv, Kpub]): + def __init__(self, key: Kpriv | Kpub): self.check_key(key) self.key = key @classmethod - def check_key(cls, key: Union[Kpriv, Kpub]) -> None: + def check_key(cls, key: Kpriv | Kpub) -> None: """Check that a given key is suitable for this alg class. This must be implemented by subclasses as required. @@ -183,7 +183,7 @@ class BaseSignatureAlg(BaseAlg): use = "sig" hashing_alg: hashes.HashAlgorithm - def sign(self, data: Union[bytes, SupportsBytes]) -> BinaPy: + def sign(self, data: bytes | SupportsBytes) -> BinaPy: """Sign arbitrary data, return the signature. Args: @@ -196,7 +196,7 @@ def sign(self, data: Union[bytes, SupportsBytes]) -> BinaPy: raise NotImplementedError def verify( - self, data: Union[bytes, SupportsBytes], signature: Union[bytes, SupportsBytes] + self, data: bytes | SupportsBytes, signature: bytes | SupportsBytes ) -> bool: """Verify a signature against some data. @@ -258,11 +258,11 @@ def generate_iv(cls) -> BinaPy: def encrypt( self, - plaintext: Union[bytes, SupportsBytes], + plaintext: bytes | SupportsBytes, *, - iv: Union[bytes, SupportsBytes], - aad: Union[bytes, SupportsBytes, None] = None, - ) -> Tuple[BinaPy, BinaPy]: + iv: bytes | SupportsBytes, + aad: bytes | SupportsBytes | None = None, + ) -> tuple[BinaPy, BinaPy]: """Encrypt arbitrary data, with optional Authenticated Encryption. This needs as parameters: @@ -286,11 +286,11 @@ def encrypt( def decrypt( self, - ciphertext: Union[bytes, SupportsBytes], + ciphertext: bytes | SupportsBytes, *, - iv: Union[bytes, SupportsBytes], - auth_tag: Union[bytes, SupportsBytes], - aad: Union[bytes, SupportsBytes, None] = None, + iv: bytes | SupportsBytes, + auth_tag: bytes | SupportsBytes, + aad: bytes | SupportsBytes | None = None, ) -> BinaPy: """Decrypt and verify a ciphertext with Authenticated Encryption. diff --git a/jwskate/jwa/ec.py b/jwskate/jwa/ec.py index 8de0ff4..3e20dad 100644 --- a/jwskate/jwa/ec.py +++ b/jwskate/jwa/ec.py @@ -1,9 +1,8 @@ """This module contains classes that describe Elliptic Curves as described in RFC7518.""" - from __future__ import annotations from dataclasses import dataclass -from typing import Any, ClassVar, Dict, Tuple, Union +from typing import Any, ClassVar from binapy import BinaPy from cryptography.hazmat.primitives.asymmetric import ec @@ -32,14 +31,14 @@ class EllipticCurve: coordinate_size: int """Coordinate size, in bytes.""" - instances: ClassVar[Dict[str, EllipticCurve]] = {} + instances: ClassVar[dict[str, EllipticCurve]] = {} """Registry of subclasses, in a {name: instance} mapping.""" def __post_init__(self) -> None: """Automatically register subclasses in the instance registry.""" self.instances[self.name] = self - def generate(self) -> Tuple[int, int, int]: + def generate(self) -> tuple[int, int, int]: """Generate a new EC key on this curve. Returns: @@ -55,8 +54,8 @@ def generate(self) -> Tuple[int, int, int]: @classmethod def get_curve( - cls, key: Union[ec.EllipticCurvePublicKey, ec.EllipticCurvePrivateKey] - ) -> "EllipticCurve": + cls, key: ec.EllipticCurvePublicKey | ec.EllipticCurvePrivateKey + ) -> EllipticCurve: """Get the appropriate `EllipticCurve` instance for a given key. The provided key must be an `EllipticCurvePublicKey` or `EllipticCurvePrivateKey` @@ -70,6 +69,7 @@ def get_curve( Raises: NotImplementedError: if the curve is not supported + """ for c in cls.instances.values(): if c.cryptography_curve.name == key.curve.name: @@ -78,8 +78,8 @@ def get_curve( @classmethod def get_jwk_parameters( - cls, key: Union[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey] - ) -> Dict[str, Any]: + cls, key: ec.EllipticCurvePrivateKey | ec.EllipticCurvePublicKey + ) -> dict[str, Any]: """Extract all private and public parameters from the given `cryptography` key. Key must be an instance of `EllipticCurvePrivateKey` or `EllipticCurvePublicKey`. @@ -92,6 +92,7 @@ def get_jwk_parameters( Raises: TypeError: if the provided key is not an EllipticCurvePrivateKey or EllipticCurvePublicKey + """ public_numbers: ec.EllipticCurvePublicNumbers if isinstance(key, ec.EllipticCurvePrivateKey): diff --git a/jwskate/jwa/encryption/__init__.py b/jwskate/jwa/encryption/__init__.py index 6784ebf..1b857e6 100644 --- a/jwskate/jwa/encryption/__init__.py +++ b/jwskate/jwa/encryption/__init__.py @@ -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 from .aesgcm import A128GCM, A192GCM, A256GCM diff --git a/jwskate/jwa/encryption/aescbchmac.py b/jwskate/jwa/encryption/aescbchmac.py index c6810a1..553c0af 100644 --- a/jwskate/jwa/encryption/aescbchmac.py +++ b/jwskate/jwa/encryption/aescbchmac.py @@ -1,6 +1,7 @@ """This module implements AES-CBC with HMAC-SHA based Encryption algorithms.""" +from __future__ import annotations -from typing import SupportsBytes, Tuple, Union +from typing import SupportsBytes from binapy import BinaPy from cryptography.hazmat.primitives import ciphers, constant_time, hashes, hmac, padding @@ -38,10 +39,10 @@ def __init__(self, key: bytes) -> None: def mac( self, - ciphertext: Union[bytes, SupportsBytes], + ciphertext: bytes | SupportsBytes, *, - iv: Union[bytes, SupportsBytes], - aad: Union[bytes, SupportsBytes, None] = None, + iv: bytes | SupportsBytes, + aad: bytes | SupportsBytes | None = None, ) -> BinaPy: """Produce a Message Authentication Code for the given `ciphertext`, `iv` and `aad`. @@ -52,6 +53,7 @@ def mac( Returns: the resulting MAC. + """ if not isinstance(ciphertext, bytes): ciphertext = bytes(ciphertext) @@ -73,11 +75,11 @@ def mac( def encrypt( self, - plaintext: Union[bytes, SupportsBytes], + plaintext: bytes | SupportsBytes, *, - iv: Union[bytes, SupportsBytes], - aad: Union[bytes, SupportsBytes, None] = None, - ) -> Tuple[BinaPy, BinaPy]: + iv: bytes | SupportsBytes, + aad: bytes | SupportsBytes | None = None, + ) -> tuple[BinaPy, BinaPy]: """Encrypt and MAC the given `plaintext`. This requires a given Initialization Vector (`iv`). @@ -90,6 +92,7 @@ def encrypt( Returns: a tuple (encrypted_data, authentication_tag) + """ if not isinstance(plaintext, bytes): plaintext = bytes(plaintext) @@ -112,11 +115,11 @@ def encrypt( def decrypt( self, - ciphertext: Union[bytes, SupportsBytes], + ciphertext: bytes | SupportsBytes, *, - iv: Union[bytes, SupportsBytes], - auth_tag: Union[bytes, SupportsBytes], - aad: Union[bytes, SupportsBytes, None] = None, + iv: bytes | SupportsBytes, + auth_tag: bytes | SupportsBytes, + aad: bytes | SupportsBytes | None = None, ) -> BinaPy: """Decrypt and authenticate a given ciphertext. diff --git a/jwskate/jwa/encryption/aesgcm.py b/jwskate/jwa/encryption/aesgcm.py index cb6e9e9..6c73d01 100644 --- a/jwskate/jwa/encryption/aesgcm.py +++ b/jwskate/jwa/encryption/aesgcm.py @@ -1,6 +1,7 @@ """This module implements AES-GCM based encryption algorithms.""" +from __future__ import annotations -from typing import SupportsBytes, Tuple, Union +from typing import SupportsBytes import cryptography.exceptions from binapy import BinaPy @@ -17,11 +18,11 @@ class BaseAESGCM(BaseAESEncryptionAlg): def encrypt( self, - plaintext: Union[bytes, SupportsBytes], + plaintext: bytes | SupportsBytes, *, - iv: Union[bytes, SupportsBytes], - aad: Union[bytes, SupportsBytes, None] = None, - ) -> Tuple[BinaPy, BinaPy]: + iv: bytes | SupportsBytes, + aad: bytes | SupportsBytes | None = None, + ) -> tuple[BinaPy, BinaPy]: """Encrypt a plaintext, with the given IV and Additional Authenticated Data.". Args: @@ -52,11 +53,11 @@ def encrypt( def decrypt( self, - ciphertext: Union[bytes, SupportsBytes], + ciphertext: bytes | SupportsBytes, *, - iv: Union[bytes, SupportsBytes], - auth_tag: Union[bytes, SupportsBytes], - aad: Union[bytes, SupportsBytes, None] = None, + iv: bytes | SupportsBytes, + auth_tag: bytes | SupportsBytes, + aad: bytes | SupportsBytes | None = None, ) -> BinaPy: """Decrypt a ciphertext. diff --git a/jwskate/jwa/key_mgmt/__init__.py b/jwskate/jwa/key_mgmt/__init__.py index bc848d1..145b939 100644 --- a/jwskate/jwa/key_mgmt/__init__.py +++ b/jwskate/jwa/key_mgmt/__init__.py @@ -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 from .aeskw import A128KW, A192KW, A256KW, BaseAesKeyWrap diff --git a/jwskate/jwa/key_mgmt/aesgcmkw.py b/jwskate/jwa/key_mgmt/aesgcmkw.py index 35ea82b..01721d7 100644 --- a/jwskate/jwa/key_mgmt/aesgcmkw.py +++ b/jwskate/jwa/key_mgmt/aesgcmkw.py @@ -1,6 +1,7 @@ """This module implements AES-GCM based Key Management algorithms.""" +from __future__ import annotations -from typing import SupportsBytes, Tuple, Union +from typing import SupportsBytes from binapy import BinaPy @@ -21,8 +22,8 @@ class BaseAesGcmKeyWrap(BaseAESGCM, BaseKeyManagementAlg): """Initialisation Vector size, in bits.""" def wrap_key( - self, plainkey: Union[bytes, SupportsBytes], *, iv: Union[bytes, SupportsBytes] - ) -> Tuple[BinaPy, BinaPy]: + self, plainkey: bytes | SupportsBytes, *, iv: bytes | SupportsBytes + ) -> tuple[BinaPy, BinaPy]: """Wrap a symmetric key, which is typically used as Content Encryption Key (CEK). This method is used by the sender of the encrypted message. @@ -42,10 +43,10 @@ def wrap_key( def unwrap_key( self, - cipherkey: Union[bytes, SupportsBytes], + cipherkey: bytes | SupportsBytes, *, - tag: Union[bytes, SupportsBytes], - iv: Union[bytes, SupportsBytes] + tag: bytes | SupportsBytes, + iv: bytes | SupportsBytes, ) -> BinaPy: """Unwrap a symmetric key. diff --git a/jwskate/jwa/key_mgmt/aeskw.py b/jwskate/jwa/key_mgmt/aeskw.py index e8d1547..631a5e7 100644 --- a/jwskate/jwa/key_mgmt/aeskw.py +++ b/jwskate/jwa/key_mgmt/aeskw.py @@ -1,5 +1,7 @@ """This module implements AES based Key Management algorithms.""" -from typing import SupportsBytes, Union +from __future__ import annotations + +from typing import SupportsBytes from binapy import BinaPy from cryptography.hazmat.primitives import keywrap @@ -48,7 +50,7 @@ def wrap_key(self, plainkey: bytes) -> BinaPy: """ return BinaPy(keywrap.aes_key_wrap(self.key, plainkey)) - def unwrap_key(self, cipherkey: Union[bytes, SupportsBytes]) -> BinaPy: + def unwrap_key(self, cipherkey: bytes | SupportsBytes) -> BinaPy: """Unwrap a key. Args: diff --git a/jwskate/jwa/key_mgmt/dir.py b/jwskate/jwa/key_mgmt/dir.py index 632e23e..4eb52f5 100644 --- a/jwskate/jwa/key_mgmt/dir.py +++ b/jwskate/jwa/key_mgmt/dir.py @@ -1,6 +1,5 @@ """This module implements direct use of a shared symmetric key as Key Management algorithm.""" - -from typing import Type +from __future__ import annotations from binapy import BinaPy @@ -13,7 +12,7 @@ class DirectKeyUse(BaseKeyManagementAlg, BaseSymmetricAlg): name = "dir" description = __doc__ - def direct_key(self, aesalg: Type[BaseSymmetricAlg]) -> BinaPy: + def direct_key(self, aesalg: type[BaseSymmetricAlg]) -> BinaPy: """Check that the current key is appropriate for a given alg and return that same key. Args: diff --git a/jwskate/jwa/key_mgmt/ecdh.py b/jwskate/jwa/key_mgmt/ecdh.py index a2c92f9..7a1184d 100644 --- a/jwskate/jwa/key_mgmt/ecdh.py +++ b/jwskate/jwa/key_mgmt/ecdh.py @@ -1,6 +1,7 @@ """This module implements Elliptic Curve Diffie-Hellman based Key Management algorithms.""" +from __future__ import annotations -from typing import Any, SupportsBytes, Type, Union +from typing import Any, SupportsBytes, Union from binapy import BinaPy from cryptography.hazmat.primitives import hashes @@ -63,12 +64,12 @@ def otherinfo(cls, alg: str, apu: bytes, apv: bytes, key_size: int) -> BinaPy: @classmethod def ecdh( cls, - private_key: Union[ - ec.EllipticCurvePrivateKey, x25519.X25519PrivateKey, x448.X448PrivateKey - ], - public_key: Union[ - 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. @@ -108,12 +109,12 @@ def ecdh( def derive( cls, *, - private_key: Union[ - ec.EllipticCurvePrivateKey, x25519.X25519PrivateKey, x448.X448PrivateKey - ], - public_key: Union[ - 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: @@ -137,9 +138,7 @@ def derive( def generate_ephemeral_key( self, - ) -> Union[ - ec.EllipticCurvePrivateKey, x25519.X25519PrivateKey, x448.X448PrivateKey - ]: + ) -> ec.EllipticCurvePrivateKey | x25519.X25519PrivateKey | x448.X448PrivateKey: """Generate an ephemeral key that is suitable for use with this algorithm. Returns: @@ -157,9 +156,9 @@ def generate_ephemeral_key( def sender_key( self, - ephemeral_private_key: Union[ - ec.EllipticCurvePrivateKey, x25519.X25519PrivateKey, x448.X448PrivateKey - ], + ephemeral_private_key: ( + ec.EllipticCurvePrivateKey | x25519.X25519PrivateKey | x448.X448PrivateKey + ), *, alg: str, key_size: int, @@ -191,9 +190,9 @@ def sender_key( def recipient_key( self, - ephemeral_public_key: Union[ - ec.EllipticCurvePublicKey, x25519.X25519PublicKey, x448.X448PublicKey - ], + ephemeral_public_key: ( + ec.EllipticCurvePublicKey | x25519.X25519PublicKey | x448.X448PublicKey + ), *, alg: str, key_size: int, @@ -227,14 +226,14 @@ def recipient_key( class BaseEcdhEs_AesKw(EcdhEs): """Base class for ECDH-ES+AESKW algorithms.""" - kwalg: Type[BaseAesKeyWrap] + kwalg: type[BaseAesKeyWrap] def wrap_key_with_epk( self, plainkey: bytes, - ephemeral_private_key: Union[ - ec.EllipticCurvePrivateKey, x25519.X25519PrivateKey, x448.X448PrivateKey - ], + ephemeral_private_key: ( + ec.EllipticCurvePrivateKey | x25519.X25519PrivateKey | x448.X448PrivateKey + ), **headers: Any, ) -> BinaPy: """Wrap a key for content encryption. @@ -255,10 +254,10 @@ def wrap_key_with_epk( def unwrap_key_with_epk( self, - cipherkey: Union[bytes, SupportsBytes], - ephemeral_public_key: Union[ - ec.EllipticCurvePublicKey, x25519.X25519PublicKey, x448.X448PublicKey - ], + cipherkey: bytes | SupportsBytes, + ephemeral_public_key: ( + ec.EllipticCurvePublicKey | x25519.X25519PublicKey | x448.X448PublicKey + ), **headers: Any, ) -> BinaPy: """Unwrap a key for content decryption. diff --git a/jwskate/jwa/key_mgmt/pbes2.py b/jwskate/jwa/key_mgmt/pbes2.py index 6d49298..b21e2bf 100644 --- a/jwskate/jwa/key_mgmt/pbes2.py +++ b/jwskate/jwa/key_mgmt/pbes2.py @@ -1,6 +1,7 @@ """This module implements password-based Key Management Algorithms relying on PBES2.""" +from __future__ import annotations -from typing import SupportsBytes, Type, Union +from typing import SupportsBytes from binapy import BinaPy from cryptography.hazmat.primitives import hashes @@ -20,10 +21,10 @@ class BasePbes2(BaseKeyManagementAlg): """ - kwalg: Type[BaseAesKeyWrap] + kwalg: type[BaseAesKeyWrap] hash_alg: hashes.HashAlgorithm - def __init__(self, password: Union[SupportsBytes, bytes, str]): + def __init__(self, password: SupportsBytes | bytes | str): if isinstance(password, str): password = password.encode("utf-8") if not isinstance(password, bytes): diff --git a/jwskate/jwa/key_mgmt/rsa.py b/jwskate/jwa/key_mgmt/rsa.py index 759587b..2d20b89 100644 --- a/jwskate/jwa/key_mgmt/rsa.py +++ b/jwskate/jwa/key_mgmt/rsa.py @@ -1,6 +1,7 @@ """This module implements RSA based Key Management algorithms.""" +from __future__ import annotations -from typing import Any, SupportsBytes, Union +from typing import Any, SupportsBytes from binapy import BinaPy from cryptography.hazmat.primitives import hashes @@ -53,7 +54,7 @@ def wrap_key(self, plainkey: bytes) -> BinaPy: with self.public_key_required() as key: return BinaPy(key.encrypt(plainkey, self.padding)) - def unwrap_key(self, cipherkey: Union[bytes, SupportsBytes]) -> BinaPy: + def unwrap_key(self, cipherkey: bytes | SupportsBytes) -> BinaPy: """Unwrap a symmetric key with this alg. Args: diff --git a/jwskate/jwa/okp.py b/jwskate/jwa/okp.py index 34d7e4c..7a38bea 100644 --- a/jwskate/jwa/okp.py +++ b/jwskate/jwa/okp.py @@ -4,15 +4,14 @@ : https: //www.rfc-editor.org/rfc/rfc8037.html """ - from __future__ import annotations from dataclasses import dataclass -from typing import Any, ClassVar, Dict, Tuple, Type, Union +from typing import Any, ClassVar, runtime_checkable from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import ed448, ed25519, x448, x25519 -from typing_extensions import Protocol, runtime_checkable +from typing_extensions import Protocol @runtime_checkable @@ -61,23 +60,23 @@ class OKPCurve: description: str """Curve description (human readable).""" - cryptography_private_key_class: Type[Any] + cryptography_private_key_class: type[Any] """`cryptography` private key class.""" - cryptography_public_key_class: Type[Any] + cryptography_public_key_class: type[Any] """`cryptography` public key class.""" use: str """Curve usage (`'sig'` or '`enc'`).""" - instances: ClassVar[Dict[str, OKPCurve]] = {} + instances: ClassVar[dict[str, OKPCurve]] = {} """Registry of subclasses, in a {name: instance} mapping.""" def __post_init__(self) -> None: """Automatically registers subclasses in the instance registry.""" self.instances[self.name] = self - def generate(self) -> Tuple[bytes, bytes]: + def generate(self) -> tuple[bytes, bytes]: """Generate a new private key on this curve. Returns: @@ -96,7 +95,7 @@ def generate(self) -> Tuple[bytes, bytes]: return x, d @classmethod - def get_curve(cls, key: Union[PublicKeyProtocol, PrivateKeyProtocol]) -> OKPCurve: + def get_curve(cls, key: PublicKeyProtocol | PrivateKeyProtocol) -> OKPCurve: """Return the appropriate `OKPCurve` instance for a given key. This takes a `cryptography` private or public key as parameter. If the key type matches an OKP curve @@ -109,6 +108,7 @@ def get_curve(cls, key: Union[PublicKeyProtocol, PrivateKeyProtocol]) -> OKPCurv Raises: NotImplementedError: if the required OKP curve is not supported + """ for c in cls.instances.values(): if isinstance( diff --git a/jwskate/jwa/signature/__init__.py b/jwskate/jwa/signature/__init__.py index 9e05fcf..4ab1852 100644 --- a/jwskate/jwa/signature/__init__.py +++ b/jwskate/jwa/signature/__init__.py @@ -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 from .eddsa import Ed448Dsa, Ed25519Dsa, EdDsa diff --git a/jwskate/jwa/signature/ec.py b/jwskate/jwa/signature/ec.py index ce44aff..7c4fd50 100644 --- a/jwskate/jwa/signature/ec.py +++ b/jwskate/jwa/signature/ec.py @@ -1,5 +1,7 @@ """This module implement Elliptic Curve signature algorithms.""" -from typing import SupportsBytes, Union +from __future__ import annotations + +from typing import SupportsBytes from binapy import BinaPy from cryptography import exceptions @@ -23,7 +25,7 @@ class BaseECSignatureAlg( @classmethod def check_key( - cls, key: Union[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey] + cls, key: ec.EllipticCurvePrivateKey | ec.EllipticCurvePublicKey ) -> None: # noqa: D102 if key.curve.name != cls.curve.cryptography_curve.name: raise ValueError( @@ -35,7 +37,7 @@ def check_key( def with_random_key(cls) -> Self: return cls(ec.generate_private_key(cls.curve.cryptography_curve)) - def sign(self, data: Union[bytes, SupportsBytes]) -> BinaPy: # noqa: D102 + def sign(self, data: bytes | SupportsBytes) -> BinaPy: # noqa: D102 if not isinstance(data, bytes): data = bytes(data) @@ -47,7 +49,7 @@ def sign(self, data: Union[bytes, SupportsBytes]) -> BinaPy: # noqa: D102 ) def verify( - self, data: Union[bytes, SupportsBytes], signature: Union[bytes, SupportsBytes] + self, data: bytes | SupportsBytes, signature: bytes | SupportsBytes ) -> bool: # noqa: D102 if not isinstance(data, bytes): data = bytes(data) diff --git a/jwskate/jwa/signature/eddsa.py b/jwskate/jwa/signature/eddsa.py index 2818979..adf6947 100644 --- a/jwskate/jwa/signature/eddsa.py +++ b/jwskate/jwa/signature/eddsa.py @@ -1,4 +1,5 @@ """This module implements the Edwards-curve Digital Signature Algorithm (EdDSA).""" +from __future__ import annotations from typing import SupportsBytes, Union @@ -31,7 +32,7 @@ class EdDsa( def with_random_key(cls) -> Self: return cls(ed25519.Ed25519PrivateKey.generate()) - def sign(self, data: Union[bytes, SupportsBytes]) -> BinaPy: # noqa: D102 + def sign(self, data: bytes | SupportsBytes) -> BinaPy: # noqa: D102 if not isinstance(data, bytes): data = bytes(data) @@ -39,7 +40,7 @@ def sign(self, data: Union[bytes, SupportsBytes]) -> BinaPy: # noqa: D102 return BinaPy(key.sign(data)) def verify( - self, data: Union[bytes, SupportsBytes], signature: Union[bytes, SupportsBytes] + self, data: bytes | SupportsBytes, signature: bytes | SupportsBytes ) -> bool: # noqa: D102 if not isinstance(data, bytes): data = bytes(data) diff --git a/jwskate/jwa/signature/hmac.py b/jwskate/jwa/signature/hmac.py index 999467f..3bf0d7a 100644 --- a/jwskate/jwa/signature/hmac.py +++ b/jwskate/jwa/signature/hmac.py @@ -1,6 +1,7 @@ """This module implements HMAC based signature algorithms.""" +from __future__ import annotations -from typing import SupportsBytes, Type, Union +from typing import SupportsBytes from binapy import BinaPy from cryptography.hazmat.primitives import hashes, hmac @@ -12,7 +13,7 @@ class BaseHMACSigAlg(BaseSymmetricAlg, BaseSignatureAlg): """Base class for HMAC signature algorithms.""" - mac: Type[hmac.HMAC] = hmac.HMAC + mac: type[hmac.HMAC] = hmac.HMAC min_key_size: int @classmethod @@ -20,7 +21,7 @@ class BaseHMACSigAlg(BaseSymmetricAlg, BaseSignatureAlg): def with_random_key(cls) -> Self: return cls(BinaPy.random_bits(cls.min_key_size)) - def sign(self, data: Union[bytes, SupportsBytes]) -> BinaPy: # noqa: D102 + def sign(self, data: bytes | SupportsBytes) -> BinaPy: # noqa: D102 if not isinstance(data, bytes): data = bytes(data) @@ -32,7 +33,7 @@ def sign(self, data: Union[bytes, SupportsBytes]) -> BinaPy: # noqa: D102 return BinaPy(signature) def verify( - self, data: Union[bytes, SupportsBytes], signature: Union[bytes, SupportsBytes] + self, data: bytes | SupportsBytes, signature: bytes | SupportsBytes ) -> bool: # noqa: D102 if not isinstance(data, bytes): data = bytes(data) diff --git a/jwskate/jwa/signature/rsa.py b/jwskate/jwa/signature/rsa.py index a163bc0..80d937b 100644 --- a/jwskate/jwa/signature/rsa.py +++ b/jwskate/jwa/signature/rsa.py @@ -1,5 +1,7 @@ """This module implements RSA signature algorithms.""" -from typing import SupportsBytes, Union +from __future__ import annotations + +from typing import SupportsBytes from binapy import BinaPy from cryptography import exceptions @@ -29,7 +31,7 @@ def with_random_key(cls) -> Self: rsa.generate_private_key(public_exponent=65537, key_size=cls.min_key_size) ) - def sign(self, data: Union[bytes, SupportsBytes]) -> BinaPy: + def sign(self, data: bytes | SupportsBytes) -> BinaPy: """Sign arbitrary data. Args: @@ -53,7 +55,7 @@ def sign(self, data: Union[bytes, SupportsBytes]) -> BinaPy: return BinaPy(key.sign(data, self.padding_alg, self.hashing_alg)) def verify( - self, data: Union[bytes, SupportsBytes], signature: Union[bytes, SupportsBytes] + self, data: bytes | SupportsBytes, signature: bytes | SupportsBytes ) -> bool: """Verify a signature against some data. diff --git a/jwskate/jwe/__init__.py b/jwskate/jwe/__init__.py index 8c7e54f..8b54337 100644 --- a/jwskate/jwe/__init__.py +++ b/jwskate/jwe/__init__.py @@ -4,6 +4,7 @@ : https: //www.rfc-editor.org/rfc/rfc7516 """ +from __future__ import annotations from .compact import InvalidJwe, JweCompact diff --git a/jwskate/jwe/compact.py b/jwskate/jwe/compact.py index 2f0d848..5c677ee 100644 --- a/jwskate/jwe/compact.py +++ b/jwskate/jwe/compact.py @@ -1,10 +1,9 @@ """This module implements the JWE Compact format.""" - from __future__ import annotations import warnings from functools import cached_property -from typing import Any, Dict, Iterable, Mapping, Optional, SupportsBytes, Type, Union +from typing import Any, Iterable, Mapping, SupportsBytes from binapy import BinaPy @@ -31,7 +30,7 @@ class JweCompact(BaseCompactToken): """ - def __init__(self, value: Union[bytes, str], max_size: int = 16 * 1024): + def __init__(self, value: bytes | str, max_size: int = 16 * 1024): super().__init__(value, max_size) if self.value.count(b".") != 4: @@ -103,6 +102,7 @@ def from_parts( Returns: the initialized `JweCompact` instance + """ return cls( b".".join( @@ -127,6 +127,7 @@ def enc(self) -> str: Raises: AttributeError: if there is no enc header or it is not a string + """ return self.get_header("enc") # type: ignore[no-any-return] # header has been checked at init time @@ -134,15 +135,15 @@ def enc(self) -> str: @classmethod def encrypt( cls, - plaintext: Union[bytes, SupportsBytes], - key: Union[Jwk, Dict[str, Any], Any], + plaintext: bytes | SupportsBytes, + key: Jwk | dict[str, Any] | Any, *, enc: str, - alg: Optional[str] = None, - extra_headers: Optional[Dict[str, Any]] = None, - cek: Optional[bytes] = None, - iv: Optional[bytes] = None, - epk: Optional[Jwk] = None, + alg: str | None = None, + extra_headers: dict[str, Any] | None = None, + cek: bytes | None = None, + iv: bytes | None = None, + epk: Jwk | None = None, ) -> JweCompact: """Encrypt an arbitrary plaintext into a `JweCompact`. @@ -158,6 +159,7 @@ def encrypt( Returns: the generated JweCompact instance + """ extra_headers = extra_headers or {} key = to_jwk(key) @@ -181,16 +183,16 @@ def encrypt( headers=headers, cek=wrapped_cek, iv=iv, ciphertext=ciphertext, tag=tag ) - PBES2_ALGORITHMS: Mapping[str, Type[BasePbes2]] = { + PBES2_ALGORITHMS: Mapping[str, type[BasePbes2]] = { alg.name: alg for alg in [Pbes2_HS256_A128KW, Pbes2_HS384_A192KW, Pbes2_HS512_A256KW] } def unwrap_cek( self, - key_or_password: Union[Jwk, Dict[str, Any], bytes, str], - alg: Optional[str] = None, - algs: Optional[Iterable[str]] = None, + key_or_password: Jwk | dict[str, Any] | bytes | str, + alg: str | None = None, + algs: Iterable[str] | None = None, ) -> Jwk: """Unwrap the CEK from this `Jwe` using the provided key or password. @@ -201,6 +203,7 @@ def unwrap_cek( Returns: the unwrapped CEK, as a SymmetricJwk + """ if isinstance(key_or_password, (bytes, str)): password = key_or_password @@ -219,9 +222,9 @@ def unwrap_cek( def decrypt( self, - key: Union[Jwk, Dict[str, Any], Any], - alg: Optional[str] = None, - algs: Optional[Iterable[str]] = None, + key: Jwk | dict[str, Any] | Any, + alg: str | None = None, + algs: Iterable[str] | None = None, ) -> BinaPy: """Decrypts this `Jwe` payload using a `Jwk`. @@ -232,6 +235,7 @@ def decrypt( Returns: the decrypted payload + """ cek_jwk = self.unwrap_cek(key, alg=alg, algs=algs) @@ -247,16 +251,16 @@ def decrypt( @classmethod def encrypt_with_password( cls, - plaintext: Union[SupportsBytes, bytes], - password: Union[SupportsBytes, bytes, str], + plaintext: SupportsBytes | bytes, + password: SupportsBytes | bytes | str, *, alg: str, enc: str, - salt: Optional[bytes] = None, + salt: bytes | None = None, count: int = 2000, - cek: Optional[bytes] = None, - iv: Optional[bytes] = None, - ) -> "JweCompact": + cek: bytes | None = None, + iv: bytes | None = None, + ) -> JweCompact: """Encrypt a payload with a password and return the resulting JweCompact. This performs symmetric encryption using PBES2. @@ -315,7 +319,7 @@ def encrypt_with_password( headers=headers, cek=wrapped_cek, iv=iv, ciphertext=ciphertext, tag=tag ) - def unwrap_cek_with_password(self, password: Union[bytes, str]) -> Jwk: + def unwrap_cek_with_password(self, password: bytes | str) -> Jwk: """Unwrap a CEK using a password. Works only for password-encrypted JWE Tokens. Args: @@ -350,7 +354,7 @@ def unwrap_cek_with_password(self, password: Union[bytes, str]) -> Jwk: cek = wrapper.unwrap_key(self.wrapped_cek, salt=salt, count=p2c) return SymmetricJwk.from_bytes(cek) - def decrypt_with_password(self, password: Union[bytes, str]) -> bytes: + def decrypt_with_password(self, password: bytes | str) -> bytes: """Decrypt this JWE with a password. This only works for tokens encrypted with a password. diff --git a/jwskate/jwk/__init__.py b/jwskate/jwk/__init__.py index 572af04..e9ba1a0 100644 --- a/jwskate/jwk/__init__.py +++ b/jwskate/jwk/__init__.py @@ -1,4 +1,5 @@ """This module implements [Json Web Key RFC7517](https://tools.ietf.org/html/rfc7517).""" +from __future__ import annotations from .alg import ( ExpectedAlgRequired, diff --git a/jwskate/jwk/alg.py b/jwskate/jwk/alg.py index 2368c29..5ce5e92 100644 --- a/jwskate/jwk/alg.py +++ b/jwskate/jwk/alg.py @@ -1,7 +1,8 @@ """This module contains several utilities for algorithmic agility.""" +from __future__ import annotations import warnings -from typing import Iterable, List, Mapping, Optional, Type, TypeVar, Union +from typing import Iterable, Mapping, Type, TypeVar from jwskate.jwa import BaseAlg @@ -25,8 +26,8 @@ class MismatchingAlg(ValueError): def __init__( self, target_alg: str, - alg: Optional[str] = None, - algs: Union[Iterable[str], None] = None, + alg: str | None = None, + algs: Iterable[str] | None = None, ) -> None: self.target_alg = target_alg self.alg = alg @@ -39,8 +40,8 @@ def __init__( def select_alg_class( supported_algs: Mapping[str, T], *, - jwk_alg: Optional[str] = None, - alg: Optional[str] = None, + jwk_alg: str | None = None, + alg: str | None = None, strict: bool = False, ) -> T: """Choose the appropriate alg class to use for cryptographic operations. @@ -104,11 +105,11 @@ def select_alg_class( def select_alg_classes( supported_algs: Mapping[str, T], *, - jwk_alg: Optional[str] = None, - alg: Optional[str] = None, - algs: Optional[Iterable[str]] = None, + jwk_alg: str | None = None, + alg: str | None = None, + algs: Iterable[str] | None = None, strict: bool = False, -) -> List[T]: +) -> list[T]: """Select several appropriate algs classes to use on cryptographic operations. This method is typically used to get the list of valid algorithms when checking a signature, when several algorithms are allowed. @@ -155,7 +156,7 @@ def select_alg_classes( f"You requested {requested_alg}." ) - possible_algs: List[str] = [] + possible_algs: list[str] = [] if alg: possible_algs = [alg] elif algs: diff --git a/jwskate/jwk/base.py b/jwskate/jwk/base.py index e5d4805..b2e475d 100644 --- a/jwskate/jwk/base.py +++ b/jwskate/jwk/base.py @@ -6,26 +6,13 @@ interface to access the specific attributes for each key type. Unless you are dealing with a specific key type and want to access the internal attributes, you should only ever need to use the interface from `Jwk`. -""" +""" from __future__ import annotations import warnings from dataclasses import dataclass -from typing import ( - TYPE_CHECKING, - Any, - ClassVar, - Dict, - Iterable, - List, - Mapping, - Optional, - SupportsBytes, - Tuple, - Type, - Union, -) +from typing import TYPE_CHECKING, Any, ClassVar, Iterable, Mapping, SupportsBytes from binapy import BinaPy from cryptography.hazmat.primitives import serialization @@ -100,6 +87,7 @@ def generate_for_alg(cls, alg: str, **kwargs: Any) -> Jwk: Returns: the generated `Jwk` + """ for kty, jwk_class in cls.subclasses.items(): try: @@ -130,9 +118,9 @@ def generate_for_kty(cls, kty: str, **kwargs: Any) -> Jwk: raise UnsupportedKeyType("Unsupported Key Type:", kty) return jwk_class.generate(**kwargs) - subclasses: Dict[str, Type[Jwk]] = {} + subclasses: dict[str, type[Jwk]] = {} """A dict of 'kty' values to `Jwk` subclasses implementing each specific Key Type.""" - cryptography_key_types: Dict[Any, Type[Jwk]] = {} + cryptography_key_types: dict[Any, type[Jwk]] = {} """A dict of cryptography classes to equivalent `Jwk` subclasses.""" PARAMS: Mapping[str, JwkParameter] @@ -142,9 +130,9 @@ def generate_for_kty(cls, kty: str, **kwargs: Any) -> Jwk: CRYPTOGRAPHY_PRIVATE_KEY_CLASSES: ClassVar[Iterable[Any]] CRYPTOGRAPHY_PUBLIC_KEY_CLASSES: ClassVar[Iterable[Any]] - SIGNATURE_ALGORITHMS: Mapping[str, Type[BaseSignatureAlg]] = {} - KEY_MANAGEMENT_ALGORITHMS: Mapping[str, Type[BaseKeyManagementAlg]] = {} - ENCRYPTION_ALGORITHMS: Mapping[str, Type[BaseAESEncryptionAlg]] = {} + SIGNATURE_ALGORITHMS: Mapping[str, type[BaseSignatureAlg]] = {} + KEY_MANAGEMENT_ALGORITHMS: Mapping[str, type[BaseKeyManagementAlg]] = {} + ENCRYPTION_ALGORITHMS: Mapping[str, type[BaseAESEncryptionAlg]] = {} IANA_HASH_FUNCTION_NAMES: Mapping[str, str] = { # IANA registered names to binapy hash name @@ -169,7 +157,7 @@ def __init_subclass__(cls) -> None: for klass in cls.CRYPTOGRAPHY_PUBLIC_KEY_CLASSES: Jwk.cryptography_key_types[klass] = cls - def __new__(cls, key: Union[Jwk, Dict[str, Any], Any], **kwargs: Any) -> Jwk: + def __new__(cls, key: Jwk | dict[str, Any] | Any, **kwargs: Any) -> Jwk: """Overridden `__new__` to make the Jwk constructor smarter. The Jwk constructor will accept: @@ -181,12 +169,13 @@ def __new__(cls, key: Union[Jwk, Dict[str, Any], Any], **kwargs: Any) -> Jwk: Args: key: a dict containing JWK parameters, or another Jwk instance, or a `cryptography` key **kwargs: additional members to include in the Jwk + """ if cls == Jwk: if isinstance(key, Jwk): return cls.from_cryptography_key(key.cryptography_key, **kwargs) if isinstance(key, dict): - kty: Optional[str] = key.get("kty") + kty: str | None = key.get("kty") if kty is None: raise InvalidJwk("A Json Web Key must have a Key Type (kty)") @@ -201,7 +190,7 @@ def __new__(cls, key: Union[Jwk, Dict[str, Any], Any], **kwargs: Any) -> Jwk: return super().__new__(cls, key, **kwargs) # type: ignore[type-var] def __init__( - self, params: Union[Dict[str, Any], Any], include_kid_thumbprint: bool = False + self, params: dict[str, Any] | Any, include_kid_thumbprint: bool = False ): if isinstance( params, dict @@ -219,7 +208,7 @@ def __init__( raise InvalidJwk(params) from exc @classmethod - def _get_alg_class(cls, alg: str) -> Type[BaseAlg]: + def _get_alg_class(cls, alg: str) -> type[BaseAlg]: """Given an alg identifier, return the matching JWA wrapper. Args: @@ -229,7 +218,7 @@ def _get_alg_class(cls, alg: str) -> Type[BaseAlg]: the matching JWA wrapper """ - alg_class: Optional[Type[BaseAlg]] + alg_class: type[BaseAlg] | None alg_class = cls.SIGNATURE_ALGORITHMS.get(alg) if alg_class is not None: @@ -251,6 +240,7 @@ def is_private(self) -> bool: Returns: `True` if the key is private, `False` otherwise + """ return True @@ -305,7 +295,7 @@ def kty(self) -> str: return self.KTY @property - def alg(self) -> Optional[str]: + def alg(self) -> str | None: """Return the configured key alg, if any. Returns: @@ -318,7 +308,7 @@ def alg(self) -> Optional[str]: return alg @property - def kid(self) -> Optional[str]: + def kid(self) -> str | None: """Return the JWK key ID (kid), if present.""" kid = self.get("kid") if kid is not None and not isinstance(kid, str): # pragma: no branch @@ -326,7 +316,7 @@ def kid(self) -> Optional[str]: return kid @property - def use(self) -> Optional[str]: + def use(self) -> str | None: """Return the key use. If no `alg` parameter is present, this returns the `use` parameter from this JWK. If an @@ -340,7 +330,7 @@ def use(self) -> Optional[str]: return self.get("use") @property - def key_ops(self) -> Tuple[str, ...]: + def key_ops(self) -> tuple[str, ...]: """Return the key operations. If no `alg` parameter is present, this returns the `key_ops` parameter from this JWK. If an @@ -348,7 +338,7 @@ def key_ops(self) -> Tuple[str, ...]: presence of the `key_ops` parameter, use `jwk.get('key_ops')`. """ - key_ops: Tuple[str, ...] + key_ops: tuple[str, ...] if self.use == "sig": if self.is_symmetric: key_ops = ("sign", "verify") @@ -418,9 +408,9 @@ def thumbprint_uri(self, hashalg: str = "sha-256") -> str: def check( self, *, - is_private: Optional[bool] = None, - is_symmetric: Optional[bool] = None, - kty: Optional[str] = None, + is_private: bool | None = None, + is_symmetric: bool | None = None, + kty: str | None = None, ) -> Jwk: """Check this key for type, privateness and/or symmetricness. @@ -524,7 +514,7 @@ def _validate(self) -> None: if op in ("sign", "unwrapKey") and not self.is_private: raise InvalidJwk(f"Key Operation is '{op}' but the key is public") - def signature_class(self, alg: Optional[str] = None) -> Type[BaseSignatureAlg]: + def signature_class(self, alg: str | None = None) -> type[BaseSignatureAlg]: """Return the appropriate signature algorithm class to use with this key. The returned class is a `BaseSignatureAlg` subclass. @@ -540,7 +530,7 @@ def signature_class(self, alg: Optional[str] = None) -> Type[BaseSignatureAlg]: """ return select_alg_class(self.SIGNATURE_ALGORITHMS, jwk_alg=self.alg, alg=alg) - def encryption_class(self, alg: Optional[str] = None) -> Type[BaseAESEncryptionAlg]: + def encryption_class(self, alg: str | None = None) -> type[BaseAESEncryptionAlg]: """Return the appropriate encryption algorithm class to use with this key. The returned class is a subclass of `BaseAESEncryptionAlg`. @@ -557,8 +547,8 @@ def encryption_class(self, alg: Optional[str] = None) -> Type[BaseAESEncryptionA return select_alg_class(self.ENCRYPTION_ALGORITHMS, jwk_alg=self.alg, alg=alg) def key_management_class( - self, alg: Optional[str] = None - ) -> Type[BaseKeyManagementAlg]: + self, alg: str | None = None + ) -> type[BaseKeyManagementAlg]: """Return the appropriate key management algorithm class to use with this key. If this key doesn't have an `alg` parameter, you must supply one as parameter to this method. @@ -574,7 +564,7 @@ def key_management_class( self.KEY_MANAGEMENT_ALGORITHMS, jwk_alg=self.alg, alg=alg ) - def signature_wrapper(self, alg: Optional[str] = None) -> BaseSignatureAlg: + def signature_wrapper(self, alg: str | None = None) -> BaseSignatureAlg: """Initialize a key management wrapper with this key. This returns an instance of a `BaseSignatureAlg` subclass. @@ -595,7 +585,7 @@ def signature_wrapper(self, alg: Optional[str] = None) -> BaseSignatureAlg: return alg_class(self.cryptography_key) raise UnsupportedAlg(alg) # pragma: no cover - def encryption_wrapper(self, alg: Optional[str] = None) -> BaseAESEncryptionAlg: + def encryption_wrapper(self, alg: str | None = None) -> BaseAESEncryptionAlg: """Initialize an encryption wrapper with this key. This returns an instance of a `BaseAESEncryptionAlg` subclass. @@ -616,7 +606,7 @@ def encryption_wrapper(self, alg: Optional[str] = None) -> BaseAESEncryptionAlg: return alg_class(self.cryptography_key) # pragma: no cover raise UnsupportedAlg(alg) # pragma: no cover - def key_management_wrapper(self, alg: Optional[str] = None) -> BaseKeyManagementAlg: + def key_management_wrapper(self, alg: str | None = None) -> BaseKeyManagementAlg: """Initialize a key management wrapper with this key. This returns an instance of a `BaseKeyManagementAlg` subclass. @@ -637,7 +627,7 @@ def key_management_wrapper(self, alg: Optional[str] = None) -> BaseKeyManagement return alg_class(self.cryptography_key) raise UnsupportedAlg(alg) # pragma: no cover - def supported_signing_algorithms(self) -> List[str]: + def supported_signing_algorithms(self) -> list[str]: """Return the list of Signature algorithms that can be used with this key. Returns: @@ -646,7 +636,7 @@ def supported_signing_algorithms(self) -> List[str]: """ return list(self.SIGNATURE_ALGORITHMS) - def supported_key_management_algorithms(self) -> List[str]: + def supported_key_management_algorithms(self) -> list[str]: """Return the list of Key Management algorithms that can be used with this key. Returns: @@ -655,7 +645,7 @@ def supported_key_management_algorithms(self) -> List[str]: """ return list(self.KEY_MANAGEMENT_ALGORITHMS) - def supported_encryption_algorithms(self) -> List[str]: + def supported_encryption_algorithms(self) -> list[str]: """Return the list of Encryption algorithms that can be used with this key. Returns: @@ -664,9 +654,7 @@ def supported_encryption_algorithms(self) -> List[str]: """ return list(self.ENCRYPTION_ALGORITHMS) - def sign( - self, data: Union[bytes, SupportsBytes], alg: Optional[str] = None - ) -> BinaPy: + def sign(self, data: bytes | SupportsBytes, alg: str | None = None) -> BinaPy: """Sign a data using this Jwk, and return the generated signature. Args: @@ -683,11 +671,11 @@ def sign( def verify( self, - data: Union[bytes, SupportsBytes], - signature: Union[bytes, SupportsBytes], + data: bytes | SupportsBytes, + signature: bytes | SupportsBytes, *, - alg: Optional[str] = None, - algs: Optional[Iterable[str]] = None, + alg: str | None = None, + algs: Iterable[str] | None = None, ) -> bool: """Verify a signature using this `Jwk`, and return `True` if valid. @@ -699,11 +687,12 @@ def verify( Returns: `True` if the signature matches, `False` otherwise + """ if not self.is_symmetric and self.is_private: warnings.warn( "You are trying to validate a signature with a private key. " - "Signature should always be verified with a public key." + "Signatures should always be verified with a public key." ) public_jwk = self.public_jwk() else: @@ -719,12 +708,12 @@ def verify( def encrypt( self, - plaintext: Union[bytes, SupportsBytes], + plaintext: bytes | SupportsBytes, *, - aad: Optional[bytes] = None, - alg: Optional[str] = None, - iv: Optional[bytes] = None, - ) -> Tuple[BinaPy, BinaPy, BinaPy]: + aad: bytes | None = None, + alg: str | None = None, + iv: bytes | None = None, + ) -> tuple[BinaPy, BinaPy, BinaPy]: """Encrypt a plaintext with Authenticated Encryption using this key. Authenticated Encryption with Associated Data (AEAD) is supported, by passing Additional Authenticated Data (`aad`). @@ -747,12 +736,12 @@ def encrypt( def decrypt( self, - ciphertext: Union[bytes, SupportsBytes], + ciphertext: bytes | SupportsBytes, *, - iv: Union[bytes, SupportsBytes], - tag: Union[bytes, SupportsBytes], - aad: Union[bytes, SupportsBytes, None] = None, - alg: Optional[str] = None, + iv: bytes | SupportsBytes, + tag: bytes | SupportsBytes, + aad: bytes | SupportsBytes | None = None, + alg: str | None = None, ) -> BinaPy: """Decrypt an encrypted data using this Jwk, and return the encrypted result. @@ -775,11 +764,11 @@ def sender_key( self, enc: str, *, - alg: Optional[str] = None, - cek: Optional[bytes] = None, - epk: Optional[Jwk] = None, + alg: str | None = None, + cek: bytes | None = None, + epk: Jwk | None = None, **headers: Any, - ) -> Tuple[Jwk, BinaPy, Mapping[str, Any]]: + ) -> tuple[Jwk, BinaPy, Mapping[str, Any]]: """Produce a Content Encryption Key, to use for encryption. This method is meant to be used by encrypted token senders. Recipients should use the matching method `Jwk.recipient_key()`. @@ -822,7 +811,7 @@ def sender_key( enc_alg_class = select_alg_class(SymmetricJwk.ENCRYPTION_ALGORITHMS, alg=enc) - cek_headers: Dict[str, Any] = {} + cek_headers: dict[str, Any] = {} if isinstance(key_alg_wrapper, BaseRsaKeyWrap): if cek: @@ -888,10 +877,10 @@ def sender_key( def recipient_key( self, - wrapped_cek: Union[bytes, SupportsBytes], + wrapped_cek: bytes | SupportsBytes, enc: str, *, - alg: Optional[str] = None, + alg: str | None = None, **headers: Any, ) -> Jwk: """Produce a Content Encryption Key, to use for decryption. @@ -1032,6 +1021,7 @@ def from_cryptography_key(cls, cryptography_key: Any, **kwargs: Any) -> Jwk: Raises: TypeError: if the key type is not supported + """ for cryptography_class, jwk_class in cls.cryptography_key_types.items(): if isinstance(cryptography_key, cryptography_class): @@ -1048,14 +1038,15 @@ def _to_cryptography_key(self) -> Any: Returns: a `cryptography`key instance initialized from the current key + """ raise NotImplementedError @classmethod def from_pem( cls, - der: Union[bytes, str], - password: Union[bytes, str, None] = None, + der: bytes | str, + password: bytes | str | None = None, **kwargs: Any, ) -> Jwk: """Load a `Jwk` from a PEM encoded private or public key. @@ -1067,6 +1058,7 @@ def from_pem( Returns: a `Jwk` instance from the loaded key + """ der = der.encode() if isinstance(der, str) else der password = password.encode("UTF-8") if isinstance(password, str) else password @@ -1089,7 +1081,7 @@ def from_pem( return cls.from_cryptography_key(cryptography_key, **kwargs) - def to_pem(self, password: Union[bytes, str, None] = None) -> str: + def to_pem(self, password: bytes | str | None = None) -> str: """Serialize this key to PEM format. For private keys, you can provide a password for encryption. This password should be `bytes`. A `str` is also @@ -1127,7 +1119,7 @@ def to_pem(self, password: Union[bytes, str, None] = None) -> str: def from_der( cls, der: bytes, - password: Union[bytes, str, None] = None, + password: bytes | str | None = None, **kwargs: Any, ) -> Jwk: """Load a `Jwk` from DER.""" @@ -1150,7 +1142,7 @@ def from_der( return cls.from_cryptography_key(cryptography_key, **kwargs) - def to_der(self, password: Union[bytes, str, None] = None) -> BinaPy: + def to_der(self, password: bytes | str | None = None) -> BinaPy: """Serialize this key to DER. For private keys, you can provide a password for encryption. This password should be bytes. A `str` is also @@ -1190,7 +1182,7 @@ def to_der(self, password: Union[bytes, str, None] = None) -> BinaPy: @classmethod def generate( - cls, *, alg: Optional[str] = None, kty: Optional[str] = None, **kwargs: Any + cls, *, alg: str | None = None, kty: str | None = None, **kwargs: Any ) -> Jwk: """Generate a Private Key and return it as a `Jwk` instance. @@ -1203,13 +1195,14 @@ def generate( Returns: a `Jwk` instance with a generated key + """ if alg: key = cls.generate_for_alg(alg=alg, **kwargs) if kty is not None and key.kty != kty: raise ValueError( f"Incompatible `{alg=}` and `{kty=}` parameters. " - f"`{alg=}` points to key with `kty='{key.kty}'`." + f"`{alg=}` points to `kty='{key.kty}'`." ) return key if kty: @@ -1250,7 +1243,7 @@ def with_kid_thumbprint(self, force: bool = False) -> Jwk: def with_usage_parameters( self, - alg: Optional[str] = None, + alg: str | None = None, with_alg: bool = True, with_use: bool = True, with_key_ops: bool = True, @@ -1270,6 +1263,7 @@ def with_usage_parameters( Returns: a Jwk with the same key, with `alg`, `use` and `key_ops` parameters. + """ alg = alg or self.alg @@ -1314,9 +1308,9 @@ def __eq__(self, other: Any) -> bool: def to_jwk( key: Any, *, - kty: Optional[str] = None, - is_private: Optional[bool] = None, - is_symmetric: Optional[bool] = None, + kty: str | None = None, + is_private: bool | None = None, + is_symmetric: bool | None = None, ) -> Jwk: """Convert any supported kind of key to a `Jwk`. @@ -1337,6 +1331,7 @@ def to_jwk( Returns: a Jwk key + """ jwk = key if isinstance(key, Jwk) else Jwk(key) return jwk.check(kty=kty, is_private=is_private, is_symmetric=is_symmetric) diff --git a/jwskate/jwk/ec.py b/jwskate/jwk/ec.py index 65b463b..7b053ba 100644 --- a/jwskate/jwk/ec.py +++ b/jwskate/jwk/ec.py @@ -1,10 +1,9 @@ """This module implements JWK representing Elliptic Curve keys.""" - from __future__ import annotations import warnings from functools import cached_property -from typing import Any, List, Mapping, Optional, Type, Union +from typing import Any, Mapping from binapy import BinaPy from cryptography.hazmat.primitives.asymmetric import ec @@ -66,11 +65,11 @@ class ECJwk(Jwk): curve.name: curve for curve in [P_256, P_384, P_521, secp256k1] } - SIGNATURE_ALGORITHMS: Mapping[str, Type[BaseECSignatureAlg]] = { + SIGNATURE_ALGORITHMS: Mapping[str, type[BaseECSignatureAlg]] = { sigalg.name: sigalg for sigalg in [ES256, ES384, ES512, ES256K] } - KEY_MANAGEMENT_ALGORITHMS: Mapping[str, Type[EcdhEs]] = { + KEY_MANAGEMENT_ALGORITHMS: Mapping[str, type[EcdhEs]] = { keyalg.name: keyalg for keyalg in [EcdhEs, EcdhEs_A128KW, EcdhEs_A192KW, EcdhEs_A256KW] } @@ -110,6 +109,7 @@ def curve(self) -> EllipticCurve: Returns: the `EllipticCurve` instance + """ return self.get_curve(self.crv) @@ -125,6 +125,7 @@ def public(cls, *, crv: str, x: int, y: int, **params: str) -> ECJwk: Returns: an ECJwk initialized with the supplied parameters + """ coord_size = cls.get_curve(crv).coordinate_size return cls( @@ -167,7 +168,7 @@ def private(cls, *, crv: str, x: int, y: int, d: int, **params: Any) -> ECJwk: @classmethod @override def generate( - cls, *, crv: Optional[str] = None, alg: Optional[str] = None, **kwargs: Any + cls, *, crv: str | None = None, alg: str | None = None, **kwargs: Any ) -> ECJwk: curve: EllipticCurve = P_256 @@ -210,7 +211,7 @@ def from_cryptography_key(cls, cryptography_key: Any, **kwargs: Any) -> ECJwk: @override def _to_cryptography_key( self, - ) -> Union[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey,]: + ) -> ec.EllipticCurvePrivateKey | ec.EllipticCurvePublicKey: if self.is_private: return ec.EllipticCurvePrivateNumbers( private_value=self.ecc_private_key, @@ -252,7 +253,7 @@ def ecc_private_key(self) -> int: return BinaPy(self.d).decode_from("b64u").to_int() @override - def supported_signing_algorithms(self) -> List[str]: + def supported_signing_algorithms(self) -> list[str]: return [ name for name, alg in self.SIGNATURE_ALGORITHMS.items() @@ -260,9 +261,9 @@ def supported_signing_algorithms(self) -> List[str]: ] @override - def supported_key_management_algorithms(self) -> List[str]: + def supported_key_management_algorithms(self) -> list[str]: return list(self.KEY_MANAGEMENT_ALGORITHMS) @override - def supported_encryption_algorithms(self) -> List[str]: + def supported_encryption_algorithms(self) -> list[str]: return list(self.ENCRYPTION_ALGORITHMS) diff --git a/jwskate/jwk/jwks.py b/jwskate/jwk/jwks.py index 2e1255c..ae048ac 100644 --- a/jwskate/jwk/jwks.py +++ b/jwskate/jwk/jwks.py @@ -1,6 +1,7 @@ """This module implements Json Web Key Sets (JWKS).""" +from __future__ import annotations -from typing import Any, Dict, Iterable, List, Optional, Union +from typing import Any, Iterable from ..token import BaseJsonDict from .base import Jwk, to_jwk @@ -26,8 +27,8 @@ class JwkSet(BaseJsonDict): def __init__( self, - jwks: Optional[Dict[str, Any]] = None, - keys: Optional[Iterable[Union[Jwk, Dict[str, Any]]]] = None, + jwks: dict[str, Any] | None = None, + keys: Iterable[Jwk | dict[str, Any]] | None = None, ): if jwks is None and keys is None: keys = [] @@ -45,11 +46,12 @@ def __init__( self.add_jwk(jwk) @property - def jwks(self) -> List[Jwk]: + def jwks(self) -> list[Jwk]: """Return the list of keys from this JwkSet, as `Jwk` instances. Returns: a list of `Jwk` + """ return self.get("keys", []) @@ -82,9 +84,9 @@ def __len__(self) -> int: def add_jwk( self, - key: Union[Jwk, Dict[str, Any], Any], - kid: Optional[str] = None, - use: Optional[str] = None, + key: Jwk | dict[str, Any] | Any, + kid: str | None = None, + use: str | None = None, ) -> str: """Add a Jwk in this JwkSet. @@ -120,6 +122,7 @@ def remove_jwk(self, kid: str) -> None: Raises: KeyError: if no key matches + """ try: jwk = self.get_jwk_by_kid(kid) @@ -137,7 +140,7 @@ def is_private(self) -> bool: """ return any(key.is_private for key in self.jwks) - def public_jwks(self) -> "JwkSet": + def public_jwks(self) -> JwkSet: """Return another JwkSet with the public keys associated with the current keys. Returns: @@ -146,7 +149,7 @@ def public_jwks(self) -> "JwkSet": """ return JwkSet(keys=(key.public_jwk() for key in self.jwks)) - def verification_keys(self) -> List[Jwk]: + def verification_keys(self) -> list[Jwk]: """Return the list of keys from this JWKS that are usable for signature verification. To be usable for signature verification, a key must: @@ -169,9 +172,9 @@ def verify( self, data: bytes, signature: bytes, - alg: Optional[str] = None, - algs: Optional[Iterable[str]] = None, - kid: Optional[str] = None, + alg: str | None = None, + algs: Iterable[str] | None = None, + kid: str | None = None, ) -> bool: """Verify a signature with the keys from this key set. @@ -214,7 +217,7 @@ def verify( # no key matches, so consider the signature invalid return False - def encryption_keys(self) -> List[Jwk]: + def encryption_keys(self) -> list[Jwk]: """Return the list of keys from this JWKS that are usable for encryption. To be usable for encryption, a key must: diff --git a/jwskate/jwk/oct.py b/jwskate/jwk/oct.py index dd506a2..fa02627 100644 --- a/jwskate/jwk/oct.py +++ b/jwskate/jwk/oct.py @@ -1,9 +1,8 @@ """This module implements JWK representing Symmetric keys.""" - from __future__ import annotations import warnings -from typing import Any, List, Optional, SupportsBytes, Tuple, Union +from typing import Any, SupportsBytes from binapy import BinaPy from typing_extensions import override @@ -92,7 +91,7 @@ def public_jwk(self) -> Jwk: raise ValueError("Symmetric keys don't have a public key") @classmethod - def from_bytes(cls, k: Union[bytes, str], **params: Any) -> SymmetricJwk: + def from_bytes(cls, k: bytes | str, **params: Any) -> SymmetricJwk: """Initialize a `SymmetricJwk` from a raw secret key. The provided secret key is encoded and used as the `k` parameter for the returned `SymmetricKey`. @@ -103,13 +102,14 @@ def from_bytes(cls, k: Union[bytes, str], **params: Any) -> SymmetricJwk: Returns: the resulting `SymmetricJwk` + """ return cls(dict(kty=cls.KTY, k=BinaPy(k).to("b64u").ascii(), **params)) @classmethod @override def generate( - cls, *, alg: Optional[str] = None, key_size: Optional[int] = None, **params: Any + cls, *, alg: str | None = None, key_size: int | None = None, **params: Any ) -> SymmetricJwk: if alg: alg_class = cls._get_alg_class(alg) @@ -162,7 +162,7 @@ def thumbprint(self, hashalg: str = "SHA256") -> str: ) @override - def to_pem(self, password: Union[bytes, str, None] = None) -> str: + def to_pem(self, password: bytes | str | None = None) -> str: raise TypeError("Symmetric keys are not serializable to PEM.") @property @@ -178,12 +178,12 @@ def key_size(self) -> int: @override def encrypt( self, - plaintext: Union[bytes, SupportsBytes], + plaintext: bytes | SupportsBytes, *, - aad: Optional[bytes] = None, - alg: Optional[str] = None, - iv: Optional[bytes] = None, - ) -> Tuple[BinaPy, BinaPy, BinaPy]: + aad: bytes | None = None, + alg: str | None = None, + iv: bytes | None = None, + ) -> tuple[BinaPy, BinaPy, BinaPy]: """Encrypt arbitrary data using this key. Supports Authenticated Encryption with Additional Authenticated Data (use parameter `aad` for Additional @@ -215,12 +215,12 @@ def encrypt( @override def decrypt( self, - ciphertext: Union[bytes, SupportsBytes], + ciphertext: bytes | SupportsBytes, *, - iv: Union[bytes, SupportsBytes], - tag: Union[bytes, SupportsBytes], - aad: Union[bytes, SupportsBytes, None] = None, - alg: Optional[str] = None, + iv: bytes | SupportsBytes, + tag: bytes | SupportsBytes, + aad: bytes | SupportsBytes | None = None, + alg: str | None = None, ) -> BinaPy: """Decrypt arbitrary data, and verify Additional Authenticated Data. @@ -249,7 +249,7 @@ def decrypt( return BinaPy(plaintext) @override - def supported_key_management_algorithms(self) -> List[str]: + def supported_key_management_algorithms(self) -> list[str]: return [ name for name, alg in self.KEY_MANAGEMENT_ALGORITHMS.items() @@ -258,7 +258,7 @@ def supported_key_management_algorithms(self) -> List[str]: ] @override - def supported_encryption_algorithms(self) -> List[str]: + def supported_encryption_algorithms(self) -> list[str]: return [ name for name, alg in self.ENCRYPTION_ALGORITHMS.items() diff --git a/jwskate/jwk/okp.py b/jwskate/jwk/okp.py index 3885fdc..19eb669 100644 --- a/jwskate/jwk/okp.py +++ b/jwskate/jwk/okp.py @@ -4,11 +4,10 @@ : https: //www.rfc-editor.org/rfc/rfc8037.html """ - from __future__ import annotations from functools import cached_property -from typing import Any, Mapping, Optional +from typing import Any, Mapping from binapy import BinaPy from cryptography.hazmat.primitives import serialization @@ -104,6 +103,7 @@ def get_curve(cls, crv: str) -> OKPCurve: Raises: UnsupportedOKPCurve: if the curve is not supported + """ curve = cls.CURVES.get(crv) if curve is None: @@ -129,8 +129,8 @@ def private_key(self) -> bytes: def from_bytes( cls, private_key: bytes, - crv: Optional[str] = None, - use: Optional[str] = None, + crv: str | None = None, + use: str | None = None, **kwargs: Any, ) -> OKPJwk: """Initialize an `OKPJwk` from its private key, as `bytes`. @@ -152,6 +152,7 @@ def from_bytes( Returns: the matching `OKPJwk` + """ if crv and use: if (crv in ("Ed25519", "Ed448") and use != "sig") or ( @@ -281,6 +282,7 @@ def public(cls, *, crv: str, x: bytes, **params: Any) -> OKPJwk: Returns: the resulting `OKPJwk` + """ return cls(dict(kty=cls.KTY, crv=crv, x=BinaPy(x).to("b64u").ascii(), **params)) @@ -296,6 +298,7 @@ def private(cls, *, crv: str, x: bytes, d: bytes, **params: Any) -> OKPJwk: Returns: the resulting `OKPJwk` + """ return cls( dict( @@ -310,7 +313,7 @@ def private(cls, *, crv: str, x: bytes, d: bytes, **params: Any) -> OKPJwk: @classmethod @override def generate( - cls, *, crv: Optional[str] = None, alg: Optional[str] = None, **params: Any + cls, *, crv: str | None = None, alg: str | None = None, **params: Any ) -> OKPJwk: if crv: curve = cls.get_curve(crv) @@ -332,7 +335,7 @@ def generate( @cached_property @override - def use(self) -> Optional[str]: + def use(self) -> str | None: if self.curve in (Ed25519, Ed448): return "sig" elif self.curve in (X25519, X448): diff --git a/jwskate/jwk/rsa.py b/jwskate/jwk/rsa.py index 2b416fb..4200596 100644 --- a/jwskate/jwk/rsa.py +++ b/jwskate/jwk/rsa.py @@ -1,9 +1,8 @@ """This module implements JWK representing RSA keys.""" - from __future__ import annotations from functools import cached_property -from typing import Any, Optional, Tuple, Union +from typing import Any from binapy import BinaPy from cryptography.hazmat.primitives.asymmetric import rsa @@ -113,7 +112,7 @@ def from_cryptography_key(cls, cryptography_key: Any, **kwargs: Any) -> RSAJwk: raise TypeError("A RSAPrivateKey or a RSAPublicKey is required.") @override - def _to_cryptography_key(self) -> Union[rsa.RSAPrivateKey, rsa.RSAPublicKey]: + def _to_cryptography_key(self) -> rsa.RSAPrivateKey | rsa.RSAPublicKey: if self.is_private: return rsa.RSAPrivateNumbers( self.first_prime_factor, @@ -137,7 +136,8 @@ def public(cls, *, n: int, e: int = 65537, **params: Any) -> RSAJwk: **params: additional parameters to include in the `Jwk` Returns: - a `RsaJwk` initialized from the provided parameters + a `RSAJwk` initialized from the provided parameters + """ return cls( dict( @@ -155,14 +155,14 @@ def private( n: int, e: int = 65537, d: int, - p: Optional[int] = None, - q: Optional[int] = None, - dp: Optional[int] = None, - dq: Optional[int] = None, - qi: Optional[int] = None, + p: int | None = None, + q: int | None = None, + dp: int | None = None, + dq: int | None = None, + qi: int | None = None, **params: Any, ) -> RSAJwk: - """Initialize a private `RsaJwk` from its required parameters. + """Initialize a private `RSAJwk` from its required parameters. Args: n: the modulus @@ -177,6 +177,7 @@ def private( Returns: a `RSAJwk` initialized from the given parameters + """ return cls( dict( @@ -230,6 +231,7 @@ def generate(cls, key_size: int = 4096, **params: Any) -> RSAJwk: Returns: a generated `RSAJwk` + """ private_key = rsa.generate_private_key(65537, key_size=key_size) pn = private_key.private_numbers() @@ -261,7 +263,7 @@ def private_exponent(self) -> int: return BinaPy(self.d).decode_from("b64u").to_int() @cached_property - def prime_factors(self) -> Tuple[int, int]: + def prime_factors(self) -> tuple[int, int]: """Return the 2 prime factors `p` and `q` from this `Jwk`.""" if "p" not in self or "q" not in self: p, q = rsa.rsa_recover_prime_factors( diff --git a/jwskate/jws/__init__.py b/jwskate/jws/__init__.py index 3912378..0b9243c 100644 --- a/jwskate/jws/__init__.py +++ b/jwskate/jws/__init__.py @@ -1,4 +1,5 @@ """This module implements JWS token handling.""" +from __future__ import annotations from .compact import InvalidJws, JwsCompact from .json import JwsJsonFlat, JwsJsonGeneral diff --git a/jwskate/jws/compact.py b/jwskate/jws/compact.py index d668b34..65af5a4 100644 --- a/jwskate/jws/compact.py +++ b/jwskate/jws/compact.py @@ -1,9 +1,8 @@ """This module implements the JWS Compact format.""" - from __future__ import annotations from functools import cached_property -from typing import TYPE_CHECKING, Any, Dict, Iterable, Optional, SupportsBytes, Union +from typing import TYPE_CHECKING, Any, Iterable, SupportsBytes from binapy import BinaPy @@ -28,7 +27,7 @@ class JwsCompact(BaseCompactToken): """ - def __init__(self, value: Union[bytes, str], max_size: int = 16 * 1024): + def __init__(self, value: bytes | str, max_size: int = 16 * 1024): super().__init__(value, max_size) if self.value.count(b".") != 2: @@ -62,10 +61,10 @@ def __init__(self, value: Union[bytes, str], max_size: int = 16 * 1024): @classmethod def sign( cls, - payload: Union[bytes, SupportsBytes], - key: Union[Jwk, Dict[str, Any], Any], - alg: Optional[str] = None, - extra_headers: Optional[Dict[str, Any]] = None, + payload: bytes | SupportsBytes, + key: Jwk | dict[str, Any] | Any, + alg: str | None = None, + extra_headers: dict[str, Any] | None = None, ) -> JwsCompact: """Sign a payload and returns the resulting JwsCompact. @@ -96,8 +95,8 @@ def sign( @classmethod def from_parts( cls, - signed_part: Union[bytes, SupportsBytes, str], - signature: Union[bytes, SupportsBytes], + signed_part: bytes | SupportsBytes | str, + signature: bytes | SupportsBytes, ) -> JwsCompact: """Construct a JWS token based on its signed part and signature values. @@ -133,10 +132,10 @@ def signed_part(self) -> bytes: def verify_signature( self, - key: Union[Jwk, Dict[str, Any], Any], + key: Jwk | dict[str, Any] | Any, *, - alg: Optional[str] = None, - algs: Optional[Iterable[str]] = None, + alg: str | None = None, + algs: Iterable[str] | None = None, ) -> bool: """Verify the signature from this JwsCompact using a Jwk. diff --git a/jwskate/jws/json.py b/jwskate/jws/json.py index 9e2278a..b35b1f3 100644 --- a/jwskate/jws/json.py +++ b/jwskate/jws/json.py @@ -1,9 +1,8 @@ """This module implement the JWS JSON flat and general formats.""" - from __future__ import annotations from functools import cached_property -from typing import Any, Callable, Dict, Iterable, List, Mapping, Optional, Tuple, Union +from typing import Any, Callable, Iterable, Mapping from binapy import BinaPy @@ -51,10 +50,10 @@ def jws_signature(self) -> JwsSignature: def sign( cls, payload: bytes, - key: Union[Jwk, Dict[str, Any], Any], - alg: Optional[str] = None, - extra_protected_headers: Optional[Mapping[str, Any]] = None, - header: Optional[Any] = None, + key: Jwk | dict[str, Any] | Any, + alg: str | None = None, + extra_protected_headers: Mapping[str, Any] | None = None, + header: Any | None = None, **kwargs: Any, ) -> JwsJsonFlat: """Signs a payload into a JWS in JSON flat format. @@ -116,10 +115,10 @@ def compact(self) -> JwsCompact: def verify_signature( self, - key: Union[Jwk, Dict[str, Any], Any], + key: Jwk | dict[str, Any] | Any, *, - alg: Optional[str] = None, - algs: Optional[Iterable[str]] = None, + alg: str | None = None, + algs: Iterable[str] | None = None, ) -> bool: """Verify this JWS signature with a given key. @@ -155,24 +154,25 @@ def payload(self) -> bytes: def sign( cls, payload: bytes, - *signature_parameters: Union[ - Tuple[ - Union[Jwk, Mapping[str, Any]], + *signature_parameters: ( + tuple[ + Jwk | Mapping[str, Any], str, - Optional[Mapping[str, Any]], - Optional[Mapping[str, Any]], - ], - Tuple[ - Union[Jwk, Mapping[str, Any]], + Mapping[str, Any] | None, + Mapping[str, Any] | None, + ] + | tuple[ + Jwk | Mapping[str, Any], str, - Optional[Mapping[str, Any]], - ], - Tuple[ - Union[Jwk, Mapping[str, Any]], + Mapping[str, Any] | None, + ] + | tuple[ + Jwk | Mapping[str, Any], str, - ], - Union[Jwk, Mapping[str, Any]], - ], + ] + | Jwk + | Mapping[str, Any] + ), ) -> JwsJsonGeneral: """Sign a payload with several keys and return the resulting JWS in JSON general format. @@ -195,11 +195,12 @@ def sign( return jws @cached_property - def signatures(self) -> List[JwsSignature]: + def signatures(self) -> list[JwsSignature]: """The list of `JwsSignature` from this JWS. Returns: The list of signatures from this JWS. + """ signatures = self.get("signatures") if signatures is None: @@ -208,10 +209,10 @@ def signatures(self) -> List[JwsSignature]: def add_signature( self, - key: Union[Jwk, Dict[str, Any], Any], - alg: Optional[str] = None, - extra_protected_headers: Optional[Mapping[str, Any]] = None, - header: Optional[Mapping[str, Any]] = None, + key: Jwk | dict[str, Any] | Any, + alg: str | None = None, + extra_protected_headers: Mapping[str, Any] | None = None, + header: Mapping[str, Any] | None = None, ) -> JwsJsonGeneral: """Add a new signature in this JWS. @@ -234,7 +235,7 @@ def add_signature( def signed_part( self, signature_chooser: Callable[ - [List[JwsSignature]], JwsSignature + [list[JwsSignature]], JwsSignature ] = lambda sigs: sigs[0], ) -> bytes: """Return the signed part from a given signature. @@ -257,7 +258,7 @@ def signed_part( def compact( self, signature_chooser: Callable[ - [List[JwsSignature]], JwsSignature + [list[JwsSignature]], JwsSignature ] = lambda sigs: sigs[0], ) -> JwsCompact: """Create a compact JWS from a specific signature from this JWS. @@ -278,7 +279,7 @@ def compact( def flatten( self, signature_chooser: Callable[ - [List[JwsSignature]], JwsSignature + [list[JwsSignature]], JwsSignature ] = lambda sigs: sigs[0], ) -> JwsJsonFlat: """Create a JWS in JSON flat format from a specific signature from this JWS. @@ -300,10 +301,10 @@ def flatten( def verify_signature( self, - key: Union[Jwk, Dict[str, Any], Any], + key: Jwk | dict[str, Any] | Any, *, - alg: Optional[str] = None, - algs: Optional[Iterable[str]] = None, + alg: str | None = None, + algs: Iterable[str] | None = None, ) -> bool: """Verify the signatures from this JWS. diff --git a/jwskate/jws/signature.py b/jwskate/jws/signature.py index 5e17d1b..0417765 100644 --- a/jwskate/jws/signature.py +++ b/jwskate/jws/signature.py @@ -1,9 +1,8 @@ """This module implement JWS signatures.""" - from __future__ import annotations from functools import cached_property -from typing import Any, Dict, Iterable, Mapping, Optional, Type, TypeVar, Union +from typing import Any, Iterable, Mapping, TypeVar from binapy import BinaPy @@ -27,10 +26,10 @@ class JwsSignature(BaseJsonDict): @classmethod def from_parts( - cls: Type[S], + cls: type[S], protected: Mapping[str, Any], signature: bytes, - header: Optional[Any], + header: Any | None, **kwargs: Any, ) -> S: """Initialize a JwsSignature based on the provided parts. @@ -55,7 +54,7 @@ def from_parts( return cls(content) @cached_property - def protected(self) -> Dict[str, Any]: + def protected(self) -> dict[str, Any]: """The protected header. Returns: @@ -98,12 +97,12 @@ def signature(self) -> bytes: @classmethod def sign( - cls: Type[S], + cls: type[S], payload: bytes, - key: Union[Jwk, Dict[str, Any], Any], - alg: Optional[str] = None, - extra_protected_headers: Optional[Mapping[str, Any]] = None, - header: Optional[Any] = None, + key: Jwk | dict[str, Any] | Any, + alg: str | None = None, + extra_protected_headers: Mapping[str, Any] | None = None, + header: Any | None = None, **kwargs: Any, ) -> S: """Sign a payload and return the generated JWS signature. @@ -135,7 +134,7 @@ def sign( @classmethod def assemble_signed_part( - cls, headers: Dict[str, Any], payload: Union[bytes, str] + cls, headers: dict[str, Any], payload: bytes | str ) -> bytes: """Assemble the protected header and payload to sign, as specified in. @@ -160,10 +159,10 @@ def assemble_signed_part( def verify( self, payload: bytes, - key: Union[Jwk, Dict[str, Any], Any], + key: Jwk | dict[str, Any] | Any, *, - alg: Optional[str] = None, - algs: Optional[Iterable[str]] = None, + alg: str | None = None, + algs: Iterable[str] | None = None, ) -> bool: """Verify this signature against the given payload using the provided key. diff --git a/jwskate/jwt/__init__.py b/jwskate/jwt/__init__.py index a032c8d..909a185 100644 --- a/jwskate/jwt/__init__.py +++ b/jwskate/jwt/__init__.py @@ -1,4 +1,5 @@ """This module contains all Json Web Key (Jwk) related classes and utilities.""" +from __future__ import annotations from .base import InvalidJwt, Jwt from .signed import ExpiredJwt, InvalidClaim, InvalidSignature, SignedJwt diff --git a/jwskate/jwt/base.py b/jwskate/jwt/base.py index 5c2a36f..2b602df 100644 --- a/jwskate/jwt/base.py +++ b/jwskate/jwt/base.py @@ -1,9 +1,8 @@ """This modules contains the `Jwt` base class.""" - from __future__ import annotations from datetime import datetime, timezone -from typing import TYPE_CHECKING, Any, Dict, Iterable, Optional, Union +from typing import TYPE_CHECKING, Any, Iterable from binapy import BinaPy @@ -22,7 +21,7 @@ class InvalidJwt(ValueError): class Jwt(BaseCompactToken): """Represents a Json Web Token.""" - def __new__(cls, value: Union[bytes, str], max_size: int = 16 * 1024) -> Union[SignedJwt, JweCompact, Jwt]: # type: ignore[misc] + def __new__(cls, value: bytes | str, max_size: int = 16 * 1024) -> SignedJwt | JweCompact | Jwt: # type: ignore[misc] """Allow parsing both Signed and Encrypted JWTs. This returns the appropriate subclass or instance depending on the number of dots (.) in the serialized JWT. @@ -50,11 +49,11 @@ def __new__(cls, value: Union[bytes, str], max_size: int = 16 * 1024) -> Union[S @classmethod def sign( cls, - claims: Dict[str, Any], - key: Union[Jwk, Dict[str, Any], Any], - alg: Optional[str] = None, - typ: Optional[str] = 'JWT', - extra_headers: Optional[Dict[str, Any]] = None, + claims: dict[str, Any], + key: Jwk | dict[str, Any] | Any, + alg: str | None = None, + typ: str | None = "JWT", + extra_headers: dict[str, Any] | None = None, ) -> SignedJwt: """Sign a JSON payload with a private key and return the resulting `SignedJwt`. @@ -70,6 +69,7 @@ def sign( Returns: the resulting token + """ key = to_jwk(key) @@ -81,7 +81,7 @@ def sign( extra_headers = extra_headers or {} headers = dict(alg=alg, **extra_headers) if typ: - headers['typ'] = typ + headers["typ"] = typ if key.kid: headers["kid"] = key.kid @@ -90,10 +90,10 @@ def sign( @classmethod def sign_arbitrary( self, - claims: Dict[str, Any], - headers: Dict[str, Any], - key: Union[Jwk, Dict[str, Any], Any], - alg: Optional[str] = None, + claims: dict[str, Any], + headers: dict[str, Any], + key: Jwk | dict[str, Any] | Any, + alg: str | None = None, ) -> SignedJwt: """Sign provided headers and claims with a private key and return the resulting `SignedJwt`. @@ -106,6 +106,7 @@ def sign_arbitrary( headers: the headers to sign key: the key to use for signing alg: the alg to use for signing + """ from .signed import SignedJwt @@ -125,9 +126,9 @@ def sign_arbitrary( @classmethod def unprotected( cls, - claims: Dict[str, Any], - typ: Optional[str] = 'JWT', - extra_headers: Optional[Dict[str, Any]] = None, + claims: dict[str, Any], + typ: str | None = "JWT", + extra_headers: dict[str, Any] | None = None, ) -> SignedJwt: """Generate a JWT that is not signed and not encrypted (with alg=none). @@ -144,7 +145,7 @@ def unprotected( headers = dict(extra_headers or {}, alg="none") if typ: - headers['typ'] = typ + headers["typ"] = typ headers_part = BinaPy.serialize_to("json", headers).to("b64u") claims_part = BinaPy.serialize_to("json", claims).to("b64u") @@ -155,15 +156,15 @@ def unprotected( @classmethod def sign_and_encrypt( cls, - claims: Dict[str, Any], - sign_key: Union[Jwk, Dict[str, Any], Any], - enc_key: Union[Jwk, Dict[str, Any], Any], + claims: dict[str, Any], + sign_key: Jwk | dict[str, Any] | Any, + enc_key: Jwk | dict[str, Any] | Any, enc: str, *, - sign_alg: Optional[str] = None, - enc_alg: Optional[str] = None, - sign_extra_headers: Optional[Dict[str, Any]] = None, - enc_extra_headers: Optional[Dict[str, Any]] = None, + sign_alg: str | None = None, + enc_alg: str | None = None, + sign_extra_headers: dict[str, Any] | None = None, + enc_extra_headers: dict[str, Any] | None = None, ) -> JweCompact: """Sign a JWT, then encrypt it as JWE payload. @@ -196,7 +197,7 @@ def sign_and_encrypt( @classmethod def decrypt_nested_jwt( - cls, jwe: Union[str, JweCompact], key: Union[Jwk, Dict[str, Any], Any] + cls, jwe: str | JweCompact, key: Jwk | dict[str, Any] | Any ) -> Jwt: """Decrypt a JWE that contains a nested JWT. @@ -221,11 +222,11 @@ def decrypt_nested_jwt( @classmethod def decrypt_and_verify( cls, - jwt: Union[str, JweCompact], - enc_key: Union[Jwk, Dict[str, Any], Any], - sig_key: Union[Jwk, Dict[str, Any], None, Any], - sig_alg: Optional[str] = None, - sig_algs: Optional[Iterable[str]] = None, + jwt: str | JweCompact, + enc_key: Jwk | dict[str, Any] | Any, + sig_key: Jwk | dict[str, Any] | None | Any, + sig_alg: str | None = None, + sig_algs: Iterable[str] | None = None, ) -> SignedJwt: """Decrypt then verify the signature of a JWT nested in a JWE. @@ -289,5 +290,6 @@ def timestamp_to_datetime(cls, timestamp: int) -> datetime: Returns: the corresponding `datetime` in UTC timezone + """ return datetime.fromtimestamp(timestamp, tz=timezone.utc) diff --git a/jwskate/jwt/signed.py b/jwskate/jwt/signed.py index 4382409..ad890ed 100644 --- a/jwskate/jwt/signed.py +++ b/jwskate/jwt/signed.py @@ -1,8 +1,9 @@ """This modules contains classes and utilities to generate and validate signed JWT.""" +from __future__ import annotations from datetime import datetime, timedelta, timezone from functools import cached_property -from typing import Any, Dict, Iterable, List, Optional, Union +from typing import Any, Iterable from binapy import BinaPy @@ -35,7 +36,7 @@ class SignedJwt(Jwt): """ - def __init__(self, value: Union[bytes, str]) -> None: + def __init__(self, value: bytes | str) -> None: super().__init__(value) if self.value.count(b".") != 2: @@ -80,9 +81,9 @@ def signed_part(self) -> bytes: def verify_signature( self, - key: Union[Jwk, Dict[str, Any], Any], - alg: Optional[str] = None, - algs: Optional[Iterable[str]] = None, + key: Jwk | dict[str, Any] | Any, + alg: str | None = None, + algs: Iterable[str] | None = None, ) -> bool: """Verify this JWT signature using a given key and algorithm(s). @@ -101,7 +102,7 @@ def verify_signature( data=self.signed_part, signature=self.signature, alg=alg, algs=algs ) - def is_expired(self, leeway: int = 0) -> Optional[bool]: + def is_expired(self, leeway: int = 0) -> bool | None: """Check if this token is expired, based on its `exp` claim. Args: @@ -109,6 +110,7 @@ def is_expired(self, leeway: int = 0) -> Optional[bool]: Returns: `True` if the token is expired, `False` if it's not, `None` if there is no `exp` claim. + """ exp = self.expires_at if exp is None: @@ -116,7 +118,7 @@ def is_expired(self, leeway: int = 0) -> Optional[bool]: return exp < (datetime.now(timezone.utc) + timedelta(seconds=leeway)) @cached_property - def expires_at(self) -> Optional[datetime]: + def expires_at(self) -> datetime | None: """Get the *Expires At* (`exp`) date from this token. Returns: @@ -124,6 +126,7 @@ def expires_at(self) -> Optional[datetime]: Raises: AttributeError: if the `exp` claim cannot be parsed to a date + """ exp = self.get_claim("exp") if not exp: @@ -135,7 +138,7 @@ def expires_at(self) -> Optional[datetime]: raise AttributeError("invalid `exp `claim", exp) @cached_property - def issued_at(self) -> Optional[datetime]: + def issued_at(self) -> datetime | None: """Get the *Issued At* (`iat`) date from this token. Returns: @@ -143,6 +146,7 @@ def issued_at(self) -> Optional[datetime]: Raises: AttributeError: if the `iss` claim cannot be parsed to a date + """ iat = self.get_claim("iat") if not iat: @@ -154,7 +158,7 @@ def issued_at(self) -> Optional[datetime]: raise AttributeError("invalid `iat `claim", iat) @cached_property - def not_before(self) -> Optional[datetime]: + def not_before(self) -> datetime | None: """Get the *Not Before* (nbf) date from this token. Returns: @@ -174,7 +178,7 @@ def not_before(self) -> Optional[datetime]: raise AttributeError("invalid `nbf `claim", nbf) @cached_property - def issuer(self) -> Optional[str]: + def issuer(self) -> str | None: """Get the *Issuer* (`iss`) claim from this token. Returns: @@ -182,6 +186,7 @@ def issuer(self) -> Optional[str]: Raises: AttributeError: if the `ìss` claim value is not a string + """ iss = self.get_claim("iss") if iss is None or isinstance(iss, str): @@ -189,7 +194,7 @@ def issuer(self) -> Optional[str]: raise AttributeError("iss has an unexpected type", type(iss)) @cached_property - def audiences(self) -> List[str]: + def audiences(self) -> list[str]: """Get the *Audience(s)* (`aud`) claim from this token. If this token has a single audience, this will return a `list` anyway. @@ -199,6 +204,7 @@ def audiences(self) -> List[str]: Raises: AttributeError: if the audience is an unexpected type + """ aud = self.get_claim("aud") if aud is None: @@ -210,7 +216,7 @@ def audiences(self) -> List[str]: raise AttributeError("aud has an unexpected type", type(aud)) @cached_property - def subject(self) -> Optional[str]: + def subject(self) -> str | None: """Get the *Subject* (`sub`) from this token. Returns: @@ -218,6 +224,7 @@ def subject(self) -> Optional[str]: Raises: AttributeError: if the `sub` value is not a string + """ sub = self.get_claim("sub") if sub is None or isinstance(sub, str): @@ -225,7 +232,7 @@ def subject(self) -> Optional[str]: raise AttributeError("sub has an unexpected type", type(sub)) @cached_property - def jwt_token_id(self) -> Optional[str]: + def jwt_token_id(self) -> str | None: """Get the *JWT Token ID* (`jti`) from this token. Returns: @@ -233,6 +240,7 @@ def jwt_token_id(self) -> Optional[str]: Raises: AttributeError: if the `jti` value is not a string + """ jti = self.get_claim("jti") if jti is None or isinstance(jti, str): @@ -287,6 +295,7 @@ def __str__(self) -> str: Returns: the serialized token value. + """ return self.value.decode() @@ -295,17 +304,18 @@ def __bytes__(self) -> bytes: Returns: the serialized token value. + """ return self.value def validate( self, - key: Union[Jwk, Dict[str, Any], Any], + key: Jwk | dict[str, Any] | Any, *, - alg: Optional[str] = None, - algs: Optional[Iterable[str]] = None, - issuer: Optional[str] = None, - audience: Union[None, str] = None, + alg: str | None = None, + algs: Iterable[str] | None = None, + issuer: str | None = None, + audience: None | str = None, check_exp: bool = True, **kwargs: Any, ) -> None: @@ -333,6 +343,7 @@ def validate( InvalidSignature: if the signature is not valid InvalidClaim: if a claim doesn't validate ExpiredJwt: if the expiration date is passed + """ if not self.verify_signature(key, alg, algs): raise InvalidSignature("Signature is not valid.") diff --git a/jwskate/jwt/signer.py b/jwskate/jwt/signer.py index c5ef4e1..97ac462 100644 --- a/jwskate/jwt/signer.py +++ b/jwskate/jwt/signer.py @@ -24,7 +24,7 @@ from __future__ import annotations import uuid -from typing import Any, Callable, Dict, Iterable, Optional, Union +from typing import Any, Callable, Iterable from jwskate.jwk import Jwk @@ -67,9 +67,9 @@ def __init__( self, issuer: str, jwk: Jwk, - alg: Optional[str] = None, + alg: str | None = None, default_lifetime: int = 60, - default_leeway: Optional[int] = None, + default_leeway: int | None = None, ): self.issuer = issuer self.jwk = jwk @@ -79,12 +79,12 @@ def __init__( def sign( self, - subject: Optional[str] = None, - audience: Union[str, Iterable[str], None] = None, - extra_claims: Optional[Dict[str, Any]] = None, - extra_headers: Optional[Dict[str, Any]] = None, - lifetime: Optional[int] = None, - leeway: Optional[int] = None, + subject: str | None = None, + audience: str | Iterable[str] | None = None, + extra_claims: dict[str, Any] | None = None, + extra_headers: dict[str, Any] | None = None, + lifetime: int | None = None, + leeway: int | None = None, ) -> SignedJwt: """Sign a Jwt. @@ -144,8 +144,8 @@ def with_random_key( issuer: str, alg: str, default_lifetime: int = 60, - default_leeway: Optional[int] = None, - kid: Optional[str] = None, + default_leeway: int | None = None, + kid: str | None = None, ) -> JwtSigner: """Initialize a JwtSigner with a randomly generated key. @@ -166,7 +166,7 @@ def with_random_key( def verifier( self, audience: str, - verifiers: Optional[Iterable[Callable[[SignedJwt], None]]] = None, + verifiers: Iterable[Callable[[SignedJwt], None]] | None = None, **kwargs: Any, ) -> JwtVerifier: """Return the matching JwtVerifier, initialized with the public key.""" diff --git a/jwskate/jwt/verifier.py b/jwskate/jwt/verifier.py index 8fbcf41..976797e 100644 --- a/jwskate/jwt/verifier.py +++ b/jwskate/jwt/verifier.py @@ -1,5 +1,7 @@ """High-Level facility to verify JWT tokens signature, validity dates, issuer, audiences, etc.""" -from typing import Any, Callable, Dict, Iterable, Optional, Union +from __future__ import annotations + +from typing import Any, Callable, Iterable from jwskate import Jwk, JwkSet @@ -42,14 +44,14 @@ class JwtVerifier: def __init__( self, - jwkset: Union[JwkSet, Jwk, Dict[str, Any]], + jwkset: JwkSet | Jwk | dict[str, Any], *, - issuer: Optional[str], - audience: Optional[str] = None, - alg: Optional[str] = None, - algs: Optional[Iterable[str]] = None, + issuer: str | None, + audience: str | None = None, + alg: str | None = None, + algs: Iterable[str] | None = None, leeway: int = 10, - verifiers: Optional[Iterable[Callable[[SignedJwt], None]]] = None, + verifiers: Iterable[Callable[[SignedJwt], None]] | None = None, ) -> None: if isinstance(jwkset, Jwk): jwkset = jwkset.as_jwks() @@ -73,7 +75,7 @@ def __init__( self.leeway = leeway self.verifiers = list(verifiers) if verifiers else [] - def verify(self, jwt: Union[SignedJwt, str]) -> None: + def verify(self, jwt: SignedJwt | str) -> None: """Verify a given JWT token. This checks the token signature, issuer, audience and expiration date, plus any custom verification, diff --git a/jwskate/token.py b/jwskate/token.py index cf5148f..f32cf04 100644 --- a/jwskate/token.py +++ b/jwskate/token.py @@ -1,7 +1,9 @@ """This module contains base classes for all tokens types handled by `jwskate`.""" +from __future__ import annotations + import json from functools import cached_property -from typing import Any, Dict, Type, TypeVar, Union +from typing import Any, Dict, TypeVar class BaseCompactToken: @@ -16,7 +18,7 @@ class BaseCompactToken: """ - def __init__(self, value: Union[bytes, str], max_size: int = 16 * 1024): + def __init__(self, value: bytes | str, max_size: int = 16 * 1024): if len(value) > max_size: raise ValueError( f"This JWT size exceeds {max_size} bytes, which is abnormally big. " @@ -30,7 +32,7 @@ def __init__(self, value: Union[bytes, str], max_size: int = 16 * 1024): value = b"".join(value.split()) self.value = value - self.headers: Dict[str, Any] + self.headers: dict[str, Any] def __eq__(self, other: Any) -> bool: """Check that a Jwt is equal to another. @@ -140,7 +142,7 @@ class BaseJsonDict(Dict[str, Any]): """Base class Jwk and tokens in JSON representation.""" @classmethod - def from_json(cls: Type[D], j: str) -> D: + def from_json(cls: type[D], j: str) -> D: """Initialize an object based on a string containing a JSON representation. Args: diff --git a/mkdocs.yml b/mkdocs.yml index 4cd361e..5fca40b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -46,7 +46,6 @@ markdown_extensions: slugify: !!python/object/apply:pymdownx.slugs.slugify {kwds: {case: lower}} - meta plugins: - - include-markdown - search: lang: en - mkdocstrings: diff --git a/poetry.lock b/poetry.lock index cd51f3a..a2ea01a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "attrs" @@ -20,50 +20,47 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte [[package]] name = "binapy" -version = "0.6.1" +version = "0.7.0" description = "Binary Data manipulation, for humans." optional = false -python-versions = ">=3.7,<4.0" +python-versions = ">=3.8" files = [ - {file = "binapy-0.6.1-py3-none-any.whl", hash = "sha256:3e18d039b02f1331985e85bc44a399487f5ebc7941ca95519333a99708b4081a"}, - {file = "binapy-0.6.1.tar.gz", hash = "sha256:3d2df14cd5e5cb9da0afac1824feee0c1d55944f8038fbd6f2795e078331d6e7"}, + {file = "binapy-0.7.0-py3-none-any.whl", hash = "sha256:739cd5bebd52715b8c8face6ff815bf5798306cf276b392e959ada85b9a9bee6"}, + {file = "binapy-0.7.0.tar.gz", hash = "sha256:e26f10ec6566a670e07dcc9de4c223be60984a7b1a2e5436b7eb6555f1d9d23b"}, ] [package.dependencies] -typing-extensions = ">=4.3.0,<5.0.0" +typing-extensions = ">=4.3.0" [[package]] name = "black" -version = "23.3.0" +version = "23.7.0" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, - {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, - {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, - {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, - {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, - {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, - {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, - {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, - {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, - {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, - {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, - {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, - {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, - {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, - {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"}, - {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"}, + {file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"}, + {file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"}, + {file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"}, + {file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"}, + {file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"}, + {file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"}, + {file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"}, + {file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"}, + {file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"}, + {file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"}, + {file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"}, + {file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"}, + {file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"}, ] [package.dependencies] @@ -94,13 +91,13 @@ files = [ [[package]] name = "certifi" -version = "2023.5.7" +version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, - {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, ] [[package]] @@ -203,97 +200,97 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.1.0" +version = "3.2.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, - {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, + {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, + {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, ] [[package]] name = "click" -version = "8.1.3" +version = "8.1.6" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, + {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, + {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"}, ] [package.dependencies] @@ -387,30 +384,34 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "41.0.1" +version = "41.0.2" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.1-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:f73bff05db2a3e5974a6fd248af2566134d8981fd7ab012e5dd4ddb1d9a70699"}, - {file = "cryptography-41.0.1-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:1a5472d40c8f8e91ff7a3d8ac6dfa363d8e3138b961529c996f3e2df0c7a411a"}, - {file = "cryptography-41.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fa01527046ca5facdf973eef2535a27fec4cb651e4daec4d043ef63f6ecd4ca"}, - {file = "cryptography-41.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b46e37db3cc267b4dea1f56da7346c9727e1209aa98487179ee8ebed09d21e43"}, - {file = "cryptography-41.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d198820aba55660b4d74f7b5fd1f17db3aa5eb3e6893b0a41b75e84e4f9e0e4b"}, - {file = "cryptography-41.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:948224d76c4b6457349d47c0c98657557f429b4e93057cf5a2f71d603e2fc3a3"}, - {file = "cryptography-41.0.1-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:059e348f9a3c1950937e1b5d7ba1f8e968508ab181e75fc32b879452f08356db"}, - {file = "cryptography-41.0.1-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b4ceb5324b998ce2003bc17d519080b4ec8d5b7b70794cbd2836101406a9be31"}, - {file = "cryptography-41.0.1-cp37-abi3-win32.whl", hash = "sha256:8f4ab7021127a9b4323537300a2acfb450124b2def3756f64dc3a3d2160ee4b5"}, - {file = "cryptography-41.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:1fee5aacc7367487b4e22484d3c7e547992ed726d14864ee33c0176ae43b0d7c"}, - {file = "cryptography-41.0.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9a6c7a3c87d595608a39980ebaa04d5a37f94024c9f24eb7d10262b92f739ddb"}, - {file = "cryptography-41.0.1-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5d092fdfedaec4cbbffbf98cddc915ba145313a6fdaab83c6e67f4e6c218e6f3"}, - {file = "cryptography-41.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1a8e6c2de6fbbcc5e14fd27fb24414507cb3333198ea9ab1258d916f00bc3039"}, - {file = "cryptography-41.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cb33ccf15e89f7ed89b235cff9d49e2e62c6c981a6061c9c8bb47ed7951190bc"}, - {file = "cryptography-41.0.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5f0ff6e18d13a3de56f609dd1fd11470918f770c6bd5d00d632076c727d35485"}, - {file = "cryptography-41.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7bfc55a5eae8b86a287747053140ba221afc65eb06207bedf6e019b8934b477c"}, - {file = "cryptography-41.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:eb8163f5e549a22888c18b0d53d6bb62a20510060a22fd5a995ec8a05268df8a"}, - {file = "cryptography-41.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8dde71c4169ec5ccc1087bb7521d54251c016f126f922ab2dfe6649170a3b8c5"}, - {file = "cryptography-41.0.1.tar.gz", hash = "sha256:d34579085401d3f49762d2f7d6634d6b6c2ae1242202e860f4d26b046e3a1006"}, + {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:01f1d9e537f9a15b037d5d9ee442b8c22e3ae11ce65ea1f3316a41c78756b711"}, + {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:079347de771f9282fbfe0e0236c716686950c19dee1b76240ab09ce1624d76d7"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:439c3cc4c0d42fa999b83ded80a9a1fb54d53c58d6e59234cfe97f241e6c781d"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f14ad275364c8b4e525d018f6716537ae7b6d369c094805cae45300847e0894f"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:84609ade00a6ec59a89729e87a503c6e36af98ddcd566d5f3be52e29ba993182"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:49c3222bb8f8e800aead2e376cbef687bc9e3cb9b58b29a261210456a7783d83"}, + {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d73f419a56d74fef257955f51b18d046f3506270a5fd2ac5febbfa259d6c0fa5"}, + {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:2a034bf7d9ca894720f2ec1d8b7b5832d7e363571828037f9e0c4f18c1b58a58"}, + {file = "cryptography-41.0.2-cp37-abi3-win32.whl", hash = "sha256:d124682c7a23c9764e54ca9ab5b308b14b18eba02722b8659fb238546de83a76"}, + {file = "cryptography-41.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:9c3fe6534d59d071ee82081ca3d71eed3210f76ebd0361798c74abc2bcf347d4"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a719399b99377b218dac6cf547b6ec54e6ef20207b6165126a280b0ce97e0d2a"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:182be4171f9332b6741ee818ec27daff9fb00349f706629f5cbf417bd50e66fd"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7a9a3bced53b7f09da251685224d6a260c3cb291768f54954e28f03ef14e3766"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f0dc40e6f7aa37af01aba07277d3d64d5a03dc66d682097541ec4da03cc140ee"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:674b669d5daa64206c38e507808aae49904c988fa0a71c935e7006a3e1e83831"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7af244b012711a26196450d34f483357e42aeddb04128885d95a69bd8b14b69b"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9b6d717393dbae53d4e52684ef4f022444fc1cce3c48c38cb74fca29e1f08eaa"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:192255f539d7a89f2102d07d7375b1e0a81f7478925b3bc2e0549ebf739dae0e"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f772610fe364372de33d76edcd313636a25684edb94cee53fd790195f5989d14"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b332cba64d99a70c1e0836902720887fb4529ea49ea7f5462cf6640e095e11d2"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9a6673c1828db6270b76b22cc696f40cde9043eb90373da5c2f8f2158957f42f"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:342f3767e25876751e14f8459ad85e77e660537ca0a066e10e75df9c9e9099f0"}, + {file = "cryptography-41.0.2.tar.gz", hash = "sha256:7d230bf856164de164ecb615ccc14c7fc6de6906ddd5b491f3af90d3514c925c"}, ] [package.dependencies] @@ -445,24 +446,24 @@ dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] [[package]] name = "distlib" -version = "0.3.6" +version = "0.3.7" description = "Distribution utilities" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, - {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, + {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, + {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, ] [[package]] name = "exceptiongroup" -version = "1.1.1" +version = "1.1.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, - {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, + {file = "exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"}, + {file = "exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"}, ] [package.extras] @@ -516,13 +517,13 @@ dev = ["flake8", "markdown", "twine", "wheel"] [[package]] name = "griffe" -version = "0.29.1" +version = "0.32.3" description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "griffe-0.29.1-py3-none-any.whl", hash = "sha256:f9edae6b9bb2eb205bebbdd0512a162713b9342ff6e32dc596d95ff64aa71c1f"}, - {file = "griffe-0.29.1.tar.gz", hash = "sha256:460188b719e363019d0d0f4bf2d9f05cf2df24960b42a4138a1524a17b100d9b"}, + {file = "griffe-0.32.3-py3-none-any.whl", hash = "sha256:d9471934225818bf8f309822f70451cc6abb4b24e59e0bb27402a45f9412510f"}, + {file = "griffe-0.32.3.tar.gz", hash = "sha256:14983896ad581f59d5ad7b6c9261ff12bdaa905acccc1129341d13e545da8521"}, ] [package.dependencies] @@ -530,13 +531,13 @@ colorama = ">=0.4" [[package]] name = "identify" -version = "2.5.24" +version = "2.5.26" description = "File identification library for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "identify-2.5.24-py2.py3-none-any.whl", hash = "sha256:986dbfb38b1140e763e413e6feb44cd731faf72d1909543178aa79b0e258265d"}, - {file = "identify-2.5.24.tar.gz", hash = "sha256:0aac67d5b4812498056d28a9a512a483f5085cc28640b02b258a59dac34301d4"}, + {file = "identify-2.5.26-py2.py3-none-any.whl", hash = "sha256:c22a8ead0d4ca11f1edd6c9418c3220669b3b7533ada0a0ffa6cc0ef85cf9b54"}, + {file = "identify-2.5.26.tar.gz", hash = "sha256:7243800bce2f58404ed41b7c002e53d4d22bcf3ae1b7900c2d7aefd95394bf7f"}, ] [package.extras] @@ -555,13 +556,13 @@ files = [ [[package]] name = "importlib-metadata" -version = "6.7.0" +version = "6.8.0" description = "Read metadata from Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, - {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, + {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, + {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, ] [package.dependencies] @@ -570,7 +571,7 @@ zipp = ">=0.5" [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] [[package]] name = "iniconfig" @@ -776,26 +777,15 @@ files = [ Markdown = ">=3.3" mkdocs = ">=1.1" -[[package]] -name = "mkdocs-include-markdown-plugin" -version = "4.0.4" -description = "Mkdocs Markdown includer plugin." -optional = false -python-versions = "<3.12,>=3.7" -files = [ - {file = "mkdocs_include_markdown_plugin-4.0.4-py3-none-any.whl", hash = "sha256:792caa91c2c46fa174ad44caa786656ac3f475f2cef5ad33ad28a106f7bc4086"}, - {file = "mkdocs_include_markdown_plugin-4.0.4.tar.gz", hash = "sha256:a5d569a653a610cbbe5b9c0ebb257ce7f94b453e93c3ae2c503b682e77465773"}, -] - [[package]] name = "mkdocs-material" -version = "9.1.16" +version = "9.1.19" description = "Documentation that simply works" optional = false python-versions = ">=3.7" files = [ - {file = "mkdocs_material-9.1.16-py3-none-any.whl", hash = "sha256:f9e62558a6b01ffac314423cbc223d970c25fbc78999860226245b64e64d6751"}, - {file = "mkdocs_material-9.1.16.tar.gz", hash = "sha256:1021bfea20f00a9423530c8c2ae9be3c78b80f5a527b3f822e6de3d872e5ab79"}, + {file = "mkdocs_material-9.1.19-py3-none-any.whl", hash = "sha256:fb0a149294b319aedf36983919d8c40c9e566db21ead16258e20ebd2e6c0961c"}, + {file = "mkdocs_material-9.1.19.tar.gz", hash = "sha256:73b94b08c765e92a80645aac58d6a741fc5f587deec2b715489c714827b15a6f"}, ] [package.dependencies] @@ -849,58 +839,58 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] [[package]] name = "mkdocstrings-python" -version = "1.1.2" +version = "1.2.1" description = "A Python handler for mkdocstrings." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mkdocstrings_python-1.1.2-py3-none-any.whl", hash = "sha256:c2b652a850fec8e85034a9cdb3b45f8ad1a558686edc20ed1f40b4e17e62070f"}, - {file = "mkdocstrings_python-1.1.2.tar.gz", hash = "sha256:f28bdcacb9bcdb44b6942a5642c1ea8b36870614d33e29e3c923e204a8d8ed61"}, + {file = "mkdocstrings_python-1.2.1-py3-none-any.whl", hash = "sha256:7c08f33e9ba7b1655e9cf0608eba3ce7a9513bd8b42a68a8d24ffaf4a6a50cfc"}, + {file = "mkdocstrings_python-1.2.1.tar.gz", hash = "sha256:ae40825b3b676a94626882901ed9c8fcf9a7f0330e466ffe37ce15c525987aa9"}, ] [package.dependencies] -griffe = ">=0.24" +griffe = ">=0.30" mkdocstrings = ">=0.20" [[package]] name = "mypy" -version = "1.4.0" +version = "1.4.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.7" files = [ - {file = "mypy-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3af348e0925a59213244f28c7c0c3a2c2088b4ba2fe9d6c8d4fbb0aba0b7d05"}, - {file = "mypy-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0b2e0da7ff9dd8d2066d093d35a169305fc4e38db378281fce096768a3dbdbf"}, - {file = "mypy-1.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210fe0f39ec5be45dd9d0de253cb79245f0a6f27631d62e0c9c7988be7152965"}, - {file = "mypy-1.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f7a5971490fd4a5a436e143105a1f78fa8b3fe95b30fff2a77542b4f3227a01f"}, - {file = "mypy-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:50f65f0e9985f1e50040e603baebab83efed9eb37e15a22a4246fa7cd660f981"}, - {file = "mypy-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b1b5c875fcf3e7217a3de7f708166f641ca154b589664c44a6fd6d9f17d9e7e"}, - {file = "mypy-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b4c734d947e761c7ceb1f09a98359dd5666460acbc39f7d0a6b6beec373c5840"}, - {file = "mypy-1.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5984a8d13d35624e3b235a793c814433d810acba9eeefe665cdfed3d08bc3af"}, - {file = "mypy-1.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0f98973e39e4a98709546a9afd82e1ffcc50c6ec9ce6f7870f33ebbf0bd4f26d"}, - {file = "mypy-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:19d42b08c7532d736a7e0fb29525855e355fa51fd6aef4f9bbc80749ff64b1a2"}, - {file = "mypy-1.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6ba9a69172abaa73910643744d3848877d6aac4a20c41742027dcfd8d78f05d9"}, - {file = "mypy-1.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a34eed094c16cad0f6b0d889811592c7a9b7acf10d10a7356349e325d8704b4f"}, - {file = "mypy-1.4.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:53c2a1fed81e05ded10a4557fe12bae05b9ecf9153f162c662a71d924d504135"}, - {file = "mypy-1.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bba57b4d2328740749f676807fcf3036e9de723530781405cc5a5e41fc6e20de"}, - {file = "mypy-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:653863c75f0dbb687d92eb0d4bd9fe7047d096987ecac93bb7b1bc336de48ebd"}, - {file = "mypy-1.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7461469e163f87a087a5e7aa224102a30f037c11a096a0ceeb721cb0dce274c8"}, - {file = "mypy-1.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cf0ca95e4b8adeaf07815a78b4096b65adf64ea7871b39a2116c19497fcd0dd"}, - {file = "mypy-1.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:94a81b9354545123feb1a99b960faeff9e1fa204fce47e0042335b473d71530d"}, - {file = "mypy-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:67242d5b28ed0fa88edd8f880aed24da481929467fdbca6487167cb5e3fd31ff"}, - {file = "mypy-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3f2b353eebef669529d9bd5ae3566905a685ae98b3af3aad7476d0d519714758"}, - {file = "mypy-1.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:62bf18d97c6b089f77f0067b4e321db089d8520cdeefc6ae3ec0f873621c22e5"}, - {file = "mypy-1.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca33ab70a4aaa75bb01086a0b04f0ba8441e51e06fc57e28585176b08cad533b"}, - {file = "mypy-1.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5a0ee54c2cb0f957f8a6f41794d68f1a7e32b9968675ade5846f538504856d42"}, - {file = "mypy-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:6c34d43e3d54ad05024576aef28081d9d0580f6fa7f131255f54020eb12f5352"}, - {file = "mypy-1.4.0-py3-none-any.whl", hash = "sha256:f051ca656be0c179c735a4c3193f307d34c92fdc4908d44fd4516fbe8b10567d"}, - {file = "mypy-1.4.0.tar.gz", hash = "sha256:de1e7e68148a213036276d1f5303b3836ad9a774188961eb2684eddff593b042"}, + {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, + {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, + {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, + {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, + {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, + {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, + {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, + {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, + {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, + {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, + {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, + {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, + {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, + {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, + {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, + {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, + {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, + {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, + {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, + {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, + {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, + {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, + {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, + {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, + {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, + {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, ] [package.dependencies] mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=3.10" +typing-extensions = ">=4.1.0" [package.extras] dmypy = ["psutil (>=4.0)"] @@ -957,24 +947,24 @@ files = [ [[package]] name = "pip" -version = "23.1.2" +version = "23.2.1" description = "The PyPA recommended tool for installing Python packages." optional = false python-versions = ">=3.7" files = [ - {file = "pip-23.1.2-py3-none-any.whl", hash = "sha256:3ef6ac33239e4027d9a5598a381b9d30880a1477e50039db2eac6e8a8f6d1b18"}, - {file = "pip-23.1.2.tar.gz", hash = "sha256:0e7c86f486935893c708287b30bd050a36ac827ec7fe5e43fe7cb198dd835fba"}, + {file = "pip-23.2.1-py3-none-any.whl", hash = "sha256:7ccf472345f20d35bdc9d1841ff5f313260c2c33fe417f48c30ac46cccabf5be"}, + {file = "pip-23.2.1.tar.gz", hash = "sha256:fb0bd5435b3200c602b5bf61d2d43c2f13c02e29c1707567ae7fbc514eb9faf2"}, ] [[package]] name = "platformdirs" -version = "3.7.0" +version = "3.9.1" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.7.0-py3-none-any.whl", hash = "sha256:cfd065ba43133ff103ab3bd10aecb095c2a0035fcd1f07217c9376900d94ba07"}, - {file = "platformdirs-3.7.0.tar.gz", hash = "sha256:87fbf6473e87c078d536980ba970a472422e94f17b752cfad17024c18876d481"}, + {file = "platformdirs-3.9.1-py3-none-any.whl", hash = "sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f"}, + {file = "platformdirs-3.9.1.tar.gz", hash = "sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421"}, ] [package.extras] @@ -1041,13 +1031,13 @@ plugins = ["importlib-metadata"] [[package]] name = "pymdown-extensions" -version = "10.0.1" +version = "10.1" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.7" files = [ - {file = "pymdown_extensions-10.0.1-py3-none-any.whl", hash = "sha256:ae66d84013c5d027ce055693e09a4628b67e9dec5bce05727e45b0918e36f274"}, - {file = "pymdown_extensions-10.0.1.tar.gz", hash = "sha256:b44e1093a43b8a975eae17b03c3a77aad4681b3b56fce60ce746dbef1944c8cb"}, + {file = "pymdown_extensions-10.1-py3-none-any.whl", hash = "sha256:ef25dbbae530e8f67575d222b75ff0649b1e841e22c2ae9a20bad9472c2207dc"}, + {file = "pymdown_extensions-10.1.tar.gz", hash = "sha256:508009b211373058debb8247e168de4cbcb91b1bff7b5e961b2c3e864e00b195"}, ] [package.dependencies] @@ -1056,13 +1046,13 @@ pyyaml = "*" [[package]] name = "pyproject-api" -version = "1.5.2" +version = "1.5.3" description = "API to interact with the python pyproject.toml based projects" optional = false python-versions = ">=3.7" files = [ - {file = "pyproject_api-1.5.2-py3-none-any.whl", hash = "sha256:9cffcbfb64190f207444d7579d315f3278f2c04ba46d685fad93197b5326d348"}, - {file = "pyproject_api-1.5.2.tar.gz", hash = "sha256:999f58fa3c92b23ebd31a6bad5d1f87d456744d75e05391be7f5c729015d3d91"}, + {file = "pyproject_api-1.5.3-py3-none-any.whl", hash = "sha256:14cf09828670c7b08842249c1f28c8ee6581b872e893f81b62d5465bec41502f"}, + {file = "pyproject_api-1.5.3.tar.gz", hash = "sha256:ffb5b2d7cad43f5b2688ab490de7c4d3f6f15e0b819cb588c4b771567c9729eb"}, ] [package.dependencies] @@ -1075,13 +1065,13 @@ testing = ["covdefaults (>=2.3)", "importlib-metadata (>=6.6)", "pytest (>=7.3.1 [[package]] name = "pytest" -version = "7.3.2" +version = "7.4.0" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.3.2-py3-none-any.whl", hash = "sha256:cdcbd012c9312258922f8cd3f1b62a6580fdced17db6014896053d47cddf9295"}, - {file = "pytest-7.3.2.tar.gz", hash = "sha256:ee990a3cc55ba808b80795a79944756f315c67c12b56abd3ac993a7b8c17030b"}, + {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, + {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, ] [package.dependencies] @@ -1153,51 +1143,51 @@ six = ">=1.5" [[package]] name = "pyyaml" -version = "6.0" +version = "6.0.1" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" files = [ - {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, - {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, - {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, - {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, - {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, - {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, - {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, - {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, - {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, - {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, - {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, - {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, - {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, - {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, - {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, - {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, - {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, - {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] [[package]] @@ -1403,13 +1393,13 @@ files = [ [[package]] name = "tox" -version = "4.6.3" +version = "4.6.4" description = "tox is a generic virtualenv management and test command line tool" optional = false python-versions = ">=3.7" files = [ - {file = "tox-4.6.3-py3-none-any.whl", hash = "sha256:2946a0bb38924c3a9f9575c7fb4ca1f4c11a7c69c61592f176778892155cb50c"}, - {file = "tox-4.6.3.tar.gz", hash = "sha256:9e2c5091a117d03b583c57c4c40aecd068099c17d40520e7b165e85c19334534"}, + {file = "tox-4.6.4-py3-none-any.whl", hash = "sha256:1b8f8ae08d6a5475cad9d508236c51ea060620126fd7c3c513d0f5c7f29cc776"}, + {file = "tox-4.6.4.tar.gz", hash = "sha256:5e2ad8845764706170d3dcaac171704513cc8a725655219acb62fe4380bdadda"}, ] [package.dependencies] @@ -1418,15 +1408,15 @@ chardet = ">=5.1" colorama = ">=0.4.6" filelock = ">=3.12.2" packaging = ">=23.1" -platformdirs = ">=3.5.3" -pluggy = ">=1" +platformdirs = ">=3.8" +pluggy = ">=1.2" pyproject-api = ">=1.5.2" tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} virtualenv = ">=20.23.1" [package.extras] -docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-argparse-cli (>=1.11.1)", "sphinx-autodoc-typehints (>=1.23.2,!=1.23.4)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2023.4.21)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -testing = ["build[virtualenv] (>=0.10)", "covdefaults (>=2.3)", "detect-test-pollution (>=1.1.1)", "devpi-process (>=0.3.1)", "diff-cover (>=7.6)", "distlib (>=0.3.6)", "flaky (>=3.7)", "hatch-vcs (>=0.3)", "hatchling (>=1.17.1)", "psutil (>=5.9.5)", "pytest (>=7.3.2)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-xdist (>=3.3.1)", "re-assert (>=1.1)", "time-machine (>=2.10)", "wheel (>=0.40)"] +docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-argparse-cli (>=1.11.1)", "sphinx-autodoc-typehints (>=1.23.3,!=1.23.4)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2023.4.21)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +testing = ["build[virtualenv] (>=0.10)", "covdefaults (>=2.3)", "detect-test-pollution (>=1.1.1)", "devpi-process (>=0.3.1)", "diff-cover (>=7.6)", "distlib (>=0.3.6)", "flaky (>=3.7)", "hatch-vcs (>=0.3)", "hatchling (>=1.17.1)", "psutil (>=5.9.5)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-xdist (>=3.3.1)", "re-assert (>=1.1)", "time-machine (>=2.10)", "wheel (>=0.40)"] [[package]] name = "types-cryptography" @@ -1441,24 +1431,24 @@ files = [ [[package]] name = "typing-extensions" -version = "4.6.3" +version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" optional = false python-versions = ">=3.7" files = [ - {file = "typing_extensions-4.6.3-py3-none-any.whl", hash = "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26"}, - {file = "typing_extensions-4.6.3.tar.gz", hash = "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5"}, + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, ] [[package]] name = "urllib3" -version = "2.0.3" +version = "2.0.4" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.7" files = [ - {file = "urllib3-2.0.3-py3-none-any.whl", hash = "sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1"}, - {file = "urllib3-2.0.3.tar.gz", hash = "sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825"}, + {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, + {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, ] [package.extras] @@ -1469,13 +1459,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.23.1" +version = "20.24.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.23.1-py3-none-any.whl", hash = "sha256:34da10f14fea9be20e0fd7f04aba9732f84e593dac291b757ce42e3368a39419"}, - {file = "virtualenv-20.23.1.tar.gz", hash = "sha256:8ff19a38c1021c742148edc4f81cb43d7f8c6816d2ede2ab72af5b84c749ade1"}, + {file = "virtualenv-20.24.1-py3-none-any.whl", hash = "sha256:01aacf8decd346cf9a865ae85c0cdc7f64c8caa07ff0d8b1dfc1733d10677442"}, + {file = "virtualenv-20.24.1.tar.gz", hash = "sha256:2ef6a237c31629da6442b0bcaa3999748108c7166318d1f55cc9f8d7294e97bd"}, ] [package.dependencies] @@ -1612,18 +1602,18 @@ files = [ [[package]] name = "zipp" -version = "3.15.0" +version = "3.16.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, - {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, + {file = "zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"}, + {file = "zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [extras] doc = [] @@ -1631,5 +1621,5 @@ test = [] [metadata] lock-version = "2.0" -python-versions = ">=3.8,<3.12" -content-hash = "2c36b6e7f796a51a78a2f9f4c1243a6622507e0ee68f3c9390c606dec60a0c90" +python-versions = ">=3.8" +content-hash = "3eefb2ca4b609c82847ba877e8b46dc6f29ed1410c7f84695f137985042ae217" diff --git a/pyproject.toml b/pyproject.toml index 12a9d23..cbfb10e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,17 +1,17 @@ [tool] [tool.poetry] name = "jwskate" -version = "0.8.0" +version = "0.9.0" homepage = "https://github.com/guillp/jwskate" description = "A Pythonic implementation of Json Web Signature, Keys, Algorithms, Tokens and Encryption (RFC7514 to 7519), on top of the `cryptography` module." authors = ["Guillaume Pujol "] readme = "README.md" license = "MIT" classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', + 'Topic :: Security', 'License :: OSI Approved :: MIT License', - 'Natural Language :: English', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', @@ -24,35 +24,34 @@ packages = [ ] [tool.poetry.dependencies] -python = ">=3.8,<3.12" +python = ">=3.8" cryptography = ">=3.4" typing-extensions = ">=4.3.0" -binapy = ">=0.6.1" +binapy = ">=0.7.0" [tool.poetry.dev-dependencies] -black = ">=23.3" +black = ">=23.7" coverage = ">=7.2.3" freezegun = ">=1.2.2" isort = ">=5.12" jwcrypto = ">=1.0" livereload = ">=2.6" -mypy = ">=1.2" +mypy = ">=1.4.1" mkdocs = ">=1.4.2" mkdocstrings = { version = ">=0.21.2", extras = ["python"] } mkdocs-autorefs = ">=0.4" -mkdocs-include-markdown-plugin = ">=4.0.4" -mkdocs-material = ">=9.1.6" +mkdocs-material = ">=9.1.19" mkdocs-material-extensions = ">=1.1" -pip = ">=22.0" +pip = ">=23.2.1" pre-commit = ">=3.2.2" -pytest = ">=7.3" +pytest = ">=7.4.0" pytest-cov = ">=4.0" pytest-mypy = ">=0.10" toml = ">=0.10" tomli = ">=2.0.1" -tox = ">=4.5.1" +tox = ">=4.6.4" types-cryptography = ">=3.3" -virtualenv = ">=20.14.1" +virtualenv = ">=20.24.1" [tool.poetry.extras] test = ["pytest", "pytest-cov", "jwcrypto", "freezegun"] diff --git a/tests/__init__.py b/tests/__init__.py index dcca998..7beecc4 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1 +1,2 @@ """Unit test package for jwskate.""" +from __future__ import annotations diff --git a/tests/conftest.py b/tests/conftest.py index 805858a..82dc42f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for all tests.""" +from __future__ import annotations import pytest diff --git a/tests/test_jwa/test_base.py b/tests/test_jwa/test_base.py index 5bc2c06..83d1919 100644 --- a/tests/test_jwa/test_base.py +++ b/tests/test_jwa/test_base.py @@ -1,4 +1,4 @@ -from typing import Type +from __future__ import annotations import pytest from cryptography.hazmat.primitives.asymmetric import rsa @@ -106,7 +106,7 @@ def test_private_public_key_required() -> None: RsaEsPcks1v1_5, ), ) -def test_init_with_random_key(alg_class: Type[BaseAlg]) -> None: +def test_init_with_random_key(alg_class: type[BaseAlg]) -> None: alg = alg_class.with_random_key() assert isinstance(alg, alg_class) if issubclass(alg_class, BaseAsymmetricAlg): diff --git a/tests/test_jwa/test_encryption.py b/tests/test_jwa/test_encryption.py index 6f29af6..fe1d04c 100644 --- a/tests/test_jwa/test_encryption.py +++ b/tests/test_jwa/test_encryption.py @@ -1,4 +1,5 @@ """Tests for jwskate.jwa.encryption submodule.""" +from __future__ import annotations import pytest diff --git a/tests/test_jwa/test_examples.py b/tests/test_jwa/test_examples.py index 72b57b7..e50834f 100644 --- a/tests/test_jwa/test_examples.py +++ b/tests/test_jwa/test_examples.py @@ -1,4 +1,6 @@ """Tests for the jwkskate.jwa submodule.""" +from __future__ import annotations + from binapy import BinaPy from jwskate import A128CBC_HS256, A192CBC_HS384, EcdhEs, Jwk diff --git a/tests/test_jwa/test_key_mgmt.py b/tests/test_jwa/test_key_mgmt.py index 2e57a14..afe8895 100644 --- a/tests/test_jwa/test_key_mgmt.py +++ b/tests/test_jwa/test_key_mgmt.py @@ -1,5 +1,5 @@ """Tests for jwskate.jwa.key_mgmt submodule.""" -from typing import Type, Union +from __future__ import annotations import pytest from cryptography.hazmat.primitives.asymmetric import ec, x448, x25519 @@ -18,11 +18,11 @@ ], ) def test_ecdhes( - key_gen: Union[ - Type[ec.EllipticCurvePrivateKey], - Type[x25519.X25519PrivateKey], - Type[x448.X448PrivateKey], - ] + key_gen: ( + type[ec.EllipticCurvePrivateKey] + | type[x25519.X25519PrivateKey] + | type[x448.X448PrivateKey] + ), ) -> None: private_key = key_gen() sender_ecdhes = EcdhEs(private_key.public_key()) diff --git a/tests/test_jwa/test_signature.py b/tests/test_jwa/test_signature.py index 7cff8e8..90fab47 100644 --- a/tests/test_jwa/test_signature.py +++ b/tests/test_jwa/test_signature.py @@ -1,4 +1,5 @@ """Tests for jwskate.jwa.signature submodule.""" +from __future__ import annotations import pytest diff --git a/tests/test_jwe.py b/tests/test_jwe.py index 7b4bd9f..1588ba7 100644 --- a/tests/test_jwe.py +++ b/tests/test_jwe.py @@ -1,4 +1,6 @@ -from typing import SupportsBytes, Union +from __future__ import annotations + +from typing import SupportsBytes import pytest @@ -426,7 +428,7 @@ def encryption_alg(request: pytest.FixtureRequest) -> str: @pytest.fixture( scope="module", params=[ - 'pAvkcJv!$N8HtIuf3W@KaF&2Gv"EAD/BK[_FEoLIuvMS*aG0tm4,.?'.encode(), + b'pAvkcJv!$N8HtIuf3W@KaF&2Gv"EAD/BK[_FEoLIuvMS*aG0tm4,.?', ], ) def password(request: pytest.FixtureRequest) -> bytes: @@ -449,7 +451,7 @@ def decryption_jwk( symmetric_384_encryption_jwk: Jwk, symmetric_512_encryption_jwk: Jwk, password: bytes, -) -> Union[Jwk, bytes]: +) -> Jwk | bytes: if key_management_alg == "dir": for key in ( symmetric_128_encryption_jwk, @@ -485,7 +487,7 @@ def decryption_jwk( @pytest.fixture(scope="module") -def encryption_jwk(decryption_jwk: Union[Jwk, bytes]) -> Union[Jwk, bytes]: +def encryption_jwk(decryption_jwk: Jwk | bytes) -> Jwk | bytes: if isinstance(decryption_jwk, SymmetricJwk): return decryption_jwk elif isinstance(decryption_jwk, bytes): @@ -497,7 +499,7 @@ def encryption_jwk(decryption_jwk: Union[Jwk, bytes]) -> Union[Jwk, bytes]: @pytest.fixture(scope="module") def encrypted_jwe( encryption_plaintext: SupportsBytes, - encryption_jwk: Union[Jwk, SupportsBytes], + encryption_jwk: Jwk | SupportsBytes, key_management_alg: str, encryption_alg: str, ) -> JweCompact: @@ -533,7 +535,7 @@ def __bytes__(self) -> bytes: # noqa: D105 def test_supportsbytes( encryption_plaintext: bytes, - encryption_jwk: Union[Jwk, SupportsBytes], + encryption_jwk: Jwk | SupportsBytes, key_management_alg: str, encryption_alg: str, encrypted_jwe: JweCompact, @@ -575,7 +577,7 @@ def test_supportsbytes( def test_decrypt( encryption_plaintext: bytes, encrypted_jwe: JweCompact, - decryption_jwk: Union[Jwk, bytes], + decryption_jwk: Jwk | bytes, key_management_alg: str, encryption_alg: str, ) -> None: @@ -604,6 +606,7 @@ def test_decrypt_by_jwcrypto( decryption_jwk: the Jwk containing the decryption key key_management_alg: the key management alg encryption_alg: the encryption alg + """ import jwcrypto.jwe # type: ignore[import] import jwcrypto.jwk # type: ignore[import] @@ -643,6 +646,7 @@ def jwcrypto_encrypted_jwe( Returns: a JWE token + """ import jwcrypto.jwe import jwcrypto.jwk @@ -683,6 +687,7 @@ def test_decrypt_from_jwcrypto( decryption_jwk: the decryption key key_management_alg: the key management alg encryption_alg: the encryption alg + """ jwe = JweCompact(jwcrypto_encrypted_jwe) assert jwe.alg == key_management_alg diff --git a/tests/test_jwk/test_alg.py b/tests/test_jwk/test_alg.py index a47d892..9e15eb3 100644 --- a/tests/test_jwk/test_alg.py +++ b/tests/test_jwk/test_alg.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from jwskate import ( diff --git a/tests/test_jwk/test_ec.py b/tests/test_jwk/test_ec.py index d1d9654..41410bd 100644 --- a/tests/test_jwk/test_ec.py +++ b/tests/test_jwk/test_ec.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from cryptography.hazmat.primitives.asymmetric import ec diff --git a/tests/test_jwk/test_generate.py b/tests/test_jwk/test_generate.py index c33e1dc..9ee2bcf 100644 --- a/tests/test_jwk/test_generate.py +++ b/tests/test_jwk/test_generate.py @@ -1,6 +1,7 @@ +from __future__ import annotations + import secrets import warnings -from typing import Dict import pytest @@ -98,7 +99,7 @@ def test_generate_for_alg(alg: str) -> None: (KeyTypes.OKP, {"crv": "Ed25519"}), ), ) -def test_generate_for_kty(kty: str, kwargs: Dict[str, str]) -> None: +def test_generate_for_kty(kty: str, kwargs: dict[str, str]) -> None: jwk = Jwk.generate_for_kty(kty, **kwargs) assert jwk.kty == kty diff --git a/tests/test_jwk/test_jwk.py b/tests/test_jwk/test_jwk.py index ecae79d..ff92de6 100644 --- a/tests/test_jwk/test_jwk.py +++ b/tests/test_jwk/test_jwk.py @@ -1,5 +1,6 @@ +from __future__ import annotations + import secrets -from typing import Tuple import pytest from cryptography.hazmat.primitives.asymmetric import rsa @@ -180,7 +181,7 @@ def test_invalid_class_for_kty() -> None: ], ) def test_key_ops_without_alg( - kty: str, private_key_ops: Tuple[str], public_key_ops: Tuple[str] + kty: str, private_key_ops: tuple[str], public_key_ops: tuple[str] ) -> None: # with a key with no alg or use, we can only trust the key_ops from the key private_jwk = Jwk.generate_for_kty("RSA", key_ops=private_key_ops) @@ -200,7 +201,7 @@ def test_key_ops_without_alg( ], ) def test_use_key_ops_with_alg( - alg: str, use: str, private_key_ops: Tuple[str], public_key_ops: Tuple[str] + alg: str, use: str, private_key_ops: tuple[str], public_key_ops: tuple[str] ) -> None: # if key has an 'alg' parameter, we can deduce the use and key ops private_jwk = Jwk.generate(alg=alg) diff --git a/tests/test_jwk/test_jwks.py b/tests/test_jwk/test_jwks.py index 81a7e38..1e2c4cf 100644 --- a/tests/test_jwk/test_jwks.py +++ b/tests/test_jwk/test_jwks.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from jwskate import Jwk, JwkSet @@ -135,14 +137,14 @@ def test_public_jwkset() -> None: sig_keys = jwks.verification_keys() enc_keys = jwks.encryption_keys() - sig_kids = set(jwk.kid for jwk in sig_keys) + sig_kids = {jwk.kid for jwk in sig_keys} assert sig_kids == { "7KJgpwNvHJp_zb6SybahlC7506kvAm2cvMG_EY6jmx8", "ojHE_6b7DXtOwLKYTmeao38CV_7P9F9rYTLGm8BuJnk", "m7XoZRBgXXjEFxGhWvb_urskl4rCLmOhhPRdC6278-E", } - enc_kids = set(jwk.kid for jwk in enc_keys) + enc_kids = {jwk.kid for jwk in enc_keys} assert enc_kids == { "xAgzqjWdBD8cRifXbpmcv-9vIgjKHTdjelI-Vvu0K9Q", "zjY2pjFnBc4rOHWEwfS5Cjyxsjo2aprsctM-4oS1r8I", diff --git a/tests/test_jwk/test_okp.py b/tests/test_jwk/test_okp.py index d796871..9a1be61 100644 --- a/tests/test_jwk/test_okp.py +++ b/tests/test_jwk/test_okp.py @@ -1,4 +1,6 @@ -from typing import Any, Type +from __future__ import annotations + +from typing import Any import pytest from cryptography.hazmat.primitives.asymmetric import ed448, ed25519, x448, x25519 @@ -77,7 +79,7 @@ def test_rfc8037_ed25519() -> None: assert jwk.thumbprint() == "kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k" - payload = "Example of Ed25519 signing".encode() + payload = b"Example of Ed25519 signing" jws = JwsCompact.sign(payload, key=jwk, alg="EdDSA") assert jws.alg == "EdDSA" @@ -201,7 +203,7 @@ def test_unknown_curve() -> None: ], ) def test_from_to_cryptography( - crv: str, private_key_class: Type[Any], public_key_class: Type[Any] + crv: str, private_key_class: type[Any], public_key_class: type[Any] ) -> None: private_key = private_key_class.generate() private_jwk = Jwk(private_key) diff --git a/tests/test_jwk/test_rsa.py b/tests/test_jwk/test_rsa.py index 9b400e0..60f3e5c 100644 --- a/tests/test_jwk/test_rsa.py +++ b/tests/test_jwk/test_rsa.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from binapy import BinaPy from cryptography.hazmat.primitives import serialization diff --git a/tests/test_jwk/test_symmetric.py b/tests/test_jwk/test_symmetric.py index de52a7a..1504054 100644 --- a/tests/test_jwk/test_symmetric.py +++ b/tests/test_jwk/test_symmetric.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from jwskate import Jwk, SymmetricJwk diff --git a/tests/test_jws.py b/tests/test_jws.py index ab40c0f..f420798 100644 --- a/tests/test_jws.py +++ b/tests/test_jws.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from binapy import BinaPy @@ -482,6 +484,7 @@ def test_verify_signature_by_jwcrypto( signed_jws_compact: the Jws signed by jwskate to verify verification_jwk: the Jwk containing the verification key signature_alg: the signature alg + """ import jwcrypto.jwk # type: ignore[import] import jwcrypto.jws # type: ignore[import] @@ -505,6 +508,7 @@ def jwcrypto_signed_jws( Returns: a JWS token + """ import jwcrypto.jwk import jwcrypto.jws @@ -530,6 +534,7 @@ def test_verify_signature_from_jwcrypto( jwcrypto_signed_jws: the JWS to verify verification_jwk: the public key to verify the signature signature_alg: the alg to use + """ assert JwsCompact(jwcrypto_signed_jws).verify_signature( verification_jwk, alg=signature_alg diff --git a/tests/test_jwt.py b/tests/test_jwt.py index d1f4700..65558ef 100644 --- a/tests/test_jwt.py +++ b/tests/test_jwt.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from builtins import ValueError from datetime import datetime, timezone @@ -90,8 +92,8 @@ def test_signed_jwt() -> None: def test_unprotected() -> None: jwt = Jwt.unprotected({"foo": "bar"}) assert jwt == "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJmb28iOiJiYXIifQ." - assert jwt.alg == 'none' - assert jwt.signature == b'' + assert jwt.alg == "none" + assert jwt.signature == b"" def test_jwt_signer_and_verifier(issuer: str) -> None: @@ -160,7 +162,10 @@ def test_empty_jwt(private_jwk: Jwk) -> None: == "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IkpXSy1BQkNEIn0.e30.c_3ppMzgxnhn4CkLBCNNJ5_zdoS6S9P79cruukiuixMHoHIPF0_nzaj5LBRUXt3O47JiJyUzroi1MXNe_Kod9dqLRM8RJ9t3dbWJRNbPrgnCkqpUhNZ6frrc8jVs9Qu9xmXLDYEa4aSwPSkQTufWN1fC04Vzm8JUMVXM0AFeKjyEyUijEuqeBBFztDbIc2apyXWc5bZW7HEkhDNgKK0pWAVnXLwt4OwGQjd6ZOC5Hgx1wDbiam_abNWaDvR53JSCLM0wMpkYrONY_RPjWRycyeb9K5tHOcGbfRvQqpZGsRG-slf-bqwSOt-8G6Phc_YDv9Lw4NN-vqOxbo2lw-3Crw" ) assert bytes(jwt) == str(jwt).encode() - assert jwt.signed_part == b"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IkpXSy1BQkNEIn0.e30" + assert ( + jwt.signed_part + == b"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IkpXSy1BQkNEIn0.e30" + ) jwt.validate(key=private_jwk.public_jwk(), check_exp=False)