From 170ef8eed8a7b63a795eee2c36ba7d5085213caf Mon Sep 17 00:00:00 2001 From: Sean Kelly Date: Sun, 5 Mar 2023 09:57:33 -0600 Subject: [PATCH] Mark calls to md5 as not being used for secure purposes --- wagtail/coreutils.py | 22 ++++++++++++++++++++++ wagtail/embeds/embeds.py | 6 ++---- wagtail/users/utils.py | 8 +++++--- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/wagtail/coreutils.py b/wagtail/coreutils.py index 346e6b583432..3170bbc1f2dd 100644 --- a/wagtail/coreutils.py +++ b/wagtail/coreutils.py @@ -20,6 +20,7 @@ from django.utils.text import capfirst, slugify from django.utils.translation import check_for_language, get_supported_language_variant from django.utils.translation import gettext_lazy as _ +from hashlib import md5 if TYPE_CHECKING: from wagtail.models import Site @@ -421,6 +422,27 @@ def get_dummy_request(*, path: str = "/", site: "Site" = None) -> HttpRequest: return RequestFactory(SERVER_NAME=server_name).get(path, SERVER_PORT=server_port) +def safe_md5(data=b"", usedforsecurity=True): + """ + Safely use the MD5 hash algorithm with the given ``data`` and a flag + indicating if the purpose of the digest is for security or not. + + On security-restricted systems (such as FIPS systems), insecure hashes + like MD5 are disabled by default. But passing ``usedforsecurity`` as + ``False`` tells the underlying security implementation we're not trying + to use the digest for secure purposes and to please just go ahead and + allow it to happen. + """ + + # Although ``accepts_kwarg`` works great on Python 3.8+, on Python 3.7 it + # raises a ValueError, saying "no signature found for builtin". So, back + # to the try/except. + try: + return md5(data, usedforsecurity=usedforsecurity) + except TypeError: + return md5(data) + + class BatchProcessor: """ A class to help with processing of an unknown (and potentially very diff --git a/wagtail/embeds/embeds.py b/wagtail/embeds/embeds.py index 60d3b822e622..dbb9eb49b1dd 100644 --- a/wagtail/embeds/embeds.py +++ b/wagtail/embeds/embeds.py @@ -1,9 +1,8 @@ from datetime import datetime -from hashlib import md5 from django.utils.timezone import now -from wagtail.coreutils import accepts_kwarg +from wagtail.coreutils import accepts_kwarg, safe_md5 from .exceptions import EmbedUnsupportedProviderException from .finders import get_finders @@ -66,8 +65,7 @@ def finder(url, max_width=None, max_height=None): def get_embed_hash(url, max_width=None, max_height=None): - h = md5() - h.update(url.encode("utf-8")) + h = safe_md5(url.encode("utf-8"), usedforsecurity=False) if max_width is not None: h.update(b"\n") h.update(str(max_width).encode("utf-8")) diff --git a/wagtail/users/utils.py b/wagtail/users/utils.py index 55b32fe51c49..d133ab8e972c 100644 --- a/wagtail/users/utils.py +++ b/wagtail/users/utils.py @@ -1,10 +1,10 @@ -import hashlib - from django.conf import settings from django.utils.http import urlencode from django.utils.translation import gettext_lazy as _ from wagtail.compat import AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME +from wagtail.coreutils import safe_md5 + delete_user_perm = "{0}.delete_{1}".format( AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME.lower() @@ -38,9 +38,11 @@ def get_gravatar_url(email, size=50): if (not email) or (gravatar_provider_url is None): return None + email_bytes = email.lower().encode("utf-8") + hash = safe_md5(email_bytes, usedforsecurity=False).hexdigest() gravatar_url = "{gravatar_provider_url}/{hash}?{params}".format( gravatar_provider_url=gravatar_provider_url.rstrip("/"), - hash=hashlib.md5(email.lower().encode("utf-8")).hexdigest(), + hash=hash, params=urlencode({"s": size, "d": default}), )