mirror of https://github.com/django/django.git
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 Johnson <https://github.com/adamchainz>
|
||||||
Adam Malinowski <https://adammalinowski.co.uk/>
|
Adam Malinowski <https://adammalinowski.co.uk/>
|
||||||
Adam Vandenberg
|
Adam Vandenberg
|
||||||
|
Ade Lee <alee@redhat.com>
|
||||||
Adiyat Mubarak <adiyatmubarak@gmail.com>
|
Adiyat Mubarak <adiyatmubarak@gmail.com>
|
||||||
Adnan Umer <u.adnan@outlook.com>
|
Adnan Umer <u.adnan@outlook.com>
|
||||||
Adrian Holovaty <adrian@holovaty.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.core.signals import setting_changed
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils.crypto import (
|
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.module_loading import import_string
|
||||||
from django.utils.translation import gettext_noop as _
|
from django.utils.translation import gettext_noop as _
|
||||||
|
@ -641,7 +641,7 @@ class MD5PasswordHasher(BasePasswordHasher):
|
||||||
|
|
||||||
def encode(self, password, salt):
|
def encode(self, password, salt):
|
||||||
self._check_encode_args(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)
|
return "%s$%s$%s" % (self.algorithm, salt, hash)
|
||||||
|
|
||||||
def decode(self, encoded):
|
def decode(self, encoded):
|
||||||
|
@ -736,7 +736,7 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher):
|
||||||
def encode(self, password, salt):
|
def encode(self, password, salt):
|
||||||
if salt != '':
|
if salt != '':
|
||||||
raise ValueError('salt must be empty.')
|
raise ValueError('salt must be empty.')
|
||||||
return hashlib.md5(password.encode()).hexdigest()
|
return md5(password.encode()).hexdigest()
|
||||||
|
|
||||||
def decode(self, encoded):
|
def decode(self, encoded):
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import hashlib
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import posixpath
|
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.exceptions import ImproperlyConfigured
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
from django.core.files.storage import FileSystemStorage, get_storage_class
|
from django.core.files.storage import FileSystemStorage, get_storage_class
|
||||||
|
from django.utils.crypto import md5
|
||||||
from django.utils.functional import LazyObject
|
from django.utils.functional import LazyObject
|
||||||
|
|
||||||
|
|
||||||
|
@ -89,10 +89,10 @@ class HashedFilesMixin:
|
||||||
"""
|
"""
|
||||||
if content is None:
|
if content is None:
|
||||||
return None
|
return None
|
||||||
md5 = hashlib.md5()
|
hasher = md5(usedforsecurity=False)
|
||||||
for chunk in content.chunks():
|
for chunk in content.chunks():
|
||||||
md5.update(chunk)
|
hasher.update(chunk)
|
||||||
return md5.hexdigest()[:12]
|
return hasher.hexdigest()[:12]
|
||||||
|
|
||||||
def hashed_name(self, name, content=None, filename=None):
|
def hashed_name(self, name, content=None, filename=None):
|
||||||
# `filename` is the name of file to hash if `content` isn't given.
|
# `filename` is the name of file to hash if `content` isn't given.
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
"File-based cache backend"
|
"File-based cache backend"
|
||||||
import glob
|
import glob
|
||||||
import hashlib
|
|
||||||
import os
|
import os
|
||||||
import pickle
|
import pickle
|
||||||
import random
|
import random
|
||||||
|
@ -11,6 +10,7 @@ import zlib
|
||||||
from django.core.cache.backends.base import DEFAULT_TIMEOUT, BaseCache
|
from django.core.cache.backends.base import DEFAULT_TIMEOUT, BaseCache
|
||||||
from django.core.files import locks
|
from django.core.files import locks
|
||||||
from django.core.files.move import file_move_safe
|
from django.core.files.move import file_move_safe
|
||||||
|
from django.utils.crypto import md5
|
||||||
|
|
||||||
|
|
||||||
class FileBasedCache(BaseCache):
|
class FileBasedCache(BaseCache):
|
||||||
|
@ -128,8 +128,10 @@ class FileBasedCache(BaseCache):
|
||||||
root cache path joined with the md5sum of the key and a suffix.
|
root cache path joined with the md5sum of the key and a suffix.
|
||||||
"""
|
"""
|
||||||
key = self.make_and_validate_key(key, version=version)
|
key = self.make_and_validate_key(key, version=version)
|
||||||
return os.path.join(self._dir, ''.join(
|
return os.path.join(self._dir, ''.join([
|
||||||
[hashlib.md5(key.encode()).hexdigest(), self.cache_suffix]))
|
md5(key.encode(), usedforsecurity=False).hexdigest(),
|
||||||
|
self.cache_suffix,
|
||||||
|
]))
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import hashlib
|
from django.utils.crypto import md5
|
||||||
|
|
||||||
TEMPLATE_FRAGMENT_KEY_TEMPLATE = 'template.cache.%s.%s'
|
TEMPLATE_FRAGMENT_KEY_TEMPLATE = 'template.cache.%s.%s'
|
||||||
|
|
||||||
|
|
||||||
def make_template_fragment_key(fragment_name, vary_on=None):
|
def make_template_fragment_key(fragment_name, vary_on=None):
|
||||||
hasher = hashlib.md5()
|
hasher = md5(usedforsecurity=False)
|
||||||
if vary_on is not None:
|
if vary_on is not None:
|
||||||
for arg in vary_on:
|
for arg in vary_on:
|
||||||
hasher.update(str(arg).encode())
|
hasher.update(str(arg).encode())
|
||||||
|
|
|
@ -22,6 +22,7 @@ from django.db.backends.base.base import (
|
||||||
)
|
)
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.asyncio import async_unsafe
|
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.dateparse import parse_datetime, parse_time
|
||||||
from django.utils.duration import duration_microseconds
|
from django.utils.duration import duration_microseconds
|
||||||
from django.utils.regex_helper import _lazy_re_compile
|
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('LN', 1, none_guard(math.log))
|
||||||
create_deterministic_function('LOG', 2, none_guard(lambda x, y: math.log(y, x)))
|
create_deterministic_function('LOG', 2, none_guard(lambda x, y: math.log(y, x)))
|
||||||
create_deterministic_function('LPAD', 3, _sqlite_lpad)
|
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('MOD', 2, none_guard(math.fmod))
|
||||||
create_deterministic_function('PI', 0, lambda: math.pi)
|
create_deterministic_function('PI', 0, lambda: math.pi)
|
||||||
create_deterministic_function('POWER', 2, none_guard(operator.pow))
|
create_deterministic_function('POWER', 2, none_guard(operator.pow))
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import datetime
|
import datetime
|
||||||
import decimal
|
import decimal
|
||||||
import functools
|
import functools
|
||||||
import hashlib
|
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
from django.db import NotSupportedError
|
from django.db import NotSupportedError
|
||||||
|
from django.utils.crypto import md5
|
||||||
|
|
||||||
logger = logging.getLogger('django.db.backends')
|
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
|
Generate a 32-bit digest of a set of arguments that can be used to shorten
|
||||||
identifying names.
|
identifying names.
|
||||||
"""
|
"""
|
||||||
h = hashlib.md5()
|
h = md5(usedforsecurity=False)
|
||||||
for arg in args:
|
for arg in args:
|
||||||
h.update(arg.encode())
|
h.update(arg.encode())
|
||||||
return h.hexdigest()[:length]
|
return h.hexdigest()[:length]
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import argparse
|
import argparse
|
||||||
import ctypes
|
import ctypes
|
||||||
import faulthandler
|
import faulthandler
|
||||||
import hashlib
|
|
||||||
import io
|
import io
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
|
@ -26,6 +25,7 @@ from django.test.utils import (
|
||||||
setup_databases as _setup_databases, setup_test_environment,
|
setup_databases as _setup_databases, setup_test_environment,
|
||||||
teardown_databases as _teardown_databases, teardown_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.datastructures import OrderedSet
|
||||||
from django.utils.deprecation import RemovedInDjango50Warning
|
from django.utils.deprecation import RemovedInDjango50Warning
|
||||||
|
|
||||||
|
@ -509,7 +509,7 @@ class Shuffler:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _hash_text(cls, text):
|
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'))
|
h.update(text.encode('utf-8'))
|
||||||
return h.hexdigest()
|
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
|
An example: i18n middleware would need to distinguish caches by the
|
||||||
"Accept-language" header.
|
"Accept-language" header.
|
||||||
"""
|
"""
|
||||||
import hashlib
|
|
||||||
import time
|
import time
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.cache import caches
|
from django.core.cache import caches
|
||||||
from django.http import HttpResponse, HttpResponseNotModified
|
from django.http import HttpResponse, HttpResponseNotModified
|
||||||
|
from django.utils.crypto import md5
|
||||||
from django.utils.http import (
|
from django.utils.http import (
|
||||||
http_date, parse_etags, parse_http_date_safe, quote_etag,
|
http_date, parse_etags, parse_http_date_safe, quote_etag,
|
||||||
)
|
)
|
||||||
|
@ -118,7 +118,9 @@ def get_max_age(response):
|
||||||
|
|
||||||
def set_response_etag(response):
|
def set_response_etag(response):
|
||||||
if not response.streaming and response.content:
|
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
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@ -325,12 +327,12 @@ def _i18n_cache_key_suffix(request, cache_key):
|
||||||
|
|
||||||
def _generate_cache_key(request, method, headerlist, key_prefix):
|
def _generate_cache_key(request, method, headerlist, key_prefix):
|
||||||
"""Return a cache key from the headers given in the header list."""
|
"""Return a cache key from the headers given in the header list."""
|
||||||
ctx = hashlib.md5()
|
ctx = md5(usedforsecurity=False)
|
||||||
for header in headerlist:
|
for header in headerlist:
|
||||||
value = request.META.get(header)
|
value = request.META.get(header)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
ctx.update(value.encode())
|
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' % (
|
cache_key = 'views.decorators.cache.cache_page.%s.%s.%s.%s' % (
|
||||||
key_prefix, method, url.hexdigest(), ctx.hexdigest())
|
key_prefix, method, url.hexdigest(), ctx.hexdigest())
|
||||||
return _i18n_cache_key_suffix(request, cache_key)
|
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):
|
def _generate_cache_header_key(key_prefix, request):
|
||||||
"""Return a cache key for the header cache."""
|
"""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' % (
|
cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
|
||||||
key_prefix, url.hexdigest())
|
key_prefix, url.hexdigest())
|
||||||
return _i18n_cache_key_suffix(request, cache_key)
|
return _i18n_cache_key_suffix(request, cache_key)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import secrets
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.encoding import force_bytes
|
from django.utils.encoding import force_bytes
|
||||||
|
from django.utils.inspect import func_supports_parameter
|
||||||
|
|
||||||
|
|
||||||
class InvalidAlgorithm(ValueError):
|
class InvalidAlgorithm(ValueError):
|
||||||
|
@ -74,3 +75,18 @@ def pbkdf2(password, salt, iterations, dklen=0, digest=None):
|
||||||
password = force_bytes(password)
|
password = force_bytes(password)
|
||||||
salt = force_bytes(salt)
|
salt = force_bytes(salt)
|
||||||
return hashlib.pbkdf2_hmac(digest().name, password, salt, iterations, dklen)
|
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