Skip to content

Commit 0611fe4

Browse files
committed
Fixed an issue where transcribe streaming transcriptions that take place over two days would fail when the clock rolled over between days.
1 parent a9763ec commit 0611fe4

File tree

4 files changed

+126
-6
lines changed

4 files changed

+126
-6
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": "Amazon Transcribe Service",
4+
"description": "Fixed an issue where streaming transcriptions would fail with signature validation errors if the date changed during the request."
5+
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ public static String formatDateStamp(long timeMilli) {
4646
return DATE_FORMATTER.format(Instant.ofEpochMilli(timeMilli));
4747
}
4848

49+
public static String formatDateStamp(Instant instant) {
50+
return DATE_FORMATTER.format(instant);
51+
}
52+
4953
/**
5054
* Returns a string representation of the given date time in
5155
* yyyyMMdd'T'HHmmss'Z' format. The date returned is in the UTC zone.

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,13 @@ public ByteBuffer apply(ByteBuffer byteBuffer) {
129129
*/
130130
Map<String, HeaderValue> nonSignatureHeaders = new HashMap<>();
131131
Instant signingInstant = requestParams.getSigningClock().instant();
132-
String signingDate = Aws4SignerUtils.formatTimestamp(signingInstant);
133132
nonSignatureHeaders.put(EVENT_STREAM_DATE, HeaderValue.fromTimestamp(signingInstant));
134133

