Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/itsdangerous/signer.py: 32%

104 statements  

« prev     ^ index     » next       coverage.py v7.0.1, created at 2022-12-25 06:11 +0000

1import hashlib 

2import hmac 

3import typing as _t 

4 

5from .encoding import _base64_alphabet 

6from .encoding import base64_decode 

7from .encoding import base64_encode 

8from .encoding import want_bytes 

9from .exc import BadSignature 

10 

11_t_str_bytes = _t.Union[str, bytes] 

12_t_opt_str_bytes = _t.Optional[_t_str_bytes] 

13_t_secret_key = _t.Union[_t.Iterable[_t_str_bytes], _t_str_bytes] 

14 

15 

16class SigningAlgorithm: 

17 """Subclasses must implement :meth:`get_signature` to provide 

18 signature generation functionality. 

19 """ 

20 

21 def get_signature(self, key: bytes, value: bytes) -> bytes: 

22 """Returns the signature for the given key and value.""" 

23 raise NotImplementedError() 

24 

25 def verify_signature(self, key: bytes, value: bytes, sig: bytes) -> bool: 

26 """Verifies the given signature matches the expected 

27 signature. 

28 """ 

29 return hmac.compare_digest(sig, self.get_signature(key, value)) 

30 

31 

32class NoneAlgorithm(SigningAlgorithm): 

33 """Provides an algorithm that does not perform any signing and 

34 returns an empty signature. 

35 """ 

36 

37 def get_signature(self, key: bytes, value: bytes) -> bytes: 

38 return b"" 

39 

40 

41class HMACAlgorithm(SigningAlgorithm): 

42 """Provides signature generation using HMACs.""" 

43 

44 #: The digest method to use with the MAC algorithm. This defaults to 

45 #: SHA1, but can be changed to any other function in the hashlib 

46 #: module. 

47 default_digest_method: _t.Any = staticmethod(hashlib.sha1) 

48 

49 def __init__(self, digest_method: _t.Any = None): 

50 if digest_method is None: 

51 digest_method = self.default_digest_method 

52 

53 self.digest_method: _t.Any = digest_method 

54 

55 def get_signature(self, key: bytes, value: bytes) -> bytes: 

56 mac = hmac.new(key, msg=value, digestmod=self.digest_method) 

57 return mac.digest() 

58 

59 

60def _make_keys_list(secret_key: _t_secret_key) -> _t.List[bytes]: 

61 if isinstance(secret_key, (str, bytes)): 

62 return [want_bytes(secret_key)] 

63 

64 return [want_bytes(s) for s in secret_key] 

65 

66 

67class Signer: 

68 """A signer securely signs bytes, then unsigns them to verify that 

69 the value hasn't been changed. 

70 

71 The secret key should be a random string of ``bytes`` and should not 

72 be saved to code or version control. Different salts should be used 

73 to distinguish signing in different contexts. See :doc:`/concepts` 

74 for information about the security of the secret key and salt. 

75 

76 :param secret_key: The secret key to sign and verify with. Can be a 

77 list of keys, oldest to newest, to support key rotation. 

78 :param salt: Extra key to combine with ``secret_key`` to distinguish 

79 signatures in different contexts. 

80 :param sep: Separator between the signature and value. 

81 :param key_derivation: How to derive the signing key from the secret 

82 key and salt. Possible values are ``concat``, ``django-concat``, 

83 or ``hmac``. Defaults to :attr:`default_key_derivation`, which 

84 defaults to ``django-concat``. 

85 :param digest_method: Hash function to use when generating the HMAC 

86 signature. Defaults to :attr:`default_digest_method`, which 

87 defaults to :func:`hashlib.sha1`. Note that the security of the 

88 hash alone doesn't apply when used intermediately in HMAC. 

89 :param algorithm: A :class:`SigningAlgorithm` instance to use 

90 instead of building a default :class:`HMACAlgorithm` with the 

91 ``digest_method``. 

92 

93 .. versionchanged:: 2.0 

94 Added support for key rotation by passing a list to 

95 ``secret_key``. 

96 

97 .. versionchanged:: 0.18 

98 ``algorithm`` was added as an argument to the class constructor. 

99 

100 .. versionchanged:: 0.14 

101 ``key_derivation`` and ``digest_method`` were added as arguments 

102 to the class constructor. 

103 """ 

104 

105 #: The default digest method to use for the signer. The default is 

106 #: :func:`hashlib.sha1`, but can be changed to any :mod:`hashlib` or 

107 #: compatible object. Note that the security of the hash alone 

108 #: doesn't apply when used intermediately in HMAC. 

109 #: 

110 #: .. versionadded:: 0.14 

111 default_digest_method: _t.Any = staticmethod(hashlib.sha1) 

112 

113 #: The default scheme to use to derive the signing key from the 

114 #: secret key and salt. The default is ``django-concat``. Possible 

115 #: values are ``concat``, ``django-concat``, and ``hmac``. 

116 #: 

117 #: .. versionadded:: 0.14 

118 default_key_derivation: str = "django-concat" 

119 

