AzerothCore-website/venv/lib/python3.12/site-packages/itsdangerous/signer.py
Aaron Barbas 9bbeb35c08 Added support link to download game client, link for addons.
Fixed an issue that prevented the password reset tokens from working.
Added email templates for password reset success and new account creation.
Added more dynamic email template support.
2024-10-03 22:00:40 -05:00

257 lines
9.1 KiB
Python

import hashlib
import hmac
import typing as _t
from .encoding import _base64_alphabet
from .encoding import base64_decode
from .encoding import base64_encode
from .encoding import want_bytes
from .exc import BadSignature
_t_str_bytes = _t.Union[str, bytes]
_t_opt_str_bytes = _t.Optional[_t_str_bytes]
_t_secret_key = _t.Union[_t.Iterable[_t_str_bytes], _t_str_bytes]
class SigningAlgorithm:
"""Subclasses must implement :meth:`get_signature` to provide
signature generation functionality.
"""
def get_signature(self, key: bytes, value: bytes) -> bytes:
"""Returns the signature for the given key and value."""
raise NotImplementedError()
def verify_signature(self, key: bytes, value: bytes, sig: bytes) -> bool:
"""Verifies the given signature matches the expected
signature.
"""
return hmac.compare_digest(sig, self.get_signature(key, value))
class NoneAlgorithm(SigningAlgorithm):
"""Provides an algorithm that does not perform any signing and
returns an empty signature.
"""
def get_signature(self, key: bytes, value: bytes) -> bytes:
return b""
class HMACAlgorithm(SigningAlgorithm):
"""Provides signature generation using HMACs."""
#: The digest method to use with the MAC algorithm. This defaults to
#: SHA1, but can be changed to any other function in the hashlib
#: module.
default_digest_method: _t.Any = staticmethod(hashlib.sha1)
def __init__(self, digest_method: _t.Any = None):
if digest_method is None:
digest_method = self.default_digest_method
self.digest_method: _t.Any = digest_method
def get_signature(self, key: bytes, value: bytes) -> bytes:
mac = hmac.new(key, msg=value, digestmod=self.digest_method)
return mac.digest()
def _make_keys_list(secret_key: _t_secret_key) -> _t.List[bytes]:
if isinstance(secret_key, (str, bytes)):
return [want_bytes(secret_key)]
return [want_bytes(s) for s in secret_key]
class Signer:
"""A signer securely signs bytes, then unsigns them to verify that
the value hasn't been changed.
The secret key should be a random string of ``bytes`` and should not
be saved to code or version control. Different salts should be used
to distinguish signing in different contexts. See :doc:`/concepts`
for information about the security of the secret key and salt.
:param secret_key: The secret key to sign and verify with. Can be a
list of keys, oldest to newest, to support key rotation.
:param salt: Extra key to combine with ``secret_key`` to distinguish
signatures in different contexts.
:param sep: Separator between the signature and value.
:param key_derivation: How to derive the signing key from the secret
key and salt. Possible values are ``concat``, ``django-concat``,
or ``hmac``. Defaults to :attr:`default_key_derivation`, which
defaults to ``django-concat``.
:param digest_method: Hash function to use when generating the HMAC
signature. Defaults to :attr:`default_digest_method`, which
defaults to :func:`hashlib.sha1`. Note that the security of the
hash alone doesn't apply when used intermediately in HMAC.
:param algorithm: A :class:`SigningAlgorithm` instance to use
instead of building a default :class:`HMACAlgorithm` with the
``digest_method``.
.. versionchanged:: 2.0
Added support for key rotation by passing a list to
``secret_key``.
.. versionchanged:: 0.18
``algorithm`` was added as an argument to the class constructor.
.. versionchanged:: 0.14
``key_derivation`` and ``digest_method`` were added as arguments
to the class constructor.
"""
#: The default digest method to use for the signer. The default is
#: :func:`hashlib.sha1`, but can be changed to any :mod:`hashlib` or
#: compatible object. Note that the security of the hash alone
#: doesn't apply when used intermediately in HMAC.
#:
#: .. versionadded:: 0.14
default_digest_method: _t.Any = staticmethod(hashlib.sha1)
#: The default scheme to use to derive the signing key from the
#: secret key and salt. The default is ``django-concat``. Possible
#: values are ``concat``, ``django-concat``, and ``hmac``.
#:
#: .. versionadded:: 0.14
default_key_derivation: str = "django-concat"
def __init__(
self,
secret_key: _t_secret_key,
salt: _t_opt_str_bytes = b"itsdangerous.Signer",
sep: _t_str_bytes = b".",
key_derivation: _t.Optional[str] = None,
digest_method: _t.Optional[_t.Any] = None,
algorithm: _t.Optional[SigningAlgorithm] = None,
):
#: The list of secret keys to try for verifying signatures, from
#: oldest to newest. The newest (last) key is used for signing.
#:
#: This allows a key rotation system to keep a list of allowed
#: keys and remove expired ones.
self.secret_keys: _t.List[bytes] = _make_keys_list(secret_key)
self.sep: bytes = want_bytes(sep)
if self.sep in _base64_alphabet:
raise ValueError(
"The given separator cannot be used because it may be"
" contained in the signature itself. ASCII letters,"
" digits, and '-_=' must not be used."
)
if salt is not None:
salt = want_bytes(salt)
else:
salt = b"itsdangerous.Signer"
self.salt = salt
if key_derivation is None:
key_derivation = self.default_key_derivation
self.key_derivation: str = key_derivation
if digest_method is None:
digest_method = self.default_digest_method
self.digest_method: _t.Any = digest_method
if algorithm is None:
algorithm = HMACAlgorithm(self.digest_method)
self.algorithm: SigningAlgorithm = algorithm
@property
def secret_key(self) -> bytes:
"""The newest (last) entry in the :attr:`secret_keys` list. This
is for compatibility from before key rotation support was added.
"""
return self.secret_keys[-1]
def derive_key(self, secret_key: _t_opt_str_bytes = None) -> bytes:
"""This method is called to derive the key. The default key
derivation choices can be overridden here. Key derivation is not
intended to be used as a security method to make a complex key
out of a short password. Instead you should use large random
secret keys.
:param secret_key: A specific secret key to derive from.
Defaults to the last item in :attr:`secret_keys`.
.. versionchanged:: 2.0
Added the ``secret_key`` parameter.
"""
if secret_key is None:
secret_key = self.secret_keys[-1]
else:
secret_key = want_bytes(secret_key)
if self.key_derivation == "concat":
return _t.cast(bytes, self.digest_method(self.salt + secret_key).digest())
elif self.key_derivation == "django-concat":
return _t.cast(
bytes, self.digest_method(self.salt + b"signer" + secret_key).digest()
)
elif self.key_derivation == "hmac":
mac = hmac.new(secret_key, digestmod=self.digest_method)
mac.update(self.salt)
return mac.digest()
elif self.key_derivation == "none":
return secret_key
else:
raise TypeError("Unknown key derivation method")
def get_signature(self, value: _t_str_bytes) -> bytes:
"""Returns the signature for the given value."""
value = want_bytes(value)
key = self.derive_key()
sig = self.algorithm.get_signature(key, value)
return base64_encode(sig)
def sign(self, value: _t_str_bytes) -> bytes:
"""Signs the given string."""
value = want_bytes(value)
return value + self.sep + self.get_signature(value)
def verify_signature(self, value: _t_str_bytes, sig: _t_str_bytes) -> bool:
"""Verifies the signature for the given value."""
try:
sig = base64_decode(sig)
except Exception:
return False
value = want_bytes(value)
for secret_key in reversed(self.secret_keys):
key = self.derive_key(secret_key)
if self.algorithm.verify_signature(key, value, sig):
return True
return False
def unsign(self, signed_value: _t_str_bytes) -> bytes:
"""Unsigns the given string."""
signed_value = want_bytes(signed_value)
if self.sep not in signed_value:
raise BadSignature(f"No {self.sep!r} found in value")
value, sig = signed_value.rsplit(self.sep, 1)
if self.verify_signature(value, sig):
return value
raise BadSignature(f"Signature {sig!r} does not match", payload=value)
def validate(self, signed_value: _t_str_bytes) -> bool:
"""Only validates the given signed value. Returns ``True`` if
the signature exists and is valid.
"""
try:
self.unsign(signed_value)
return True
except BadSignature:
return False