mirror of
https://github.com/django/django.git
synced 2024-12-22 09:05:43 +00:00
Fixed #28401 -- Allowed hashlib.md5() calls to work with FIPS kernels.
md5 is not an approved algorithm in FIPS mode, and trying to instantiate a hashlib.md5() will fail when the system is running in FIPS mode. md5 is allowed when in a non-security context. There is a plan to add a keyword parameter (usedforsecurity) to hashlib.md5() to annotate whether or not the instance is being used in a security context. In the case where it is not, the instantiation of md5 will be allowed. See https://bugs.python.org/issue9216 for more details. Some downstream python versions already support this parameter. To support these versions, a new encapsulation of md5() has been added. This encapsulation will pass through the usedforsecurity parameter in the case where the parameter is supported, and strip it if it is not. Co-authored-by: Mariusz Felisiak <felisiak.mariusz@gmail.com>
This commit is contained in:
parent
b1b26b37af
commit
d10c7bfe56
1
AUTHORS
1
AUTHORS
@ -19,6 +19,7 @@ answer newbie questions, and generally made Django that much better:
|
||||
Adam Johnson <https://github.com/adamchainz>
|
||||
Adam Malinowski <https://adammalinowski.co.uk/>
|
||||
Adam Vandenberg
|
||||
Ade Lee <alee@redhat.com>
|
||||
Adiyat Mubarak <adiyatmubarak@gmail.com>
|
||||
Adnan Umer <u.adnan@outlook.com>
|
||||
Adrian Holovaty <adrian@holovaty.com>
|
||||
|
@ -11,7 +11,7 @@ from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.signals import setting_changed
|
||||
from django.dispatch import receiver
|
||||
from django.utils.crypto import (
|
||||
RANDOM_STRING_CHARS, constant_time_compare, get_random_string, pbkdf2,
|
||||
RANDOM_STRING_CHARS, constant_time_compare, get_random_string, md5, pbkdf2,
|
||||
)
|
||||
from django.utils.module_loading import import_string
|
||||
from django.utils.translation import gettext_noop as _
|
||||
@ -641,7 +641,7 @@ class MD5PasswordHasher(BasePasswordHasher):
|
||||
|
||||
def encode(self, password, salt):
|
||||
self._check_encode_args(password, salt)
|
||||
hash = hashlib.md5((salt + password).encode()).hexdigest()
|
||||
hash = md5((salt + password).encode()).hexdigest()
|
||||
return "%s$%s$%s" % (self.algorithm, salt, hash)
|
||||
|
||||
def decode(self, encoded):
|
||||
@ -736,7 +736,7 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher):
|
||||
def encode(self, password, salt):
|
||||
if salt != '':
|
||||
raise ValueError('salt must be empty.')
|
||||
return hashlib.md5(password.encode()).hexdigest()
|
||||
return md5(password.encode()).hexdigest()
|
||||
|
||||
def decode(self, encoded):
|
||||
return {
|
||||
|
@ -1,4 +1,3 @@
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import posixpath
|
||||
@ -10,6 +9,7 @@ from django.contrib.staticfiles.utils import check_settings, matches_patterns
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.files.storage import FileSystemStorage, get_storage_class
|
||||
from django.utils.crypto import md5
|
||||
from django.utils.functional import LazyObject
|
||||
|
||||
|
||||
@ -89,10 +89,10 @@ class HashedFilesMixin:
|
||||
"""
|
||||
if content is None:
|
||||
return None
|
||||
md5 = hashlib.md5()
|
||||
hasher = md5(usedforsecurity=False)
|
||||
for chunk in content.chunks():
|
||||
md5.update(chunk)
|
||||
return md5.hexdigest()[:12]
|
||||
hasher.update(chunk)
|
||||
return hasher.hexdigest()[:12]
|
||||
|
||||
def hashed_name(self, name, content=None, filename=None):
|
||||
# `filename` is the name of file to hash if `content` isn't given.
|
||||
|
8
django/core/cache/backends/filebased.py
vendored
8
django/core/cache/backends/filebased.py
vendored
@ -1,6 +1,5 @@
|
||||
"File-based cache backend"
|
||||
import glob
|
||||
import hashlib
|
||||
import os
|
||||
import pickle
|
||||
import random
|
||||
@ -11,6 +10,7 @@ import zlib
|
||||
from django.core.cache.backends.base import DEFAULT_TIMEOUT, BaseCache
|
||||
from django.core.files import locks
|
||||
from django.core.files.move import file_move_safe
|
||||
from django.utils.crypto import md5
|
||||
|
||||
|
||||
class FileBasedCache(BaseCache):
|
||||
@ -128,8 +128,10 @@ class FileBasedCache(BaseCache):
|
||||
root cache path joined with the md5sum of the key and a suffix.
|
||||
"""
|
||||
key = self.make_and_validate_key(key, version=version)
|
||||
return os.path.join(self._dir, ''.join(
|
||||
[hashlib.md5(key.encode()).hexdigest(), self.cache_suffix]))
|
||||
return os.path.join(self._dir, ''.join([
|
||||
md5(key.encode(), usedforsecurity=False).hexdigest(),
|
||||
self.cache_suffix,
|
||||
]))
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
|
4
django/core/cache/utils.py
vendored
4
django/core/cache/utils.py
vendored
@ -1,10 +1,10 @@
|
||||
import hashlib
|
||||
from django.utils.crypto import md5
|
||||
|
||||
TEMPLATE_FRAGMENT_KEY_TEMPLATE = 'template.cache.%s.%s'
|
||||
|
||||
|
||||
def make_template_fragment_key(fragment_name, vary_on=None):
|
||||
hasher = hashlib.md5()
|
||||
hasher = md5(usedforsecurity=False)
|
||||
if vary_on is not None:
|
||||
for arg in vary_on:
|
||||
hasher.update(str(arg).encode())
|
||||
|
@ -22,6 +22,7 @@ from django.db.backends.base.base import (
|
||||
)
|
||||
from django.utils import timezone
|
||||
from django.utils.asyncio import async_unsafe
|
||||
from django.utils.crypto import md5
|
||||
from django.utils.dateparse import parse_datetime, parse_time
|
||||
from django.utils.duration import duration_microseconds
|
||||
from django.utils.regex_helper import _lazy_re_compile
|
||||
@ -233,7 +234,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
||||
create_deterministic_function('LN', 1, none_guard(math.log))
|
||||
create_deterministic_function('LOG', 2, none_guard(lambda x, y: math.log(y, x)))
|
||||
create_deterministic_function('LPAD', 3, _sqlite_lpad)
|
||||
create_deterministic_function('MD5', 1, none_guard(lambda x: hashlib.md5(x.encode()).hexdigest()))
|
||||
create_deterministic_function('MD5', 1, none_guard(lambda x: md5(x.encode()).hexdigest()))
|
||||
create_deterministic_function('MOD', 2, none_guard(math.fmod))
|
||||
create_deterministic_function('PI', 0, lambda: math.pi)
|
||||
create_deterministic_function('POWER', 2, none_guard(operator.pow))
|
||||
|
@ -1,12 +1,12 @@
|
||||
import datetime
|
||||
import decimal
|
||||
import functools
|
||||
import hashlib
|
||||
import logging
|
||||
import time
|
||||
from contextlib import contextmanager
|
||||
|
||||
from django.db import NotSupportedError
|
||||
from django.utils.crypto import md5
|
||||
|
||||
logger = logging.getLogger('django.db.backends')
|
||||
|
||||
@ -216,7 +216,7 @@ def names_digest(*args, length):
|
||||
Generate a 32-bit digest of a set of arguments that can be used to shorten
|
||||
identifying names.
|
||||
"""
|
||||
h = hashlib.md5()
|
||||
h = md5(usedforsecurity=False)
|
||||
for arg in args:
|
||||
h.update(arg.encode())
|
||||
return h.hexdigest()[:length]
|
||||
|
@ -1,7 +1,6 @@
|
||||
import argparse
|
||||
import ctypes
|
||||
import faulthandler
|
||||
import hashlib
|
||||
import io
|
||||
import itertools
|
||||
import logging
|
||||
@ -26,6 +25,7 @@ from django.test.utils import (
|
||||
setup_databases as _setup_databases, setup_test_environment,
|
||||
teardown_databases as _teardown_databases, teardown_test_environment,
|
||||
)
|
||||
from django.utils.crypto import new_hash
|
||||
from django.utils.datastructures import OrderedSet
|
||||
from django.utils.deprecation import RemovedInDjango50Warning
|
||||
|
||||
@ -509,7 +509,7 @@ class Shuffler:
|
||||
|
||||
@classmethod
|
||||
def _hash_text(cls, text):
|
||||
h = hashlib.new(cls.hash_algorithm)
|
||||
h = new_hash(cls.hash_algorithm, usedforsecurity=False)
|
||||
h.update(text.encode('utf-8'))
|
||||
return h.hexdigest()
|
||||
|
||||
|
@ -16,13 +16,13 @@ cache keys to prevent delivery of wrong content.
|
||||
An example: i18n middleware would need to distinguish caches by the
|
||||
"Accept-language" header.
|
||||
"""
|
||||
import hashlib
|
||||
import time
|
||||
from collections import defaultdict
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import caches
|
||||
from django.http import HttpResponse, HttpResponseNotModified
|
||||
from django.utils.crypto import md5
|
||||
from django.utils.http import (
|
||||
http_date, parse_etags, parse_http_date_safe, quote_etag,
|
||||
)
|
||||
@ -118,7 +118,9 @@ def get_max_age(response):
|
||||
|
||||
def set_response_etag(response):
|
||||
if not response.streaming and response.content:
|
||||
response.headers['ETag'] = quote_etag(hashlib.md5(response.content).hexdigest())
|
||||
response.headers['ETag'] = quote_etag(
|
||||
md5(response.content, usedforsecurity=False).hexdigest(),
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
@ -325,12 +327,12 @@ def _i18n_cache_key_suffix(request, cache_key):
|
||||
|
||||
def _generate_cache_key(request, method, headerlist, key_prefix):
|
||||
"""Return a cache key from the headers given in the header list."""
|
||||
ctx = hashlib.md5()
|
||||
ctx = md5(usedforsecurity=False)
|
||||
for header in headerlist:
|
||||
value = request.META.get(header)
|
||||
if value is not None:
|
||||
ctx.update(value.encode())
|
||||
url = hashlib.md5(request.build_absolute_uri().encode('ascii'))
|
||||
url = md5(request.build_absolute_uri().encode('ascii'), usedforsecurity=False)
|
||||
cache_key = 'views.decorators.cache.cache_page.%s.%s.%s.%s' % (
|
||||
key_prefix, method, url.hexdigest(), ctx.hexdigest())
|
||||
return _i18n_cache_key_suffix(request, cache_key)
|
||||
@ -338,7 +340,7 @@ def _generate_cache_key(request, method, headerlist, key_prefix):
|
||||
|
||||
def _generate_cache_header_key(key_prefix, request):
|
||||
"""Return a cache key for the header cache."""
|
||||
url = hashlib.md5(request.build_absolute_uri().encode('ascii'))
|
||||
url = md5(request.build_absolute_uri().encode('ascii'), usedforsecurity=False)
|
||||
cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
|
||||
key_prefix, url.hexdigest())
|
||||
return _i18n_cache_key_suffix(request, cache_key)
|
||||
|
@ -7,6 +7,7 @@ import secrets
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.encoding import force_bytes
|
||||
from django.utils.inspect import func_supports_parameter
|
||||
|
||||
|
||||
class InvalidAlgorithm(ValueError):
|
||||
@ -74,3 +75,18 @@ def pbkdf2(password, salt, iterations, dklen=0, digest=None):
|
||||
password = force_bytes(password)
|
||||
salt = force_bytes(salt)
|
||||
return hashlib.pbkdf2_hmac(digest().name, password, salt, iterations, dklen)
|
||||
|
||||
|
||||
# TODO: Remove when dropping support for PY38. inspect.signature() is used to
|
||||
# detect whether the usedforsecurity argument is available as this fix may also
|
||||
# have been applied by downstream package maintainers to other versions in
|
||||
# their repositories.
|
||||
if func_supports_parameter(hashlib.md5, 'usedforsecurity'):
|
||||
md5 = hashlib.md5
|
||||
new_hash = hashlib.new
|
||||
else:
|
||||
def md5(data=b'', *, usedforsecurity=True):
|
||||
return hashlib.md5(data)
|
||||
|
||||
def new_hash(hash_algorithm, *, usedforsecurity=True):
|
||||
return hashlib.new(hash_algorithm)
|
||||
|
Loading…
Reference in New Issue
Block a user