Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/jwt/api_jwk.py: 31%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from __future__ import annotations
3import json
4import time
5from typing import Any, Iterator
7from .algorithms import get_default_algorithms, has_crypto, requires_cryptography
8from .exceptions import (
9 InvalidKeyError,
10 MissingCryptographyError,
11 PyJWKError,
12 PyJWKSetError,
13 PyJWTError,
14)
15from .types import JWKDict
18class PyJWK:
19 def __init__(self, jwk_data: JWKDict, algorithm: str | None = None) -> None:
20 """A class that represents a `JSON Web Key <https://www.rfc-editor.org/rfc/rfc7517>`_.
22 :param jwk_data: The decoded JWK data.
23 :type jwk_data: dict[str, typing.Any]
24 :param algorithm: The key algorithm. If not specified, the key's ``alg`` will be used.
25 :type algorithm: str or None
26 :raises InvalidKeyError: If the key type (``kty``) is not found or unsupported, or if the curve (``crv``) is not found or unsupported.
27 :raises MissingCryptographyError: If the algorithm requires ``cryptography`` to be installed and it is not available.
28 :raises PyJWKError: If unable to find an algorithm for the key.
29 """
30 self._algorithms = get_default_algorithms()
31 self._jwk_data = jwk_data
33 kty = self._jwk_data.get("kty", None)
34 if not kty:
35 raise InvalidKeyError(f"kty is not found: {self._jwk_data}")
37 if not algorithm and isinstance(self._jwk_data, dict):
38 algorithm = self._jwk_data.get("alg", None)
40 if not algorithm:
41 # Determine alg with kty (and crv).
42 crv = self._jwk_data.get("crv", None)
43 if kty == "EC":
44 if crv == "P-256" or not crv:
45 algorithm = "ES256"
46 elif crv == "P-384":
47 algorithm = "ES384"
48 elif crv == "P-521":
49 algorithm = "ES512"
50 elif crv == "secp256k1":
51 algorithm = "ES256K"
52 else:
53 raise InvalidKeyError(f"Unsupported crv: {crv}")
54 elif kty == "RSA":
55 algorithm = "RS256"
56 elif kty == "oct":
57 algorithm = "HS256"
58 elif kty == "OKP":
59 if not crv:
60 raise InvalidKeyError(f"crv is not found: {self._jwk_data}")
61 if crv == "Ed25519":
62 algorithm = "EdDSA"
63 else:
64 raise InvalidKeyError(f"Unsupported crv: {crv}")
65 else:
66 raise InvalidKeyError(f"Unsupported kty: {kty}")
68 if not has_crypto and algorithm in requires_cryptography:
69 raise MissingCryptographyError(
70 f"{algorithm} requires 'cryptography' to be installed."
71 )
73 self.algorithm_name = algorithm
75 if algorithm in self._algorithms:
76 self.Algorithm = self._algorithms[algorithm]
77 else:
78 raise PyJWKError(f"Unable to find an algorithm for key: {self._jwk_data}")
80 self.key = self.Algorithm.from_jwk(self._jwk_data)
82 @staticmethod
83 def from_dict(obj: JWKDict, algorithm: str | None = None) -> PyJWK:
84 """Creates a :class:`PyJWK` object from a JSON-like dictionary.
86 :param obj: The JWK data, as a dictionary
87 :type obj: dict[str, typing.Any]
88 :param algorithm: The key algorithm. If not specified, the key's ``alg`` will be used.
89 :type algorithm: str or None
90 :rtype: PyJWK
91 """
92 return PyJWK(obj, algorithm)
94 @staticmethod
95 def from_json(data: str, algorithm: None = None) -> PyJWK:
96 """Create a :class:`PyJWK` object from a JSON string.
97 Implicitly calls :meth:`PyJWK.from_dict()`.
99 :param str data: The JWK data, as a JSON string.
100 :param algorithm: The key algorithm. If not specific, the key's ``alg`` will be used.
101 :type algorithm: str or None
103 :rtype: PyJWK
104 """
105 obj = json.loads(data)
106 return PyJWK.from_dict(obj, algorithm)
108 @property
109 def key_type(self) -> str | None:
110 """The `kty` property from the JWK.
112 :rtype: str or None
113 """
114 return self._jwk_data.get("kty", None)
116 @property
117 def key_id(self) -> str | None:
118 """The `kid` property from the JWK.
120 :rtype: str or None
121 """
122 return self._jwk_data.get("kid", None)
124 @property
125 def public_key_use(self) -> str | None:
126 """The `use` property from the JWK.
128 :rtype: str or None
129 """
130 return self._jwk_data.get("use", None)
133class PyJWKSet:
134 def __init__(self, keys: list[JWKDict]) -> None:
135 self.keys = []
137 if not keys:
138 raise PyJWKSetError("The JWK Set did not contain any keys")
140 if not isinstance(keys, list):
141 raise PyJWKSetError("Invalid JWK Set value")
143 for key in keys:
144 try:
145 self.keys.append(PyJWK(key))
146 except PyJWTError as error:
147 if isinstance(error, MissingCryptographyError):
148 raise error
149 # skip unusable keys
150 continue
152 if len(self.keys) == 0:
153 raise PyJWKSetError(
154 "The JWK Set did not contain any usable keys. Perhaps 'cryptography' is not installed?"
155 )
157 @staticmethod
158 def from_dict(obj: dict[str, Any]) -> PyJWKSet:
159 keys = obj.get("keys", [])
160 return PyJWKSet(keys)
162 @staticmethod
163 def from_json(data: str) -> PyJWKSet:
164 obj = json.loads(data)
165 return PyJWKSet.from_dict(obj)
167 def __getitem__(self, kid: str) -> PyJWK:
168 for key in self.keys:
169 if key.key_id == kid:
170 return key
171 raise KeyError(f"keyset has no key for kid: {kid}")
173 def __iter__(self) -> Iterator[PyJWK]:
174 return iter(self.keys)
177class PyJWTSetWithTimestamp:
178 def __init__(self, jwk_set: PyJWKSet):
179 self.jwk_set = jwk_set
180 self.timestamp = time.monotonic()
182 def get_jwk_set(self) -> PyJWKSet:
183 return self.jwk_set
185 def get_timestamp(self) -> float:
186 return self.timestamp