Skip to content

Commit dddefe2

Browse files
committed
Fix handling of SSE-C keys when copying unencrypted to encrypted objects or objects with different keys
1 parent 082884f commit dddefe2

File tree

3 files changed

+111
-8
lines changed

3 files changed

+111
-8
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"type": "bugfix",
3+
"category": "``s3``",
4+
"description": "Fix handling of SSE-C keys when copying unencrypted to encrypted objects or objects with different encryption keys"
5+
}

awscli/customizations/s3/subcommands.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1481,16 +1481,18 @@ def _map_sse_c_params(self, request_parameters, paths_type):
14811481
# SSE-C key and algorithm. Note the reverse FileGenerator does
14821482
# not need any of these because it is used only for sync operations
14831483
# which only use ListObjects which does not require HeadObject.
1484-
RequestParamsMapper.map_head_object_params(
1485-
request_parameters['HeadObject'], self.parameters
1486-
)
14871484
if paths_type == 's3s3':
1485+
head_params = self.parameters.copy()
1486+
head_params['sse_c'] = self.parameters.get('sse_c_copy_source')
1487+
head_params['sse_c_key'] = self.parameters.get(
1488+
'sse_c_copy_source_key'
1489+
)
1490+
RequestParamsMapper.map_head_object_params(
1491+
request_parameters['HeadObject'], head_params
1492+
)
1493+
else:
14881494
RequestParamsMapper.map_head_object_params(
1489-
request_parameters['HeadObject'],
1490-
{
1491-
'sse_c': self.parameters.get('sse_c_copy_source'),
1492-
'sse_c_key': self.parameters.get('sse_c_copy_source_key'),
1493-
},
1495+
request_parameters['HeadObject'], self.parameters
14941496
)
14951497

14961498

tests/functional/s3/test_cp_command.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,102 @@ def test_cp_with_sse_c_copy_source_fileb(self):
757757
}
758758
self.assertDictEqual(self.operations_called[1][1], expected_args)
759759

760+
def test_s3s3_cp_with_destination_sse_c(self):
761+
"""S3->S3 copy with an unencrypted source and encrypted destination"""
762+
self.parsed_responses = [
763+
{
764+
"AcceptRanges": "bytes",
765+
"LastModified": "Tue, 12 Jul 2016 21:26:07 GMT",
766+
"ContentLength": 4,
767+
"ETag": '"d3b07384d113edec49eaa6238ad5ff00"',
768+
"Metadata": {},
769+
"ContentType": "binary/octet-stream",
770+
},
771+
{
772+
"AcceptRanges": "bytes",
773+
"Metadata": {},
774+
"ContentType": "binary/octet-stream",
775+
"ContentLength": 4,
776+
"ETag": '"d3b07384d113edec49eaa6238ad5ff00"',
777+
"LastModified": "Tue, 12 Jul 2016 21:26:07 GMT",
778+
"Body": BytesIO(b'foo\n'),
779+
},
780+
{},
781+
]
782+
cmdline = (
783+
'%s s3://bucket-one/key.txt s3://bucket/key.txt '
784+
'--sse-c --sse-c-key foo' % self.prefix
785+
)
786+
self.run_cmd(cmdline, expected_rc=0)
787+
self.assertEqual(len(self.operations_called), 2)
788+
self.assertEqual(self.operations_called[0][0].name, 'HeadObject')
789+
expected_head_args = {
790+
'Bucket': 'bucket-one',
791+
'Key': 'key.txt',
792+
# don't expect to see SSE-c params for the source
793+
}
794+
self.assertDictEqual(self.operations_called[0][1], expected_head_args)
795+
796+
self.assertEqual(self.operations_called[1][0].name, 'CopyObject')
797+
expected_copy_args = {
798+
'Key': 'key.txt',
799+
'Bucket': 'bucket',
800+
'CopySource': {'Bucket': 'bucket-one', 'Key': 'key.txt'},
801+
'SSECustomerAlgorithm': 'AES256',
802+
'SSECustomerKey': 'foo',
803+
}
804+
self.assertDictEqual(self.operations_called[1][1], expected_copy_args)
805+
806+
def test_s3s3_cp_with_different_sse_c_keys(self):
807+
"""S3->S3 copy with different SSE-C keys for source and destination"""
808+
self.parsed_responses = [
809+
{
810+
"AcceptRanges": "bytes",
811+
"LastModified": "Tue, 12 Jul 2016 21:26:07 GMT",
812+
"ContentLength": 4,
813+
"ETag": '"d3b07384d113edec49eaa6238ad5ff00"',
814+
"Metadata": {},
815+
"ContentType": "binary/octet-stream",
816+
},
817+
{
818+
"AcceptRanges": "bytes",
819+
"Metadata": {},
820+
"ContentType": "binary/octet-stream",
821+
"ContentLength": 4,
822+
"ETag": '"d3b07384d113edec49eaa6238ad5ff00"',
823+
"LastModified": "Tue, 12 Jul 2016 21:26:07 GMT",
824+
"Body": BytesIO(b'foo\n'),
825+
},
826+
{},
827+
]
828+
cmdline = (
829+
'%s s3://bucket-one/key.txt s3://bucket/key.txt '
830+
'--sse-c-copy-source --sse-c-copy-source-key foo --sse-c --sse-c-key bar'
831+
% self.prefix
832+
)
833+
self.run_cmd(cmdline, expected_rc=0)
834+
self.assertEqual(len(self.operations_called), 2)
835+
self.assertEqual(self.operations_called[0][0].name, 'HeadObject')
836+
expected_head_args = {
837+
'Bucket': 'bucket-one',
838+
'Key': 'key.txt',
839+
'SSECustomerAlgorithm': 'AES256',
840+
'SSECustomerKey': 'foo',
841+
}
842+
self.assertDictEqual(self.operations_called[0][1], expected_head_args)
843+
844+
self.assertEqual(self.operations_called[1][0].name, 'CopyObject')
845+
expected_copy_args = {
846+
'Key': 'key.txt',
847+
'Bucket': 'bucket',
848+
'CopySource': {'Bucket': 'bucket-one', 'Key': 'key.txt'},
849+
'SSECustomerAlgorithm': 'AES256',
850+
'SSECustomerKey': 'bar',
851+
'CopySourceSSECustomerAlgorithm': 'AES256',
852+
'CopySourceSSECustomerKey': 'foo',
853+
}
854+
self.assertDictEqual(self.operations_called[1][1], expected_copy_args)
855+
760856
# Note ideally the kms sse with a key id would be integration tests
761857
# However, you cannot delete kms keys so there would be no way to clean
762858
# up the tests

0 commit comments

Comments
 (0)