diff --git a/.changes/next-release/bugfix-cloudfront-sign.json b/.changes/next-release/bugfix-cloudfront-sign.json new file mode 100644 index 000000000000..c43169b0d3d5 --- /dev/null +++ b/.changes/next-release/bugfix-cloudfront-sign.json @@ -0,0 +1,5 @@ +{ + "category": "cloudfront", + "description": "Raise a friendly error message when SHA1 signing is unavailable on the platform while running ``aws cloudfront sign``.", + "type": "bugfix" +} diff --git a/awscli/customizations/cloudfront.py b/awscli/customizations/cloudfront.py index f785dd4d6a90..5423e3ba6067 100644 --- a/awscli/customizations/cloudfront.py +++ b/awscli/customizations/cloudfront.py @@ -19,6 +19,7 @@ from botocore.utils import parse_to_aware_datetime from awscrt.crypto import RSA, RSASignatureAlgorithm +from awscrt.exceptions import AwsCrtError from awscli.arguments import CustomArgument from awscli.customizations.commands import BasicCommand @@ -306,7 +307,18 @@ def __init__(self, private_key): self.priv_key = RSA.new_private_key_from_pem_data(key_bytes) def sign(self, message): - return self.priv_key.sign( - RSASignatureAlgorithm.PKCS1_5_SHA1, - hashlib.sha1(message).digest() - ) + try: + return self.priv_key.sign( + RSASignatureAlgorithm.PKCS1_5_SHA1, + hashlib.sha1(message).digest() + ) + except (AwsCrtError, RuntimeError) as e: + if 'AWS_ERROR_CAL_UNSUPPORTED_ALGORITHM' in str(e): + raise RuntimeError( + "Failed to sign the URL using the SHA1 hash algorithm: " + f"{e} CloudFront signed URLs require RSA PKCS1 v1.5 " + "signing with SHA1, which may be disabled by default on " + "your platform. Enable SHA1 support in your system's " + "crypto provider and try again." + ) from e + raise diff --git a/tests/functional/cloudfront/test_sign.py b/tests/functional/cloudfront/test_sign.py index 94183e3f980c..e15eaf0e39d2 100644 --- a/tests/functional/cloudfront/test_sign.py +++ b/tests/functional/cloudfront/test_sign.py @@ -108,6 +108,43 @@ def test_custom_policy(self): self.run_cmd(cmdline)[0], 'http://example.com/hi', expected_params ) + def test_raises_friendly_error_when_sha1_unavailable(self): + cmdline = ( + self.prefix + + '--private-key file://' + + self.private_key_file + + ' --date-less-than 2016-1-1' + ) + + crt_error = RuntimeError( + '7174 (AWS_ERROR_CAL_UNSUPPORTED_ALGORITHM): The specified ' + 'algorithm is unsupported on this platform.' + ) + with mock.patch( + 'awscli.customizations.cloudfront.RSA.sign', + side_effect=crt_error, + ): + stdout, stderr, rc = self.run_cmd(cmdline, expected_rc=255) + self.assertNotEqual(rc, 0) + self.assertIn('SHA1', stderr) + + def test_unrelated_signing_error_is_not_reported_as_sha1(self): + cmdline = ( + self.prefix + + '--private-key file://' + + self.private_key_file + + ' --date-less-than 2016-1-1' + ) + other_error = RuntimeError('some other signing failure') + with mock.patch( + 'awscli.customizations.cloudfront.RSA.sign', + side_effect=other_error, + ): + stdout, stderr, rc = self.run_cmd(cmdline, expected_rc=255) + self.assertNotEqual(rc, 0) + self.assertNotIn('SHA1', stderr) + self.assertIn('some other signing failure', stderr) + class TestSignPKCS8(BaseAWSCommandParamsTest): # A private key only for testing purpose.