joserfc 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- joserfc-0.1.0/LICENSE +28 -0
- joserfc-0.1.0/PKG-INFO +38 -0
- joserfc-0.1.0/README.md +3 -0
- joserfc-0.1.0/pyproject.toml +43 -0
- joserfc-0.1.0/setup.cfg +4 -0
- joserfc-0.1.0/src/joserfc/__init__.py +5 -0
- joserfc-0.1.0/src/joserfc/errors.py +88 -0
- joserfc-0.1.0/src/joserfc/jwe.py +70 -0
- joserfc-0.1.0/src/joserfc/jwk.py +86 -0
- joserfc-0.1.0/src/joserfc/jws.py +192 -0
- joserfc-0.1.0/src/joserfc/jwt.py +79 -0
- joserfc-0.1.0/src/joserfc/registry.py +145 -0
- joserfc-0.1.0/src/joserfc/rfc7515/__init__.py +0 -0
- joserfc-0.1.0/src/joserfc/rfc7515/compact.py +63 -0
- joserfc-0.1.0/src/joserfc/rfc7515/json.py +123 -0
- joserfc-0.1.0/src/joserfc/rfc7515/model.py +36 -0
- joserfc-0.1.0/src/joserfc/rfc7515/registry.py +55 -0
- joserfc-0.1.0/src/joserfc/rfc7515/types.py +92 -0
- joserfc-0.1.0/src/joserfc/rfc7516/__init__.py +0 -0
- joserfc-0.1.0/src/joserfc/rfc7516/compact.py +57 -0
- joserfc-0.1.0/src/joserfc/rfc7516/message.py +100 -0
- joserfc-0.1.0/src/joserfc/rfc7516/models.py +79 -0
- joserfc-0.1.0/src/joserfc/rfc7516/registry.py +97 -0
- joserfc-0.1.0/src/joserfc/rfc7516/types.py +63 -0
- joserfc-0.1.0/src/joserfc/rfc7517/__init__.py +13 -0
- joserfc-0.1.0/src/joserfc/rfc7517/keygen.py +34 -0
- joserfc-0.1.0/src/joserfc/rfc7517/keyset.py +56 -0
- joserfc-0.1.0/src/joserfc/rfc7517/models.py +258 -0
- joserfc-0.1.0/src/joserfc/rfc7517/pem.py +120 -0
- joserfc-0.1.0/src/joserfc/rfc7517/types.py +26 -0
- joserfc-0.1.0/src/joserfc/rfc7518/__init__.py +0 -0
- joserfc-0.1.0/src/joserfc/rfc7518/ec_key.py +135 -0
- joserfc-0.1.0/src/joserfc/rfc7518/jwe_algs.py +357 -0
- joserfc-0.1.0/src/joserfc/rfc7518/jwe_encs.py +138 -0
- joserfc-0.1.0/src/joserfc/rfc7518/jwe_zips.py +22 -0
- joserfc-0.1.0/src/joserfc/rfc7518/jws_algs.py +201 -0
- joserfc-0.1.0/src/joserfc/rfc7518/oct_key.py +61 -0
- joserfc-0.1.0/src/joserfc/rfc7518/rsa_key.py +154 -0
- joserfc-0.1.0/src/joserfc/rfc7518/util.py +12 -0
- joserfc-0.1.0/src/joserfc/rfc7519/__init__.py +0 -0
- joserfc-0.1.0/src/joserfc/rfc7519/claims.py +41 -0
- joserfc-0.1.0/src/joserfc/rfc7519/registry.py +22 -0
- joserfc-0.1.0/src/joserfc/rfc7519/validators.py +133 -0
- joserfc-0.1.0/src/joserfc/rfc7638/__init__.py +16 -0
- joserfc-0.1.0/src/joserfc/rfc8037/__init__.py +0 -0
- joserfc-0.1.0/src/joserfc/rfc8037/jws_eddsa.py +22 -0
- joserfc-0.1.0/src/joserfc/rfc8037/okp_key.py +163 -0
- joserfc-0.1.0/src/joserfc/rfc8812/__init__.py +14 -0
- joserfc-0.1.0/src/joserfc/util.py +58 -0
- joserfc-0.1.0/src/joserfc.egg-info/PKG-INFO +38 -0
- joserfc-0.1.0/src/joserfc.egg-info/SOURCES.txt +52 -0
- joserfc-0.1.0/src/joserfc.egg-info/dependency_links.txt +1 -0
- joserfc-0.1.0/src/joserfc.egg-info/requires.txt +1 -0
- joserfc-0.1.0/src/joserfc.egg-info/top_level.txt +1 -0
joserfc-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023, Hsiaoming Yang
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
joserfc-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: joserfc
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The ultimate Python library for JOSE RFCs, including JWS, JWE, JWK, JWA, JWT
|
|
5
|
+
Author-email: Hsiaoming Yang <me@lepture.com>
|
|
6
|
+
License: BSD 3-Clause License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2023, Hsiaoming Yang
|
|
9
|
+
|
|
10
|
+
Redistribution and use in source and binary forms, with or without
|
|
11
|
+
modification, are permitted provided that the following conditions are met:
|
|
12
|
+
|
|
13
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
14
|
+
list of conditions and the following disclaimer.
|
|
15
|
+
|
|
16
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
17
|
+
this list of conditions and the following disclaimer in the documentation
|
|
18
|
+
and/or other materials provided with the distribution.
|
|
19
|
+
|
|
20
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
21
|
+
contributors may be used to endorse or promote products derived from
|
|
22
|
+
this software without specific prior written permission.
|
|
23
|
+
|
|
24
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
25
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
26
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
27
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
28
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
29
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
30
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
31
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
32
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
33
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
34
|
+
|
|
35
|
+
Project-URL: Documentation, https://jose.authlib.org/
|
|
36
|
+
Project-URL: Blog, https://blog.authlib.org/
|
|
37
|
+
Requires-Python: >=3.8
|
|
38
|
+
License-File: LICENSE
|
joserfc-0.1.0/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "joserfc"
|
|
3
|
+
description = "The ultimate Python library for JOSE RFCs, including JWS, JWE, JWK, JWA, JWT"
|
|
4
|
+
authors = [{name = "Hsiaoming Yang", email="me@lepture.com"}]
|
|
5
|
+
dependencies = [
|
|
6
|
+
"cryptography",
|
|
7
|
+
]
|
|
8
|
+
license = {file = "LICENSE"}
|
|
9
|
+
requires-python = ">=3.8"
|
|
10
|
+
dynamic = ["version"]
|
|
11
|
+
|
|
12
|
+
[project.urls]
|
|
13
|
+
Documentation = "https://jose.authlib.org/"
|
|
14
|
+
Blog = "https://blog.authlib.org/"
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["setuptools"]
|
|
18
|
+
build-backend = "setuptools.build_meta"
|
|
19
|
+
|
|
20
|
+
[tool.setuptools.dynamic]
|
|
21
|
+
version = {attr = "joserfc.__version__"}
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.packages.find]
|
|
24
|
+
where = ["src"]
|
|
25
|
+
|
|
26
|
+
[tool.pytest.ini_options]
|
|
27
|
+
pythonpath = ["src", "."]
|
|
28
|
+
testpaths = ["tests"]
|
|
29
|
+
filterwarnings = ["error"]
|
|
30
|
+
|
|
31
|
+
[tool.coverage.run]
|
|
32
|
+
branch = true
|
|
33
|
+
source = ["joserfc"]
|
|
34
|
+
|
|
35
|
+
[tool.coverage.paths]
|
|
36
|
+
source = ["src"]
|
|
37
|
+
|
|
38
|
+
[tool.coverage.report]
|
|
39
|
+
exclude_lines = [
|
|
40
|
+
"pragma: no cover",
|
|
41
|
+
"raise NotImplementedError",
|
|
42
|
+
"@(abc\\.)?abstractmethod",
|
|
43
|
+
]
|
joserfc-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class JoseError(Exception):
|
|
5
|
+
"""Base Exception for all errors in joserfc."""
|
|
6
|
+
|
|
7
|
+
#: short-string error code
|
|
8
|
+
error: str = ''
|
|
9
|
+
#: long-string to describe this error
|
|
10
|
+
description: str = ''
|
|
11
|
+
|
|
12
|
+
def __init__(self, description: Optional[str]=None, error: Optional[str]=None):
|
|
13
|
+
if error is not None:
|
|
14
|
+
self.error = error
|
|
15
|
+
if description is not None:
|
|
16
|
+
self.description = description
|
|
17
|
+
|
|
18
|
+
message = '{}: {}'.format(self.error, self.description)
|
|
19
|
+
super(JoseError, self).__init__(message)
|
|
20
|
+
|
|
21
|
+
def __repr__(self):
|
|
22
|
+
return '<{} "{}">'.format(self.__class__.__name__, self.error)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DecodeError(JoseError):
|
|
26
|
+
error: str = 'decode_error'
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class MissingAlgorithmError(JoseError):
|
|
30
|
+
error: str = 'missing_algorithm'
|
|
31
|
+
description: str = 'Missing "alg" value in header'
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class MissingEncryptionError(JoseError):
|
|
35
|
+
error: str = 'missing_encryption'
|
|
36
|
+
description: str = 'Missing "enc" value in header'
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class InvalidKeyManagementModeError(JoseError):
|
|
40
|
+
error: str = 'invalid_key_management_mode'
|
|
41
|
+
description: str = ''
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class BadSignatureError(JoseError):
|
|
45
|
+
error: str = 'bad_signature'
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class InvalidClaimError(JoseError):
|
|
49
|
+
error: str = 'invalid_claim'
|
|
50
|
+
|
|
51
|
+
def __init__(self, claim):
|
|
52
|
+
description = f'Invalid claim: "{claim}"'
|
|
53
|
+
super(InvalidClaimError, self).__init__(description=description)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class MissingClaimError(JoseError):
|
|
57
|
+
error: str = 'missing_claim'
|
|
58
|
+
|
|
59
|
+
def __init__(self, claim):
|
|
60
|
+
description = f'Missing claim: "{claim}"'
|
|
61
|
+
super(MissingClaimError, self).__init__(description=description)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class InsecureClaimError(JoseError):
|
|
65
|
+
error: str = 'insecure_claim'
|
|
66
|
+
|
|
67
|
+
def __init__(self, claim):
|
|
68
|
+
description = f'Insecure claim "{claim}"'
|
|
69
|
+
super(InsecureClaimError, self).__init__(description=description)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ExpiredTokenError(JoseError):
|
|
73
|
+
error: str = 'expired_token'
|
|
74
|
+
description: str = 'The token is expired'
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class InvalidTokenError(JoseError):
|
|
78
|
+
error: str = 'invalid_token'
|
|
79
|
+
description: str = 'The token is not valid yet'
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class InvalidTypeError(JoseError):
|
|
83
|
+
error: str = 'invalid_type'
|
|
84
|
+
description: str = 'The "typ" value in header is invalid'
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class InvalidPayloadError(JoseError):
|
|
88
|
+
error: str = 'invalid_payload'
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from typing import Optional, AnyStr
|
|
2
|
+
from .rfc7516 import types
|
|
3
|
+
from .rfc7516.types import Header, EncryptionData, Recipient
|
|
4
|
+
from .rfc7516.registry import (
|
|
5
|
+
JWERegistry,
|
|
6
|
+
default_registry,
|
|
7
|
+
)
|
|
8
|
+
from .rfc7516.message import perform_encrypt, perform_decrypt
|
|
9
|
+
from .rfc7516.compact import represent_compact, extract_compact
|
|
10
|
+
from .rfc7518.jwe_algs import JWE_ALG_MODELS
|
|
11
|
+
from .rfc7518.jwe_encs import JWE_ENC_MODELS
|
|
12
|
+
from .rfc7518.jwe_zips import JWE_ZIP_MODELS
|
|
13
|
+
from .jwk import KeyFlexible, guess_key
|
|
14
|
+
from .util import to_bytes
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
'types',
|
|
19
|
+
'JWERegistry',
|
|
20
|
+
'encrypt_compact',
|
|
21
|
+
'decrypt_compact',
|
|
22
|
+
'extract_compact',
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
def __register():
|
|
26
|
+
for _alg in JWE_ALG_MODELS:
|
|
27
|
+
JWERegistry.register(_alg)
|
|
28
|
+
|
|
29
|
+
for _enc in JWE_ENC_MODELS:
|
|
30
|
+
JWERegistry.register(_enc)
|
|
31
|
+
|
|
32
|
+
for _zip in JWE_ZIP_MODELS:
|
|
33
|
+
JWERegistry.register(_zip)
|
|
34
|
+
|
|
35
|
+
__register()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def encrypt_compact(
|
|
39
|
+
protected: Header,
|
|
40
|
+
payload: bytes,
|
|
41
|
+
public_key: KeyFlexible,
|
|
42
|
+
registry: Optional[JWERegistry]=None) -> bytes:
|
|
43
|
+
|
|
44
|
+
if registry is None:
|
|
45
|
+
registry = default_registry
|
|
46
|
+
|
|
47
|
+
obj = EncryptionData(protected, payload)
|
|
48
|
+
recipient = Recipient(obj)
|
|
49
|
+
key = guess_key(public_key, recipient)
|
|
50
|
+
recipient.recipient_key = key
|
|
51
|
+
obj.recipients.append(recipient)
|
|
52
|
+
obj.compact = True
|
|
53
|
+
perform_encrypt(obj, registry)
|
|
54
|
+
return represent_compact(obj)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def decrypt_compact(
|
|
58
|
+
value: AnyStr,
|
|
59
|
+
private_key: KeyFlexible,
|
|
60
|
+
registry: Optional[JWERegistry]=None) -> EncryptionData:
|
|
61
|
+
|
|
62
|
+
value = to_bytes(value)
|
|
63
|
+
obj = extract_compact(value)
|
|
64
|
+
if registry is None:
|
|
65
|
+
registry = default_registry
|
|
66
|
+
|
|
67
|
+
recipient = obj.recipients[0]
|
|
68
|
+
key = guess_key(private_key, recipient)
|
|
69
|
+
recipient.recipient_key = key
|
|
70
|
+
return perform_decrypt(obj, registry)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from typing import Callable, Union, Any, Protocol
|
|
3
|
+
from .rfc7517 import (
|
|
4
|
+
SymmetricKey,
|
|
5
|
+
AsymmetricKey,
|
|
6
|
+
Key,
|
|
7
|
+
KeySet,
|
|
8
|
+
JWK_REGISTRY,
|
|
9
|
+
)
|
|
10
|
+
from .rfc7517.keygen import (
|
|
11
|
+
import_key,
|
|
12
|
+
generate_key,
|
|
13
|
+
)
|
|
14
|
+
from .rfc7517 import types
|
|
15
|
+
from .rfc7518.oct_key import OctKey
|
|
16
|
+
from .rfc7518.rsa_key import RSAKey
|
|
17
|
+
from .rfc7518.ec_key import ECKey
|
|
18
|
+
from .rfc8037.okp_key import OKPKey
|
|
19
|
+
from .rfc8812 import register_secp256k1
|
|
20
|
+
from .registry import Header
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
KeyCallable = Callable[[Any, bool], Key]
|
|
24
|
+
KeyFlexible = Union[Key, KeySet, KeyCallable]
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
'types',
|
|
28
|
+
'JWK_REGISTRY',
|
|
29
|
+
'SymmetricKey',
|
|
30
|
+
'AsymmetricKey',
|
|
31
|
+
'Key',
|
|
32
|
+
'KeyCallable',
|
|
33
|
+
'KeyFlexible',
|
|
34
|
+
'OctKey',
|
|
35
|
+
'RSAKey',
|
|
36
|
+
'ECKey',
|
|
37
|
+
'OKPKey',
|
|
38
|
+
'KeySet',
|
|
39
|
+
'generate_key',
|
|
40
|
+
'import_key',
|
|
41
|
+
'guess_key',
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
# register thumbprint method
|
|
45
|
+
register_secp256k1()
|
|
46
|
+
|
|
47
|
+
# register all key types
|
|
48
|
+
JWK_REGISTRY[OctKey.key_type] = OctKey
|
|
49
|
+
JWK_REGISTRY[RSAKey.key_type] = RSAKey
|
|
50
|
+
JWK_REGISTRY[ECKey.key_type] = ECKey
|
|
51
|
+
JWK_REGISTRY[OKPKey.key_type] = OKPKey
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class GuestProtocol(Protocol):
|
|
55
|
+
def headers(self) -> Header:
|
|
56
|
+
...
|
|
57
|
+
|
|
58
|
+
def set_kid(self, kid: str):
|
|
59
|
+
...
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def guess_key(key: KeyFlexible, obj: GuestProtocol) -> Key:
|
|
63
|
+
"""Guess key from a various sources.
|
|
64
|
+
|
|
65
|
+
:param key: a very flexible key
|
|
66
|
+
:param obj: a protocol that has ``headers`` and ``set_kid`` methods
|
|
67
|
+
"""
|
|
68
|
+
if isinstance(key, (SymmetricKey, AsymmetricKey)):
|
|
69
|
+
return key
|
|
70
|
+
|
|
71
|
+
elif isinstance(key, KeySet):
|
|
72
|
+
headers = obj.headers()
|
|
73
|
+
kid = headers.get('kid')
|
|
74
|
+
|
|
75
|
+
if not kid:
|
|
76
|
+
# choose one key by random
|
|
77
|
+
key: Key = random.choice(key.keys)
|
|
78
|
+
# use side effect to add kid information
|
|
79
|
+
obj.set_kid(key.kid)
|
|
80
|
+
return key
|
|
81
|
+
return key.get_by_kid(kid)
|
|
82
|
+
|
|
83
|
+
elif callable(key):
|
|
84
|
+
return key(obj)
|
|
85
|
+
|
|
86
|
+
raise ValueError("Invalid key")
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
from typing import Optional, AnyStr, List, Union
|
|
2
|
+
from .rfc7515 import types
|
|
3
|
+
from .rfc7515.model import JWSAlgModel
|
|
4
|
+
from .rfc7515.registry import (
|
|
5
|
+
JWSRegistry,
|
|
6
|
+
default_registry,
|
|
7
|
+
)
|
|
8
|
+
from .rfc7515.compact import (
|
|
9
|
+
sign_compact,
|
|
10
|
+
extract_compact,
|
|
11
|
+
verify_compact,
|
|
12
|
+
)
|
|
13
|
+
from .rfc7515.json import (
|
|
14
|
+
sign_json,
|
|
15
|
+
verify_json,
|
|
16
|
+
extract_json,
|
|
17
|
+
)
|
|
18
|
+
from .rfc7515.types import (
|
|
19
|
+
Header,
|
|
20
|
+
HeaderMember,
|
|
21
|
+
HeaderDict,
|
|
22
|
+
SignatureData,
|
|
23
|
+
JSONSerialization,
|
|
24
|
+
)
|
|
25
|
+
from .rfc7518.jws_algs import JWS_ALGORITHMS
|
|
26
|
+
from .rfc8037.jws_eddsa import EdDSA
|
|
27
|
+
from .rfc8812 import ES256K
|
|
28
|
+
from .errors import BadSignatureError
|
|
29
|
+
from .jwk import Key, KeyFlexible, guess_key
|
|
30
|
+
from .util import to_bytes, urlsafe_b64encode
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
'types',
|
|
34
|
+
'JWSAlgModel',
|
|
35
|
+
'JWSRegistry',
|
|
36
|
+
'SignatureData',
|
|
37
|
+
|
|
38
|
+
'serialize_compact',
|
|
39
|
+
'deserialize_compact',
|
|
40
|
+
'extract_compact',
|
|
41
|
+
'validate_compact',
|
|
42
|
+
|
|
43
|
+
'serialize_json',
|
|
44
|
+
'deserialize_json',
|
|
45
|
+
'extract_json',
|
|
46
|
+
'validate_json',
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
# register supported alg models
|
|
50
|
+
def __register():
|
|
51
|
+
# register alg in RFC7518
|
|
52
|
+
for _alg in JWS_ALGORITHMS:
|
|
53
|
+
JWSRegistry.register(_alg)
|
|
54
|
+
# register alg in RFC8037
|
|
55
|
+
JWSRegistry.register(EdDSA)
|
|
56
|
+
# register alg in RFC8812
|
|
57
|
+
JWSRegistry.register(ES256K)
|
|
58
|
+
|
|
59
|
+
__register()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def serialize_compact(
|
|
63
|
+
header: Header,
|
|
64
|
+
payload: bytes,
|
|
65
|
+
key: KeyFlexible,
|
|
66
|
+
registry: Optional[JWSRegistry]=None) -> bytes:
|
|
67
|
+
"""Generate a JWS Compact Serialization. The JWS Compact Serialization
|
|
68
|
+
represents digitally signed or MACed content as a compact, URL-safe
|
|
69
|
+
string, per Section 7.1.
|
|
70
|
+
|
|
71
|
+
.. code-block:: text
|
|
72
|
+
|
|
73
|
+
BASE64URL(UTF8(JWS Protected Header)) || '.' ||
|
|
74
|
+
BASE64URL(JWS Payload) || '.' ||
|
|
75
|
+
BASE64URL(JWS Signature)
|
|
76
|
+
|
|
77
|
+
:param header: protected header part of the JWS, in dict
|
|
78
|
+
:param payload: payload data of the JWS, in bytes
|
|
79
|
+
:param key: a flexible private key to sign the signature
|
|
80
|
+
:param registry: a JWSRegistry to use
|
|
81
|
+
:return: JWS in bytes
|
|
82
|
+
|
|
83
|
+
.. note:: The returned value is in bytes
|
|
84
|
+
"""
|
|
85
|
+
if registry is None:
|
|
86
|
+
registry = default_registry
|
|
87
|
+
|
|
88
|
+
registry.check_header(header)
|
|
89
|
+
member = HeaderMember(header)
|
|
90
|
+
obj = SignatureData([member], payload)
|
|
91
|
+
obj.compact = True
|
|
92
|
+
alg: JWSAlgModel = registry.get_alg(header['alg'])
|
|
93
|
+
key: Key = guess_key(key, member)
|
|
94
|
+
return sign_compact(obj, alg, key)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def validate_compact(
|
|
98
|
+
obj: SignatureData,
|
|
99
|
+
key: KeyFlexible,
|
|
100
|
+
registry: Optional[JWSRegistry]=None):
|
|
101
|
+
"""Validate the JWS Compact Serialization with the given key.
|
|
102
|
+
This method is usually used together with ``extract_compact``.
|
|
103
|
+
|
|
104
|
+
:param obj: object of the JWS Compact Serialization
|
|
105
|
+
:param key: a flexible public key to verify the signature
|
|
106
|
+
:param registry: a JWSRegistry to use
|
|
107
|
+
:raise: ValueError or BadSignatureError
|
|
108
|
+
"""
|
|
109
|
+
if registry is None:
|
|
110
|
+
registry = default_registry
|
|
111
|
+
member = obj.members[0]
|
|
112
|
+
alg: JWSAlgModel = registry.get_alg(member.protected['alg'])
|
|
113
|
+
key: Key = guess_key(key, member)
|
|
114
|
+
if not verify_compact(obj, alg, key):
|
|
115
|
+
raise BadSignatureError()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def deserialize_compact(
|
|
119
|
+
value: AnyStr,
|
|
120
|
+
key: KeyFlexible,
|
|
121
|
+
registry: Optional[JWSRegistry]=None) -> SignatureData:
|
|
122
|
+
"""Extract and validate the JWS (in string) with the given key.
|
|
123
|
+
|
|
124
|
+
:param value: a string (or bytes) of the JWS
|
|
125
|
+
:param key: a flexible public key to verify the signature
|
|
126
|
+
:param registry: a JWSRegistry to use
|
|
127
|
+
:return: object of the JWS Compact Serialization
|
|
128
|
+
"""
|
|
129
|
+
obj = extract_compact(to_bytes(value))
|
|
130
|
+
validate_compact(obj, key, registry)
|
|
131
|
+
return obj
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def serialize_json(
|
|
135
|
+
members: Union[HeaderDict, List[HeaderDict]],
|
|
136
|
+
payload: bytes,
|
|
137
|
+
key: KeyFlexible,
|
|
138
|
+
registry: Optional[JWSRegistry]=None) -> JSONSerialization:
|
|
139
|
+
if registry is None:
|
|
140
|
+
registry = default_registry
|
|
141
|
+
|
|
142
|
+
if isinstance(members, dict):
|
|
143
|
+
flatten = True
|
|
144
|
+
registry.check_header(members['protected'])
|
|
145
|
+
members = [members]
|
|
146
|
+
else:
|
|
147
|
+
flatten = False
|
|
148
|
+
for member in members:
|
|
149
|
+
registry.check_header(member['protected'])
|
|
150
|
+
|
|
151
|
+
members = [HeaderMember(**member) for member in members]
|
|
152
|
+
obj = SignatureData(members, payload)
|
|
153
|
+
obj.segments['payload'] = urlsafe_b64encode(payload)
|
|
154
|
+
obj.flatten = flatten
|
|
155
|
+
|
|
156
|
+
find_key = lambda d: guess_key(key, d)
|
|
157
|
+
return sign_json(obj, registry.get_alg, find_key)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def validate_json(
|
|
161
|
+
obj: SignatureData,
|
|
162
|
+
key: KeyFlexible,
|
|
163
|
+
registry: Optional[JWSRegistry]=None):
|
|
164
|
+
"""Validate the JWS JSON Serialization with the given key.
|
|
165
|
+
This method is usually used together with ``extract_json``.
|
|
166
|
+
|
|
167
|
+
:param obj: object of the JWS JSON Serialization
|
|
168
|
+
:param key: a flexible public key to verify the signature
|
|
169
|
+
:param registry: a JWSRegistry to use
|
|
170
|
+
:raise: ValueError or BadSignatureError
|
|
171
|
+
"""
|
|
172
|
+
if registry is None:
|
|
173
|
+
registry = default_registry
|
|
174
|
+
find_key = lambda d: guess_key(key, d)
|
|
175
|
+
if not verify_json(obj, registry.get_alg, find_key):
|
|
176
|
+
raise BadSignatureError()
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def deserialize_json(
|
|
180
|
+
value: JSONSerialization,
|
|
181
|
+
key: KeyFlexible,
|
|
182
|
+
registry: Optional[JWSRegistry]=None) -> SignatureData:
|
|
183
|
+
"""Extract and validate the JWS (in string) with the given key.
|
|
184
|
+
|
|
185
|
+
:param value: a dict of the JSON signature
|
|
186
|
+
:param key: a flexible public key to verify the signature
|
|
187
|
+
:param registry: a JWSRegistry to use
|
|
188
|
+
:return: object of the SignatureData
|
|
189
|
+
"""
|
|
190
|
+
obj = extract_json(value)
|
|
191
|
+
validate_json(obj, key, registry)
|
|
192
|
+
return obj
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from typing import Optional, AnyStr
|
|
2
|
+
from .rfc7515.compact import extract_compact
|
|
3
|
+
from .rfc7519.claims import Claims, convert_claims
|
|
4
|
+
from .rfc7519.validators import ClaimsOption, JWTClaimsRequests
|
|
5
|
+
from .rfc7519.registry import JWTRegistry, default_registry
|
|
6
|
+
from .jws import serialize_compact, validate_compact
|
|
7
|
+
from .jwk import KeyFlexible
|
|
8
|
+
from .errors import InvalidTypeError, InvalidPayloadError
|
|
9
|
+
from .util import to_bytes
|
|
10
|
+
from .registry import Header
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
'Header',
|
|
15
|
+
'Claims',
|
|
16
|
+
'Token',
|
|
17
|
+
'ClaimsOption',
|
|
18
|
+
'JWTRegistry',
|
|
19
|
+
'JWTClaimsRequests',
|
|
20
|
+
'encode',
|
|
21
|
+
'decode',
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Token:
|
|
26
|
+
def __init__(self, header: Header, claims: Claims):
|
|
27
|
+
self.header = header
|
|
28
|
+
self.claims = claims
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def encode(
|
|
32
|
+
header: Header,
|
|
33
|
+
claims: Claims,
|
|
34
|
+
key: KeyFlexible,
|
|
35
|
+
registry: Optional[JWTRegistry]=None) -> str:
|
|
36
|
+
"""Encode a JSON Web Token with the given header, and claims.
|
|
37
|
+
|
|
38
|
+
:param header: A dict of the JWT header
|
|
39
|
+
:param claims: A dict of the JWT claims to be encoded
|
|
40
|
+
:param key: key used to sign the signature
|
|
41
|
+
:param registry: a JWTRegistry to use
|
|
42
|
+
"""
|
|
43
|
+
# add ``typ`` in header
|
|
44
|
+
header['typ'] = 'JWT'
|
|
45
|
+
payload = convert_claims(claims)
|
|
46
|
+
if registry is None:
|
|
47
|
+
registry = default_registry
|
|
48
|
+
result = serialize_compact(header, payload, key, registry)
|
|
49
|
+
return result.decode('utf-8')
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def decode(
|
|
53
|
+
value: AnyStr,
|
|
54
|
+
key: KeyFlexible,
|
|
55
|
+
registry: Optional[JWTRegistry]=None) -> Token:
|
|
56
|
+
"""Decode the JSON Web Token string with the given key, and validate
|
|
57
|
+
it with the claims requests. This method is a combination of the
|
|
58
|
+
:function:`extract` and :function:`validate`.
|
|
59
|
+
|
|
60
|
+
:param value: text of the JWT
|
|
61
|
+
:param key: key used to verify the signature
|
|
62
|
+
:param registry: a JWTRegistry to use
|
|
63
|
+
:raise: BadSignatureError
|
|
64
|
+
"""
|
|
65
|
+
obj = extract_compact(to_bytes(value))
|
|
66
|
+
try:
|
|
67
|
+
token = Token(obj.headers(), obj.claims)
|
|
68
|
+
except ValueError:
|
|
69
|
+
raise InvalidPayloadError('Payload should be a JSON dict')
|
|
70
|
+
if registry is None:
|
|
71
|
+
registry = default_registry
|
|
72
|
+
|
|
73
|
+
typ = token.header.get('typ')
|
|
74
|
+
if typ and typ != 'JWT':
|
|
75
|
+
raise InvalidTypeError()
|
|
76
|
+
|
|
77
|
+
registry.check_claims(token.claims)
|
|
78
|
+
validate_compact(obj, key, registry)
|
|
79
|
+
return token
|