11import {
22 AwsCredentialIdentity ,
3- ChecksumConstructor ,
4- DateInput ,
53 EventSigner ,
64 EventSigningArguments ,
75 FormattedEvent ,
8- HashConstructor ,
9- HeaderBag ,
106 HttpRequest ,
117 MessageSigner ,
12- Provider ,
138 RequestPresigner ,
149 RequestPresigningArguments ,
1510 RequestSigner ,
@@ -20,8 +15,6 @@ import {
2015 StringSigner ,
2116} from "@smithy/types" ;
2217import { toHex } from "@smithy/util-hex-encoding" ;
23- import { normalizeProvider } from "@smithy/util-middleware" ;
24- import { escapeUri } from "@smithy/util-uri-escape" ;
2518import { toUint8Array } from "@smithy/util-utf8" ;
2619
2720import {
@@ -42,78 +35,20 @@ import {
4235} from "./constants" ;
4336import { createScope , getSigningKey } from "./credentialDerivation" ;
4437import { getCanonicalHeaders } from "./getCanonicalHeaders" ;
45- import { getCanonicalQuery } from "./getCanonicalQuery" ;
4638import { getPayloadHash } from "./getPayloadHash" ;
4739import { HeaderFormatter } from "./HeaderFormatter" ;
4840import { hasHeader } from "./headerUtil" ;
4941import { moveHeadersToQuery } from "./moveHeadersToQuery" ;
5042import { prepareRequest } from "./prepareRequest" ;
51- import { iso8601 } from "./utilDate " ;
43+ import { SignatureV4Base , SignatureV4CryptoInit , SignatureV4Init } from "./SignatureV4Base " ;
5244
5345/**
5446 * @public
5547 */
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+ {
11752 private readonly headerFormatter = new HeaderFormatter ( ) ;
11853
11954 constructor ( {
@@ -124,13 +59,14 @@ export class SignatureV4 implements RequestPresigner, RequestSigner, StringSigne
12459 sha256,
12560 uriEscapePath = true ,
12661 } : 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+ } ) ;
13470 }
13571
13672 public async presign ( originalRequest : HttpRequest , options : RequestPresigningArguments = { } ) : Promise < HttpRequest > {
@@ -148,7 +84,7 @@ export class SignatureV4 implements RequestPresigner, RequestSigner, StringSigne
14884 this . validateResolvedCredentials ( credentials ) ;
14985 const region = signingRegion ?? ( await this . regionProvider ( ) ) ;
15086
151- const { longDate, shortDate } = formatDate ( signingDate ) ;
87+ const { longDate, shortDate } = this . formatDate ( signingDate ) ;
15288 if ( expiresIn > MAX_PRESIGNED_TTL ) {
15389 return Promise . reject (
15490 "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
167103 request . query [ EXPIRES_QUERY_PARAM ] = expiresIn . toString ( 10 ) ;
168104
169105 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 ) ;
171107
172108 request . query [ SIGNATURE_QUERY_PARAM ] = await this . getSignature (
173109 longDate ,
@@ -200,7 +136,7 @@ export class SignatureV4 implements RequestPresigner, RequestSigner, StringSigne
200136 { signingDate = new Date ( ) , priorSignature, signingRegion, signingService } : EventSigningArguments
201137 ) : Promise < string > {
202138 const region = signingRegion ?? ( await this . regionProvider ( ) ) ;
203- const { shortDate, longDate } = formatDate ( signingDate ) ;
139+ const { shortDate, longDate } = this . formatDate ( signingDate ) ;
204140 const scope = createScope ( shortDate , region , signingService ?? this . service ) ;
205141 const hashedPayload = await getPayloadHash ( { headers : { } , body : payload } as any , this . sha256 ) ;
206142 const hash = new this . sha256 ( ) ;
@@ -246,7 +182,7 @@ export class SignatureV4 implements RequestPresigner, RequestSigner, StringSigne
246182 const credentials = await this . credentialProvider ( ) ;
247183 this . validateResolvedCredentials ( credentials ) ;
248184 const region = signingRegion ?? ( await this . regionProvider ( ) ) ;
249- const { shortDate } = formatDate ( signingDate ) ;
185+ const { shortDate } = this . formatDate ( signingDate ) ;
250186
251187 const hash = new this . sha256 ( await this . getSigningKey ( credentials , region , shortDate , signingService ) ) ;
252188 hash . update ( toUint8Array ( stringToSign ) ) ;
@@ -267,7 +203,7 @@ export class SignatureV4 implements RequestPresigner, RequestSigner, StringSigne
267203 this . validateResolvedCredentials ( credentials ) ;
268204 const region = signingRegion ?? ( await this . regionProvider ( ) ) ;
269205 const request = prepareRequest ( requestToSign ) ;
270- const { longDate, shortDate } = formatDate ( signingDate ) ;
206+ const { longDate, shortDate } = this . formatDate ( signingDate ) ;
271207 const scope = createScope ( shortDate , region , signingService ?? this . service ) ;
272208
273209 request . headers [ AMZ_DATE_HEADER ] = longDate ;
@@ -291,75 +227,24 @@ export class SignatureV4 implements RequestPresigner, RequestSigner, StringSigne
291227 request . headers [ AUTH_HEADER ] =
292228 `${ ALGORITHM_IDENTIFIER } ` +
293229 `Credential=${ credentials . accessKeyId } /${ scope } , ` +
294- `SignedHeaders=${ getCanonicalHeaderList ( canonicalHeaders ) } , ` +
230+ `SignedHeaders=${ this . getCanonicalHeaderList ( canonicalHeaders ) } , ` +
295231 `Signature=${ signature } ` ;
296232
297233 return request ;
298234 }
299235
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-
356236 private async getSignature (
357237 longDate : string ,
358238 credentialScope : string ,
359239 keyPromise : Promise < Uint8Array > ,
360240 canonicalRequest : string
361241 ) : 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+ ) ;
363248
364249 const hash = new this . sha256 ( await keyPromise ) ;
365250 hash . update ( toUint8Array ( stringToSign ) ) ;
@@ -374,26 +259,4 @@ ${toHex(hashedRequest)}`;
374259 ) : Promise < Uint8Array > {
375260 return getSigningKey ( this . sha256 , credentials , shortDate , region , service || this . service ) ;
376261 }
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- }
389262}
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