Skip to content

Do not send Md5 checksum header if ChecksumMode is enabled #4731

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/next-release/bugfix-AmazonS3-16d0bb1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"category": "Amazon S3",
"contributor": "",
"type": "bugfix",
"description": "Fixes double checksum validation for GetObject. Now when ChecksumMode is enabled, x-amz-te:append-md5 will not be sent, and only the flexible checksum will be validated. If ChecksumMode is enabled and no ChecksumAlgorithm was returned, no validation will be performed."
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,15 @@
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import software.amazon.awssdk.auth.signer.S3SignerExecutionAttribute;
import software.amazon.awssdk.authcrt.signer.internal.DefaultAwsCrtS3V4aSigner;
Expand All @@ -42,6 +45,7 @@
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
import software.amazon.awssdk.core.internal.async.FileAsyncRequestBody;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.services.s3.S3IntegrationTestBase;
import software.amazon.awssdk.services.s3.model.ChecksumAlgorithm;
import software.amazon.awssdk.services.s3.model.ChecksumMode;
Expand Down Expand Up @@ -291,4 +295,98 @@ public void putObject_with_bufferCreatedFromZeroCapacityByteBuffer() {
assertThat(interceptor.responseValidation()).isEqualTo(ChecksumValidation.VALIDATED);
assertThat(response).isEqualTo("");
}

private static Stream<Arguments> getObjectChecksumValidationParams() {
return Stream.of(Arguments.of(true, ChecksumAlgorithm.CRC32, ChecksumMode.ENABLED),
Arguments.of(true, null, ChecksumMode.ENABLED),
Arguments.of(true, ChecksumAlgorithm.CRC32, null),
Arguments.of(true, null, null),
Arguments.of(false, ChecksumAlgorithm.CRC32, ChecksumMode.ENABLED),
Arguments.of(false, null, ChecksumMode.ENABLED),
Arguments.of(false, ChecksumAlgorithm.CRC32, null),
Arguments.of(false, null, null));
}

@ParameterizedTest
@MethodSource("getObjectChecksumValidationParams")
public void testGetObjectChecksumValidation(boolean checksumValidationEnabled, ChecksumAlgorithm checksumAlgorithm,
ChecksumMode checksumMode) {
S3AsyncClient s3Async = s3AsyncClientBuilder().overrideConfiguration(o -> o.addExecutionInterceptor(interceptor))
.serviceConfiguration(S3Configuration.builder()
.checksumValidationEnabled(checksumValidationEnabled)
.build())
.build();

s3Async.putObject(PutObjectRequest.builder()
.bucket(BUCKET)
.key(KEY)
.checksumAlgorithm(checksumAlgorithm)
.build(), AsyncRequestBody.fromString("Hello world")).join();

s3Async.getObject(GetObjectRequest.builder()
.bucket(BUCKET)
.key(KEY)
.checksumMode(checksumMode)
.build(), AsyncResponseTransformer.toBytes()).join();

validateChecksumValidation(checksumValidationEnabled, checksumAlgorithm, checksumMode);
interceptor.reset();
}

private void validateChecksumValidation(boolean checksumValidationEnabled, ChecksumAlgorithm checksumAlgorithm,
ChecksumMode checksumMode) {
if (checksumValidationEnabled) {
if (checksumMode == ChecksumMode.ENABLED) {
assertChecksumModeEnabledWithChecksumValidationEnabled(checksumAlgorithm);
} else {
assertChecksumModeNotEnabledWithChecksumValidationEnabled();
}
} else {
if (checksumMode == ChecksumMode.ENABLED) {
assertChecksumModeEnabledWithChecksumValidationDisabled(checksumAlgorithm);
} else {
assertChecksumModeNotEnabledWithChecksumValidationDisabled();
}
}
}

private void assertChecksumModeEnabledWithChecksumValidationEnabled(ChecksumAlgorithm checksumAlgorithm) {
if (checksumAlgorithm == null) {
assertRequestAndResponseDoNotContainMd5Header();
assertThat(interceptor.responseFlexibleChecksumHeader()).isNull();
} else {
assertRequestAndResponseDoNotContainMd5Header();
assertThat(interceptor.responseFlexibleChecksumHeader()).isNotNull();
}
}

private void assertChecksumModeNotEnabledWithChecksumValidationEnabled() {
assertRequestAndResponseContainMd5Header();
assertThat(interceptor.responseFlexibleChecksumHeader()).isNull();
}

