Skip to content

Commit cdc37ba

Browse files
committed
Use signing date when deriving signing key
Fix an issue where the signing key is created only once at the start of the request for event streaming requests. This causes requests that span two or more days to have signing errors once the date changes because the signing key was derived only once using the date at the beginning of the request.
1 parent aae2961 commit cdc37ba

File tree

7 files changed

+103
-47
lines changed

7 files changed

+103
-47
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"type": "bugfix",
3+
"category": "AWS SDK for Java v2",
4+
"description": "Fix an issue where the signing key is created only once at the start of the request for event streaming requests. This causes requests that span two or more days to have signing errors once the date changes because the signing key was derived only once using the date at the beginning of the request."
5+
}

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

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,15 @@
1515

1616
package software.amazon.awssdk.auth.signer.internal;
1717

18-
import static software.amazon.awssdk.utils.DateUtils.numberOfDaysSinceEpoch;
1918
import static software.amazon.awssdk.utils.StringUtils.lowerCase;
2019

2120
import java.io.InputStream;
2221
import java.nio.charset.Charset;
22+
import java.time.Instant;
2323
import java.util.ArrayList;
2424
import java.util.Arrays;
2525
import java.util.List;
2626
import java.util.Map;
27-
import java.util.concurrent.TimeUnit;
2827
import software.amazon.awssdk.annotations.SdkInternalApi;
2928
import software.amazon.awssdk.auth.credentials.AwsCredentials;
3029
import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
@@ -160,24 +159,28 @@ protected abstract void processRequestPayload(SdkHttpFullRequest.Builder mutable
160159
* http://docs.aws.amazon
161160
* .com/general/latest/gr/sigv4-calculate-signature.html
162161
*/
163-
protected byte[] deriveSigningKey(AwsCredentials credentials, Aws4SignerRequestParams signerRequestParams) {
164-
165-
String cacheKey = computeSigningCacheKeyName(credentials, signerRequestParams);
166-
long daysSinceEpochSigningDate = numberOfDaysSinceEpoch(signerRequestParams.getRequestSigningDateTimeMilli());
162+
protected final byte[] deriveSigningKey(AwsCredentials credentials, Aws4SignerRequestParams signerRequestParams) {
163+
return deriveSigningKey(credentials,
164+
Instant.ofEpochMilli(signerRequestParams.getRequestSigningDateTimeMilli()),
165+
signerRequestParams.getRegionName(),
166+
signerRequestParams.getServiceSigningName());
167+
}
167168

169+
protected final byte[] deriveSigningKey(AwsCredentials credentials, Instant signingInstant, String region, String service) {
170+
String cacheKey = createSigningCacheKeyName(credentials, region, service);
168171
SignerKey signerKey = SIGNER_CACHE.get(cacheKey);
169172

170-
if (signerKey != null && daysSinceEpochSigningDate == signerKey.getNumberOfDaysSinceEpoch()) {
173+
if (signerKey != null && signerKey.isValidForDate(signingInstant)) {
171174
return signerKey.getSigningKey();
172175
}
173176

174177
LOG.trace(() -> "Generating a new signing key as the signing key not available in the cache for the date: " +
175-
TimeUnit.DAYS.toMillis(daysSinceEpochSigningDate));
178+
signingInstant.toEpochMilli());
176179
byte[] signingKey = newSigningKey(credentials,
177-
signerRequestParams.getFormattedRequestSigningDate(),
178-
signerRequestParams.getRegionName(),
179-
signerRequestParams.getServiceSigningName());
180-
SIGNER_CACHE.add(cacheKey, new SignerKey(daysSinceEpochSigningDate, signingKey));
180+
Aws4SignerUtils.formatDateStamp(signingInstant),
181+
region,
182+
service);
183+
SIGNER_CACHE.add(cacheKey, new SignerKey(signingInstant, signingKey));
181184
return signingKey;
182185
}
183186

@@ -228,14 +231,10 @@ private String createStringToSign(String canonicalRequest,
228231
return stringToSign;
229232
}
230233

231-
232-
/**
233-
* Computes the name to be used to reference the signing key in the cache.
234-
*/
235-
private String computeSigningCacheKeyName(AwsCredentials credentials,
236-
Aws4SignerRequestParams signerRequestParams) {
237-
return credentials.secretAccessKey() + "-" + signerRequestParams.getRegionName() + "-" +
238-
signerRequestParams.getServiceSigningName();
234+
private String createSigningCacheKeyName(AwsCredentials credentials,
235+
String regionName,
236+
String serviceName) {
237+
return credentials.secretAccessKey() + "-" + regionName + "-" + serviceName;
239238
}
240239

241240
/**

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

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import java.util.regex.Pattern;
2121
import software.amazon.awssdk.annotations.SdkInternalApi;
2222
import software.amazon.awssdk.annotations.SdkTestInternalApi;
23-
import software.amazon.awssdk.auth.credentials.AwsCredentials;
2423
import software.amazon.awssdk.auth.signer.params.Aws4SignerParams;
2524
import software.amazon.awssdk.core.async.AsyncRequestBody;
2625
import software.amazon.awssdk.core.exception.SdkClientException;
@@ -57,11 +56,8 @@ public AsyncRequestBody signAsyncRequestBody(SdkHttpFullRequest request, AsyncRe
5756
@SdkTestInternalApi
5857
protected final AsyncRequestBody signAsync(SdkHttpFullRequest request, AsyncRequestBody asyncRequestBody,
5958
Aws4SignerRequestParams requestParams, Aws4SignerParams signingParams) {
60-
AwsCredentials sanitizedCredentials = sanitizeCredentials(signingParams.awsCredentials());
61-
byte[] signingKey = deriveSigningKey(sanitizedCredentials, requestParams);
62-
6359
String headerSignature = getHeaderSignature(request);
64-
return transformRequestProvider(headerSignature, signingKey, requestParams, signingParams, asyncRequestBody);
60+
return transformRequestProvider(headerSignature, requestParams, signingParams, asyncRequestBody);
6561
}
6662

6763
/**
@@ -70,7 +66,6 @@ protected final AsyncRequestBody signAsync(SdkHttpFullRequest request, AsyncRequ
7066
* Can be overriden by subclasses to provide specific signing method
7167
*/
7268
protected abstract AsyncRequestBody transformRequestProvider(String headerSignature,
73-
byte[] signingKey,
7469
Aws4SignerRequestParams signerRequestParams,
7570
Aws4SignerParams signerParams,
7671
AsyncRequestBody asyncRequestBody);

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

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.reactivestreams.Publisher;
2929
import org.reactivestreams.Subscriber;
3030
import software.amazon.awssdk.annotations.SdkInternalApi;
31+
import software.amazon.awssdk.auth.credentials.AwsCredentials;
3132
import software.amazon.awssdk.auth.signer.params.Aws4SignerParams;
3233
import software.amazon.awssdk.core.async.AsyncRequestBody;
3334
import software.amazon.awssdk.core.async.SdkPublisher;
@@ -67,7 +68,6 @@ public SdkHttpFullRequest sign(SdkHttpFullRequest request, Aws4SignerParams sign
6768

6869
@Override
6970
protected AsyncRequestBody transformRequestProvider(String headerSignature,
70-
byte[] signingKey,
7171
Aws4SignerRequestParams signerRequestParams,
7272
Aws4SignerParams signerParams,
7373
AsyncRequestBody asyncRequestBody) {
@@ -80,7 +80,8 @@ protected AsyncRequestBody transformRequestProvider(String headerSignature,
8080
* Map publisher with signing function
8181
*/
8282
Publisher<ByteBuffer> publisherWithSignedFrame =
83-
transformRequestBodyPublisher(publisherWithTrailingEmptyFrame, headerSignature, signingKey, signerRequestParams);
83+
transformRequestBodyPublisher(publisherWithTrailingEmptyFrame, headerSignature,
84+
signerParams.awsCredentials(), signerRequestParams);
8485

8586
AsyncRequestBody transformedRequestBody = AsyncRequestBody.fromPublisher(publisherWithSignedFrame);
8687

@@ -105,16 +106,16 @@ private static Publisher<ByteBuffer> appendEmptyFrame(Publisher<ByteBuffer> publ
105106
}
106107

107108
private Publisher<ByteBuffer> transformRequestBodyPublisher(Publisher<ByteBuffer> publisher, String headerSignature,
108-
byte[] signingKey, Aws4SignerRequestParams signerRequestParams) {
109+
AwsCredentials credentials,
110+
Aws4SignerRequestParams signerRequestParams) {
109111
return SdkPublisher.adapt(publisher)
110-
.map(getDataFrameSigner(headerSignature, signingKey, signerRequestParams));
112+
.map(getDataFrameSigner(headerSignature, credentials, signerRequestParams));
111113
}
112114

113-
private Function<ByteBuffer, ByteBuffer> getDataFrameSigner(String headerSignature, byte[] signingKey,
115+
private Function<ByteBuffer, ByteBuffer> getDataFrameSigner(String headerSignature,
116+
AwsCredentials credentials,
114117
Aws4SignerRequestParams signerRequestParams) {
115118
return new Function<ByteBuffer, ByteBuffer>() {
116-
117-
final byte[] key = signingKey;
118119
final Aws4SignerRequestParams requestParams = signerRequestParams;
119120

120121
/**
@@ -131,11 +132,20 @@ public ByteBuffer apply(ByteBuffer byteBuffer) {
131132
Instant signingInstant = requestParams.getSigningClock().instant();
132133
nonSignatureHeaders.put(EVENT_STREAM_DATE, HeaderValue.fromTimestamp(signingInstant));
133134

135+
/**
136+
* Derive Signing Key
137+
*/
138+
AwsCredentials sanitizedCredentials = sanitizeCredentials(credentials);
139+
byte[] signingKey = deriveSigningKey(sanitizedCredentials,
140+
signingInstant,
141+
requestParams.getRegionName(),
142+
requestParams.getServiceSigningName());
134143
/**
135144
* Calculate rolling signature
136145
*/
146+
137147
byte[] payload = byteBuffer.array();
138-
byte[] signatureBytes = signEventStream(priorSignature, key, signingInstant, requestParams,
148+
byte[] signatureBytes = signEventStream(priorSignature, signingKey, signingInstant, requestParams,
139149
nonSignatureHeaders, payload);
140150
priorSignature = BinaryUtils.toHex(signatureBytes);
141151

@@ -249,5 +259,4 @@ public Optional<Long> contentLength() {
249259
return transformedRequestBody.contentLength();
250260
}
251261
}
252-
253262
}

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

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515

1616
package software.amazon.awssdk.auth.signer.internal;
1717

18+
import java.time.Instant;
1819
import software.amazon.awssdk.annotations.Immutable;
1920
import software.amazon.awssdk.annotations.SdkInternalApi;
21+
import software.amazon.awssdk.utils.DateUtils;
2022

2123
/**
2224
* Holds the signing key and the number of days since epoch for the date for
@@ -26,29 +28,25 @@
2628
@SdkInternalApi
2729
public final class SignerKey {
2830

29-
private final long numberOfDaysSinceEpoch;
31+
private final long daysSinceEpoch;
3032

3133
private final byte[] signingKey;
3234

33-
public SignerKey(long numberOfDaysSinceEpoch, byte[] signingKey) {
34-
if (numberOfDaysSinceEpoch <= 0L) {
35+
public SignerKey(Instant date, byte[] signingKey) {
36+
if (date == null) {
3537
throw new IllegalArgumentException(
36-
"Not able to cache signing key. Signing date to be cached is invalid");
38+
"Not able to cache signing key. Signing date to be is null");
3739
}
3840
if (signingKey == null) {
3941
throw new IllegalArgumentException(
4042
"Not able to cache signing key. Signing Key to be cached are null");
4143
}
42-
this.numberOfDaysSinceEpoch = numberOfDaysSinceEpoch;
44+
this.daysSinceEpoch = DateUtils.numberOfDaysSinceEpoch(date.toEpochMilli());
4345
this.signingKey = signingKey.clone();
4446
}
4547

46-
/**
47-
* Returns the number of days since epoch for the date used for generating
48-
* signing key.
49-
*/
50-
public long getNumberOfDaysSinceEpoch() {
51-
return numberOfDaysSinceEpoch;
48+
public boolean isValidForDate(Instant other) {
49+
return daysSinceEpoch == DateUtils.numberOfDaysSinceEpoch(other.toEpochMilli());
5250
}
5351

5452
/**

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public void openStreamEventSignaturesCanRollOverBetweenDays() {
7373
Map<String, HeaderValue> lastMessageHeaders = signedMessages.get(2).getHeaders();
7474
assertThat(lastMessageHeaders.get(":date").getTimestamp()).isEqualTo("2020-01-02T00:00:00Z");
7575
assertThat(Base64.getEncoder().encodeToString(lastMessageHeaders.get(":chunk-signature").getByteArray()))
76-
.isEqualTo("vgDFcmKOVDUSSzKaBzcj0v9gUSgCK5IwnQ4dMB38NlE=");
76+
.isEqualTo("UTRGo0D7BQytiVkH1VofR/8f3uFsM4V5QR1A8grr1+M=");
7777

7878
}
7979

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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+
package software.amazon.awssdk.auth.signer.internal;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import java.time.Instant;
20+
import org.junit.Test;
21+
22+
public class SignerKeyTest {
23+
24+
@Test
25+
public void isValidForDate_dayBefore_false() {
26+
Instant signerDate = Instant.parse("2020-03-03T23:59:59Z");
27+
SignerKey key = new SignerKey(signerDate, new byte[0]);
28+
Instant dayBefore = Instant.parse("2020-03-02T23:59:59Z");
29+
30+
assertThat(key.isValidForDate(dayBefore)).isFalse();
31+
}
32+
33+
@Test
34+
public void isValidForDate_sameDay_true() {
35+
Instant signerDate = Instant.parse("2020-03-03T23:59:59Z");
36+
SignerKey key = new SignerKey(signerDate, new byte[0]);
37+
Instant sameDay = Instant.parse("2020-03-03T01:02:03Z");
38+
39+
assertThat(key.isValidForDate(sameDay)).isTrue();
40+
}
41+
42+
@Test
43+
public void isValidForDate_dayAfter_false() {
44+
Instant signerDate = Instant.parse("2020-03-03T23:59:59Z");
45+
SignerKey key = new SignerKey(signerDate, new byte[0]);
46+
Instant dayAfter = Instant.parse("2020-03-04T00:00:00Z");
47+
48+
assertThat(key.isValidForDate(dayAfter)).isFalse();
49+
}
50+
}

0 commit comments

Comments
 (0)