Skip to content

Commit f461838

Browse files
authored
Do not sign Transfer-Encoding Header if present on the request (#5895)
* Do not sign Transfer-Encoding Header if present on the request * Added changelogs * Added skip in HeaderTransformsHelper too , to be consistent
1 parent 9389cfa commit f461838

File tree

8 files changed

+95
-8
lines changed

8 files changed

+95
-8
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "bugfix",
3+
"category": "AWS SDK for Java v2",
4+
"contributor": "",
5+
"description": "Transfer-Encoding headers will no longer be signed during SigV4 authentication"
6+
}

core/auth/src/main/java/software/amazon/awssdk/auth/signer/internal/AbstractAws4Signer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public abstract class AbstractAws4Signer<T extends Aws4SignerParams, U extends A
6868
private static final FifoCache<SignerKey> SIGNER_CACHE =
6969
new FifoCache<>(SIGNER_CACHE_MAX_SIZE);
7070
private static final List<String> LIST_OF_HEADERS_TO_IGNORE_IN_LOWER_CASE =
71-
Arrays.asList("connection", "x-amzn-trace-id", "user-agent", "expect");
71+
Arrays.asList("connection", "x-amzn-trace-id", "user-agent", "expect", "transfer-encoding");
7272

7373
protected SdkHttpFullRequest.Builder doSign(SdkHttpFullRequest request,
7474
Aws4SignerRequestParams requestParams,

core/auth/src/main/java/software/amazon/awssdk/auth/signer/internal/util/HeaderTransformsHelper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
public final class HeaderTransformsHelper {
3333

3434
private static final List<String> LIST_OF_HEADERS_TO_IGNORE_IN_LOWER_CASE =
35-
Arrays.asList("connection", "x-amzn-trace-id", "user-agent", "expect");
35+
Arrays.asList("connection", "x-amzn-trace-id", "user-agent", "expect", "transfer-encoding");
3636

3737
private HeaderTransformsHelper() {
3838
}

core/auth/src/test/java/software/amazon/awssdk/auth/signer/Aws4SignerTest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,4 +398,18 @@ public void signing_with_Crc32Checksum_with__trailer_header_already_present() th
398398
assertThat(signed.firstMatchingHeader(SignerConstant.X_AMZ_CONTENT_SHA256)).isNotPresent();
399399
assertThat(signed.firstMatchingHeader("Authorization")).hasValue(expectedAuthorization);
400400
}
401+
402+
@Test
403+
public void TransferEncodingIsNotSigned_NotSigned() {
404+
AwsBasicCredentials credentials = AwsBasicCredentials.create("akid", "skid");
405+
SdkHttpFullRequest.Builder request = generateBasicRequest();
406+
request.putHeader("Transfer-Encoding", "chunked");
407+
408+
SdkHttpFullRequest actual = SignerTestUtils.signRequest(signer, request.build(), credentials, "demo", signingOverrideClock, "us-east-1");
409+
410+
assertThat(actual.firstMatchingHeader("Authorization"))
411+
.hasValue("AWS4-HMAC-SHA256 Credential=akid/19810216/us-east-1/demo/aws4_request, " +
412+
"SignedHeaders=host;x-amz-archive-description;x-amz-date, " +
413+
"Signature=581d0042389009a28d461124138f1fe8eeb8daed87611d2a2b47fd3d68d81d73");
414+
}
401415
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
17+
package software.amazon.awssdk.auth.signer.internal.util;
18+
import java.util.Arrays;
19+
import java.util.Collections;
20+
import java.util.HashMap;
21+
import java.util.List;
22+
import java.util.Map;
23+
import java.util.TreeMap;
24+
import org.junit.jupiter.api.Test;
25+
26+
import static org.junit.jupiter.api.Assertions.*;
27+
28+
class HeaderTransformsHelperTest {
29+
30+
@Test
31+
void shouldExcludeIgnoredHeadersWhenCanonicalizing() {
32+
Map<String, List<String>> headers = new HashMap<>();
33+
34+
// Ignored headers that should be excluded from signing
35+
headers.put("connection", Collections.singletonList("keep-alive"));
36+
headers.put("x-amzn-trace-id", Collections.singletonList("Root=1-234567890"));
37+
headers.put("user-agent", Collections.singletonList("md/user"));
38+
headers.put("expect", Collections.singletonList("100-continue"));
39+
headers.put("transfer-encoding", Collections.singletonList("chunked"));
40+
41+
// Headers that should be included in signing
42+
headers.put("Content-Type", Collections.singletonList("application/json"));
43+
headers.put("Host", Collections.singletonList("example.com"));
44+
45+
Map<String, List<String>> canonicalizedHeaders = HeaderTransformsHelper.canonicalizeSigningHeaders(headers);
46+
47+
assertEquals(2, canonicalizedHeaders.size(), "Should only contain non-ignored headers");
48+
49+
// Verify included headers
50+
assertTrue(canonicalizedHeaders.containsKey("content-type"), "Should contain content-type header");
51+
assertTrue(canonicalizedHeaders.containsKey("host"), "Should contain host header");
52+
53+
// Verify excluded headers
54+
assertFalse(canonicalizedHeaders.containsKey("connection"), "Should not contain connection header");
55+
assertFalse(canonicalizedHeaders.containsKey("x-amzn-trace-id"), "Should not contain x-amzn-trace-id header");
56+
assertFalse(canonicalizedHeaders.containsKey("user-agent"), "Should not contain user-agent header");
57+
assertFalse(canonicalizedHeaders.containsKey("expect"), "Should not contain expect header");
58+
assertFalse(canonicalizedHeaders.containsKey("transfer-encoding"), "Should not contain transfer-encoding header");
59+
}
60+
61+
}

core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4CanonicalRequest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
@Immutable
4444
public final class V4CanonicalRequest {
4545
private static final List<String> HEADERS_TO_IGNORE_IN_LOWER_CASE =
46-
Arrays.asList("connection", "x-amzn-trace-id", "user-agent", "expect");
46+
Arrays.asList("connection", "x-amzn-trace-id", "user-agent", "expect", "transfer-encoding");
4747

4848
private final SdkHttpRequest request;
4949
private final String contentHash;

core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4CanonicalRequestTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ public void canonicalRequest_WithForbiddenHeaders_shouldExcludeForbidden() {
8484
.method(SdkHttpMethod.PUT)
8585
.putHeader("foo", "bar")
8686
.putHeader("x-amzn-trace-id", "wontBePresent")
87+
.putHeader("Transfer-Encoding", "wontBePresent")
8788
.build();
8889
V4CanonicalRequest cr = new V4CanonicalRequest(request, "sha-256",
8990
new V4CanonicalRequest.Options(true,

test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/AsyncRequestCompressionTest.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
import org.junit.jupiter.api.BeforeEach;
2727
import org.junit.jupiter.api.Test;
2828
import org.reactivestreams.Subscriber;
29-
import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
29+
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
30+
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
3031
import software.amazon.awssdk.core.SdkBytes;
3132
import software.amazon.awssdk.core.async.AsyncRequestBody;
3233
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
@@ -44,17 +45,19 @@
4445
public class AsyncRequestCompressionTest {
4546
private static final String UNCOMPRESSED_BODY =
4647
"RequestCompressionTest-RequestCompressionTest-RequestCompressionTest-RequestCompressionTest-RequestCompressionTest";
48+
private static String TRANSFER_ENCODING_HEADER = "Transfer-Encoding";
4749
private String compressedBody;
4850
private int compressedLen;
4951
private MockAsyncHttpClient mockAsyncHttpClient;
5052
private ProtocolRestJsonAsyncClient asyncClient;
5153
private Compressor compressor;
54+
private static final AwsBasicCredentials CLIENT_CREDENTIALS = AwsBasicCredentials.create("akid", "skid");
5255

5356
@BeforeEach
5457
public void setUp() {
5558
mockAsyncHttpClient = new MockAsyncHttpClient();
5659
asyncClient = ProtocolRestJsonAsyncClient.builder()
57-
.credentialsProvider(AnonymousCredentialsProvider.create())
60+
.credentialsProvider(StaticCredentialsProvider.create(CLIENT_CREDENTIALS))
5861
.region(Region.US_EAST_1)
5962
.httpClient(mockAsyncHttpClient)
6063
.build();
@@ -129,7 +132,9 @@ public void asyncStreamingOperation_compressionEnabled_compressesCorrectly() {
129132
assertThat(loggedBody).isEqualTo(compressedBody);
130133
assertThat(loggedRequest.firstMatchingHeader("Content-encoding").get()).isEqualTo("gzip");
131134
assertThat(loggedRequest.matchingHeaders("Content-Length")).isEmpty();
132-
assertThat(loggedRequest.firstMatchingHeader("Transfer-Encoding").get()).isEqualTo("chunked");
135+
assertThat(loggedRequest.firstMatchingHeader(TRANSFER_ENCODING_HEADER).get()).isEqualTo("chunked");
136+
assertThat(loggedRequest.firstMatchingHeader("Authorization").get())
137+
.doesNotContainIgnoringCase(TRANSFER_ENCODING_HEADER);
133138
}
134139

135140
@Test
@@ -172,8 +177,8 @@ public void asyncStreamingOperation_compressionEnabledWithRetry_compressesCorrec
172177

173178
assertThat(loggedBody).isEqualTo(compressedBody);
174179
assertThat(loggedRequest.firstMatchingHeader("Content-encoding").get()).isEqualTo("gzip");
175-
assertThat(loggedRequest.matchingHeaders("Content-Length")).isEmpty();
176-
assertThat(loggedRequest.firstMatchingHeader("Transfer-Encoding").get()).isEqualTo("chunked");
180+
assertThat(loggedRequest.matchingHeaders("Content-Length")).isEmpty();assertThat(loggedRequest.firstMatchingHeader(TRANSFER_ENCODING_HEADER).get()).isEqualTo("chunked");
181+
assertThat(loggedRequest.firstMatchingHeader("Authorization").get()).doesNotContainIgnoringCase(TRANSFER_ENCODING_HEADER);
177182
}
178183

179184
private HttpExecuteResponse mockResponse() {

0 commit comments

Comments
 (0)