Browse Source

replace md5 with swift utils version

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() is added to
swift/common/utils.py.  This encapsulation is identical to the one being
added to oslo.utils, but is recreated here to avoid adding a dependency.

This patch is to replace the instances of hashlib.md5() with this new
encapsulation, adding an annotation indicating whether the usage is
a security context or not.

While this patch seems large, it is really just the same change over and
again.  Reviewers need to pay particular attention as to whether the
keyword parameter (usedforsecurity) is set correctly.   Right now, all
of them appear to be not used in a security context.

Now that all the instances have been converted, we can update the bandit
run to look for these instances and ensure that new invocations do not
creep in.

With this latest patch, the functional and unit tests all pass
on a FIPS enabled system.

Co-Authored-By: Pete Zaitcev
Change-Id: Ibb4917da4c083e1e094156d748708b87387f2d87
changes/66/751966/16
Ade Lee 9 months ago
parent
commit
5320ecbaf2
61 changed files with 759 additions and 509 deletions
  1. +1
    -1
      bandit.yaml
  2. +4
    -4
      swift/account/reaper.py
  3. +2
    -2
      swift/cli/info.py
  4. +3
    -3
      swift/common/db.py
  5. +2
    -2
      swift/common/memcached.py
  6. +3
    -3
      swift/common/middleware/crypto/encrypter.py
  7. +2
    -3
      swift/common/middleware/dlo.py
  8. +4
    -4
      swift/common/middleware/s3api/controllers/multi_upload.py
  9. +4
    -3
      swift/common/middleware/s3api/s3request.py
  10. +10
    -8
      swift/common/middleware/slo.py
  11. +2
    -3
      swift/common/request_helpers.py
  12. +4
    -4
      swift/common/ring/ring.py
  13. +35
    -6
      swift/common/utils.py
  14. +12
    -9
      swift/obj/diskfile.py
  15. +3
    -3
      swift/obj/expirer.py
  16. +3
    -3
      swift/obj/mem_diskfile.py
  17. +2
    -3
      swift/obj/server.py
  18. +6
    -5
      swift/proxy/controllers/obj.py
  19. +14
    -10
      test/functional/s3api/test_multi_upload.py
  20. +13
    -13
      test/functional/s3api/test_object.py
  21. +3
    -2
      test/functional/s3api/utils.py
  22. +3
    -4
      test/functional/swift_test_client.py
  23. +2
    -2
      test/functional/test_object.py
  24. +47
    -46
      test/functional/test_object_versioning.py
  25. +57
    -38
      test/functional/test_slo.py
  26. +9
    -7
      test/functional/test_symlink.py
  27. +4
    -3
      test/functional/tests.py
  28. +3
    -3
      test/probe/common.py
  29. +2
    -2
      test/probe/test_container_merge_policy_index.py
  30. +2
    -2
      test/probe/test_object_handoff.py
  31. +4
    -4
      test/probe/test_reconstructor_rebuild.py
  32. +3
    -3
      test/probe/test_reconstructor_revert.py
  33. +3
    -3
      test/probe/test_sharder.py
  34. +14
    -10
      test/s3api/test_versioning.py
  35. +5
    -5
      test/unit/__init__.py
  36. +4
    -4
      test/unit/account/test_backend.py
  37. +2
    -2
      test/unit/common/middleware/crypto/crypto_helpers.py
  38. +2
    -3
      test/unit/common/middleware/helpers.py
  39. +3
    -2
      test/unit/common/middleware/s3api/test_acl.py
  40. +27
    -13
      test/unit/common/middleware/s3api/test_multi_delete.py
  41. +18
    -17
      test/unit/common/middleware/s3api/test_multi_upload.py
  42. +4
    -3
      test/unit/common/middleware/s3api/test_obj.py
  43. +5
    -4
      test/unit/common/middleware/s3api/test_s3api.py
  44. +13
    -8
      test/unit/common/middleware/s3api/test_s3request.py
  45. +2
    -3
      test/unit/common/middleware/test_copy.py
  46. +4
    -4
      test/unit/common/middleware/test_dlo.py
  47. +26
    -18
      test/unit/common/middleware/test_object_versioning.py
  48. +3
    -4
      test/unit/common/middleware/test_slo.py
  49. +2
    -3
      test/unit/common/ring/test_ring.py
  50. +21
    -10
      test/unit/common/test_direct_client.py
  51. +11
    -8
      test/unit/common/test_memcached.py
  52. +82
    -2
      test/unit/common/test_utils.py
  53. +2
    -3
      test/unit/container/test_backend.py
  54. +3
    -3
      test/unit/container/test_sharder.py
  55. +2
    -3
      test/unit/obj/common.py
  56. +23
    -22
      test/unit/obj/test_auditor.py
  57. +2
    -3
      test/unit/obj/test_diskfile.py
  58. +57
    -44
      test/unit/obj/test_reconstructor.py
  59. +36
    -27
      test/unit/obj/test_server.py
  60. +75
    -44
      test/unit/proxy/controllers/test_obj.py
  61. +40
    -26
      test/unit/proxy/test_server.py

+ 1
- 1
bandit.yaml View File

@ -80,7 +80,7 @@
# B703 : django_mark_safe
# (optional) list included test IDs here, eg '[B101, B406]':
tests: [B102, B103, B302, B306, B308, B309, B310, B401, B501, B502, B506, B601, B602, B609]
tests: [B102, B103, B302, B303, B304, B305, B306, B308, B309, B310, B401, B501, B502, B506, B601, B602, B609]
# (optional) list skipped test IDs here, eg '[B101, B406]':
skips:


+ 4
- 4
swift/account/reaper.py View File

@ -19,7 +19,6 @@ import socket
from logging import DEBUG
from math import sqrt
from time import time
from hashlib import md5
import itertools
from eventlet import GreenPool, sleep, Timeout
@ -35,7 +34,7 @@ from swift.common.request_helpers import USE_REPLICATION_NETWORK_HEADER
from swift.common.ring import Ring
from swift.common.ring.utils import is_local_device
from swift.common.utils import get_logger, whataremyips, config_true_value, \
Timestamp
Timestamp, md5
from swift.common.daemon import Daemon
from swift.common.storage_policy import POLICIES, PolicyError
@ -271,8 +270,9 @@ class AccountReaper(Daemon):
container_ = container.encode('utf-8')
else:
container_ = container
this_shard = int(md5(container_).hexdigest(), 16) % \
len(nodes)
this_shard = (
int(md5(container_, usedforsecurity=False)
.hexdigest(), 16) % len(nodes))
if container_shard not in (this_shard, None):
continue


