I have tried several methods, but they all give the same error when creating a SAS token programatically.
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:32b24722-801e-0024-1e9c-3b3c1d000000 Time:2024-11-20T22:36:21.0299082Z</Message><AuthenticationErrorDetail>Signature fields not well formed.</AuthenticationErrorDetail></Error>
Of course, this all works when using a SAS token generated in the portal. I have a (working) storage account and container and the containers key (I assume this is base64 encoded and needs to be decoded prior to signing).
bool downloadFile(const std::string& blobName, const std::string& localFilePath) {
if (!m_curl) {
std::cerr << "Failed to initialize cURL" << std::endl;
return false;
}
// Generate SAS token just before the file download
//IotHubHelpers iotHubHelper;
std::string resourceUri = m_storage_account + ".blob.core.windows.net/" + m_container_name + "/" + blobName;
std::string account_key = "Only Half the eyJBuYU2S2G99MDFfb5K6aGRrZJRdAlonKchD+AStQfq0Ig=="; // Your container access key
std::string start_time = "2024-11-19T19:21:22Z";
std::string expiry_time = "2028-11-20T03:21:22Z";
std::string permissions = "rwdlacupiyx";
std::string sasToken = generate_blob_sas_token(
//m_storage_account,
//m_container_name,
//blobName,
account_key //,
//permissions,
//start_time,
//expiry_time
);
if (sasToken.empty()) {
std::cerr << "Error: Failed to generate SAS token." << std::endl;
return false;
}
std::cout << "SAS Token: " << sasToken << std::endl;
// Construct URL with SAS token
std::string url = "https://" + resourceUri + "?" + sasToken;
std::cout << "Downloading from URL: " << url << std::endl;
// Open the file stream in binary mode
std::ofstream outFile(localFilePath, std::ios::binary);
if (!outFile) {
std::cerr << "Failed to open file for writing: " << localFilePath << std::endl;
return false;
}
// Set cURL options
curl_easy_setopt(m_curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, writeData);
curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &outFile);
curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYHOST, 2L);
CURLcode res = curl_easy_perform(m_curl);
if (res != CURLE_OK) {
std::cerr << "cURL error: " << curl_easy_strerror(res) << std::endl;
outFile.close();
return false;
}
outFile.close();
return true;
}
This is the Token generator, where I cant replicate a token from Azure Portal.
std::string generate_blob_sas_token(const std::string& key) {
const std::string canonicalizedResource = "/blob/fotacontainer/fota/testfile.txt";
std::vector<std::pair<std::string, std::string>> sas_token_properties = {
{"sp", "r"},
{"st", "2024-11-19T19:21:22Z"}, //get_utc_time(-120)},
{"se", "2025-11-19T19:21:22Z"}, //get_utc_time(1440)},
{"canonicalizedResource", canonicalizedResource},
{"si", ""},
{"sip", ""},
{"spr", "https"},
{"sv", "2023-01-03"},
{"sr", "b"},
{"sst", ""},
{"ses", ""},
{"rscc", ""},
{"rscd", ""},
{"rsce", ""},
{"rscl", ""},
{"rsct", ""}
};
std::vector<std::string> values;
for (const auto& entry : sas_token_properties) {
values.push_back(entry.second);
}
std::string string_to_sign = join_with_newline(values);
std::cout << string_to_sign << std::endl;
// The keys we get from the storage account are base64 encoded, so we need to decode them first
std::string decoded_key = base64_decode(key);
unsigned char* digest = HMAC(EVP_sha256(), decoded_key.data(), decoded_key.size(),
reinterpret_cast<const unsigned char*>(string_to_sign.data()), string_to_sign.size(), nullptr, nullptr);
std::string signature = base64_encode(digest, SHA256_DIGEST_LENGTH);
std::vector<std::string> parameters;
for (const auto& entry : sas_token_properties) {
if (!entry.second.empty() && entry.first != "canonicalizedResource") {
parameters.push_back(entry.first + "=" + url_encode(entry.second));
}
}
parameters.push_back("sig=" + url_encode(signature));
return join(parameters, "&");
}
Using the code above (needs cleanup, yes), I get the error message
<?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:32b24722-801e-0024-1e9c-3b3c1d000000 Time:2024-11-20T22:36:21.0299082Z</Message><AuthenticationErrorDetail>Signature fields not well formed.</AuthenticationErrorDetail></Error>
I must be missing something basic in the algorithm to assemble and generate the SAS Token. I've read the docs, and looking online. still no solution I can find.
Unable to generate a valid Azure Storage SAS token in Cpp
You can use the below code to generate Azure blob sas
token using cpp with crypto
library.
Code:
#include <iostream>
#include <iomanip>
#include <sstream>
#include <ctime>
#include <string>
#include <cryptopp/cryptlib.h>
#include <cryptopp/hex.h>
#include <cryptopp/hmac.h>
#include <cryptopp/sha.h>
#include <cryptopp/base64.h>
#include <cryptopp/filters.h>
#include <cctype>
#include <iomanip>
using namespace CryptoPP;
std::string GenerateSas(const std::string& storageAccountKey, const std::string& input) {
std::string decodedKey;
StringSource(storageAccountKey, true, new Base64Decoder(new StringSink(decodedKey)));
// Create HMAC-SHA256 signature
std::string digest;
HMAC<SHA256> hmac((const byte*)decodedKey.data(), decodedKey.size());
StringSource(input, true,
new HashFilter(hmac, new StringSink(digest))); // Compute HMAC digest
// Encode the digest to Base64
std::string encodedDigest;
StringSource(digest, true, new Base64Encoder(new StringSink(encodedDigest), false));
encodedDigest.erase(std::remove(encodedDigest.begin(), encodedDigest.end(), '\n'), encodedDigest.end());
return encodedDigest;
}
std::string TimePointToString(const std::chrono::seconds& timePoint) {
std::time_t t = timePoint.count();
std::tm tm{};
gmtime_s(&tm, &t);
std::ostringstream oss;
oss << std::put_time(&tm, "%Y-%m-%dT%H:%M:%SZ");
return oss.str();
}
// URL encode function
std::string UrlEncode(const std::string& str) {
std::ostringstream escaped;
for (char c : str) {
if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
escaped << c;
}
else {
escaped << '%' << std::setw(2) << std::setfill('0') << std::hex << (int)(unsigned char)c;
}
}
return escaped.str();
}
// Function to generate SAS token
void GenerateSasToken(const std::string& blobName, const std::string& accessKey, const std::string& containerName) {
// Define SAS token parameters
auto currentTime = std::chrono::system_clock::now();
auto currentTimeSeconds = std::chrono::duration_cast<std::chrono::seconds>(currentTime.time_since_epoch());
auto expirationTime = currentTimeSeconds + std::chrono::minutes(120);
// Convert time to ISO 8601 format
std::string signedStart = TimePointToString(currentTimeSeconds);
std::string signedExpiry = TimePointToString(expirationTime);
// Define other SAS parameters
std::string signedPermissions = "rwl";
std::string signedService = "b"; // 'b' for blob
std::string signedProtocol = "https";
std::string signedVersion = "2022-11-02";
std::string accountName = "venkat326123";
// Create canonicalized resource
std::string canonicalizedResource = "/blob/" + accountName + "/" + containerName + "/" + blobName;
// Construct the string to sign
std::ostringstream stringToSign;
stringToSign << signedPermissions << "\n"
<< signedStart << "\n"
<< signedExpiry << "\n"
<< canonicalizedResource << "\n"
<< "\n\n"
<< signedProtocol << "\n"
<< signedVersion << "\n"
<< signedService << "\n"
<< "\n\n\n\n\n\n";
// Generate the signature
std::string signature = GenerateSas(accessKey, stringToSign.str());
// URL encode the signature
std::string urlEncodedSignature = UrlEncode(signature);
// Construct the SAS token
std::ostringstream sasToken;
sasToken << "sv=" << signedVersion
<< "&sr=" << signedService
<< "&sp=" << signedPermissions
<< "&st=" << signedStart
<< "&se=" << signedExpiry
<< "&spr=" << signedProtocol
<< "&sig=" << urlEncodedSignature;
std::cout << "Blob URL: https://" << accountName << ".blob.core.windows.net/"
<< containerName << "/" << blobName << "?" << sasToken.str() << std::endl;
}
int main() {
// Define blob details
std::string blobName = "scenery.jpg";
std::string containerName = "test";
std::string accessKey = "<storage account key>";
// Generate SAS token
GenerateSasToken(blobName, accessKey, containerName);
return 0;
}
Output:
Blob URL: https://venkat326123.blob.core.windows.net/test/scenery.jpg?sv=2022-11-02&sr=b&sp=rwl&st=2024-11-21T09:20:49Z&se=2024-11-21T11:20:49Z&spr=https&sig=erc8cD0Jxxxxxxx1hxxxGR%2btK9muys%3d
I verified the blob URL in the browser.
Browser: