Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/itsdangerous/signer.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

105 statements  

1from __future__ import annotations 

2 

3import collections.abc as cabc 

4import hashlib 

5import hmac 

6import typing as t 

7 

8from .encoding import _base64_alphabet 

9from .encoding import base64_decode 

10from .encoding import base64_encode 

11from .encoding import want_bytes 

12from .exc import BadSignature 

13 

14 

15class SigningAlgorithm: 

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

17 signature generation functionality. 

18 """ 

19 

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

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

22 raise NotImplementedError() 

23 

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

25 """Verifies the given signature matches the expected 

26 signature. 

27 """ 

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

29 

30 

31class NoneAlgorithm(SigningAlgorithm): 

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

33 returns an empty signature. 

34 """ 

35 

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

37 return b"" 

38 

39 

40def _lazy_sha1(string: bytes = b"") -> t.Any: 

41 """Don't access ``hashlib.sha1`` until runtime. FIPS builds may not include 

42 SHA-1, in which case the import and use as a default would fail before the 

43 developer can configure something else. 

44 """ 

45 return hashlib.sha1(string) 

46 

47 

48class HMACAlgorithm(SigningAlgorithm): 

49 """Provides signature generation using HMACs.""" 

50 

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

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

53 #: module. 

54 default_digest_method: t.Any = staticmethod(_lazy_sha1) 

55 

56 def __init__(self, digest_method: t.Any = None): 

57 if digest_method is None: 

58 digest_method = self.default_digest_method 

59 

60 self.digest_method: t.Any = digest_method 

61 

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

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

64 return mac.digest() 

65 

66 

67def _make_keys_list( 

68 secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], 

69) -> list[bytes]: 

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

71 return [want_bytes(secret_key)] 

72 

73 return [want_bytes(s) for s in secret_key] # pyright: ignore 

74 

75 

76class Signer: 

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

78 the value hasn't been changed. 

79 

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

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

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

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

84 

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

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

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

88 signatures in different contexts. 

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

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

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

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

93 defaults to ``django-concat``. 

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

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

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

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

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

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

100 ``digest_method``. 

101 

102 .. versionchanged:: 2.0 

103 Added support for key rotation by passing a list to 

104 ``secret_key``. 

105 

106 .. versionchanged:: 0.18 

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

108 

109 .. versionchanged:: 0.14 

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

111 to the class constructor. 

112 """ 

113 

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

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

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

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

118 #: 

119 #: .. versionadded:: 0.14 

120 default_digest_method: t.Any = staticmethod(_lazy_sha1) 

121 

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

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

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

125 #: 

126 #: .. versionadded:: 0.14 

127 default_key_derivation: str = "django-concat" 

128 

129 def __init__( 

130 self, 

131 secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], 

132 salt: str | bytes | None = b"itsdangerous.Signer", 

133 sep: str | bytes = b".", 

134 key_derivation: str | None = None, 

135 digest_method: t.Any | None = None, 

136 algorithm: SigningAlgorithm | None = None, 

137 ): 

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

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

140 #: 

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

142 #: keys and remove expired ones. 

143 self.secret_keys: list[bytes] = _make_keys_list(secret_key) 

144 self.sep: bytes = want_bytes(sep) 

145 

146 if self.sep in _base64_alphabet: 

147 raise ValueError( 

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

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

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

151 ) 

152 

153 if salt is not None: 

154 salt = want_bytes(salt) 

155 else: 

156 salt = b"itsdangerous.Signer" 

157 

158 self.salt = salt 

159 

160 if key_derivation is None: 

161 key_derivation = self.default_key_derivation 

162 

163 self.key_derivation: str = key_derivation 

164 

165 if digest_method is None: 

166 digest_method = self.default_digest_method 

167 

168 self.digest_method: t.Any = digest_method 

169 

170 if algorithm is None: 

171 algorithm = HMACAlgorithm(self.digest_method) 

172 

173 self.algorithm: SigningAlgorithm = algorithm 

174 

175 @property 

176 def secret_key(self) -> bytes: 

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

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

179 """ 

180 return self.secret_keys[-1] 

181 

182 def derive_key(self, secret_key: str | bytes | None = None) -> bytes: 

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

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

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

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

187 secret keys. 

188 

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

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

191 

192 .. versionchanged:: 2.0 

193 Added the ``secret_key`` parameter. 

194 """ 

195 if secret_key is None: 

196 secret_key = self.secret_keys[-1] 

197 else: 

198 secret_key = want_bytes(secret_key) 

199 

200 if self.key_derivation == "concat": 

201 return t.cast(bytes, self.digest_method(self.salt + secret_key).digest()) 

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

203 return t.cast( 

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

205 ) 

206 elif self.key_derivation == "hmac": 

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

208 mac.update(self.salt) 

209 return mac.digest() 

210 elif self.key_derivation == "none": 

211 return secret_key 

212 else: 

213 raise TypeError("Unknown key derivation method") 

214 

215 def get_signature(self, value: str | bytes) -> bytes: 

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

217 value = want_bytes(value) 

218 key = self.derive_key() 

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

220 return base64_encode(sig) 

221 

222 def sign(self, value: str | bytes) -> bytes: 

223 """Signs the given string.""" 

224 value = want_bytes(value) 

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

226 

227 def verify_signature(self, value: str | bytes, sig: str | bytes) -> bool: 

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

229 try: 

230 sig = base64_decode(sig) 

231 except Exception: 

232 return False 

233 

234 value = want_bytes(value) 

235 

236 for secret_key in reversed(self.secret_keys): 

237 key = self.derive_key(secret_key) 

238 

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

240 return True 

241 

242 return False 

243 

244 def unsign(self, signed_value: str | bytes) -> bytes: 

245 """Unsigns the given string.""" 

246 signed_value = want_bytes(signed_value) 

247 

248 if self.sep not in signed_value: 

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

250 

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

252 

253 if self.verify_signature(value, sig): 

254 return value 

255 

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

257 

258 def validate(self, signed_value: str | bytes) -> bool: 

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

260 the signature exists and is valid. 

261 """ 

262 try: 

263 self.unsign(signed_value) 

264 return True 

265 except BadSignature: 

266 return False