Skip to content
Open
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/feature-AWSSDKforJavav2-c710394.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "feature",
"category": "AWS SDK for Java v2",
"contributor": "",
"description": "HTTPS requests now skip payload signing by default for improved performance. HTTP and signing-dependent features are unaffected."
}
Original file line number Diff line number Diff line change
Expand Up @@ -134,23 +134,31 @@ public static boolean useChunkEncoding(boolean payloadSigningEnabled, boolean ch

public static boolean isPayloadSigning(BaseSignRequest<?, ? extends AwsCredentialsIdentity> request) {
boolean isAnonymous = CredentialUtils.isAnonymous(request.identity());
boolean isPayloadSigningEnabled = request.requireProperty(PAYLOAD_SIGNING_ENABLED, true);
Boolean payloadSigningEnabled = request.property(PAYLOAD_SIGNING_ENABLED);
boolean isEncrypted = "https".equals(request.request().protocol());
boolean hasPayload = request.payload().isPresent();

if (isAnonymous) {
return false;
}

// presigning requests should always have a null payload, and should always be unsigned-payload
if (!isEncrypted && request.payload().isPresent()) {
if (!isPayloadSigningEnabled) {
if (payloadSigningEnabled != null) {
// presigning requests should always have a null payload, and should always be unsigned-payload
if (!isEncrypted && hasPayload && !payloadSigningEnabled) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If payloadSigningEnabled is not null, i.e., configured by the user or set on the service model, should we always enable payload signing regardless of isEncrypted?

log.debug(() -> "Payload signing was disabled for an HTTP request with a payload. " +
"Signing will be enabled. Use HTTPS for unsigned payloads.");
return true;
}
return true;
return payloadSigningEnabled;
}

return isPayloadSigningEnabled;
// For HTTPS, skip payload signing by default
if (isEncrypted) {
return false;
}

// For HTTP, sign payload by default
return hasPayload;
}

public static boolean isEventStreaming(SdkHttpRequest request) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,13 @@ public interface AwsV4FamilyHttpSigner<T extends Identity> extends HttpSigner<T>
SignerProperty.create(AwsV4FamilyHttpSigner.class, "ExpirationDuration");

/**
* Whether to indicate that a payload is signed or not. This property defaults to true. This can be set false to disable
* payload signing.
* Whether to indicate that a payload is signed or not.
* <p>
* When this value is true and {@link #CHUNK_ENCODING_ENABLED} is false, the whole payload must be read to generate
* the payload signature. For very large payloads, this could impact memory usage and call latency. Some services
* support this value being disabled, especially over HTTPS where SSL provides some of its own protections against
* payload tampering.
* <b>Default behavior (when not explicitly set):</b>
* <ul>
* <li>HTTPS requests: Payload signing is disabled by default</li>
* <li>HTTP requests: Payload signing is enabled by default</li>
* </ul>
*/
SignerProperty<Boolean> PAYLOAD_SIGNING_ENABLED =
SignerProperty.create(AwsV4FamilyHttpSigner.class, "PayloadSigningEnabled");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ void sign_withBasicRequest_shouldSignWithHeaders() {
assertThat(signedRequest.request().firstMatchingHeader("X-Amz-Date")).hasValue("20200803T174823Z");
assertThat(signedRequest.request().firstMatchingHeader("X-Amz-Region-Set")).hasValue("aws-global");
assertThat(signedRequest.request().firstMatchingHeader("Authorization")).isPresent();
assertThat(signedRequest.request().firstMatchingHeader("x-amz-content-sha256")).contains(PAYLOAD_SHA256_HEX);
assertThat(signedRequest.request().firstMatchingHeader("x-amz-content-sha256")).hasValue("UNSIGNED-PAYLOAD");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing default behavior

}

@Test
Expand Down Expand Up @@ -172,7 +172,7 @@ void signAsync_withBasicRequest_shouldSignWithHeaders() {
assertThat(signedRequest.request().firstMatchingHeader("X-Amz-Date")).hasValue("20200803T174823Z");
assertThat(signedRequest.request().firstMatchingHeader("X-Amz-Region-Set")).hasValue("aws-global");
assertThat(signedRequest.request().firstMatchingHeader("Authorization")).isPresent();
assertThat(signedRequest.request().firstMatchingHeader("x-amz-content-sha256")).contains(PAYLOAD_SHA256_HEX);
assertThat(signedRequest.request().firstMatchingHeader("x-amz-content-sha256")).hasValue("UNSIGNED-PAYLOAD");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing default behavior

}

@Test
Expand Down Expand Up @@ -564,6 +564,7 @@ void sign_WithChunkEncodingTrue_DelegatesToAwsChunkedPayloadSigner() {
.putHeader(Header.CONTENT_LENGTH, "20"),
signRequest -> signRequest
.putProperty(CHUNK_ENCODING_ENABLED, true)
.putProperty(PAYLOAD_SIGNING_ENABLED, true)
);

SignedRequest signedRequest = signer.sign(request);
Expand All @@ -587,6 +588,7 @@ void signAsync_WithChunkEncodingTrue_DelegatesToAwsChunkedPayloadSigner() {
.putHeader(Header.CONTENT_LENGTH, contentLength),
signRequest -> signRequest
.putProperty(CHUNK_ENCODING_ENABLED, true)
.putProperty(PAYLOAD_SIGNING_ENABLED, true)
);

AsyncSignedRequest signedRequest = signer.signAsync(request).join();
Expand All @@ -610,6 +612,7 @@ void sign_WithChunkEncodingTrueAndChecksumAlgorithm_DelegatesToAwsChunkedPayload
signRequest -> signRequest
.putProperty(CHUNK_ENCODING_ENABLED, true)
.putProperty(CHECKSUM_ALGORITHM, CRC32)
.putProperty(PAYLOAD_SIGNING_ENABLED, true)
);

SignedRequest signedRequest = signer.sign(request);
Expand All @@ -636,6 +639,7 @@ void signAsync_WithChunkEncodingTrueAndChecksumAlgorithm_DelegatesToAwsChunkedPa
signRequest -> signRequest
.putProperty(CHUNK_ENCODING_ENABLED, true)
.putProperty(CHECKSUM_ALGORITHM, CRC32)
.putProperty(PAYLOAD_SIGNING_ENABLED, true)
);