private void assertChecksumModeEnabledWithChecksumValidationDisabled(ChecksumAlgorithm checksumAlgorithm) {
if (checksumAlgorithm == null) {
assertRequestAndResponseDoNotContainMd5Header();
assertThat(interceptor.responseFlexibleChecksumHeader()).isNull();
} else {
assertRequestAndResponseDoNotContainMd5Header();
assertThat(interceptor.responseFlexibleChecksumHeader()).isNotNull();
}
}

private void assertChecksumModeNotEnabledWithChecksumValidationDisabled() {
assertRequestAndResponseDoNotContainMd5Header();
assertThat(interceptor.responseFlexibleChecksumHeader()).isNull();
}

private void assertRequestAndResponseContainMd5Header() {
assertThat(interceptor.requestTransferEncodingHeader()).isEqualTo("append-md5");
assertThat(interceptor.responseTransferEncodingHeader()).isEqualTo("append-md5");
}

private void assertRequestAndResponseDoNotContainMd5Header() {
assertThat(interceptor.requestTransferEncodingHeader()).isNull();
assertThat(interceptor.responseTransferEncodingHeader()).isNull();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,14 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import software.amazon.awssdk.authcrt.signer.internal.DefaultAwsCrtS3V4aSigner;
import software.amazon.awssdk.core.HttpChecksumConstant;
import software.amazon.awssdk.core.ResponseInputStream;
Expand All @@ -41,6 +45,7 @@
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.services.s3.S3IntegrationTestBase;
import software.amazon.awssdk.services.s3.model.ChecksumAlgorithm;
import software.amazon.awssdk.services.s3.model.ChecksumMode;
Expand All @@ -50,7 +55,6 @@
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.services.s3.utils.CaptureChecksumValidationInterceptor;
import software.amazon.awssdk.services.s3.utils.ChecksumUtils;
import software.amazon.awssdk.testutils.RandomTempFile;

public class HttpChecksumIntegrationTest extends S3IntegrationTestBase {
Expand Down Expand Up @@ -149,7 +153,7 @@ public void invalidHeaderChecksumCalculatedByUserNotOverWrittenBySdkClient() {
}

@Test
public void syncValidUnsignedTrailerChecksumCalculatedBySdkClient() throws InterruptedException {
public void syncValidUnsignedTrailerChecksumCalculatedBySdkClient() {
s3Https.putObject(PutObjectRequest.builder()
.bucket(BUCKET)
.key(KEY)
Expand All @@ -173,8 +177,6 @@ public void syncValidUnsignedTrailerChecksumCalculatedBySdkClient() throws Inter

@Test
public void syncValidSignedTrailerChecksumCalculatedBySdkClient() {


s3.putObject(PutObjectRequest.builder()
.bucket(BUCKET)
.key(KEY)
Expand All @@ -198,13 +200,10 @@ public void syncValidSignedTrailerChecksumCalculatedBySdkClient() {
assertThat(interceptor.validationAlgorithm()).isEqualTo(Algorithm.CRC32);
assertThat(interceptor.responseValidation()).isEqualTo(ChecksumValidation.VALIDATED);
assertThat(text).isEqualTo("Hello world");

}

@Test
public void syncValidSignedTrailerChecksumCalculatedBySdkClient_Empty_String() {


s3.putObject(PutObjectRequest.builder()
.bucket(BUCKET)
.key(KEY)
Expand All @@ -230,7 +229,6 @@ public void syncValidSignedTrailerChecksumCalculatedBySdkClient_Empty_String() {

@Test
public void syncValidSignedTrailerChecksumCalculatedBySdkClientWithSigv4a() {

s3.putObject(PutObjectRequest.builder()
.bucket(BUCKET)
.key(KEY)
Expand All @@ -254,7 +252,6 @@ public void syncValidSignedTrailerChecksumCalculatedBySdkClientWithSigv4a() {

@Test
public void syncValidSignedTrailerChecksumCalculatedBySdkClientWithSigv4a_withContentEncoding() {

s3.putObject(PutObjectRequest.builder()
.bucket(BUCKET)
.key(KEY)
Expand Down Expand Up @@ -334,7 +331,7 @@ public void syncSignedPayloadForHugeMessage() {
}

@Test
public void syncUnsignedPayloadMultiPartForHugeMessage() throws InterruptedException {
public void syncUnsignedPayloadMultiPartForHugeMessage() {
s3Https.putObject(PutObjectRequest.builder()
.bucket(BUCKET)
.key(KEY)
Expand All @@ -360,8 +357,7 @@ public void syncUnsignedPayloadMultiPartForHugeMessage() throws InterruptedExcep


@Test
public void syncValidUnsignedTrailerChecksumCalculatedBySdkClient_withSmallFileRequestBody() throws InterruptedException,
IOException {
public void syncValidUnsignedTrailerChecksumCalculatedBySdkClient_withSmallFileRequestBody() throws IOException {
File randomFileOfFixedLength = new RandomTempFile(10 * KB);

s3Https.putObject(PutObjectRequest.builder()
Expand Down Expand Up @@ -410,4 +406,97 @@ public void syncValidUnsignedTrailerChecksumCalculatedBySdkClient_withHugeFileRe
assertThat(text).isEqualTo(new String(bytes));
}

private static Stream<Arguments> getObjectChecksumValidationParams() {
return Stream.of(Arguments.of(true, ChecksumAlgorithm.CRC32, ChecksumMode.ENABLED),
Arguments.of(true, null, ChecksumMode.ENABLED),
Arguments.of(true, ChecksumAlgorithm.CRC32, null),
Arguments.of(true, null, null),
Arguments.of(false, ChecksumAlgorithm.CRC32, ChecksumMode.ENABLED),
Arguments.of(false, null, ChecksumMode.ENABLED),
Arguments.of(false, ChecksumAlgorithm.CRC32, null),
Arguments.of(false, null, null));
}

@ParameterizedTest
@MethodSource("getObjectChecksumValidationParams")
public void testGetObjectChecksumValidation(boolean checksumValidationEnabled, ChecksumAlgorithm checksumAlgorithm,
ChecksumMode checksumMode) {
S3Client s3 = s3ClientBuilder().overrideConfiguration(o -> o.addExecutionInterceptor(interceptor))
.serviceConfiguration(S3Configuration.builder()
.checksumValidationEnabled(checksumValidationEnabled)
.build())
.build();

s3.putObject(PutObjectRequest.builder()
.bucket(BUCKET)
.key(KEY)
.checksumAlgorithm(checksumAlgorithm)
.build(), RequestBody.fromString("Hello world"));

s3.getObject(GetObjectRequest.builder()
.bucket(BUCKET)
.key(KEY)
.checksumMode(checksumMode)
.build());

validateChecksumValidation(checksumValidationEnabled, checksumAlgorithm, checksumMode);
interceptor.reset();
}

private void validateChecksumValidation(boolean checksumValidationEnabled, ChecksumAlgorithm checksumAlgorithm,
ChecksumMode checksumMode) {
if (checksumValidationEnabled) {
if (checksumMode == ChecksumMode.ENABLED) {
assertChecksumModeEnabledWithChecksumValidationEnabled(checksumAlgorithm);
} else {
assertChecksumModeNotEnabledWithChecksumValidationEnabled();
}
} else {
if (checksumMode == ChecksumMode.ENABLED) {
assertChecksumModeEnabledWithChecksumValidationDisabled(checksumAlgorithm);
} else {
assertChecksumModeNotEnabledWithChecksumValidationDisabled();
}
}
}

private void assertChecksumModeEnabledWithChecksumValidationEnabled(ChecksumAlgorithm checksumAlgorithm) {
if (checksumAlgorithm == null) {
assertRequestAndResponseDoNotContainMd5Header();
assertThat(interceptor.responseFlexibleChecksumHeader()).isNull();
} else {
assertRequestAndResponseDoNotContainMd5Header();
assertThat(interceptor.responseFlexibleChecksumHeader()).isNotNull();
}
}

private void assertChecksumModeNotEnabledWithChecksumValidationEnabled() {
assertRequestAndResponseContainMd5Header();
assertThat(interceptor.responseFlexibleChecksumHeader()).isNull();
}

private void assertChecksumModeEnabledWithChecksumValidationDisabled(ChecksumAlgorithm checksumAlgorithm) {
if (checksumAlgorithm == null) {
assertRequestAndResponseDoNotContainMd5Header();
assertThat(interceptor.responseFlexibleChecksumHeader()).isNull();
} else {
assertRequestAndResponseDoNotContainMd5Header();
assertThat(interceptor.responseFlexibleChecksumHeader()).isNotNull();
}
}

private void assertChecksumModeNotEnabledWithChecksumValidationDisabled() {
assertRequestAndResponseDoNotContainMd5Header();
assertThat(interceptor.responseFlexibleChecksumHeader()).isNull();
}

private void assertRequestAndResponseContainMd5Header() {
assertThat(interceptor.requestTransferEncodingHeader()).isEqualTo("append-md5");
assertThat(interceptor.responseTransferEncodingHeader()).isEqualTo("append-md5");
}

private void assertRequestAndResponseDoNotContainMd5Header() {
assertThat(interceptor.requestTransferEncodingHeader()).isNull();
assertThat(interceptor.responseTransferEncodingHeader()).isNull();
}
}
Loading