Skip to content

Commit a026bf6

Browse files
committed
Transition SignatureV4 from using raw promise chains to async/await
1 parent dd2a21e commit a026bf6

File tree

3 files changed

+114
-143
lines changed

3 files changed

+114
-143
lines changed

packages/credential-provider-imds/src/fromInstanceMetadata.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export function fromInstanceMetadata(
2727
)).trim()
2828
} = init;
2929

30-
return await retry(async () => {
30+
return retry(async () => {
3131
const credsResponse = JSON.parse(
3232
await requestFromEc2Imds(timeout, profile)
3333
);

packages/signature-v4/src/SignatureV4.ts

Lines changed: 98 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -133,119 +133,101 @@ export class SignatureV4 implements RequestSigner {
133133
}
134134
}
135135

136-
presignRequest<StreamType>({
136+
public async presignRequest<StreamType>({
137137
request: originalRequest,
138138
expiration,
139139
signingDate = new Date(),
140140
hoistHeaders = true,
141141
unsignableHeaders = UNSIGNABLE_HEADERS,
142142
unsignedPayload = this.unsignedPayload,
143143
}: PresigningArguments<StreamType>): Promise<HttpRequest<StreamType>> {
144-
return Promise.all([this.regionProvider(), this.credentialProvider()])
145-
.then(([region, credentials]) => {
146-
const {longDate, shortDate} = formatDate(signingDate);
147-
const ttl = getTtl(signingDate, expiration);
148-
if (ttl > MAX_PRESIGNED_TTL) {
149-
return Promise.reject('Signature version 4 presigned URLs'
150-
+ ' must have an expiration date less than one week in'
151-
+ ' the future');
152-
}
153-
154-
const scope = createScope(shortDate, region, this.service);
155-
const keyPromise = this.getSigningKey(credentials, shortDate);
156-
157-
const wrapperFn = hoistHeaders
158-
? moveHeadersToQuery
159-
: ensureRequestHasQuery;
160-
const request = wrapperFn(prepareRequest(originalRequest));
161-
162-
if (credentials.sessionToken) {
163-
request.query[TOKEN_QUERY_PARAM] = credentials.sessionToken;
164-
}
165-
request.query[ALGORITHM_QUERY_PARAM] = ALGORITHM_IDENTIFIER;
166-
request.query[CREDENTIAL_QUERY_PARAM]
167-
= `${credentials.accessKeyId}/${scope}`;
168-
request.query[AMZ_DATE_QUERY_PARAM] = longDate;
169-
request.query[EXPIRES_QUERY_PARAM] = ttl.toString(10);
170-
171-
return this.getPayloadHash(request, unsignedPayload)
172-
.then(payloadHash => {
173-
if (this.applyChecksum) {
174-
request.query[SHA256_QUERY_PARAM] = payloadHash;
175-
}
176-
177-
const canonicalHeaders = getCanonicalHeaders(
178-
request,
179-
unsignableHeaders
180-
);
181-
request.query[SIGNED_HEADERS_QUERY_PARAM]
182-
= getCanonicalHeaderList(canonicalHeaders);
183-
const canonicalRequest = this.createCanonicalRequest(
184-
request,
185-
canonicalHeaders,
186-
payloadHash
187-
);
188-
189-
return this.getSignature(
190-
longDate,
191-
scope,
192-
keyPromise,
193-
canonicalRequest
194-
)
195-
}).then(signature => {
196-
request.query[SIGNATURE_QUERY_PARAM] = signature;
197-
return request;
198-
});
199-
});
144+
const [region, credentials] = await Promise.all([
145+
this.regionProvider(),
146+
this.credentialProvider()
147+
]);
148+
149+
const {longDate, shortDate} = formatDate(signingDate);
150+
const ttl = getTtl(signingDate, expiration);
151+
if (ttl > MAX_PRESIGNED_TTL) {
152+
return Promise.reject('Signature version 4 presigned URLs'
153+
+ ' must have an expiration date less than one week in'
154+
+ ' the future');
155+
}
156+
157+
const scope = createScope(shortDate, region, this.service);
158+
159+
const wrapperFn = hoistHeaders ? moveHeadersToQuery : ensureReqHasQuery;
160+
const request = wrapperFn(prepareRequest(originalRequest));
161+
162+
if (credentials.sessionToken) {
163+
request.query[TOKEN_QUERY_PARAM] = credentials.sessionToken;
164+
}
165+
request.query[ALGORITHM_QUERY_PARAM] = ALGORITHM_IDENTIFIER;
166+
request.query[CREDENTIAL_QUERY_PARAM] = `${credentials.accessKeyId}/${scope}`;
167+
request.query[AMZ_DATE_QUERY_PARAM] = longDate;
168+
request.query[EXPIRES_QUERY_PARAM] = ttl.toString(10);
169+
170+
const payloadHash = await this.getPayloadHash(request, unsignedPayload);
171+
if (this.applyChecksum) {
172+
request.query[SHA256_QUERY_PARAM] = payloadHash;
173+
}
174+
175+
const canonicalHeaders = getCanonicalHeaders(request, unsignableHeaders);
176+
request.query[SIGNED_HEADERS_QUERY_PARAM] = getCanonicalHeaderList(canonicalHeaders);
177+
178+
request.query[SIGNATURE_QUERY_PARAM] = await this.getSignature(
179+
longDate,
180+
scope,
181+
this.getSigningKey(credentials, region, shortDate),
182+
this.createCanonicalRequest(
183+
request,
184+
canonicalHeaders,
185+
payloadHash
186+
)
187+
);
188+
189+
return request;
200190
}
201191

202-
signRequest<StreamType>({
192+
public async signRequest<StreamType>({
203193
request: originalRequest,
204194
signingDate = new Date(),
205195
unsignableHeaders = UNSIGNABLE_HEADERS,
206196
unsignedPayload = this.unsignedPayload,
207197
}: SigningArguments<StreamType>): Promise<HttpRequest<StreamType>> {
208-
return Promise.all([this.regionProvider(), this.credentialProvider()])
209-
.then(([region, credentials]) => {
210-
const request = prepareRequest(originalRequest);
211-
const {longDate, shortDate} = formatDate(signingDate);
212-
const scope = createScope(shortDate, region, this.service);
213-
const keyPromise = this.getSigningKey(credentials, shortDate);
214-
215-
request.headers[AMZ_DATE_HEADER] = longDate;
216-
if (credentials.sessionToken) {
217-
request.headers[TOKEN_HEADER] = credentials.sessionToken;
218-
}
219-
220-
return this.getPayloadHash(request, unsignedPayload)
221-
.then(payloadHash => {
222-
if (this.applyChecksum || payloadHash === UNSIGNED_PAYLOAD) {
223-
request.headers[SHA256_HEADER] = payloadHash;
224-
}
225-
const canonicalHeaders = getCanonicalHeaders(
226-
request,
227-
unsignableHeaders
228-
);
229-
const canonicalRequest = this.createCanonicalRequest(
230-
request,
231-
canonicalHeaders,
232-
payloadHash
233-
);
234-
235-
return this.getSignature(
236-
longDate,
237-
scope,
238-
keyPromise,
239-
canonicalRequest
240-
).then(signature => {
241-
request.headers[AUTH_HEADER] = `${ALGORITHM_IDENTIFIER} `
242-
+ `Credential=${credentials.accessKeyId}/${scope}, `
243-
+ `SignedHeaders=${getCanonicalHeaderList(canonicalHeaders)}, `
244-
+ `Signature=${signature}`;
245-
return request;
246-
});
247-
});
248-
});
198+
const [region, credentials] = await Promise.all([
199+
this.regionProvider(),
200+
this.credentialProvider()
201+
]);
202+
203+
const request = prepareRequest(originalRequest);
204+
const {longDate, shortDate} = formatDate(signingDate);
205+
const scope = createScope(shortDate, region, this.service);
206+
207+
request.headers[AMZ_DATE_HEADER] = longDate;
208+
if (credentials.sessionToken) {
209+
request.headers[TOKEN_HEADER] = credentials.sessionToken;
210+
}
211+
212+
const payloadHash = await this.getPayloadHash(request, unsignedPayload);
213+
214+
if (this.applyChecksum || payloadHash === UNSIGNED_PAYLOAD) {
215+
request.headers[SHA256_HEADER] = payloadHash;
216+
}
217+
const canonicalHeaders = getCanonicalHeaders(request, unsignableHeaders);
218+
const signature = await this.getSignature(
219+
longDate,
220+
scope,
221+
this.getSigningKey(credentials, region, shortDate),
222+
this.createCanonicalRequest(request, canonicalHeaders, payloadHash)
223+
);
224+
225+
request.headers[AUTH_HEADER] = `${ALGORITHM_IDENTIFIER} `
226+
+ `Credential=${credentials.accessKeyId}/${scope}, `
227+
+ `SignedHeaders=${getCanonicalHeaderList(canonicalHeaders)}, `
228+
+ `Signature=${signature}`;
229+
230+
return request;
249231
}
250232

251233
private createCanonicalRequest(
@@ -263,20 +245,19 @@ ${sortedHeaders.join(';')}
263245
${payloadHash}`;
264246
}
265247

266-
private createStringToSign(
248+
private async createStringToSign(
267249
longDate: string,
268250
credentialScope: string,
269251
canonicalRequest: string
270252
): Promise<string> {
271253
const hash = new this.sha256();
272254
hash.update(canonicalRequest);
255+
const hashedRequest = await hash.digest();
273256

274-
return hash.digest().then(hashedRequest => (
275-
`${ALGORITHM_IDENTIFIER}
257+
return`${ALGORITHM_IDENTIFIER}
276258
${longDate}
277259
${credentialScope}
278-
${toHex(hashedRequest)}`
279-
));
260+
${toHex(hashedRequest)}`;
280261
}
281262

282263
private getCanonicalPath(
@@ -290,53 +271,50 @@ ${toHex(hashedRequest)}`
290271
return path;
291272
}
292273

293-
private getPayloadHash<StreamType>(
274+
private async getPayloadHash<StreamType>(
294275
request: HttpRequest<StreamType>,
295276
unsignedPayload: boolean
296277
): Promise<string> {
297-
298-
299278
if (unsignedPayload && request.protocol === 'https:') {
300-
return Promise.resolve(UNSIGNED_PAYLOAD);
279+
return UNSIGNED_PAYLOAD;
301280
}
302281

303282
return getPayloadHash(request, this.sha256);
304283
}
305284

306-
private getSignature(
285+
private async getSignature(
307286
longDate: string,
308287
credentialScope: string,
309288
keyPromise: Promise<Uint8Array>,
310289
canonicalRequest: string
311290
): Promise<string> {
312-
return this.createStringToSign(
291+
const stringToSign = await this.createStringToSign(
313292
longDate,
314293
credentialScope,
315294
canonicalRequest
316-
).then(stringToSign => {
317-
return keyPromise.then(key => {
318-
const hash = new this.sha256(key);
319-
hash.update(stringToSign);
320-
return hash.digest();
321-
});
322-
}).then(toHex);
295+
);
296+
297+
const hash = new this.sha256(await keyPromise);
298+
hash.update(stringToSign);
299+
return toHex(await hash.digest());
323300
}
324301

325302
private getSigningKey(
326303
credentials: Credentials,
304+
region: string,
327305
shortDate: string
328306
): Promise<Uint8Array> {
329-
return this.regionProvider().then(region => getSigningKey(
307+
return getSigningKey(
330308
this.sha256,
331309
credentials,
332310
shortDate,
333311
region,
334312
this.service
335-
));
313+
);
336314
}
337315
}
338316

339-
function ensureRequestHasQuery<StreamType>(
317+
function ensureReqHasQuery<StreamType>(
340318
request: HttpRequest<StreamType>
341319
): HttpRequest<StreamType> & {query: QueryParameterBag} {
342320
const {query = {} as QueryParameterBag} = request;

packages/signature-v4/src/getPayloadHash.ts

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,28 @@ import {toHex} from '@aws/util-hex-encoding';
66
/**
77
* @internal
88
*/
9-
export function getPayloadHash(
9+
export async function getPayloadHash(
1010
{headers, body}: HttpRequest<any>,
1111
hashConstructor: HashConstructor
1212
): Promise<string> {
1313
if (SHA256_HEADER in headers) {
14-
return Promise.resolve(headers[SHA256_HEADER]);
14+
return headers[SHA256_HEADER];
1515
}
1616

17-
let hashPromise: Promise<Uint8Array>;
18-
1917
if (body == undefined) {
20-
hashPromise = Promise.resolve(EMPTY_DATA_SHA_256);
21-
} else {
22-
const hash = new hashConstructor();
23-
24-
if (
25-
typeof body === 'string'
26-
|| ArrayBuffer.isView(body)
27-
|| isArrayBuffer(body)
28-
) {
29-
hash.update(body);
30-
hashPromise = hash.digest();
31-
} else {
32-
// As any body that is not a string or binary data is a stream, this
33-
// body is unsignable. Attempt to send the request with an unsigned
34-
// payload, which may or may not be accepted by the service.
35-
return Promise.resolve(UNSIGNED_PAYLOAD);
36-
}
18+
return toHex(EMPTY_DATA_SHA_256);
19+
} else if (
20+
typeof body === 'string'
21+
|| ArrayBuffer.isView(body)
22+
|| isArrayBuffer(body)
23+
) {
24+
const hashCtor = new hashConstructor();
25+
hashCtor.update(body);
26+
return toHex(await hashCtor.digest());
3727
}
3828

39-
return hashPromise.then(toHex);
29+
// As any defined body that is not a string or binary data is a stream, this
30+
// body is unsignable. Attempt to send the request with an unsigned payload,
31+
// which may or may not be accepted by the service.
32+
return UNSIGNED_PAYLOAD;
4033
}

0 commit comments

Comments
 (0)