AsyncSignedRequest signedRequest = signer.signAsync(request).join();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -319,6 +320,7 @@ void sign_WithEventStreamContentTypeWithoutHttpAuthAwsEventStreamModule_throws()
httpRequest -> httpRequest
.putHeader("Content-Type", "application/vnd.amazon.eventstream"),
signRequest -> {
signRequest.putProperty(PAYLOAD_SIGNING_ENABLED, true);
}
);

Expand All @@ -339,6 +341,7 @@ void signAsync_WithEventStreamContentTypeWithoutHttpAuthAwsEventStreamModule_thr
httpRequest -> httpRequest
.putHeader("Content-Type", "application/vnd.amazon.eventstream"),
signRequest -> {
signRequest.putProperty(PAYLOAD_SIGNING_ENABLED, true);
}
);

Expand Down Expand Up @@ -372,6 +375,7 @@ void signAsync_WithEventStreamContentType_DelegatesToEventStreamPayloadSigner()
httpRequest -> httpRequest
.putHeader("Content-Type", "application/vnd.amazon.eventstream"),
signRequest -> {
signRequest.putProperty(PAYLOAD_SIGNING_ENABLED, true);
}
);

Expand Down Expand Up @@ -414,6 +418,7 @@ void sign_WithChunkEncodingTrue_DelegatesToAwsChunkedPayloadSigner() {
.putHeader(Header.CONTENT_LENGTH, "20"),
signRequest -> signRequest
.putProperty(CHUNK_ENCODING_ENABLED, true)
.putProperty(PAYLOAD_SIGNING_ENABLED, true)
);

SignedRequest signedRequest = signer.sign(request);
Expand All @@ -432,6 +437,7 @@ void signAsync_WithChunkEncodingTrue_DelegatesToAwsChunkedPayloadSigner_futureBe
.putHeader(Header.CONTENT_LENGTH, "20"),
signRequest -> signRequest
.putProperty(CHUNK_ENCODING_ENABLED, true)
.putProperty(PAYLOAD_SIGNING_ENABLED, true)
);

AsyncSignedRequest signedRequest = signer.signAsync(request).join();
Expand All @@ -452,6 +458,7 @@ void sign_WithChunkEncodingTrueAndChecksumAlgorithm_DelegatesToAwsChunkedPayload
signRequest -> signRequest
.putProperty(CHUNK_ENCODING_ENABLED, true)
.putProperty(CHECKSUM_ALGORITHM, CRC32)
.putProperty(PAYLOAD_SIGNING_ENABLED, true)
);