135134
/**
136135
* Calculate rolling signature
137136
*/
138137
byte[] payload = byteBuffer.array();
139-
byte[] signatureBytes = signEventStream(priorSignature, key, signingDate, requestParams,
138+
byte[] signatureBytes = signEventStream(priorSignature, key, signingInstant, requestParams,
140139
nonSignatureHeaders, payload);
141140
priorSignature = BinaryUtils.toHex(signatureBytes);
142141

@@ -162,7 +161,7 @@ public ByteBuffer apply(ByteBuffer byteBuffer) {
162161
*
163162
* @param priorSignature signature of previous frame (Header frame is the 0th frame)
164163
* @param signingKey derived signing key
165-
* @param date siging date
164+
* @param signingInstant the instant at which this message is being signed
166165
* @param requestParams request parameters
167166
* @param nonSignatureHeaders non-signature headers
168167
* @param payload event stream payload
@@ -171,7 +170,7 @@ public ByteBuffer apply(ByteBuffer byteBuffer) {
171170
private byte[] signEventStream(
172171
String priorSignature,
173172
byte[] signingKey,
174-
String date,
173+
Instant signingInstant,
175174
Aws4SignerRequestParams requestParams,
176175
Map<String, HeaderValue> nonSignatureHeaders,
177176
byte[] payload) {
@@ -180,9 +179,9 @@ private byte[] signEventStream(
180179
String stringToSign =
181180
EVENT_STREAM_PAYLOAD +
182181
SignerConstant.LINE_SEPARATOR +
183-
date +
182+
Aws4SignerUtils.formatTimestamp(signingInstant) +
184183
SignerConstant.LINE_SEPARATOR +
185-
requestParams.getScope() +
184+
computeScope(signingInstant, requestParams) +
186185
SignerConstant.LINE_SEPARATOR +
187186
priorSignature +
188187
SignerConstant.LINE_SEPARATOR +
@@ -195,6 +194,13 @@ private byte[] signEventStream(
195194
SigningAlgorithm.HmacSHA256);
196195
}
197196

197+
private String computeScope(Instant signingInstant, Aws4SignerRequestParams requestParams) {
198+
return Aws4SignerUtils.formatDateStamp(signingInstant) + "/" +
199+
requestParams.getRegionName() + "/" +
200+
requestParams.getServiceSigningName() + "/" +
201+
SignerConstant.AWS4_TERMINATOR;
202+
}
203+
198204
/**
199205
* Sort headers in alphabetic order, with exception that EVENT_STREAM_SIGNATURE header always at last
200206
*
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package software.amazon.awssdk.auth.signer;
2+
3+
import static java.nio.charset.StandardCharsets.UTF_8;
4+
import static org.assertj.core.api.Assertions.assertThat;
5+
6+
import io.reactivex.Flowable;
7+
import java.net.URI;
8+
import java.nio.ByteBuffer;
9+
import java.time.Clock;
10+
import java.time.Instant;
11+
import java.time.ZoneId;
12+
import java.time.ZoneOffset;
13+
import java.util.Base64;
14+
import java.util.Collections;
15+
import java.util.List;
16+
import java.util.Map;
17+
import java.util.concurrent.Callable;
18+
import org.junit.Test;
19+
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
20+
import software.amazon.awssdk.auth.credentials.AwsCredentials;
21+
import software.amazon.awssdk.auth.signer.internal.SignerTestUtils;
22+
import software.amazon.awssdk.core.async.AsyncRequestBody;
23+
import software.amazon.awssdk.http.SdkHttpFullRequest;
24+
import software.amazon.awssdk.http.SdkHttpMethod;
25+
import software.amazon.awssdk.regions.Region;
26+
import software.amazon.eventstream.HeaderValue;
27+
import software.amazon.eventstream.Message;
28+
import software.amazon.eventstream.MessageDecoder;
29+
30+
public class EventStreamAws4SignerTest {
31+
/**
32+
* Verify that when an event stream is open from one day to the next, the signature is properly signed for the day of the
33+
* event.
34+
*/
35+
@Test
36+
public void openStreamEventSignaturesCanRollOverBetweenDays() {
37+
EventStreamAws4Signer signer = EventStreamAws4Signer.create();
38+
39+
Region region = Region.US_WEST_2;
40+
AwsCredentials credentials = AwsBasicCredentials.create("a", "s");
41+
String signingName = "name";
42+
AdjustableClock clock = new AdjustableClock();
43+
clock.time = Instant.parse("2020-01-01T23:59:59Z");
44+
45+
SdkHttpFullRequest initialRequest = SdkHttpFullRequest.builder()
46+
.uri(URI.create("http://localhost:8080"))
47+
.method(SdkHttpMethod.GET)
48+
.build();
49+
SdkHttpFullRequest signedRequest = SignerTestUtils.signRequest(signer, initialRequest, credentials, signingName, clock,
50+
region.id());
51+
52+
ByteBuffer event = new Message(Collections.emptyMap(), "foo".getBytes(UTF_8)).toByteBuffer();
53+
54+
Callable<ByteBuffer> lastEvent = () -> {
55+
clock.time = Instant.parse("2020-01-02T00:00:00Z");
56+
return event;
57+
};
58+
59+
AsyncRequestBody requestBody = AsyncRequestBody.fromPublisher(Flowable.concatArray(Flowable.just(event),
60+
Flowable.fromCallable(lastEvent)));
61+
62+
AsyncRequestBody signedBody = SignerTestUtils.signAsyncRequest(signer, signedRequest, requestBody, credentials,
63+
signingName, clock, region.id());
64+
65+
List<Message> signedMessages = readMessages(signedBody);
66+
assertThat(signedMessages.size()).isEqualTo(3);
67+
68+
Map<String, HeaderValue> firstMessageHeaders = signedMessages.get(0).getHeaders();
69+
assertThat(firstMessageHeaders.get(":date").getTimestamp()).isEqualTo("2020-01-01T23:59:59Z");
70+
assertThat(Base64.getEncoder().encodeToString(firstMessageHeaders.get(":chunk-signature").getByteArray()))
71+
.isEqualTo("EFt7ZU043r/TJE8U+1GxJXscmNxoqmIdGtUIl8wE9u0=");
72+
73+
Map<String, HeaderValue> lastMessageHeaders = signedMessages.get(2).getHeaders();
74+
assertThat(lastMessageHeaders.get(":date").getTimestamp()).isEqualTo("2020-01-02T00:00:00Z");
75+
assertThat(Base64.getEncoder().encodeToString(lastMessageHeaders.get(":chunk-signature").getByteArray()))
76+
.isEqualTo("vgDFcmKOVDUSSzKaBzcj0v9gUSgCK5IwnQ4dMB38NlE=");
77+
78+
}
79+
80+
private List<Message> readMessages(AsyncRequestBody signedBody) {
81+
MessageDecoder decoder = new MessageDecoder();
82+
Flowable.fromPublisher(signedBody).blockingForEach(x -> decoder.feed(x.array()));
83+
return decoder.getDecodedMessages();
84+
}
85+
86+
private static class AdjustableClock extends Clock {
87+
private Instant time;
88+
89+
@Override
90+
public ZoneId getZone() {
91+
return ZoneOffset.UTC;
92+
}
93+
94+
@Override
95+
public Clock withZone(ZoneId zone) {
96+
throw new UnsupportedOperationException();
97+
}
98+
99+
@Override
100+
public Instant instant() {
101+
return time;
102+
}
103+
}
104+
105+
}

0 commit comments

Comments
 (0)