Despite following the documentation found here: https://learn.microsoft.com/en-us/rest/api/storageservices/create-account-sas, I am unable to generate a working account SAS token for use with Azure Blob Storage. Particularly, one which authorizes writing new blobs and Index Tags for that blob. So far, I am unable to generate one which authorizes writing, alone. My suspicion is that the order of elements in the string to sign are correct, but something is going wrong when generating the hashed signature. The server response is shown below. Below that is pseudocode of the process used to generate the signature.
Server response:
I have inserted '\n' where there are unused optional headers; signedIP and signedEncryptionScope.
"<?xml version="1.0" encoding="utf-8"?>
<Error>
<Code>AuthenticationFailed</Code>
<Message>
Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
RequestId: ...
Time:...</Message>
<AuthenticationErrorDetail>
Signature did not match. String to sign used was
<ACCOUNT_NAME>
rwlactf
b
co
2024-10-01T04:00:00Z
2024-11-01T04:00:00Z
\n
https
2020-12-06
\n
</AuthenticationErrorDetail></Error>"
SAS token + URI generation code:
The following code is intended to be run remotely, in environments where I cannot guarantee that more advanced modules may be installed at will. All solutions must be in native PS 5.1 and .NET 4.5 and above, as the environments may include Windows Server 2012 and 2012 R2. The point is to allow the script in which this code runs to periodically upload data, such as logs, to a blob storage account for later processing.
Obviously sensitive parts of the code, including transmitted key values are intended to be lifted from here and executed more securely in the production version, but are included within the block until proof of concept is achieved.
Assume that the $Account_Key value is the account access key, having been converted to a more complex hash for transmission, and then restored to its original value, as retrieved from the web UI prior to use here. That is; if the key is displayed in the web UI as ABCDEFG12345, it has been hashed, transmitted to the endpoint, the hash reversed, and fed to this function again as ABCDEFG12345.
function Generate-SasUri {
# Load the required assembly to URL-decode elements for stringToSign
Add-Type -AssemblyName System.Web
$sv = "2020-12-06"
$ss = "b" # signedServices
$srt = "co"
$sp = "rwlactf" # signedPermissions
# Start and expiry times
#$startTime = [System.DateTime]::UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ")
#$expiryTime = (Get-Date).AddMinutes(5).ToString("yyyy-MM-ddTHH:mm:ssZ")
# Temporarily forcing start/expiry time for verification of working token compared to one generated within the web UI.
$st = [System.DateTime]::new(2024, 10, 1, 04, 0, 0, [System.DateTimeKind]::Utc).ToString("yyyy-MM-ddTHH:mm:ssZ")
$se = [System.DateTime]::new(2024, 11, 1, 04, 0, 0, [System.DateTimeKind]::Utc).ToString("yyyy-MM-ddTHH:mm:ssZ")
$spr = "https"
$stringToSign = (
"$ACCOUNT_NAME`n" + # accountName
"$sp`n" + # signedPermissions
"$ss`n" + # signedService
"$srt`n" + # signedResourceType
"$st`n" + # signedStart
"$se`n" + # signedExpiry
"`n" + # signedIP
"$spr`n" + # signedProtocol
"$sv`n" + # signedVersion
"`n" # signedEncryptionScope
)
$stringToSign = [System.Web.HttpUtility]::UrlDecode($stringToSign)
$stringToSignBytes = [System.Text.Encoding]::UTF8.GetBytes($stringToSign)
$keyBytes = [System.Convert]::FromBase64String($ACCOUNT_KEY)
$hmacsha256 = New-Object System.Security.Cryptography.HMACSHA256
$hmacsha256.key = $keyBytes
$hashBytes = $hmacsha256.ComputeHash($stringToSignBytes)
$sig = [Convert]::ToBase64String($hashBytes)
$URI = "https://$ACCOUNT_NAME.blob.core.windows.net/?sv=$sv&ss=$ss&srt=$srt&sp=$sp&se=$se&st=$st&spr=$spr&sig=$sig"
return $URI
}