Skip to content

Commit 28af7f9

Browse files
committed
Add SHA256 fallback if MD5 isn't available on the system
1 parent 0689552 commit 28af7f9

2 files changed

Lines changed: 38 additions & 7 deletions

File tree

awscli/customizations/s3uploader.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@
1111
# ANY KIND, either express or implied. See the License for the specific
1212
# language governing permissions and limitations under the License.
1313

14-
import hashlib
1514
import logging
1615
import os
1716
import sys
1817
import threading
1918

2019
import botocore
2120
import botocore.exceptions
21+
from botocore.compat import get_md5
2222
from s3transfer.manager import TransferManager
2323
from s3transfer.subscribers import BaseSubscriber
2424

@@ -27,6 +27,16 @@
2727
LOG = logging.getLogger(__name__)
2828

2929

30+
def _get_checksum():
31+
hashlib_params = {"usedforsecurity": False}
32+
try:
33+
checksum = get_md5(**hashlib_params)
34+
except botocore.exceptions.MD5UnavailableError:
35+
import hashlib
36+
checksum = hashlib.sha256(**hashlib_params)
37+
return checksum
38+
39+
3040
class NoSuchBucketError(Exception):
3141
def __init__(self, **kwargs):
3242
msg = self.fmt.format(**kwargs)
@@ -134,7 +144,7 @@ def upload(self, file_name, remote_path):
134144

135145
def upload_with_dedup(self, file_name, extension=None):
136146
"""
137-
Makes and returns name of the S3 object based on the file's MD5 sum
147+
Makes and returns name of the S3 object based on the file's checksum
138148
139149
:param file_name: file to upload
140150
:param extension: String of file extension to append to the object
@@ -146,8 +156,8 @@ def upload_with_dedup(self, file_name, extension=None):
146156
# and re-upload only if necessary. So the template points to same file
147157
# in multiple places, this will upload only once
148158

149-
filemd5 = self.file_checksum(file_name)
150-
remote_path = filemd5
159+
file_checksum = self.file_checksum(file_name)
160+
remote_path = file_checksum
151161
if extension:
152162
remote_path = remote_path + "." + extension
153163

@@ -175,7 +185,7 @@ def make_url(self, obj_path):
175185

176186
def file_checksum(self, file_name):
177187
with open(file_name, "rb") as file_handle:
178-
md5 = hashlib.md5()
188+
checksum = _get_checksum()
179189
# Read file in chunks of 4096 bytes
180190
block_size = 4096
181191

@@ -185,13 +195,13 @@ def file_checksum(self, file_name):
185195

186196
buf = file_handle.read(block_size)
187197
while len(buf) > 0:
188-
md5.update(buf)
198+
checksum.update(buf)
189199
buf = file_handle.read(block_size)
190200

191201
# Restore file cursor's position
192202
file_handle.seek(curpos)
193203

194-
return md5.hexdigest()
204+
return checksum.hexdigest()
195205

196206
def to_path_style_s3_url(self, key, version=None):
197207
"""

tests/unit/customizations/test_s3uploader.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,27 @@ def test_file_checksum(self):
348348
finally:
349349
shutil.rmtree(tempdir)
350350

351+
@mock.patch("awscli.customizations.s3uploader.get_md5")
352+
def test_file_checksum_fips_fallback(self, get_md5_mock):
353+
num_chars = 4096*5
354+
data = ''.join(random.choice(string.ascii_uppercase)
355+
for _ in range(num_chars)).encode('utf-8')
356+
checksum = hashlib.sha256(usedforsecurity=False)
357+
checksum.update(data)
358+
expected_checksum = checksum.hexdigest()
359+
360+
tempdir = tempfile.mkdtemp()
361+
get_md5_mock.side_effect = botocore.exceptions.MD5UnavailableError()
362+
try:
363+
filename = os.path.join(tempdir, 'tempfile')
364+
with open(filename, 'wb') as f:
365+
f.write(data)
366+
367+
actual_checksum = self.s3uploader.file_checksum(filename)
368+
self.assertEqual(expected_checksum, actual_checksum)
369+
finally:
370+
shutil.rmtree(tempdir)
371+
351372
def test_make_url(self):
352373
path = "Hello/how/are/you"
353374
expected = f"s3://{self.bucket_name}/{path}"

0 commit comments

Comments
 (0)