120 def __init__( 

121 self, 

122 secret_key: _t_secret_key, 

123 salt: _t_opt_str_bytes = b"itsdangerous.Signer", 

124 sep: _t_str_bytes = b".", 

125 key_derivation: _t.Optional[str] = None, 

126 digest_method: _t.Optional[_t.Any] = None, 

127 algorithm: _t.Optional[SigningAlgorithm] = None, 

128 ): 

129 #: The list of secret keys to try for verifying signatures, from 

130 #: oldest to newest. The newest (last) key is used for signing. 

131 #: 

132 #: This allows a key rotation system to keep a list of allowed 

133 #: keys and remove expired ones. 

134 self.secret_keys: _t.List[bytes] = _make_keys_list(secret_key) 

135 self.sep: bytes = want_bytes(sep) 

136 

137 if self.sep in _base64_alphabet: 

138 raise ValueError( 

139 "The given separator cannot be used because it may be" 

140 " contained in the signature itself. ASCII letters," 

141 " digits, and '-_=' must not be used." 

142 ) 

143 

144 if salt is not None: 

145 salt = want_bytes(salt) 

146 else: 

147 salt = b"itsdangerous.Signer" 

148 

149 self.salt = salt 

150 

151 if key_derivation is None: 

152 key_derivation = self.default_key_derivation 

153 

154 self.key_derivation: str = key_derivation 

155 

156 if digest_method is None: 

157 digest_method = self.default_digest_method 

158 

159 self.digest_method: _t.Any = digest_method 

160 

161 if algorithm is None: 

162 algorithm = HMACAlgorithm(self.digest_method) 

163 

164 self.algorithm: SigningAlgorithm = algorithm 

165 

166 @property 

167 def secret_key(self) -> bytes: 

168 """The newest (last) entry in the :attr:`secret_keys` list. This 

169 is for compatibility from before key rotation support was added. 

170 """ 

171 return self.secret_keys[-1] 

172 

173 def derive_key(self, secret_key: _t_opt_str_bytes = None) -> bytes: 

174 """This method is called to derive the key. The default key 

175 derivation choices can be overridden here. Key derivation is not 

176 intended to be used as a security method to make a complex key 

177 out of a short password. Instead you should use large random 

178 secret keys. 

179 

180 :param secret_key: A specific secret key to derive from. 

181 Defaults to the last item in :attr:`secret_keys`. 

182 

183 .. versionchanged:: 2.0 

184 Added the ``secret_key`` parameter. 

185 """ 

186 if secret_key is None: 

187 secret_key = self.secret_keys[-1] 

188 else: 

189 secret_key = want_bytes(secret_key) 

190 

191 if self.key_derivation == "concat": 

192 return _t.cast(bytes, self.digest_method(self.salt + secret_key).digest()) 

193 elif self.key_derivation == "django-concat": 

194 return _t.cast( 

195 bytes, self.digest_method(self.salt + b"signer" + secret_key).digest() 

196 ) 

197 elif self.key_derivation == "hmac": 

198 mac = hmac.new(secret_key, digestmod=self.digest_method) 

199 mac.update(self.salt) 

200 return mac.digest() 

201 elif self.key_derivation == "none": 

202 return secret_key 

203 else: 

204 raise TypeError("Unknown key derivation method") 

205 

206 def get_signature(self, value: _t_str_bytes) -> bytes: 

207 """Returns the signature for the given value.""" 

208 value = want_bytes(value) 

209 key = self.derive_key() 

210 sig = self.algorithm.get_signature(key, value) 

211 return base64_encode(sig) 

212 

213 def sign(self, value: _t_str_bytes) -> bytes: 

214 """Signs the given string.""" 

215 value = want_bytes(value) 

216 return value + self.sep + self.get_signature(value) 

217 

218 def verify_signature(self, value: _t_str_bytes, sig: _t_str_bytes) -> bool: 

219 """Verifies the signature for the given value.""" 

220 try: 

221 sig = base64_decode(sig) 

222 except Exception: 

223 return False 

224 

225 value = want_bytes(value) 

226 

227 for secret_key in reversed(self.secret_keys): 

228 key = self.derive_key(secret_key) 

229 

230 if self.algorithm.verify_signature(key, value, sig): 

231 return True 

232 

233 return False 

234 

235 def unsign(self, signed_value: _t_str_bytes) -> bytes: 

236 """Unsigns the given string.""" 

237 signed_value = want_bytes(signed_value) 

238 

239 if self.sep not in signed_value: 

240 raise BadSignature(f"No {self.sep!r} found in value") 

241 

242 value, sig = signed_value.rsplit(self.sep, 1) 

243 

244 if self.verify_signature(value, sig): 

245 return value 

246 

247 raise BadSignature(f"Signature {sig!r} does not match", payload=value) 

248 

249 def validate(self, signed_value: _t_str_bytes) -> bool: 

250 """Only validates the given signed value. Returns ``True`` if 

251 the signature exists and is valid. 

252 """ 

253 try: 

254 self.unsign(signed_value) 

255 return True 

256 except BadSignature: 

257 return False