signer.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. import hashlib
  2. import hmac
  3. import typing as _t
  4. from .encoding import _base64_alphabet
  5. from .encoding import base64_decode
  6. from .encoding import base64_encode
  7. from .encoding import want_bytes
  8. from .exc import BadSignature
  9. _t_str_bytes = _t.Union[str, bytes]
  10. _t_opt_str_bytes = _t.Optional[_t_str_bytes]
  11. _t_secret_key = _t.Union[_t.Iterable[_t_str_bytes], _t_str_bytes]
  12. class SigningAlgorithm:
  13. """Subclasses must implement :meth:`get_signature` to provide
  14. signature generation functionality.
  15. """
  16. def get_signature(self, key: bytes, value: bytes) -> bytes:
  17. """Returns the signature for the given key and value."""
  18. raise NotImplementedError()
  19. def verify_signature(self, key: bytes, value: bytes, sig: bytes) -> bool:
  20. """Verifies the given signature matches the expected
  21. signature.
  22. """
  23. return hmac.compare_digest(sig, self.get_signature(key, value))
  24. class NoneAlgorithm(SigningAlgorithm):
  25. """Provides an algorithm that does not perform any signing and
  26. returns an empty signature.
  27. """
  28. def get_signature(self, key: bytes, value: bytes) -> bytes:
  29. return b""
  30. class HMACAlgorithm(SigningAlgorithm):
  31. """Provides signature generation using HMACs."""
  32. #: The digest method to use with the MAC algorithm. This defaults to
  33. #: SHA1, but can be changed to any other function in the hashlib
  34. #: module.
  35. default_digest_method: _t.Any = staticmethod(hashlib.sha1)
  36. def __init__(self, digest_method: _t.Any = None):
  37. if digest_method is None:
  38. digest_method = self.default_digest_method
  39. self.digest_method: _t.Any = digest_method
  40. def get_signature(self, key: bytes, value: bytes) -> bytes:
  41. mac = hmac.new(key, msg=value, digestmod=self.digest_method)
  42. return mac.digest()
  43. def _make_keys_list(secret_key: _t_secret_key) -> _t.List[bytes]:
  44. if isinstance(secret_key, (str, bytes)):
  45. return [want_bytes(secret_key)]
  46. return [want_bytes(s) for s in secret_key]
  47. class Signer:
  48. """A signer securely signs bytes, then unsigns them to verify that
  49. the value hasn't been changed.
  50. The secret key should be a random string of ``bytes`` and should not
  51. be saved to code or version control. Different salts should be used
  52. to distinguish signing in different contexts. See :doc:`/concepts`
  53. for information about the security of the secret key and salt.
  54. :param secret_key: The secret key to sign and verify with. Can be a
  55. list of keys, oldest to newest, to support key rotation.
  56. :param salt: Extra key to combine with ``secret_key`` to distinguish
  57. signatures in different contexts.
  58. :param sep: Separator between the signature and value.
  59. :param key_derivation: How to derive the signing key from the secret
  60. key and salt. Possible values are ``concat``, ``django-concat``,
  61. or ``hmac``. Defaults to :attr:`default_key_derivation`, which
  62. defaults to ``django-concat``.
  63. :param digest_method: Hash function to use when generating the HMAC
  64. signature. Defaults to :attr:`default_digest_method`, which
  65. defaults to :func:`hashlib.sha1`. Note that the security of the
  66. hash alone doesn't apply when used intermediately in HMAC.
  67. :param algorithm: A :class:`SigningAlgorithm` instance to use
  68. instead of building a default :class:`HMACAlgorithm` with the
  69. ``digest_method``.
  70. .. versionchanged:: 2.0
  71. Added support for key rotation by passing a list to
  72. ``secret_key``.
  73. .. versionchanged:: 0.18
  74. ``algorithm`` was added as an argument to the class constructor.
  75. .. versionchanged:: 0.14
  76. ``key_derivation`` and ``digest_method`` were added as arguments
  77. to the class constructor.
  78. """
  79. #: The default digest method to use for the signer. The default is
  80. #: :func:`hashlib.sha1`, but can be changed to any :mod:`hashlib` or
  81. #: compatible object. Note that the security of the hash alone
  82. #: doesn't apply when used intermediately in HMAC.
  83. #:
  84. #: .. versionadded:: 0.14
  85. default_digest_method: _t.Any = staticmethod(hashlib.sha1)
  86. #: The default scheme to use to derive the signing key from the
  87. #: secret key and salt. The default is ``django-concat``. Possible
  88. #: values are ``concat``, ``django-concat``, and ``hmac``.
  89. #:
  90. #: .. versionadded:: 0.14
  91. default_key_derivation: str = "django-concat"
  92. def __init__(
  93. self,
  94. secret_key: _t_secret_key,
  95. salt: _t_opt_str_bytes = b"itsdangerous.Signer",
  96. sep: _t_str_bytes = b".",
  97. key_derivation: _t.Optional[str] = None,
  98. digest_method: _t.Optional[_t.Any] = None,
  99. algorithm: _t.Optional[SigningAlgorithm] = None,
  100. ):
  101. #: The list of secret keys to try for verifying signatures, from
  102. #: oldest to newest. The newest (last) key is used for signing.
  103. #:
  104. #: This allows a key rotation system to keep a list of allowed
  105. #: keys and remove expired ones.
  106. self.secret_keys: _t.List[bytes] = _make_keys_list(secret_key)
  107. self.sep: bytes = want_bytes(sep)
  108. if self.sep in _base64_alphabet:
  109. raise ValueError(
  110. "The given separator cannot be used because it may be"
  111. " contained in the signature itself. ASCII letters,"
  112. " digits, and '-_=' must not be used."
  113. )
  114. if salt is not None:
  115. salt = want_bytes(salt)
  116. else:
  117. salt = b"itsdangerous.Signer"
  118. self.salt = salt
  119. if key_derivation is None:
  120. key_derivation = self.default_key_derivation
  121. self.key_derivation: str = key_derivation
  122. if digest_method is None:
  123. digest_method = self.default_digest_method
  124. self.digest_method: _t.Any = digest_method
  125. if algorithm is None:
  126. algorithm = HMACAlgorithm(self.digest_method)
  127. self.algorithm: SigningAlgorithm = algorithm
  128. @property
  129. def secret_key(self) -> bytes:
  130. """The newest (last) entry in the :attr:`secret_keys` list. This
  131. is for compatibility from before key rotation support was added.
  132. """
  133. return self.secret_keys[-1]
  134. def derive_key(self, secret_key: _t_opt_str_bytes = None) -> bytes:
  135. """This method is called to derive the key. The default key
  136. derivation choices can be overridden here. Key derivation is not
  137. intended to be used as a security method to make a complex key
  138. out of a short password. Instead you should use large random
  139. secret keys.
  140. :param secret_key: A specific secret key to derive from.
  141. Defaults to the last item in :attr:`secret_keys`.
  142. .. versionchanged:: 2.0
  143. Added the ``secret_key`` parameter.
  144. """
  145. if secret_key is None:
  146. secret_key = self.secret_keys[-1]
  147. else:
  148. secret_key = want_bytes(secret_key)
  149. if self.key_derivation == "concat":
  150. return _t.cast(bytes, self.digest_method(self.salt + secret_key).digest())
  151. elif self.key_derivation == "django-concat":
  152. return _t.cast(
  153. bytes, self.digest_method(self.salt + b"signer" + secret_key).digest()
  154. )
  155. elif self.key_derivation == "hmac":
  156. mac = hmac.new(secret_key, digestmod=self.digest_method)
  157. mac.update(self.salt)
  158. return mac.digest()
  159. elif self.key_derivation == "none":
  160. return secret_key
  161. else:
  162. raise TypeError("Unknown key derivation method")
  163. def get_signature(self, value: _t_str_bytes) -> bytes:
  164. """Returns the signature for the given value."""
  165. value = want_bytes(value)
  166. key = self.derive_key()
  167. sig = self.algorithm.get_signature(key, value)
  168. return base64_encode(sig)
  169. def sign(self, value: _t_str_bytes) -> bytes:
  170. """Signs the given string."""
  171. value = want_bytes(value)
  172. return value + self.sep + self.get_signature(value)
  173. def verify_signature(self, value: _t_str_bytes, sig: _t_str_bytes) -> bool:
  174. """Verifies the signature for the given value."""
  175. try:
  176. sig = base64_decode(sig)
  177. except Exception:
  178. return False
  179. value = want_bytes(value)
  180. for secret_key in reversed(self.secret_keys):
  181. key = self.derive_key(secret_key)
  182. if self.algorithm.verify_signature(key, value, sig):
  183. return True
  184. return False
  185. def unsign(self, signed_value: _t_str_bytes) -> bytes:
  186. """Unsigns the given string."""
  187. signed_value = want_bytes(signed_value)
  188. if self.sep not in signed_value:
  189. raise BadSignature(f"No {self.sep!r} found in value")
  190. value, sig = signed_value.rsplit(self.sep, 1)
  191. if self.verify_signature(value, sig):
  192. return value
  193. raise BadSignature(f"Signature {sig!r} does not match", payload=value)
  194. def validate(self, signed_value: _t_str_bytes) -> bool:
  195. """Only validates the given signed value. Returns ``True`` if
  196. the signature exists and is valid.
  197. """
  198. try:
  199. self.unsign(signed_value)
  200. return True
  201. except BadSignature:
  202. return False