SignedRequest signedRequest = signer.sign(request);
Expand All @@ -472,6 +479,7 @@ void signAsync_WithChunkEncodingTrueAndChecksumAlgorithm_DelegatesToAwsChunkedPa
signRequest -> signRequest
.putProperty(CHUNK_ENCODING_ENABLED, true)
.putProperty(CHECKSUM_ALGORITHM, CRC32)
.putProperty(PAYLOAD_SIGNING_ENABLED, true)
);

AsyncSignedRequest signedRequest = signer.signAsync(request).join();
Expand Down Expand Up @@ -979,6 +987,103 @@ void signAsync_WithPayloadSigningFalse_chunkEncodingTrue_noContentLengthHeader_t
.hasMessageContaining("Content-Length header must be specified");
}

@Test
void sign_WithHttpsAndNoProperties_UsesUnsignedPayload() {
SignRequest<? extends AwsCredentialsIdentity> request = generateBasicRequest(
AwsCredentialsIdentity.create("access", "secret"),
httpRequest -> {
},
signRequest -> {
}
);

SignedRequest signedRequest = signer.sign(request);

assertThat(signedRequest.request().firstMatchingHeader("x-amz-content-sha256")).hasValue("UNSIGNED-PAYLOAD");
}

@Test
void asyncSign_WithHttpsAndNoProperties_UsesUnsignedPayload() {
AsyncSignRequest<? extends AwsCredentialsIdentity> request = generateBasicAsyncRequest(
AwsCredentialsIdentity.create("access", "secret"),
httpRequest -> {
},
signRequest -> {
}
);

AsyncSignedRequest signedRequest = signer.signAsync(request).join();

assertThat(signedRequest.request().firstMatchingHeader("x-amz-content-sha256")).hasValue("UNSIGNED-PAYLOAD");
}

@Test
void sign_WithHttpsExplicitSigning_SignsPayload() {
SignRequest<? extends AwsCredentialsIdentity> request = generateBasicRequest(
AwsCredentialsIdentity.create("access", "secret"),
httpRequest -> {
},
signRequest -> {
signRequest.putProperty(PAYLOAD_SIGNING_ENABLED, true);
}
);

byte[] sha256Value = computeChecksum(SHA256, testPayload());
SignedRequest signedRequest = signer.sign(request);

assertThat(signedRequest.request().firstMatchingHeader("x-amz-content-sha256")).hasValue(BinaryUtils.toHex(sha256Value));
}

@Test
void asyncSign_WithHttpsExplicitSigning_SignsPayload() {
AsyncSignRequest<? extends AwsCredentialsIdentity> request = generateBasicAsyncRequest(
AwsCredentialsIdentity.create("access", "secret"),
httpRequest -> {
},
signRequest -> {
signRequest.putProperty(PAYLOAD_SIGNING_ENABLED, true);
}
);

byte[] sha256Value = computeChecksum(SHA256, testPayload());
AsyncSignedRequest signedRequest = signer.signAsync(request).join();

assertThat(signedRequest.request().firstMatchingHeader("x-amz-content-sha256")).hasValue(BinaryUtils.toHex(sha256Value));
}

@Test
void sign_WithHTTPAndNoProperties_SignsPayload() {
SignRequest<? extends AwsCredentialsIdentity> request = generateBasicRequest(
AwsCredentialsIdentity.create("access", "secret"),
httpRequest -> {
httpRequest.uri(URI.create("http://demo.us-east-1.amazonaws.com"));
},
signRequest -> {
}
);

byte[] sha256Value = computeChecksum(SHA256, testPayload());
SignedRequest signedRequest = signer.sign(request);

assertThat(signedRequest.request().firstMatchingHeader("x-amz-content-sha256")).hasValue(BinaryUtils.toHex(sha256Value));
}

@Test
void asyncSign_WithHTTPAndNoProperties_SignsPayload() {
AsyncSignRequest<? extends AwsCredentialsIdentity> request = generateBasicAsyncRequest(
AwsCredentialsIdentity.create("access", "secret"),
httpRequest -> {
httpRequest.uri(URI.create("http://demo.us-east-1.amazonaws.com"));
},
signRequest -> {
}
);

byte[] sha256Value = computeChecksum(SHA256, testPayload());
AsyncSignedRequest signedRequest = signer.signAsync(request).join();

assertThat(signedRequest.request().firstMatchingHeader("x-amz-content-sha256")).hasValue(BinaryUtils.toHex(sha256Value));
}

