import { GaxiosOptions } from 'gaxios' ; import { Headers } from './oauth2client' ; import { Crypto , createCrypto , fromArrayBufferToHex } from '../crypto/crypto' ; type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'HEAD' | 'DELETE' | 'CONNECT' | 'OPTIONS' | 'TRACE' ; /** Interface defining the AWS authorization header map for signed requests. */ interface AwsAuthHeaderMap { amzDate ? : string ; authorizationHeader : string ; canonicalQuerystring : string ; } /**
 * Interface defining AWS security credentials.
 * These are either determined from AWS security_credentials endpoint or
 * AWS environment variables.
 */ interface AwsSecurityCredentials { accessKeyId : string ; secretAccessKey : string ; token ? : string ; } /**
 * Interface defining the parameters needed to compute the AWS
 * authentication header map.
 */ interface GenerateAuthHeaderMapOptions { crypto : Crypto ; host : string ; canonicalUri : string ; canonicalQuerystring : string ; method : HttpMethod ; region : string ; securityCredentials : AwsSecurityCredentials ; requestPayload ? : string ; additionalAmzHeaders ? : Headers ; } /** AWS Signature Version 4 signing algorithm identifier.  */ const AWS_ALGORITHM = 'AWS4-HMAC-SHA256' ; /**
 * The termination string for the AWS credential scope value as defined in
 * https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
 */ const AWS_REQUEST_TYPE = 'aws4_request' ; /**
 * Implements an AWS API request signer based on the AWS Signature Version 4
 * signing process.
 * https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
 */ export class AwsRequestSigner { private readonly crypto : Crypto ; param getCredentials param region constructor ( private readonly getCredentials , private readonly region ) { this . crypto = createCrypto ( ) ; } param amzOptions return async getRequestOptions ( amzOptions ) { if ( ! amzOptions . url ) { throw new Error ( '"url" is required in "amzOptions"' ) ; } const requestPayloadData = typeof amzOptions . data === 'object' ? JSON . stringify ( amzOptions . data ) : amzOptions . data ; const url = amzOptions . url ; const method = amzOptions . method || 'GET' ; const requestPayload = amzOptions . body || requestPayloadData ; const additionalAmzHeaders = amzOptions . headers ; const awsSecurityCredentials = await this . getCredentials ( ) ; const uri = new URL ( url ) ; const headerMap = await generateAuthenticationHeaderMap ( { crypto : this . crypto , host : uri . host , canonicalUri : uri . pathname , canonicalQuerystring : uri . search . substr ( 1 ) , method , region : this . region , securityCredentials : awsSecurityCredentials , requestPayload , additionalAmzHeaders , } ) ; const headers = Object . assign ( headerMap . amzDate ? { 'x-amz-date' : headerMap . amzDate } : { } , { Authorization : headerMap . authorizationHeader , host : uri . host , } , additionalAmzHeaders || { } ) ; if ( awsSecurityCredentials . token ) { Object . assign ( headers , { 'x-amz-security-token' : awsSecurityCredentials . token , } ) ; } const awsSignedReq = { url , method : method , headers , } ; if ( typeof requestPayload !== 'undefined' ) { awsSignedReq . body = requestPayload ; } return awsSignedReq ; } } param crypto param key param msg return async function sign ( crypto , key , msg ) { return await crypto . signWithHmacSha256 ( key , msg ) ; } param crypto param key param dateStamp param region param serviceName return async function getSigningKey ( crypto , key , dateStamp , region , serviceName ) { const kDate = await sign ( crypto , `AWS4${ key }` , dateStamp ) ; const kRegion = await sign ( crypto , kDate , region ) ; const kService = await sign ( crypto , kRegion , serviceName ) ; const kSigning = await sign ( crypto , kService , 'aws4_request' ) ; return kSigning ; } param option return async function generateAuthenticationHeaderMap ( options ) { const additionalAmzHeaders = options . additionalAmzHeaders || { } ; const requestPayload = options . requestPayload || '' ; const serviceName = options . host . split ( '.' ) [ 0 ] ; const now = new Date ( ) ; const amzDate = now . toISOString ( ) . replace ( /[-:]/g , '' ) . replace ( /\.[0-9]+/ , '' ) ; const dateStamp = now . toISOString ( ) . replace ( /[-]/g , '' ) . replace ( /T.*/ , '' ) ; const reformattedAdditionalAmzHeaders = { } ; Object . keys ( additionalAmzHeaders ) . forEach ( key => { reformattedAdditionalAmzHeaders [ key . toLowerCase ( ) ] = additionalAmzHeaders [ key ] ; } ) ; if ( options . securityCredentials . token ) { reformattedAdditionalAmzHeaders [ 'x-amz-security-token' ] = options . securityCredentials . token ; } const amzHeaders = Object . assign ( { host : options . host , } , reformattedAdditionalAmzHeaders . date ? { } : { 'x-amz-date' : amzDate } , reformattedAdditionalAmzHeaders ) ; let canonicalHeaders = '' ; const signedHeadersList = Object . keys ( amzHeaders ) . sort ( ) ; signedHeadersList . forEach ( key => { canonicalHeaders += `${ key }:${ amzHeaders [ key ] }\n` ; } ) ; const signedHeaders = signedHeadersList . join ( ';' ) ; const payloadHash = await options . crypto . sha256DigestHex ( requestPayload ) ; const canonicalRequest = `${ options . method }\n` + `${ options . canonicalUri }\n` + `${ options . canonicalQuerystring }\n` + `${ canonicalHeaders }\n` + `${ signedHeaders }\n` + `${ payloadHash }` ; const credentialScope = `${ dateStamp }/${ options . region }/${ serviceName }/${ AWS_REQUEST_TYPE }` ; const stringToSign = `${ AWS_ALGORITHM }\n` + `${ amzDate }\n` + `${ credentialScope }\n` + ( await options . crypto . sha256DigestHex ( canonicalRequest ) ) ; const signingKey = await getSigningKey ( options . crypto , options . securityCredentials . secretAccessKey , dateStamp , options . region , serviceName ) ; const signature = await sign ( options . crypto , signingKey , stringToSign ) ; const authorizationHeader = `${ AWS_ALGORITHM } Credential=${ options . securityCredentials . accessKeyId }/` + `${ credentialScope }, SignedHeaders=${ signedHeaders }, ` + `Signature=${ fromArrayBufferToHex ( signature ) }` ; return { amzDate : reformattedAdditionalAmzHeaders . date ? undefined : amzDate , authorizationHeader , canonicalQuerystring : options . canonicalQuerystring , } ; }