+ 2
- 2
swift/cli/info.py View File

@ -15,7 +15,6 @@ import itertools
import json
import os
import sqlite3
from hashlib import md5
from collections import defaultdict
from six.moves import urllib
@ -32,6 +31,7 @@ from swift.obj.diskfile import get_data_dir, read_metadata, DATADIR_BASE, \
extract_policy
from swift.common.storage_policy import POLICIES
from swift.common.middleware.crypto.crypto_utils import load_crypto_meta
from swift.common.utils import md5
class InfoSystemExit(Exception):
@ -545,7 +545,7 @@ def print_obj(datafile, check_etag=True, swift_dir='/etc/swift',
# Optional integrity check; it's useful, but slow.
file_len = None
if check_etag:
h = md5()
h = md5(usedforsecurity=False)
file_len = 0
while True:
data = fp.read(64 * 1024)


+ 3
- 3
swift/common/db.py View File

@ -17,7 +17,6 @@
from contextlib import contextmanager, closing
import base64
import hashlib
import json
import logging
import os
@ -36,7 +35,7 @@ import sqlite3
from swift.common.constraints import MAX_META_COUNT, MAX_META_OVERALL_SIZE, \
check_utf8
from swift.common.utils import Timestamp, renamer, \
mkdirs, lock_parent_directory, fallocate
mkdirs, lock_parent_directory, fallocate, md5
from swift.common.exceptions import LockTimeout
from swift.common.swob import HTTPBadRequest
@ -186,7 +185,8 @@ def chexor(old, name, timestamp):
"""
if name is None:
raise Exception('name is None!')
new = hashlib.md5(('%s-%s' % (name, timestamp)).encode('utf8')).hexdigest()
new = md5(('%s-%s' % (name, timestamp)).encode('utf8'),
usedforsecurity=False).hexdigest()
return '%032x' % (int(old, 16) ^ int(new, 16))


+ 2
- 2
swift/common/memcached.py View File

@ -50,13 +50,13 @@ import json
import logging
import time
from bisect import bisect
from hashlib import md5
from eventlet.green import socket
from eventlet.pools import Pool
from eventlet import Timeout
from six.moves import range
from swift.common import utils
from swift.common.utils import md5
DEFAULT_MEMCACHED_PORT = 11211
@ -81,7 +81,7 @@ def md5hash(key):
key = key.encode('utf-8')
else:
key = key.encode('utf-8', errors='surrogateescape')
return md5(key).hexdigest().encode('ascii')
return md5(key, usedforsecurity=False).hexdigest().encode('ascii')
def sanitize_timeout(timeout):


+ 3
- 3
swift/common/middleware/crypto/encrypter.py View File

@ -27,7 +27,7 @@ from swift.common.request_helpers import get_object_transient_sysmeta, \
from swift.common.swob import Request, Match, HTTPException, \
HTTPUnprocessableEntity, wsgi_to_bytes, bytes_to_wsgi, normalize_etag
from swift.common.utils import get_logger, config_true_value, \
MD5_OF_EMPTY_STRING
MD5_OF_EMPTY_STRING, md5
def encrypt_header_val(crypto, value, key):
@ -91,8 +91,8 @@ class EncInputWrapper(object):
self.body_crypto_meta['key_id'] = self.keys['id']
self.body_crypto_ctxt = self.crypto.create_encryption_ctxt(
body_key, self.body_crypto_meta.get('iv'))
self.plaintext_md5 = hashlib.md5()
self.ciphertext_md5 = hashlib.md5()
self.plaintext_md5 = md5(usedforsecurity=False)
self.ciphertext_md5 = md5(usedforsecurity=False)
def install_footers_callback(self, req):
# the proxy controller will call back for footer metadata after


+ 2
- 3
swift/common/middleware/dlo.py View File

@ -122,7 +122,6 @@ import json
import six
from hashlib import md5
from swift.common import constraints
from swift.common.exceptions import ListingIterError, SegmentError
from swift.common.http import is_success
@ -131,7 +130,7 @@ from swift.common.swob import Request, Response, HTTPException, \
str_to_wsgi, wsgi_to_str, wsgi_quote, wsgi_unquote, normalize_etag
from swift.common.utils import get_logger, \
RateLimitedIterator, quote, close_if_possible, closing_if_possible, \
drain_and_close
drain_and_close, md5
from swift.common.request_helpers import SegmentedIterable, \
update_ignore_range_header
from swift.common.wsgi import WSGIContext, make_subrequest, load_app_config
@ -333,7 +332,7 @@ class GetContext(WSGIContext):
if have_complete_listing:
response_headers = [(h, v) for h, v in response_headers
if h.lower() != "etag"]
etag = md5()
etag = md5(usedforsecurity=False)
for seg_dict in segments:
etag.update(normalize_etag(seg_dict['hash']).encode('utf8'))
response_headers.append(('Etag', '"%s"' % etag.hexdigest()))


+ 4
- 4
swift/common/middleware/s3api/controllers/multi_upload.py View File

@ -61,7 +61,6 @@ Static Large Object when the multipart upload is completed.
import binascii
import copy
from hashlib import md5
import os
import re
import time
@ -69,7 +68,7 @@ import time
import six
from swift.common.swob import Range, bytes_to_wsgi, normalize_etag
from swift.common.utils import json, public, reiterate
from swift.common.utils import json, public, reiterate, md5
from swift.common.db import utf8encode
from swift.common.request_helpers import get_container_update_override_key
@ -636,7 +635,7 @@ class UploadController(Controller):
headers['Content-Type'] = content_type
container = req.container_name + MULTIUPLOAD_SUFFIX
s3_etag_hasher = md5()
s3_etag_hasher = md5(usedforsecurity=False)
manifest = []
previous_number = 0
try:
@ -646,7 +645,8 @@ class UploadController(Controller):
if 'content-md5' in req.headers:
# If an MD5 was provided, we need to verify it.
# Note that S3Request already took care of translating to ETag
if req.headers['etag'] != md5(xml).hexdigest():
if req.headers['etag'] != md5(
xml, usedforsecurity=False).hexdigest():
raise BadDigest(content_md5=req.headers['content-md5'])
# We're only interested in the body here, in the
# multipart-upload controller -- *don't* let it get


+ 4
- 3
swift/common/middleware/s3api/s3request.py View File

@ -17,7 +17,7 @@ import base64
import binascii
from collections import defaultdict, OrderedDict
from email.header import Header
from hashlib import sha1, sha256, md5
from hashlib import sha1, sha256
import hmac
import re
import six
@ -26,7 +26,7 @@ from six.moves.urllib.parse import quote, unquote, parse_qsl
import string
from swift.common.utils import split_path, json, get_swift_info, \
close_if_possible
close_if_possible, md5
from swift.common import swob
from swift.common.http import HTTP_OK, HTTP_CREATED, HTTP_ACCEPTED, \
HTTP_NO_CONTENT, HTTP_UNAUTHORIZED, HTTP_FORBIDDEN, HTTP_NOT_FOUND, \
@ -866,7 +866,8 @@ class S3Request(swob.Request):
raise InvalidRequest('Missing required header for this request: '
'Content-MD5')
digest = base64.b64encode(md5(body).digest()).strip().decode('ascii')
digest = base64.b64encode(md5(
body, usedforsecurity=False).digest()).strip().decode('ascii')
if self.environ['HTTP_CONTENT_MD5'] != digest:
raise BadDigest(content_md5=self.environ['HTTP_CONTENT_MD5'])


+ 10
- 8
swift/common/middleware/slo.py View File

@ -330,7 +330,6 @@ import json
import mimetypes
import re
import time
from hashlib import md5
import six
@ -348,7 +347,7 @@ from swift.common.utils import get_logger, config_true_value, \
get_valid_utf8_str, override_bytes_from_content_type, split_path, \
register_swift_info, RateLimitedIterator, quote, close_if_possible, \
closing_if_possible, LRUCache, StreamingPile, strict_b64decode, \
Timestamp, drain_and_close, get_expirer_container
Timestamp, drain_and_close, get_expirer_container, md5
from swift.common.request_helpers import SegmentedIterable, \
get_sys_meta_prefix, update_etag_is_at_header, resolve_etag_is_at_header, \
get_container_update_override_key, update_ignore_range_header
@ -927,7 +926,9 @@ class SloGetContext(WSGIContext):
if header.lower() == 'content-length':
new_headers.append(('Content-Length', len(json_data)))
elif header.lower() == 'etag':
new_headers.append(('Etag', md5(json_data).hexdigest()))
new_headers.append(
('Etag', md5(json_data, usedforsecurity=False)
.hexdigest()))
else:
new_headers.append((header, value))
self._response_headers = new_headers
@ -965,7 +966,7 @@ class SloGetContext(WSGIContext):
# Prep to calculate content_length & etag if necessary
if slo_etag is None:
calculated_etag = md5()
calculated_etag = md5(usedforsecurity=False)
if content_length is None:
calculated_content_length = 0
@ -977,7 +978,8 @@ class SloGetContext(WSGIContext):
if slo_etag is None:
if 'raw_data' in seg_dict:
r = md5(seg_dict['raw_data']).hexdigest()
r = md5(seg_dict['raw_data'],
usedforsecurity=False).hexdigest()
elif seg_dict.get('range'):
r = '%s:%s;' % (seg_dict['hash'], seg_dict['range'])
else:
@ -1347,11 +1349,11 @@ class StaticLargeObject(object):
out_content_type, resp_dict, problem_segments, 'upload')
return
slo_etag = md5()
slo_etag = md5(usedforsecurity=False)
for seg_data in data_for_storage:
if 'data' in seg_data:
raw_data = base64.b64decode(seg_data['data'])
r = md5(raw_data).hexdigest()
r = md5(raw_data, usedforsecurity=False).hexdigest()
elif seg_data.get('range'):
r = '%s:%s;' % (seg_data['hash'], seg_data['range'])
else:
@ -1386,7 +1388,7 @@ class StaticLargeObject(object):
SYSMETA_SLO_ETAG: slo_etag,
SYSMETA_SLO_SIZE: total_size,
'X-Static-Large-Object': 'True',
'Etag': md5(json_data).hexdigest(),
'Etag': md5(json_data, usedforsecurity=False).hexdigest(),
})
# Ensure container listings have both etags. However, if any


+ 2
- 3
swift/common/request_helpers.py View File

@ -20,7 +20,6 @@ Why not swift.common.utils, you ask? Because this way we can import things
from swob in here without creating circular imports.
"""
import hashlib
import itertools
import sys
import time
@ -40,7 +39,7 @@ from swift.common.utils import split_path, validate_device_partition, \
close_if_possible, maybe_multipart_byteranges_to_document_iters, \
multipart_byteranges_to_document_iters, parse_content_type, \
parse_content_range, csv_append, list_from_csv, Spliterator, quote, \
RESERVED, config_true_value
RESERVED, config_true_value, md5
from swift.common.wsgi import make_subrequest
@ -604,7 +603,7 @@ class SegmentedIterable(object):
seg_hash = None
if seg_resp.etag and not seg_req.headers.get('Range'):
# Only calculate the MD5 if it we can use it to validate
seg_hash = hashlib.md5()
seg_hash = md5(usedforsecurity=False)
document_iters = maybe_multipart_byteranges_to_document_iters(
seg_resp.app_iter,


+ 4
- 4
swift/common/ring/ring.py View File

@ -22,7 +22,6 @@ from os.path import getmtime
import struct
from time import time
import os
from hashlib import md5
from itertools import chain, count
from tempfile import NamedTemporaryFile
import sys
@ -32,7 +31,7 @@ import six
from six.moves import range
from swift.common.exceptions import RingLoadError
from swift.common.utils import hash_path, validate_configuration
from swift.common.utils import hash_path, validate_configuration, md5
from swift.common.ring.utils import tiers_for_dev
@ -53,7 +52,7 @@ class RingReader(object):
self._buffer = b''
self.size = 0
self.raw_size = 0
self._md5 = md5()
self._md5 = md5(usedforsecurity=False)
self._decomp = zlib.decompressobj(32 + zlib.MAX_WBITS)
@property
@ -538,7 +537,8 @@ class Ring(object):
(d['region'], d['zone'], d['ip']) for d in primary_nodes)
parts = len(self._replica2part2dev_id[0])
part_hash = md5(str(part).encode('ascii')).digest()
part_hash = md5(str(part).encode('ascii'),
usedforsecurity=False).digest()
start = struct.unpack_from('>I', part_hash)[0] >> self._part_shift
inc = int(parts / 65536) or 1
# Multiple loops for execution speed; the checks and bookkeeping get


+ 35
- 6
swift/common/utils.py View File

@ -40,7 +40,7 @@ import uuid
import functools
import platform
import email.parser
from hashlib import md5, sha1
from hashlib import sha1
from random import random, shuffle
from contextlib import contextmanager, closing
import ctypes
@ -674,7 +674,10 @@ class StrAnonymizer(str):
if not self:
return self
else:
h = getattr(hashlib, self.method)()
if self.method == 'md5':
h = md5(usedforsecurity=False)
else:
h = getattr(hashlib, self.method)()
if self.salt:
h.update(six.b(self.salt))
h.update(six.b(self))
@ -2735,10 +2738,10 @@ def hash_path(account, container=None, object=None, raw_digest=False):
else object.encode('utf8'))
if raw_digest:
return md5(HASH_PATH_PREFIX + b'/' + b'/'.join(paths)
+ HASH_PATH_SUFFIX).digest()
+ HASH_PATH_SUFFIX, usedforsecurity=False).digest()
else:
return md5(HASH_PATH_PREFIX + b'/' + b'/'.join(paths)
+ HASH_PATH_SUFFIX).hexdigest()
+ HASH_PATH_SUFFIX, usedforsecurity=False).hexdigest()
def get_zero_indexed_base_string(base, index):
@ -4858,6 +4861,31 @@ def get_md5_socket():
return md5_sockfd
try:
_test_md5 = hashlib.md5(usedforsecurity=False) # nosec
def md5(string=b'', usedforsecurity=True):
"""Return an md5 hashlib object using usedforsecurity parameter
For python distributions that support the usedforsecurity keyword
parameter, this passes the parameter through as expected.
See https://bugs.python.org/issue9216
"""
return hashlib.md5(string, usedforsecurity=usedforsecurity) # nosec
except TypeError:
def md5(string=b'', usedforsecurity=True):
"""Return an md5 hashlib object without usedforsecurity parameter
For python distributions that do not yet support this keyword
parameter, we drop the parameter
"""
return hashlib.md5(string) # nosec
def md5_factory():
return md5(usedforsecurity=False)
class ShardRange(object):
"""
A ShardRange encapsulates sharding state related to a container including
@ -4999,7 +5027,8 @@ class ShardRange(object):
if not isinstance(parent_container, bytes):
parent_container = parent_container.encode('utf-8')
return "%s-%s-%s-%s" % (root_container,
hashlib.md5(parent_container).hexdigest(),
md5(parent_container,
usedforsecurity=False).hexdigest(),
cls._to_timestamp(timestamp).internal,
index)
@ -5583,7 +5612,7 @@ def md5_hash_for_file(fname):
:returns: MD5 checksum, hex encoded
"""
with open(fname, 'rb') as f:
md5sum = md5()
md5sum = md5(usedforsecurity=False)
for block in iter(lambda: f.read(MD5_BLOCK_READ_BYTES), b''):
md5sum.update(block)
return md5sum.hexdigest()


