Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/jwt/api_jwk.py: 30%

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

99 statements  

1from __future__ import annotations 

2 

3import json 

4import time 

5from typing import Any 

6 

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 

16 

17 

18class PyJWK: 

19 def __init__(self, jwk_data: JWKDict, algorithm: str | None = None) -> None: 

20 self._algorithms = get_default_algorithms() 

21 self._jwk_data = jwk_data 

22 

23 kty = self._jwk_data.get("kty", None) 

24 if not kty: 

25 raise InvalidKeyError(f"kty is not found: {self._jwk_data}") 

26 

27 if not algorithm and isinstance(self._jwk_data, dict): 

28 algorithm = self._jwk_data.get("alg", None) 

29 

30 if not algorithm: 

31 # Determine alg with kty (and crv). 

32 crv = self._jwk_data.get("crv", None) 

33 if kty == "EC": 

34 if crv == "P-256" or not crv: 

35 algorithm = "ES256" 

36 elif crv == "P-384": 

37 algorithm = "ES384" 

38 elif crv == "P-521": 

39 algorithm = "ES512" 

40 elif crv == "secp256k1": 

41 algorithm = "ES256K" 

42 else: 

43 raise InvalidKeyError(f"Unsupported crv: {crv}") 

44 elif kty == "RSA": 

45 algorithm = "RS256" 

46 elif kty == "oct": 

47 algorithm = "HS256" 

48 elif kty == "OKP": 

49 if not crv: 

50 raise InvalidKeyError(f"crv is not found: {self._jwk_data}") 

51 if crv == "Ed25519": 

52 algorithm = "EdDSA" 

53 else: 

54 raise InvalidKeyError(f"Unsupported crv: {crv}") 

55 else: 

56 raise InvalidKeyError(f"Unsupported kty: {kty}") 

57 

58 if not has_crypto and algorithm in requires_cryptography: 

59 raise MissingCryptographyError( 

60 f"{algorithm} requires 'cryptography' to be installed." 

61 ) 

62 

63 self.algorithm_name = algorithm 

64 

65 if algorithm in self._algorithms: 

66 self.Algorithm = self._algorithms[algorithm] 

67 else: 

68 raise PyJWKError(f"Unable to find an algorithm for key: {self._jwk_data}") 

69 

70 self.key = self.Algorithm.from_jwk(self._jwk_data) 

71 

72 @staticmethod 

73 def from_dict(obj: JWKDict, algorithm: str | None = None) -> PyJWK: 

74 return PyJWK(obj, algorithm) 

75 

76 @staticmethod 

77 def from_json(data: str, algorithm: None = None) -> PyJWK: 

78 obj = json.loads(data) 

79 return PyJWK.from_dict(obj, algorithm) 

80 

81 @property 

82 def key_type(self) -> str | None: 

83 return self._jwk_data.get("kty", None) 

84 

85 @property 

86 def key_id(self) -> str | None: 

87 return self._jwk_data.get("kid", None) 

88 

89 @property 

90 def public_key_use(self) -> str | None: 

91 return self._jwk_data.get("use", None) 

92 

93 

94class PyJWKSet: 

95 def __init__(self, keys: list[JWKDict]) -> None: 

96 self.keys = [] 

97 

98 if not keys: 

99 raise PyJWKSetError("The JWK Set did not contain any keys") 

100 

101 if not isinstance(keys, list): 

102 raise PyJWKSetError("Invalid JWK Set value") 

103 

104 for key in keys: 

105 try: 

106 self.keys.append(PyJWK(key)) 

107 except PyJWTError as error: 

108 if isinstance(error, MissingCryptographyError): 

109 raise error 

110 # skip unusable keys 

111 continue 

112 

113 if len(self.keys) == 0: 

114 raise PyJWKSetError( 

115 "The JWK Set did not contain any usable keys. Perhaps 'cryptography' is not installed?" 

116 ) 

117 

118 @staticmethod 

119 def from_dict(obj: dict[str, Any]) -> PyJWKSet: 

120 keys = obj.get("keys", []) 

121 return PyJWKSet(keys) 

122 

123 @staticmethod 

124 def from_json(data: str) -> PyJWKSet: 

125 obj = json.loads(data) 

126 return PyJWKSet.from_dict(obj) 

127 

128 def __getitem__(self, kid: str) -> PyJWK: 

129 for key in self.keys: 

130 if key.key_id == kid: 

131 return key 

132 raise KeyError(f"keyset has no key for kid: {kid}") 

133 

134 

135class PyJWTSetWithTimestamp: 

136 def __init__(self, jwk_set: PyJWKSet): 

137 self.jwk_set = jwk_set 

138 self.timestamp = time.monotonic() 

139 

140 def get_jwk_set(self) -> PyJWKSet: 

141 return self.jwk_set 

142 

143 def get_timestamp(self) -> float: 

144 return self.timestamp