private static byte[] computeChecksum(ChecksumAlgorithm algorithm, byte[] data) {
SdkChecksum checksum = SdkChecksum.forAlgorithm(algorithm);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,30 @@
import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.MD5;
import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.SHA1;
import static software.amazon.awssdk.checksums.DefaultChecksumAlgorithm.SHA256;
import static software.amazon.awssdk.http.auth.aws.TestUtils.generateBasicRequest;
import static software.amazon.awssdk.http.auth.aws.internal.signer.util.ChecksumUtil.checksumHeaderName;
import static software.amazon.awssdk.http.auth.aws.internal.signer.util.ChecksumUtil.fromChecksumAlgorithm;
import static software.amazon.awssdk.http.auth.aws.internal.signer.util.ChecksumUtil.isPayloadSigning;
import static software.amazon.awssdk.http.auth.aws.internal.signer.util.ChecksumUtil.readAll;
import static software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner.PAYLOAD_SIGNING_ENABLED;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.net.URI;
import java.util.stream.Stream;
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.mockito.Mockito;
import software.amazon.awssdk.checksums.SdkChecksum;
import software.amazon.awssdk.checksums.internal.Crc32Checksum;
import software.amazon.awssdk.checksums.internal.Crc64NvmeChecksum;
import software.amazon.awssdk.checksums.internal.DigestAlgorithmChecksum;
import software.amazon.awssdk.http.auth.spi.signer.SignRequest;
import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity;

public class ChecksumUtilTest {

Expand Down Expand Up @@ -87,4 +97,32 @@ public void readAll_throwNonIoException_shouldNotWrap() throws IOException {
Mockito.when(mock.read(Mockito.any(byte[].class))).thenThrow(npe);
assertThatThrownBy(() -> readAll(mock)).isEqualTo(npe);
}

@ParameterizedTest(name = "{0}")
@MethodSource("payloadSigningTestCases")
void isPayloadSigning_returnsExpectedValue(String description, Boolean propertyValue,
String protocol, boolean expected) {
SignRequest<? extends AwsCredentialsIdentity> request = generateBasicRequest(
AwsCredentialsIdentity.create("access", "secret"),
httpRequest -> httpRequest.uri(URI.create(protocol + "://demo.us-east-1.amazonaws.com")),
signRequest -> {
if (propertyValue != null) {
signRequest.putProperty(PAYLOAD_SIGNING_ENABLED, propertyValue);
}
}
);

assertEquals(expected, isPayloadSigning(request));
}

private static Stream<Arguments> payloadSigningTestCases() {
return Stream.of(
Arguments.of("Explicit true overrides HTTPS", true, "https", true),
Arguments.of("Explicit false with HTTPS", false, "https", false),
Arguments.of("HTTPS with no property skips signing", null, "https", false),
Arguments.of("HTTP with no property signs payload", null, "http", true)
);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ static String fixedTimePresignedUrl() {
"&X-Amz-SignedHeaders=host" +
"&X-Amz-Credential=foo%2F20161221%2Fus-east-1%2Frds%2Faws4_request" +
"&X-Amz-Expires=604800" +
"&X-Amz-Signature=00822ebbba95e2e6ac09112aa85621fbef060a596e3e1480f9f4ac61493e9821";
"&X-Amz-Signature=c2ef95311d2d530c13b478b1bea9b8fcec9bc8f2160645b9533b648fe7fd2371";
}

private Map<String, List<String>> rawQueryParameters(SdkHttpFullRequest request) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ static String fixedTimePresignedUrl() {
"&X-Amz-SignedHeaders=host" +
"&X-Amz-Credential=foo%2F20161221%2Fus-east-1%2Frds%2Faws4_request" +
"&X-Amz-Expires=604800" +
"&X-Amz-Signature=00822ebbba95e2e6ac09112aa85621fbef060a596e3e1480f9f4ac61493e9821";
"&X-Amz-Signature=c2ef95311d2d530c13b478b1bea9b8fcec9bc8f2160645b9533b648fe7fd2371";
}

private Map<String, List<String>> rawQueryParameters(SdkHttpFullRequest request) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ static String fixedTimePresignedUrl() {
"&X-Amz-SignedHeaders=host" +
"&X-Amz-Credential=foo%2F20161221%2Fus-east-1%2Frds%2Faws4_request" +
"&X-Amz-Expires=604800" +
"&X-Amz-Signature=00822ebbba95e2e6ac09112aa85621fbef060a596e3e1480f9f4ac61493e9821";
"&X-Amz-Signature=c2ef95311d2d530c13b478b1bea9b8fcec9bc8f2160645b9533b648fe7fd2371";
}

private Map<String, List<String>> rawQueryParameters(SdkHttpFullRequest request) {
Expand Down
Loading
Loading