1
1
import {
2
2
AwsCredentialIdentity ,
3
- ChecksumConstructor ,
4
- DateInput ,
5
3
EventSigner ,
6
4
EventSigningArguments ,
7
5
FormattedEvent ,
8
- HashConstructor ,
9
- HeaderBag ,
10
6
HttpRequest ,
11
7
MessageSigner ,
12
- Provider ,
13
8
RequestPresigner ,
14
9
RequestPresigningArguments ,
15
10
RequestSigner ,
@@ -20,8 +15,6 @@ import {
20
15
StringSigner ,
21
16
} from "@smithy/types" ;
22
17
import { toHex } from "@smithy/util-hex-encoding" ;
23
- import { normalizeProvider } from "@smithy/util-middleware" ;
24
- import { escapeUri } from "@smithy/util-uri-escape" ;
25
18
import { toUint8Array } from "@smithy/util-utf8" ;
26
19
27
20
import {
@@ -42,78 +35,20 @@ import {
42
35
} from "./constants" ;
43
36
import { createScope , getSigningKey } from "./credentialDerivation" ;
44
37
import { getCanonicalHeaders } from "./getCanonicalHeaders" ;
45
- import { getCanonicalQuery } from "./getCanonicalQuery" ;
46
38
import { getPayloadHash } from "./getPayloadHash" ;
47
39
import { HeaderFormatter } from "./HeaderFormatter" ;
48
40
import { hasHeader } from "./headerUtil" ;
49
41
import { moveHeadersToQuery } from "./moveHeadersToQuery" ;
50
42
import { prepareRequest } from "./prepareRequest" ;
51
- import { iso8601 } from "./utilDate " ;
43
+ import { SignatureV4Base , SignatureV4CryptoInit , SignatureV4Init } from "./SignatureV4Base " ;
52
44
53
45
/**
54
46
* @public
55
47
*/
56
- export interface SignatureV4Init {
57
- /**
58
- * The service signing name.
59
- */
60
- service : string ;
61
-
62
- /**
63
- * The region name or a function that returns a promise that will be
64
- * resolved with the region name.
65
- */
66
- region : string | Provider < string > ;
67
-
68
- /**
69
- * The credentials with which the request should be signed or a function
70
- * that returns a promise that will be resolved with credentials.
71
- */
72
- credentials : AwsCredentialIdentity | Provider < AwsCredentialIdentity > ;
73
-
74
- /**
75
- * A constructor function for a hash object that will calculate SHA-256 HMAC
76
- * checksums.
77
- */
78
- sha256 : ChecksumConstructor | HashConstructor ;
79
-
80
- /**
81
- * Whether to uri-escape the request URI path as part of computing the
82
- * canonical request string. This is required for every AWS service, except
83
- * Amazon S3, as of late 2017.
84
- *
85
- * @default [true]
86
- */
87
- uriEscapePath ?: boolean ;
88
-
89
- /**
90
- * Whether to calculate a checksum of the request body and include it as
91
- * either a request header (when signing) or as a query string parameter
92
- * (when presigning). This is required for AWS Glacier and Amazon S3 and optional for
93
- * every other AWS service as of late 2017.
94
- *
95
- * @default [true]
96
- */
97
- applyChecksum ?: boolean ;
98
- }
99
-
100
- /**
101
- * @public
102
- */
103
- export interface SignatureV4CryptoInit {
104
- sha256 : ChecksumConstructor | HashConstructor ;
105
- }
106
-
107
- /**
108
- * @public
109
- */
110
- export class SignatureV4 implements RequestPresigner , RequestSigner , StringSigner , EventSigner , MessageSigner {
111
- private readonly service : string ;
112
- private readonly regionProvider : Provider < string > ;
113
- private readonly credentialProvider : Provider < AwsCredentialIdentity > ;
114
- private readonly sha256 : ChecksumConstructor | HashConstructor ;
115
- private readonly uriEscapePath : boolean ;
116
- private readonly applyChecksum : boolean ;
48
+ export class SignatureV4
49
+ extends SignatureV4Base
50
+ implements RequestPresigner , RequestSigner , StringSigner , EventSigner , MessageSigner
51
+ {
117
52
private readonly headerFormatter = new HeaderFormatter ( ) ;
118
53
119
54
constructor ( {
@@ -124,13 +59,14 @@ export class SignatureV4 implements RequestPresigner, RequestSigner, StringSigne
124
59
sha256,
125
60
uriEscapePath = true ,
126
61
} : SignatureV4Init & SignatureV4CryptoInit ) {
127
- this . service = service ;
128
- this . sha256 = sha256 ;
129
- this . uriEscapePath = uriEscapePath ;
130
- // default to true if applyChecksum isn't set
131
- this . applyChecksum = typeof applyChecksum === "boolean" ? applyChecksum : true ;
132
- this . regionProvider = normalizeProvider ( region ) ;
133
- this . credentialProvider = normalizeProvider ( credentials ) ;
62
+ super ( {
63
+ applyChecksum,
64
+ credentials,
65
+ region,
66
+ service,
67
+ sha256,
68
+ uriEscapePath,
69
+ } ) ;
134
70
}
135
71
136
72
public async presign ( originalRequest : HttpRequest , options : RequestPresigningArguments = { } ) : Promise < HttpRequest > {
@@ -148,7 +84,7 @@ export class SignatureV4 implements RequestPresigner, RequestSigner, StringSigne
148
84
this . validateResolvedCredentials ( credentials ) ;
149
85
const region = signingRegion ?? ( await this . regionProvider ( ) ) ;
150
86
151
- const { longDate, shortDate } = formatDate ( signingDate ) ;
87
+ const { longDate, shortDate } = this . formatDate ( signingDate ) ;
152
88
if ( expiresIn > MAX_PRESIGNED_TTL ) {
153
89
return Promise . reject (
154
90
"Signature version 4 presigned URLs" + " must have an expiration date less than one week in" + " the future"
@@ -167,7 +103,7 @@ export class SignatureV4 implements RequestPresigner, RequestSigner, StringSigne
167
103
request . query [ EXPIRES_QUERY_PARAM ] = expiresIn . toString ( 10 ) ;
168
104
169
105
const canonicalHeaders = getCanonicalHeaders ( request , unsignableHeaders , signableHeaders ) ;
170
- request . query [ SIGNED_HEADERS_QUERY_PARAM ] = getCanonicalHeaderList ( canonicalHeaders ) ;
106
+ request . query [ SIGNED_HEADERS_QUERY_PARAM ] = this . getCanonicalHeaderList ( canonicalHeaders ) ;
171
107
172
108
request . query [ SIGNATURE_QUERY_PARAM ] = await this . getSignature (
173
109
longDate ,
@@ -200,7 +136,7 @@ export class SignatureV4 implements RequestPresigner, RequestSigner, StringSigne
200
136
{ signingDate = new Date ( ) , priorSignature, signingRegion, signingService } : EventSigningArguments
201
137
) : Promise < string > {
202
138
const region = signingRegion ?? ( await this . regionProvider ( ) ) ;
203
- const { shortDate, longDate } = formatDate ( signingDate ) ;
139
+ const { shortDate, longDate } = this . formatDate ( signingDate ) ;
204
140
const scope = createScope ( shortDate , region , signingService ?? this . service ) ;
205
141
const hashedPayload = await getPayloadHash ( { headers : { } , body : payload } as any , this . sha256 ) ;
206
142
const hash = new this . sha256 ( ) ;
@@ -246,7 +182,7 @@ export class SignatureV4 implements RequestPresigner, RequestSigner, StringSigne
246
182
const credentials = await this . credentialProvider ( ) ;
247
183
this . validateResolvedCredentials ( credentials ) ;
248
184
const region = signingRegion ?? ( await this . regionProvider ( ) ) ;
249
- const { shortDate } = formatDate ( signingDate ) ;
185
+ const { shortDate } = this . formatDate ( signingDate ) ;
250
186
251
187
const hash = new this . sha256 ( await this . getSigningKey ( credentials , region , shortDate , signingService ) ) ;
252
188
hash . update ( toUint8Array ( stringToSign ) ) ;
@@ -267,7 +203,7 @@ export class SignatureV4 implements RequestPresigner, RequestSigner, StringSigne
267
203
this . validateResolvedCredentials ( credentials ) ;
268
204
const region = signingRegion ?? ( await this . regionProvider ( ) ) ;
269
205
const request = prepareRequest ( requestToSign ) ;
270
- const { longDate, shortDate } = formatDate ( signingDate ) ;
206
+ const { longDate, shortDate } = this . formatDate ( signingDate ) ;
271
207
const scope = createScope ( shortDate , region , signingService ?? this . service ) ;
272
208
273
209
request . headers [ AMZ_DATE_HEADER ] = longDate ;
@@ -291,75 +227,24 @@ export class SignatureV4 implements RequestPresigner, RequestSigner, StringSigne
291
227
request . headers [ AUTH_HEADER ] =
292
228
`${ ALGORITHM_IDENTIFIER } ` +
293
229
`Credential=${ credentials . accessKeyId } /${ scope } , ` +
294
- `SignedHeaders=${ getCanonicalHeaderList ( canonicalHeaders ) } , ` +
230
+ `SignedHeaders=${ this . getCanonicalHeaderList ( canonicalHeaders ) } , ` +
295
231
`Signature=${ signature } ` ;
296
232
297
233
return request ;
298
234
}
299
235
300
- private createCanonicalRequest ( request : HttpRequest , canonicalHeaders : HeaderBag , payloadHash : string ) : string {
301
- const sortedHeaders = Object . keys ( canonicalHeaders ) . sort ( ) ;
302
- return `${ request . method }
303
- ${ this . getCanonicalPath ( request ) }
304
- ${ getCanonicalQuery ( request ) }
305
- ${ sortedHeaders . map ( ( name ) => `${ name } :${ canonicalHeaders [ name ] } ` ) . join ( "\n" ) }
306
-
307
- ${ sortedHeaders . join ( ";" ) }
308
- ${ payloadHash } `;
309
- }
310
-
311
- private async createStringToSign (
312
- longDate : string ,
313
- credentialScope : string ,
314
- canonicalRequest : string
315
- ) : Promise < string > {
316
- const hash = new this . sha256 ( ) ;
317
- hash . update ( toUint8Array ( canonicalRequest ) ) ;
318
- const hashedRequest = await hash . digest ( ) ;
319
-
320
- return `${ ALGORITHM_IDENTIFIER }
321
- ${ longDate }
322
- ${ credentialScope }
323
- ${ toHex ( hashedRequest ) } `;
324
- }
325
-
326
- private getCanonicalPath ( { path } : HttpRequest ) : string {
327
- if ( this . uriEscapePath ) {
328
- // Non-S3 services, we normalize the path and then double URI encode it.
329
- // Ref: "Remove Dot Segments" https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4
330
- const normalizedPathSegments = [ ] ;
331
- for ( const pathSegment of path . split ( "/" ) ) {
332
- if ( pathSegment ?. length === 0 ) continue ;
333
- if ( pathSegment === "." ) continue ;
334
- if ( pathSegment === ".." ) {
335
- normalizedPathSegments . pop ( ) ;
336
- } else {
337
- normalizedPathSegments . push ( pathSegment ) ;
338
- }
339
- }
340
- // Joining by single slashes to remove consecutive slashes.
341
- const normalizedPath = `${ path ?. startsWith ( "/" ) ? "/" : "" } ${ normalizedPathSegments . join ( "/" ) } ${
342
- normalizedPathSegments . length > 0 && path ?. endsWith ( "/" ) ? "/" : ""
343
- } `;
344
-
345
- // Double encode and replace non-standard characters !'()* according to RFC 3986
346
- const doubleEncoded = escapeUri ( normalizedPath ) ;
347
- return doubleEncoded . replace ( / % 2 F / g, "/" ) ;
348
- }
349
-
350
- // For S3, we shouldn't normalize the path. For example, object name
351
- // my-object//example//photo.user should not be normalized to
352
- // my-object/example/photo.user
353
- return path ;
354
- }
355
-
356
236
private async getSignature (
357
237
longDate : string ,
358
238
credentialScope : string ,
359
239
keyPromise : Promise < Uint8Array > ,
360
240
canonicalRequest : string
361
241
) : Promise < string > {
362
- const stringToSign = await this . createStringToSign ( longDate , credentialScope , canonicalRequest ) ;
242
+ const stringToSign = await this . createStringToSign (
243
+ longDate ,
244
+ credentialScope ,
245
+ canonicalRequest ,
246
+ ALGORITHM_IDENTIFIER
247
+ ) ;
363
248
364
249
const hash = new this . sha256 ( await keyPromise ) ;
365
250
hash . update ( toUint8Array ( stringToSign ) ) ;
@@ -374,26 +259,4 @@ ${toHex(hashedRequest)}`;
374
259
) : Promise < Uint8Array > {
375
260
return getSigningKey ( this . sha256 , credentials , shortDate , region , service || this . service ) ;
376
261
}
377
-
378
- private validateResolvedCredentials ( credentials : unknown ) {
379
- if (
380
- typeof credentials !== "object" ||
381
- // @ts -expect-error: Property 'accessKeyId' does not exist on type 'object'.ts(2339)
382
- typeof credentials . accessKeyId !== "string" ||
383
- // @ts -expect-error: Property 'secretAccessKey' does not exist on type 'object'.ts(2339)
384
- typeof credentials . secretAccessKey !== "string"
385
- ) {
386
- throw new Error ( "Resolved credential object is not valid" ) ;
387
- }
388
- }
389
262
}
390
-
391
- const formatDate = ( now : DateInput ) : { longDate : string ; shortDate : string } => {
392
- const longDate = iso8601 ( now ) . replace ( / [ \- : ] / g, "" ) ;
393
- return {
394
- longDate,
395
- shortDate : longDate . slice ( 0 , 8 ) ,
396
- } ;
397
- } ;
398
-
399
- const getCanonicalHeaderList = ( headers : object ) : string => Object . keys ( headers ) . sort ( ) . join ( ";" ) ;
0 commit comments