From 72e728b4fed1b9f6a003b8b67ca236711c3aa24d Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Thu, 7 May 2026 13:21:42 -0700 Subject: [PATCH 1/3] chore: disable ranged gets --- src/s3_encryption/__init__.py | 6 +++ test/integration/test_i_ranged_get.py | 73 +++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 test/integration/test_i_ranged_get.py diff --git a/src/s3_encryption/__init__.py b/src/s3_encryption/__init__.py index 6ee409ad..22dd015f 100644 --- a/src/s3_encryption/__init__.py +++ b/src/s3_encryption/__init__.py @@ -511,6 +511,12 @@ def get_object(self, **kwargs): Raises: S3EncryptionClientError: If decryption fails or the object is not properly encrypted. """ + # Ranged gets are not supported — decryption requires the full ciphertext. + if "Range" in kwargs: + raise S3EncryptionClientError( + "Ranged gets are not supported by the S3 Encryption Client." + ) + # Extract EncryptionContext if provided (not a standard S3 parameter) encryption_context = kwargs.pop("EncryptionContext", None) _validate_encryption_context(encryption_context) diff --git a/test/integration/test_i_ranged_get.py b/test/integration/test_i_ranged_get.py new file mode 100644 index 00000000..1834d385 --- /dev/null +++ b/test/integration/test_i_ranged_get.py @@ -0,0 +1,73 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Integration test for ranged get (Range parameter on get_object). + +The S3 Encryption Client does not support ranged gets because decryption +requires the full ciphertext (IV, encrypted data, and auth tag). Passing +a Range parameter retrieves only a slice of the ciphertext, which causes +decryption to fail. +""" +import os +from datetime import datetime + +import boto3 +import pytest + +from s3_encryption import S3EncryptionClient, S3EncryptionClientConfig +from s3_encryption.exceptions import S3EncryptionClientError +from s3_encryption.materials.kms_keyring import KmsKeyring +from s3_encryption.materials.materials import AlgorithmSuite, CommitmentPolicy + +bucket = os.environ.get("CI_S3_BUCKET", "s3ec-python-github-test-bucket") +region = os.environ.get("CI_AWS_REGION", "us-west-2") +kms_key_id = os.environ.get( + "CI_KMS_KEY_ALIAS", "arn:aws:kms:us-west-2:370957321024:alias/S3EC-Python-Github-KMS-Key" +) + + +def _make_client(algorithm_suite, commitment_policy): + """Create an S3EncryptionClient with the given algorithm config.""" + kms_client = boto3.client("kms", region_name=region) + keyring = KmsKeyring(kms_client, kms_key_id) + wrapped_client = boto3.client("s3") + config = S3EncryptionClientConfig( + keyring, + encryption_algorithm=algorithm_suite, + commitment_policy=commitment_policy, + ) + return S3EncryptionClient(wrapped_client, config) + + +def _unique_key(prefix): + """Generate a unique S3 key with a timestamp suffix.""" + return prefix + datetime.now().strftime("%Y-%m-%d-%H:%M:%S-%f") + + +ALGORITHM_CONFIGS = [ + pytest.param( + AlgorithmSuite.ALG_AES_256_GCM_IV12_TAG16_NO_KDF, + CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT, + id="AES_GCM", + ), + pytest.param( + AlgorithmSuite.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY, + CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT, + id="KC_GCM", + ), +] + + +@pytest.mark.parametrize("algorithm_suite,commitment_policy", ALGORITHM_CONFIGS) +def test_ranged_get_fails(algorithm_suite, commitment_policy): + """A ranged get on an encrypted object should fail because the client + cannot decrypt a partial ciphertext.""" + key = _unique_key("ranged-get-") + # Use a body large enough that a byte-range is meaningful + data = b"A" * 1024 + + s3ec = _make_client(algorithm_suite, commitment_policy) + s3ec.put_object(Bucket=bucket, Key=key, Body=data) + + # Attempt a ranged get — should raise immediately with a clear message + with pytest.raises(S3EncryptionClientError, match="Ranged gets are not supported"): + s3ec.get_object(Bucket=bucket, Key=key, Range="bytes=0-255") From cc3a1d0309752678a037efd2f4d7a7aa7b98a682 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Thu, 7 May 2026 13:26:37 -0700 Subject: [PATCH 2/3] format --- test/integration/test_i_ranged_get.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/integration/test_i_ranged_get.py b/test/integration/test_i_ranged_get.py index 1834d385..da9222f5 100644 --- a/test/integration/test_i_ranged_get.py +++ b/test/integration/test_i_ranged_get.py @@ -7,6 +7,7 @@ a Range parameter retrieves only a slice of the ciphertext, which causes decryption to fail. """ + import os from datetime import datetime @@ -60,7 +61,8 @@ def _unique_key(prefix): @pytest.mark.parametrize("algorithm_suite,commitment_policy", ALGORITHM_CONFIGS) def test_ranged_get_fails(algorithm_suite, commitment_policy): """A ranged get on an encrypted object should fail because the client - cannot decrypt a partial ciphertext.""" + cannot decrypt a partial ciphertext. + """ key = _unique_key("ranged-get-") # Use a body large enough that a byte-range is meaningful data = b"A" * 1024 From 5b0aeaea233fb9e1bb41ed741c5757d63b9eb919 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Thu, 7 May 2026 14:35:55 -0700 Subject: [PATCH 3/3] tweak message --- src/s3_encryption/__init__.py | 2 +- test/integration/test_i_ranged_get.py | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/s3_encryption/__init__.py b/src/s3_encryption/__init__.py index 22dd015f..caad4f66 100644 --- a/src/s3_encryption/__init__.py +++ b/src/s3_encryption/__init__.py @@ -514,7 +514,7 @@ def get_object(self, **kwargs): # Ranged gets are not supported — decryption requires the full ciphertext. if "Range" in kwargs: raise S3EncryptionClientError( - "Ranged gets are not supported by the S3 Encryption Client." + "Ranged gets are currently not supported by the S3 Encryption Client for Python." ) # Extract EncryptionContext if provided (not a standard S3 parameter) diff --git a/test/integration/test_i_ranged_get.py b/test/integration/test_i_ranged_get.py index da9222f5..d9bb65af 100644 --- a/test/integration/test_i_ranged_get.py +++ b/test/integration/test_i_ranged_get.py @@ -58,18 +58,15 @@ def _unique_key(prefix): ] -@pytest.mark.parametrize("algorithm_suite,commitment_policy", ALGORITHM_CONFIGS) +@pytest.mark.parametrize(("algorithm_suite", "commitment_policy"), ALGORITHM_CONFIGS) def test_ranged_get_fails(algorithm_suite, commitment_policy): - """A ranged get on an encrypted object should fail because the client - cannot decrypt a partial ciphertext. - """ + """Ranged gets are rejected with a clear error.""" key = _unique_key("ranged-get-") - # Use a body large enough that a byte-range is meaningful data = b"A" * 1024 s3ec = _make_client(algorithm_suite, commitment_policy) s3ec.put_object(Bucket=bucket, Key=key, Body=data) # Attempt a ranged get — should raise immediately with a clear message - with pytest.raises(S3EncryptionClientError, match="Ranged gets are not supported"): + with pytest.raises(S3EncryptionClientError, match="Ranged gets are currently not supported"): s3ec.get_object(Bucket=bucket, Key=key, Range="bytes=0-255")