Invoke-WebRequest overriding the Date header's format

Hello, I’m trying to write a script that need to perform an authenticated request to AWS S3. As per AWS’s documentation, the request requires to be authenticated via a summary of the request’s method, Date header, bucket name and object key being signed with the secret key using SHA1. I created a PS function that automatically generates the date string with the same format as shown in the documentation and the resulting Authorization header with the signed content. I then tested this using PostMan and it worked as expected.

However, when I’m trying to perform this request using either Invoke-WebRequest or Invoke-RestMethod it fails, claiming that the signatures do not match. Luckily, S3’s response includes the request summary that should be signed to perform the authentication and I found that the date string is being changed from the format I sent to using the UTC time zone and replacing the time zone offset by “GMT”, here is an example of what I mean (some values were redacted for security reasons). The content being printed after the execution of Get-AWSAuthHeaders is the string that is being signed.

>$headers = Get-AWSAuthHeaders GET
GET


Wed, 28 Apr 2021 18:51:53 -0300
/[REDACTED]/clipboard
>$headers
Name                           Value
----                           -----
Date                           Wed, 28 Apr 2021 18:51:53 -0300
Authorization                  AWS [REDACTED]:iPvDMgLYHnHVbmnThKHJu7nbuAM=
>try {
>> Invoke-WebRequest -Method GET -Headers $headers -Uri https://[REDACTED].s3-sa-east-1.amazonaws.com/clipboard
>> } catch {
>>         $result = $_.Exception.Response.GetResponseStream()
>>         $reader = New-Object System.IO.StreamReader($result)
>>         $reader.BaseStream.Position = 0
>>         $reader.DiscardBufferedData()
>>         $responseBody = $reader.ReadToEnd();
>> }
>$responseBody
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message><AWSAccessKeyId>[REDACTED]]</AWSAccessKeyId><StringToSign>GET


Wed, 28 Apr 2021 21:51:53 GMT
/[REDACTED]/clipboard</StringToSign><SignatureProvided>iPvDMgLYHnHVbmnThKHJu7nbuAM=</SignatureProvided><StringToSignBytes>[REDACTED]</StringToSignBytes><RequestId>58A6CR4XMSPC8ZCT</RequestId><HostId>fldDRoFiacucacOXVJ93mMVCOhikNKft+01fHuDPKsMz31nhSneVGb96/A23AAn8KE/NAqVV3BY=</HostId></Error>

As you can see, S3 is trying to sign a string with the date in a different format than I’m passing to Invoke-WebRequest and given that this does not happen when using PostMan, means that it’s PowerShell changing the header value on my side and not S3 on AWS’ side.

Here is the code for Get-AWSAuthHeaders in case that it helps (the conf object holds just configuration info for the redacted values):

function Get-AWSAuthHeaders ($method) {
    $bucket_name = $conf.bucket_name
    $aws_access_key = $conf.aws_access_key
    $date_str = (Get-Date).ToString('ddd, d MMM yyyy HH:mm:ss -0300', [CultureInfo]'en-us')
    $hmacsha = New-Object System.Security.Cryptography.HMACSHA1
    $string_to_sign = "$method`n`n`n$date_str`n/$bucket_name/clipboard"
    Write-Host $string_to_sign
    $hmacsha.key = [Text.Encoding]::ASCII.GetBytes($conf.aws_secret_key)
    $signature = $hmacsha.ComputeHash([Text.Encoding]::ASCII.GetBytes($string_to_sign))
    $signed_b64 = [Convert]::ToBase64String($signature)
    return @{
        "Authorization" = "AWS $aws_access_key`:$signed_b64"
        "Date" = $date_str
    }
}

I also tried using PostMan’s feature to export the request as code and it results in the same issue (I also tested exporting it as cURL and it works fine too but I need it using PowerShell, not cURL).

Why are you escaping the colon? Give this a try

function Get-AWSAuthHeaders ($method) {
    $bucket_name = $conf.bucket_name
    $aws_access_key = $conf.aws_access_key
    $date_str = (Get-Date).ToString('ddd, d MMM yyyy HH:mm:ss -0300', [CultureInfo]'en-us')
    $hmacsha = New-Object System.Security.Cryptography.HMACSHA1

    $string_to_sign = @'
{0}


{1}
/{2}/clipboard
'@ -f $method,$date_str,$bucket_name

    Write-Host $string_to_sign
    $hmacsha.key = [Text.Encoding]::ASCII.GetBytes($conf.aws_secret_key)
    $signature = $hmacsha.ComputeHash([Text.Encoding]::ASCII.GetBytes($string_to_sign))
    $signed_b64 = [Convert]::ToBase64String($signature)
    return @{
        Authorization = 'AWS {0}:{1}' -f $aws_access_key,$signed_b64
        Date          = $date_str
    }
}

I’m escaping the colon because without escaping Powershell thought that it was part of the $aws_access_key variable and threw the following error. Still the resulting string is the exact same as using -f

Variable reference is not valid. ':' was not followed by a valid variable name character. Consider using ${} to
delimit the name.

The output of your function has the same result as mine, as I explained in my post, the values from that function sent to PostMan or cURL work well, it’s when they are used in Invoke-WebRequest that it doesn’t work.

Just because they look the same, doesn’t mean they are the same. You may try it regardless to see if it works. Good luck.

The values are the same, I tested it. And like I said, the difference comes when using the exact same string in Powershell instead of cURL or PostMan.