+ 12
- 9
swift/obj/diskfile.py View File

@ -40,7 +40,6 @@ import os
import re
import time
import uuid
from hashlib import md5
import logging
import traceback
import xattr
@ -66,7 +65,8 @@ from swift.common.utils import mkdirs, Timestamp, \
config_true_value, listdir, split_path, remove_file, \
get_md5_socket, F_SETPIPE_SZ, decode_timestamps, encode_timestamps, \
MD5_OF_EMPTY_STRING, link_fd_to_path, \
O_TMPFILE, makedirs_count, replace_partition_in_path, remove_directory
O_TMPFILE, makedirs_count, replace_partition_in_path, remove_directory, \
md5, md5_factory
from swift.common.splice import splice, tee
from swift.common.exceptions import DiskFileQuarantined, DiskFileNotExist, \
DiskFileCollision, DiskFileNoSpace, DiskFileDeviceUnavailable, \
@ -222,14 +222,16 @@ def read_metadata(fd, add_missing_checksum=False):
# exist. This is fine; it just means that this object predates the
# introduction of metadata checksums.
if add_missing_checksum:
new_checksum = md5(metadata).hexdigest().encode('ascii')
new_checksum = (md5(metadata, usedforsecurity=False)
.hexdigest().encode('ascii'))
try:
xattr.setxattr(fd, METADATA_CHECKSUM_KEY, new_checksum)
except (IOError, OSError) as e:
logging.error("Error adding metadata: %s" % e)
if metadata_checksum:
computed_checksum = md5(metadata).hexdigest().encode('ascii')
computed_checksum = (md5(metadata, usedforsecurity=False)
.hexdigest().encode('ascii'))
if metadata_checksum != computed_checksum:
raise DiskFileBadMetadataChecksum(
"Metadata checksum mismatch for %s: "
@ -254,7 +256,8 @@ def write_metadata(fd, metadata, xattr_size=65536):
:param metadata: metadata to write
"""
metastr = pickle.dumps(_encode_metadata(metadata), PICKLE_PROTOCOL)
metastr_md5 = md5(metastr).hexdigest().encode('ascii')
metastr_md5 = (
md5(metastr, usedforsecurity=False).hexdigest().encode('ascii'))
key = 0
try:
while metastr:
@ -1113,11 +1116,11 @@ class BaseDiskFileManager(object):
:param policy: storage policy used
"""
if six.PY2:
hashes = defaultdict(md5)
hashes = defaultdict(md5_factory)
else:
class shim(object):
def __init__(self):
self.md5 = md5()
self.md5 = md5(usedforsecurity=False)
def update(self, s):
if isinstance(s, str):
@ -1686,7 +1689,7 @@ class BaseDiskFileWriter(object):
self._fd = None
self._tmppath = None
self._size = size
self._chunks_etag = md5()
self._chunks_etag = md5(usedforsecurity=False)
self._bytes_per_sync = bytes_per_sync
self._diskfile = diskfile
self.next_part_power = next_part_power
@ -2003,7 +2006,7 @@ class BaseDiskFileReader(object):
def _init_checks(self):
if self._fp.tell() == 0:
self._started_at_0 = True
self._iter_etag = md5()
self._iter_etag = md5(usedforsecurity=False)
def _update_checks(self, chunk):
if self._iter_etag:


+ 3
- 3
swift/obj/expirer.py View File

@ -20,7 +20,6 @@ from time import time
from os.path import join
from swift import gettext_ as _
from collections import defaultdict, deque
import hashlib
from eventlet import sleep, Timeout
from eventlet.greenpool import GreenPool
@ -30,7 +29,7 @@ from swift.common.daemon import Daemon
from swift.common.internal_client import InternalClient, UnexpectedResponse
from swift.common.utils import get_logger, dump_recon_cache, split_path, \
Timestamp, config_true_value, normalize_delete_at_timestamp, \
RateLimitedIterator
RateLimitedIterator, md5
from swift.common.http import HTTP_NOT_FOUND, HTTP_CONFLICT, \
HTTP_PRECONDITION_FAILED
from swift.common.swob import wsgi_quote, str_to_wsgi
@ -218,7 +217,8 @@ class ObjectExpirer(Daemon):
if not isinstance(name, bytes):
name = name.encode('utf8')
# md5 is only used for shuffling mod
return int(hashlib.md5(name).hexdigest(), 16) % divisor
return int(md5(
name, usedforsecurity=False).hexdigest(), 16) % divisor
def iter_task_accounts_to_expire(self):
"""


+ 3
- 3
swift/obj/mem_diskfile.py View File

@ -17,7 +17,6 @@
import io
import time
import hashlib
from contextlib import contextmanager
from eventlet import Timeout
@ -27,6 +26,7 @@ from swift.common.exceptions import DiskFileQuarantined, DiskFileNotExist, \
DiskFileCollision, DiskFileDeleted, DiskFileNotOpen
from swift.common.request_helpers import is_sys_meta
from swift.common.swob import multi_range_iterator
from swift.common.utils import md5
from swift.obj.diskfile import DATAFILE_SYSTEM_META, RESERVED_DATAFILE_META
@ -103,7 +103,7 @@ class DiskFileWriter(object):
self._name = name
self._fp = None
self._upload_size = 0
self._chunks_etag = hashlib.md5()
self._chunks_etag = md5(usedforsecurity=False)
def open(self):
"""
@ -197,7 +197,7 @@ class DiskFileReader(object):
self._read_to_eof = False
if self._fp.tell() == 0:
self._started_at_0 = True
self._iter_etag = hashlib.md5()
self._iter_etag = md5(usedforsecurity=False)
while True:
chunk = self._fp.read()
if chunk:


+ 2
- 3
swift/obj/server.py View File

@ -26,7 +26,6 @@ import traceback
import socket
import math
from swift import gettext_ as _
from hashlib import md5
from eventlet import sleep, wsgi, Timeout, tpool
from eventlet.greenthread import spawn
@ -37,7 +36,7 @@ from swift.common.utils import public, get_logger, \
get_expirer_container, parse_mime_headers, \
iter_multipart_mime_documents, extract_swift_bytes, safe_json_loads, \
config_auto_int_value, split_path, get_redirect_data, \
normalize_timestamp
normalize_timestamp, md5
from swift.common.bufferedhttp import http_connect
from swift.common.constraints import check_object_creation, \
valid_timestamp, check_utf8, AUTO_CREATE_ACCOUNT_PREFIX
@ -583,7 +582,7 @@ class ObjectController(BaseStorageServer):
footer_md5 = footer_hdrs.get('Content-MD5')
if not footer_md5:
raise HTTPBadRequest(body="no Content-MD5 in footer")
if footer_md5 != md5(footer_body).hexdigest():
if footer_md5 != md5(footer_body, usedforsecurity=False).hexdigest():
raise HTTPUnprocessableEntity(body="footer MD5 mismatch")
try:


+ 6
- 5
swift/proxy/controllers/obj.py View File

@ -35,7 +35,6 @@ import mimetypes
import time
import math
import random
from hashlib import md5
import sys
from greenlet import GreenletExit
@ -49,7 +48,8 @@ from swift.common.utils import (
GreenAsyncPile, GreenthreadSafeIterator, Timestamp, WatchdogTimeout,
normalize_delete_at_timestamp, public, get_expirer_container,
document_iters_to_http_response_body, parse_content_range,
quorum_size, reiterate, close_if_possible, safe_json_loads)
quorum_size, reiterate, close_if_possible, safe_json_loads, md5,
md5_factory)
from swift.common.bufferedhttp import http_connect
from swift.common.constraints import check_metadata, check_object_creation
from swift.common import constraints
@ -1784,7 +1784,8 @@ class MIMEPutter(Putter):
self._start_object_data()
footer_body = json.dumps(footer_metadata).encode('ascii')
footer_md5 = md5(footer_body).hexdigest().encode('ascii')
footer_md5 = md5(
footer_body, usedforsecurity=False).hexdigest().encode('ascii')
tail_boundary = (b"--%s" % (self.mime_boundary,))
if not self.multiphase:
@ -3178,7 +3179,7 @@ class ECObjectController(BaseObjectController):
bytes_transferred = 0
chunk_transform = chunk_transformer(policy)
chunk_transform.send(None)
frag_hashers = collections.defaultdict(md5)
frag_hashers = collections.defaultdict(md5_factory)
def send_chunk(chunk):
# Note: there's two different hashers in here. etag_hasher is
@ -3411,7 +3412,7 @@ class ECObjectController(BaseObjectController):
# the same as the request body sent proxy -> object, we
# can't rely on the object-server to do the etag checking -
# so we have to do it here.
etag_hasher = md5()
etag_hasher = md5(usedforsecurity=False)
min_conns = policy.quorum
putters = self._get_put_connections(


+ 14
- 10
test/functional/s3api/test_multi_upload.py View File

@ -23,13 +23,13 @@ import boto
# pylint: disable-msg=E0611,F0401
from distutils.version import StrictVersion
from hashlib import md5
from six.moves import zip, zip_longest
import test.functional as tf
from swift.common.middleware.s3api.etree import fromstring, tostring, \
Element, SubElement
from swift.common.middleware.s3api.utils import mktime
from swift.common.utils import md5
from test.functional.s3api import S3ApiBase
from test.functional.s3api.s3_test_client import Connection
@ -180,7 +180,7 @@ class TestS3ApiMultiUpload(S3ApiBase):
# Upload Part
key, upload_id = uploads[0]
content = b'a' * self.min_segment_size
etag = md5(content).hexdigest()
etag = md5(content, usedforsecurity=False).hexdigest()
status, headers, body = \
self._upload_part(bucket, key, upload_id, content)
self.assertEqual(status, 200)
@ -196,7 +196,7 @@ class TestS3ApiMultiUpload(S3ApiBase):
src_bucket = 'bucket2'
src_obj = 'obj3'
src_content = b'b' * self.min_segment_size
etag = md5(src_content).hexdigest()
etag = md5(src_content, usedforsecurity=False).hexdigest()
# prepare src obj
self.conn.make_request('PUT', src_bucket)
@ -312,7 +312,8 @@ class TestS3ApiMultiUpload(S3ApiBase):
concatted_etags = b''.join(
etag.strip('"').encode('ascii') for etag in etags)
exp_etag = '"%s-%s"' % (
md5(binascii.unhexlify(concatted_etags)).hexdigest(), len(etags))
md5(binascii.unhexlify(concatted_etags),
usedforsecurity=False).hexdigest(), len(etags))
etag = elem.find('ETag').text
self.assertEqual(etag, exp_etag)
@ -324,7 +325,8 @@ class TestS3ApiMultiUpload(S3ApiBase):
self.assertEqual(headers['content-type'], 'foo/bar')
self.assertEqual(headers['x-amz-meta-baz'], 'quux')
swift_etag = '"%s"' % md5(concatted_etags).hexdigest()
swift_etag = '"%s"' % md5(
concatted_etags, usedforsecurity=False).hexdigest()
# TODO: GET via swift api, check against swift_etag
# Should be safe to retry
@ -375,7 +377,7 @@ class TestS3ApiMultiUpload(S3ApiBase):
self.assertIsNotNone(last_modified)
exp_content = b'a' * self.min_segment_size
etag = md5(exp_content).hexdigest()
etag = md5(exp_content, usedforsecurity=False).hexdigest()
self.assertEqual(resp_etag, etag)
# Also check that the etag is correct in part listings
@ -858,7 +860,9 @@ class TestS3ApiMultiUpload(S3ApiBase):
src_content = b'y' * (self.min_segment_size // 2) + b'z' * \
self.min_segment_size
src_range = 'bytes=0-%d' % (self.min_segment_size - 1)
etag = md5(src_content[:self.min_segment_size]).hexdigest()
etag = md5(
src_content[:self.min_segment_size],
usedforsecurity=False).hexdigest()
# prepare src obj
self.conn.make_request('PUT', src_bucket)
@ -951,7 +955,7 @@ class TestS3ApiMultiUpload(S3ApiBase):
src_obj = 'obj4'
src_content = b'y' * (self.min_segment_size // 2) + b'z' * \
self.min_segment_size
etags = [md5(src_content).hexdigest()]
etags = [md5(src_content, usedforsecurity=False).hexdigest()]
# prepare null-version src obj
self.conn.make_request('PUT', src_bucket)
@ -969,7 +973,7 @@ class TestS3ApiMultiUpload(S3ApiBase):
src_obj2 = 'obj5'
src_content2 = b'stub'
etags.append(md5(src_content2).hexdigest())
etags.append(md5(src_content2, usedforsecurity=False).hexdigest())
# prepare src obj w/ real version
self.conn.make_request('PUT', src_bucket, src_obj2, body=src_content2)
@ -1098,7 +1102,7 @@ class TestS3ApiMultiUploadSigV4(TestS3ApiMultiUpload):
# Complete Multipart Upload
key, upload_id = uploads[0]
etags = [md5(content).hexdigest()]
etags = [md5(content, usedforsecurity=False).hexdigest()]
xml = self._gen_comp_xml(etags)
status, headers, body = \
self._complete_multi_upload(bucket, key, upload_id, xml)


+ 13
- 13
test/functional/s3api/test_object.py View File

@ -24,13 +24,13 @@ from distutils.version import StrictVersion
import email.parser
from email.utils import formatdate, parsedate
from time import mktime
from hashlib import md5
import six
from six.moves.urllib.parse import quote
import test.functional as tf
from swift.common.middleware.s3api.etree import fromstring
from swift.common.utils import md5
from test.functional.s3api import S3ApiBase
from test.functional.s3api.s3_test_client import Connection
@ -61,7 +61,7 @@ class TestS3ApiObject(S3ApiBase):
def test_object(self):
obj = 'object name with %-sign'
content = b'abc123'
etag = md5(content).hexdigest()
etag = md5(content, usedforsecurity=False).hexdigest()
# PUT Object
status, headers, body = \
@ -252,7 +252,7 @@ class TestS3ApiObject(S3ApiBase):
def test_put_object_content_encoding(self):
obj = 'object'
etag = md5().hexdigest()
etag = md5(usedforsecurity=False).hexdigest()
headers = {'Content-Encoding': 'gzip'}
status, headers, body = \
self.conn.make_request('PUT', self.bucket, obj, headers)
@ -267,7 +267,7 @@ class TestS3ApiObject(S3ApiBase):
def test_put_object_content_md5(self):
obj = 'object'
content = b'abcdefghij'
etag = md5(content).hexdigest()
etag = md5(content, usedforsecurity=False).hexdigest()
headers = {'Content-MD5': calculate_md5(content)}
status, headers, body = \
self.conn.make_request('PUT', self.bucket, obj, headers, content)
@ -278,7 +278,7 @@ class TestS3ApiObject(S3ApiBase):
def test_put_object_content_type(self):
obj = 'object'
content = b'abcdefghij'
etag = md5(content).hexdigest()
etag = md5(content, usedforsecurity=False).hexdigest()
headers = {'Content-Type': 'text/plain'}
status, headers, body = \
self.conn.make_request('PUT', self.bucket, obj, headers, content)
@ -320,7 +320,7 @@ class TestS3ApiObject(S3ApiBase):
def test_put_object_expect(self):
obj = 'object'
content = b'abcdefghij'
etag = md5(content).hexdigest()
etag = md5(content, usedforsecurity=False).hexdigest()
headers = {'Expect': '100-continue'}
status, headers, body = \
self.conn.make_request('PUT', self.bucket, obj, headers, content)
@ -333,7 +333,7 @@ class TestS3ApiObject(S3ApiBase):
expected_headers = req_headers
obj = 'object'
content = b'abcdefghij'
etag = md5(content).hexdigest()
etag = md5(content, usedforsecurity=False).hexdigest()
status, headers, body = \
self.conn.make_request('PUT', self.bucket, obj,
req_headers, content)
@ -389,7 +389,7 @@ class TestS3ApiObject(S3ApiBase):
def test_put_object_storage_class(self):
obj = 'object'
content = b'abcdefghij'
etag = md5(content).hexdigest()
etag = md5(content, usedforsecurity=False).hexdigest()
headers = {'X-Amz-Storage-Class': 'STANDARD'}
status, headers, body = \
self.conn.make_request('PUT', self.bucket, obj, headers, content)
@ -435,7 +435,7 @@ class TestS3ApiObject(S3ApiBase):
def test_put_object_copy_source(self):
obj = 'object'
content = b'abcdefghij'
etag = md5(content).hexdigest()
etag = md5(content, usedforsecurity=False).hexdigest()
self.conn.make_request('PUT', self.bucket, obj, body=content)
dst_bucket = 'dst-bucket'
@ -521,7 +521,7 @@ class TestS3ApiObject(S3ApiBase):
obj = 'object'
dst_bucket = 'dst-bucket'
dst_obj = 'dst_object'
etag = md5().hexdigest()
etag = md5(usedforsecurity=False).hexdigest()
self.conn.make_request('PUT', self.bucket, obj)
self.conn.make_request('PUT', dst_bucket)
@ -541,7 +541,7 @@ class TestS3ApiObject(S3ApiBase):
obj = 'object'
dst_bucket = 'dst-bucket'
dst_obj = 'dst_object'
etag = md5().hexdigest()
etag = md5(usedforsecurity=False).hexdigest()
self.conn.make_request('PUT', self.bucket, obj)
self.conn.make_request('PUT', dst_bucket)
@ -561,7 +561,7 @@ class TestS3ApiObject(S3ApiBase):
obj = 'object'
dst_bucket = 'dst-bucket'
dst_obj = 'dst_object'
etag = md5().hexdigest()
etag = md5(usedforsecurity=False).hexdigest()
self.conn.make_request('PUT', self.bucket, obj)
self.conn.make_request('PUT', dst_bucket)
@ -580,7 +580,7 @@ class TestS3ApiObject(S3ApiBase):
obj = 'object'
dst_bucket = 'dst-bucket'
dst_obj = 'dst_object'
etag = md5().hexdigest()
etag = md5(usedforsecurity=False).hexdigest()
self.conn.make_request('PUT', self.bucket, obj)
self.conn.make_request('PUT', dst_bucket)


+ 3
- 2
test/functional/s3api/utils.py View File

@ -14,8 +14,8 @@
# limitations under the License.
from base64 import b64encode
from hashlib import md5
from swift.common.middleware.s3api.etree import fromstring
from swift.common.utils import md5
def get_error_code(body):
@ -29,4 +29,5 @@ def get_error_msg(body):
def calculate_md5(body):
return b64encode(md5(body).digest()).strip().decode('ascii')
return b64encode(
md5(body, usedforsecurity=False).digest()).strip().decode('ascii')

+ 3
- 4
test/functional/swift_test_client.py View File

@ -13,7 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import hashlib
import io
import json
import os
@ -33,7 +32,7 @@ from swiftclient import get_auth
from swift.common import constraints
from swift.common.http import is_success
from swift.common.swob import str_to_wsgi, wsgi_to_str
from swift.common.utils import config_true_value
from swift.common.utils import config_true_value, md5
from test import safe_repr
@ -851,7 +850,7 @@ class File(Base):
if isinstance(data, bytes):
data = io.BytesIO(data)
checksum = hashlib.md5()
checksum = md5(usedforsecurity=False)
buff = data.read(block_size)
while buff:
checksum.update(buff)
@ -1058,7 +1057,7 @@ class File(Base):
raise ResponseError(self.conn.response, 'GET',
self.conn.make_path(self.path))
checksum = hashlib.md5()
checksum = md5(usedforsecurity=False)
scratch = self.conn.response.read(8192)
while len(scratch) > 0:


+ 2
- 2
test/functional/test_object.py View File

@ -16,7 +16,6 @@
# limitations under the License.
import datetime
import hashlib
import json
import unittest
from uuid import uuid4
@ -29,6 +28,7 @@ from six.moves import range
from test.functional import check_response, retry, requires_acls, \
requires_policies, SkipTest, requires_bulk
import test.functional as tf
from swift.common.utils import md5
def setUpModule():
@ -1741,7 +1741,7 @@ class TestObject(unittest.TestCase):
expect_quoted = tf.cluster_info.get('etag_quoter', {}).get(
'enable_by_default', False)
expected_etag = hashlib.md5(b'test').hexdigest()
expected_etag = md5(b'test', usedforsecurity=False).hexdigest()
if expect_quoted:
expected_etag = '"%s"' % expected_etag
self.assertEqual(resp.headers['etag'], expected_etag)


+ 47
- 46
test/functional/test_object_versioning.py View File

@ -21,13 +21,12 @@ import time
import six
from copy import deepcopy
from hashlib import md5
from six.moves.urllib.parse import quote, unquote
import test.functional as tf
from swift.common.swob import normalize_etag
from swift.common.utils import MD5_OF_EMPTY_STRING, config_true_value
from swift.common.utils import MD5_OF_EMPTY_STRING, config_true_value, md5
from swift.common.middleware.versioned_writes.object_versioning import \
DELETE_MARKER_CONTENT_TYPE
@ -338,7 +337,9 @@ class TestObjectVersioning(TestObjectVersioningBase):
obj = self.env.unversioned_container.file(oname)
resp = obj.write(body, return_resp=True)
etag = resp.getheader('etag')
self.assertEqual(md5(body).hexdigest(), normalize_etag(etag))
self.assertEqual(
md5(body, usedforsecurity=False).hexdigest(),
normalize_etag(etag))
# un-versioned object is cool with with if-match
self.assertEqual(body, obj.read(hdrs={'if-match': etag}))
@ -569,7 +570,7 @@ class TestObjectVersioning(TestObjectVersioningBase):
'name': obj_name,
'content_type': version['content_type'],
'version_id': version['version_id'],
'hash': md5(version['body']).hexdigest(),
'hash': md5(version['body'], usedforsecurity=False).hexdigest(),
'bytes': len(version['body'],)
} for version in reversed(versions)]
for item, is_latest in zip(expected, (True, False, False)):
@ -1263,14 +1264,14 @@ class TestContainerOperations(TestObjectVersioningBase):
# v1
resp = obj.write(b'version1', hdrs={
'Content-Type': 'text/jibberish11',
'ETag': md5(b'version1').hexdigest(),
'ETag': md5(b'version1', usedforsecurity=False).hexdigest(),
}, return_resp=True)
obj1_v1['id'] = resp.getheader('x-object-version-id')
# v2
resp = obj.write(b'version2', hdrs={
'Content-Type': 'text/jibberish12',
'ETag': md5(b'version2').hexdigest(),
'ETag': md5(b'version2', usedforsecurity=False).hexdigest(),
}, return_resp=True)
obj1_v2 = {}
obj1_v2['name'] = obj1_v1['name']
@ -1279,7 +1280,7 @@ class TestContainerOperations(TestObjectVersioningBase):
# v3
resp = obj.write(b'version3', hdrs={
'Content-Type': 'text/jibberish13',
'ETag': md5(b'version3').hexdigest(),
'ETag': md5(b'version3', usedforsecurity=False).hexdigest(),
}, return_resp=True)
obj1_v3 = {}
obj1_v3['name'] = obj1_v1['name']
@ -1333,20 +1334,20 @@ class TestContainerOperations(TestObjectVersioningBase):
obj = self.env.unversioned_container.file(objs[0])
obj.write(b'data', hdrs={
'Content-Type': 'text/jibberish11',
'ETag': md5(b'data').hexdigest(),
'ETag': md5(b'data', usedforsecurity=False).hexdigest(),
})
obj.delete()
obj = self.env.unversioned_container.file(objs[1])
obj.write(b'first', hdrs={
'Content-Type': 'text/blah-blah-blah',
'ETag': md5(b'first').hexdigest(),
'ETag': md5(b'first', usedforsecurity=False).hexdigest(),
})
obj = self.env.unversioned_container.file(objs[2])
obj.write(b'second', hdrs={
'Content-Type': 'text/plain',
'ETag': md5(b'second').hexdigest(),
'ETag': md5(b'second', usedforsecurity=False).hexdigest(),
})
return objs
@ -1385,21 +1386,21 @@ class TestContainerOperations(TestObjectVersioningBase):
'name': obj1_v3['name'],
'bytes': 8,
'content_type': 'text/jibberish13',
'hash': md5(b'version3').hexdigest(),
'hash': md5(b'version3', usedforsecurity=False).hexdigest(),
'is_latest': False,
'version_id': obj1_v3['id'],
}, {
'name': obj1_v2['name'],
'bytes': 8,
'content_type': 'text/jibberish12',
'hash': md5(b'version2').hexdigest(),
'hash': md5(b'version2', usedforsecurity=False).hexdigest(),
'is_latest': False,
'version_id': obj1_v2['id'],
}, {
'name': obj1_v1['name'],
'bytes': 8,
'content_type': 'text/jibberish11',
'hash': md5(b'version1').hexdigest(),
'hash': md5(b'version1', usedforsecurity=False).hexdigest(),
'is_latest': False,
'version_id': obj1_v1['id'],
}])
@ -1418,21 +1419,21 @@ class TestContainerOperations(TestObjectVersioningBase):
'name': obj1_v1['name'],
'bytes': 8,
'content_type': 'text/jibberish11',
'hash': md5(b'version1').hexdigest(),
'hash': md5(b'version1', usedforsecurity=False).hexdigest(),
'is_latest': False,
'version_id': obj1_v1['id'],
}, {
'name': obj1_v2['name'],
'bytes': 8,
'content_type': 'text/jibberish12',
'hash': md5(b'version2').hexdigest(),
'hash': md5(b'version2', usedforsecurity=False).hexdigest(),
'is_latest': False,
'version_id': obj1_v2['id'],
}, {
'name': obj1_v3['name'],
'bytes': 8,
'content_type': 'text/jibberish13',
'hash': md5(b'version3').hexdigest(),
'hash': md5(b'version3', usedforsecurity=False).hexdigest(),
'is_latest': False,
'version_id': obj1_v3['id'],
}, {
@ -1481,21 +1482,21 @@ class TestContainerOperations(TestObjectVersioningBase):
'name': obj1_v3['name'],
'bytes': 8,
'content_type': 'text/jibberish13',
'hash': md5(b'version3').hexdigest(),
'hash': md5(b'version3', usedforsecurity=False).hexdigest(),
'is_latest': False,
'version_id': obj1_v3['id'],
}, {
'name': obj1_v2['name'],
'bytes': 8,
'content_type': 'text/jibberish12',
'hash': md5(b'version2').hexdigest(),
'hash': md5(b'version2', usedforsecurity=False).hexdigest(),
'is_latest': False,
'version_id': obj1_v2['id'],
}, {
'name': obj1_v1['name'],
'bytes': 8,
'content_type': 'text/jibberish11',
'hash': md5(b'version1').hexdigest(),
'hash': md5(b'version1', usedforsecurity=False).hexdigest(),
'is_latest': False,
'version_id': obj1_v1['id'],
}])
@ -1516,21 +1517,21 @@ class TestContainerOperations(TestObjectVersioningBase):
'name': obj1_v1['name'],
'bytes': 8,
'content_type': 'text/jibberish11',
'hash': md5(b'version1').hexdigest(),
'hash': md5(b'version1', usedforsecurity=False).hexdigest(),
'is_latest': False,
'version_id': obj1_v1['id'],
}, {
'name': obj1_v1['name'],
'bytes': 8,
'content_type': 'text/jibberish12',
'hash': md5(b'version2').hexdigest(),
'hash': md5(b'version2', usedforsecurity=False).hexdigest(),
'is_latest': False,
'version_id': obj1_v2['id'],
}, {
'name': obj1_v1['name'],
'bytes': 8,
'content_type': 'text/jibberish13',
'hash': md5(b'version3').hexdigest(),
'hash': md5(b'version3', usedforsecurity=False).hexdigest(),
'is_latest': False,
'version_id': obj1_v3['id'],
}, {
@ -1601,7 +1602,7 @@ class TestContainerOperations(TestObjectVersioningBase):
'name': obj1_v3['name'],
'bytes': 8,
'content_type': 'text/jibberish13',
'hash': md5(b'version3').hexdigest(),
'hash': md5(b'version3', usedforsecurity=False).hexdigest(),
'is_latest': False,
'version_id': obj1_v3['id'],
}])
@ -1623,14 +1624,14 @@ class TestContainerOperations(TestObjectVersioningBase):
'name': obj1_v2['name'],
'bytes': 8,
'content_type': 'text/jibberish12',
'hash': md5(b'version2').hexdigest(),
'hash': md5(b'version2', usedforsecurity=False).hexdigest(),
'is_latest': False,
'version_id': obj1_v2['id'],
}, {
'name': obj1_v1['name'],
'bytes': 8,
'content_type': 'text/jibberish11',