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 collections.abc import Iterator
6from typing import Any
8from .algorithms import get_default_algorithms, has_crypto, requires_cryptography
9from .exceptions import (
10 InvalidKeyError,
11 MissingCryptographyError,
12 PyJWKError,
13 PyJWKSetError,
14 PyJWTError,
15)
16from .types import JWKDict
19class PyJWK:
20 def __init__(self, jwk_data: JWKDict, algorithm: str | None = None) -> None:
21 """A class that represents a `JSON Web Key <https://www.rfc-editor.org/rfc/rfc7517>`_.
23 :param jwk_data: The decoded JWK data.
24 :type jwk_data: dict[str, typing.Any]
25 :param algorithm: The key algorithm. If not specified, the key's ``alg`` will be used.
26 :type algorithm: str or None
27 :raises InvalidKeyError: If the key type (``kty``) is not found or unsupported, or if the curve (``crv``) is not found or unsupported.
28 :raises MissingCryptographyError: If the algorithm requires ``cryptography`` to be installed and it is not available.
29 :raises PyJWKError: If unable to find an algorithm for the key.
30 """
31 self._algorithms = get_default_algorithms()
32 self._jwk_data = jwk_data
34 kty = self._jwk_data.get("kty", None)
35 if not kty:
36 raise InvalidKeyError(f"kty is not found: {self._jwk_data}")
38 if not algorithm and isinstance(self._jwk_data, dict):
39 algorithm = self._jwk_data.get("alg", None)
41 if not algorithm:
42 # Determine alg with kty (and crv).
43 crv = self._jwk_data.get("crv", None)
44 if kty == "EC":
45 if crv == "P-256" or not crv:
46 algorithm = "ES256"
47 elif crv == "P-384":
48 algorithm = "ES384"
49 elif crv == "P-521":
50 algorithm = "ES512"
51 elif crv == "secp256k1":
52 algorithm = "ES256K"
53 else:
54 raise InvalidKeyError(f"Unsupported crv: {crv}")
55 elif kty == "RSA":
56 algorithm = "RS256"
57 elif kty == "oct":
58 algorithm = "HS256"
59 elif kty == "OKP":
60 if not crv:
61 raise InvalidKeyError(f"crv is not found: {self._jwk_data}")
62 if crv == "Ed25519":
63 algorithm = "EdDSA"
64 else:
65 raise InvalidKeyError(f"Unsupported crv: {crv}")
66 else:
67 raise InvalidKeyError(f"Unsupported kty: {kty}")
69 if not has_crypto and algorithm in requires_cryptography:
70 raise MissingCryptographyError(
71 f"{algorithm} requires 'cryptography' to be installed."
72 )
74 self.algorithm_name = algorithm
76 if algorithm in self._algorithms:
77 self.Algorithm = self._algorithms[algorithm]
78 else:
79 raise PyJWKError(f"Unable to find an algorithm for key: {self._jwk_data}")
81 self.key = self.Algorithm.from_jwk(self._jwk_data)
83 @staticmethod
84 def from_dict(obj: JWKDict, algorithm: str | None = None) -> PyJWK:
85 """Creates a :class:`PyJWK` object from a JSON-like dictionary.
87 :param obj: The JWK data, as a dictionary
88 :type obj: dict[str, typing.Any]
89 :param algorithm: The key algorithm. If not specified, the key's ``alg`` will be used.
90 :type algorithm: str or None
91 :rtype: PyJWK
92 """
93 return PyJWK(obj, algorithm)
95 @staticmethod
96 def from_json(data: str, algorithm: None = None) -> PyJWK:
97 """Create a :class:`PyJWK` object from a JSON string.
98 Implicitly calls :meth:`PyJWK.from_dict()`.
100 :param str data: The JWK data, as a JSON string.
101 :param algorithm: The key algorithm. If not specific, the key's ``alg`` will be used.
102 :type algorithm: str or None
104 :rtype: PyJWK
105 """
106 obj = json.loads(data)
107 return PyJWK.from_dict(obj, algorithm)
109 @property
110 def key_type(self) -> str | None:
111 """The `kty` property from the JWK.
113 :rtype: str or None
114 """
115 return self._jwk_data.get("kty", None)
117 @property
118 def key_id(self) -> str | None:
119 """The `kid` property from the JWK.
121 :rtype: str or None
122 """
123 return self._jwk_data.get("kid", None)
125 @property
126 def public_key_use(self) -> str | None:
127 """The `use` property from the JWK.
129 :rtype: str or None
130 """
131 return self._jwk_data.get("use", None)
134class PyJWKSet:
135 def __init__(self, keys: list[JWKDict]) -> None:
136 self.keys = []
138 if not keys:
139 raise PyJWKSetError("The JWK Set did not contain any keys")
141 if not isinstance(keys, list):
142 raise PyJWKSetError("Invalid JWK Set value")
144 for key in keys:
145 try:
146 self.keys.append(PyJWK(key))
147 except PyJWTError as error:
148 if isinstance(error, MissingCryptographyError):
149 raise error
150 # skip unusable keys
151 continue
153 if len(self.keys) == 0:
154 raise PyJWKSetError(
155 "The JWK Set did not contain any usable keys. Perhaps 'cryptography' is not installed?"
156 )
158 @staticmethod
159 def from_dict(obj: dict[str, Any]) -> PyJWKSet:
160 keys = obj.get("keys", [])
161 return PyJWKSet(keys)
163 @staticmethod
164 def from_json(data: str) -> PyJWKSet:
165 obj = json.loads(data)
166 return PyJWKSet.from_dict(obj)
168 def __getitem__(self, kid: str) -> PyJWK:
169 for key in self.keys:
170 if key.key_id == kid:
171 return key
172 raise KeyError(f"keyset has no key for kid: {kid}")
174 def __iter__(self) -> Iterator[PyJWK]:
175 return iter(self.keys)
178class PyJWTSetWithTimestamp:
179 def __init__(self, jwk_set: PyJWKSet):
180 self.jwk_set = jwk_set
181 self.timestamp = time.monotonic()
183 def get_jwk_set(self) -> PyJWKSet:
184 return self.jwk_set
186 def get_timestamp(self) -> float:
187 return self.timestamp