Skip to content
Merged
2 changes: 2 additions & 0 deletions .vscode/cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@
"Schulze",
"scus",
"SDDL",
"Sddl",
"sddl",
"sdpath",
"serializers",
"Seriot",
Expand Down
2 changes: 1 addition & 1 deletion sdk/storage/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "cpp",
"TagPrefix": "cpp/storage",
"Tag": "cpp/storage_c3a4a21ce9"
"Tag": "cpp/storage_90767ef070"
}
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,29 @@ namespace Azure { namespace Storage { namespace Sas {
const Blobs::Models::UserDelegationKey& userDelegationKey,
const std::string& accountName);

/**
* @brief For debugging purposes only.
*
* @param credential
* The storage account's shared key credential.
* @return Returns the string to sign that will be used to generate the signature for the SAS
* URL.
*/
std::string GenerateSasStringToSign(const StorageSharedKeyCredential& credential);

/**
* @brief For debugging purposes only.
*
* @param userDelegationKey UserDelegationKey returned from
* BlobServiceClient.GetUserDelegationKey.
* @param accountName The name of the storage account.
* @return Returns the string to sign that will be used to generate the signature for the SAS
* URL.
*/
std::string GenerateSasStringToSign(
const Blobs::Models::UserDelegationKey& userDelegationKey,
const std::string& accountName);

private:
std::string Permissions;
};
Expand Down
610 changes: 213 additions & 397 deletions sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/rest_client.hpp

Large diffs are not rendered by default.

80 changes: 80 additions & 0 deletions sdk/storage/azure-storage-blobs/src/blob_sas_builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,4 +323,84 @@ namespace Azure { namespace Storage { namespace Sas {
return builder.GetAbsoluteUrl();
}

std::string BlobSasBuilder::GenerateSasStringToSign(const StorageSharedKeyCredential& credential)
{
std::string canonicalName = "/blob/" + credential.AccountName + "/" + BlobContainerName;
if (Resource == BlobSasResource::Blob || Resource == BlobSasResource::BlobSnapshot
|| Resource == BlobSasResource::BlobVersion)
{
canonicalName += "/" + BlobName;
}
std::string protocol = _detail::SasProtocolToString(Protocol);
std::string resource = BlobSasResourceToString(Resource);

std::string snapshotVersion;
if (Resource == BlobSasResource::BlobSnapshot)
{
snapshotVersion = Snapshot;
}
else if (Resource == BlobSasResource::BlobVersion)
{
snapshotVersion = BlobVersionId;
}

std::string startsOnStr = StartsOn.HasValue()
? StartsOn.Value().ToString(
Azure::DateTime::DateFormat::Rfc3339, Azure::DateTime::TimeFractionFormat::Truncate)
: "";
std::string expiresOnStr = Identifier.empty()
? ExpiresOn.ToString(
Azure::DateTime::DateFormat::Rfc3339, Azure::DateTime::TimeFractionFormat::Truncate)
: "";

return Permissions + "\n" + startsOnStr + "\n" + expiresOnStr + "\n" + canonicalName + "\n"
+ Identifier + "\n" + (IPRange.HasValue() ? IPRange.Value() : "") + "\n" + protocol + "\n"
+ SasVersion + "\n" + resource + "\n" + snapshotVersion + "\n" + EncryptionScope + "\n"
+ CacheControl + "\n" + ContentDisposition + "\n" + ContentEncoding + "\n" + ContentLanguage
+ "\n" + ContentType;
}

std::string BlobSasBuilder::GenerateSasStringToSign(
const Blobs::Models::UserDelegationKey& userDelegationKey,
const std::string& accountName)
{
std::string canonicalName = "/blob/" + accountName + "/" + BlobContainerName;
if (Resource == BlobSasResource::Blob || Resource == BlobSasResource::BlobSnapshot
|| Resource == BlobSasResource::BlobVersion)
{
canonicalName += "/" + BlobName;
}
std::string protocol = _detail::SasProtocolToString(Protocol);
std::string resource = BlobSasResourceToString(Resource);

std::string snapshotVersion;
if (Resource == BlobSasResource::BlobSnapshot)
{
snapshotVersion = Snapshot;
}
else if (Resource == BlobSasResource::BlobVersion)
{
snapshotVersion = BlobVersionId;
}

std::string startsOnStr = StartsOn.HasValue()
? StartsOn.Value().ToString(
Azure::DateTime::DateFormat::Rfc3339, Azure::DateTime::TimeFractionFormat::Truncate)
: "";
std::string expiresOnStr = ExpiresOn.ToString(
Azure::DateTime::DateFormat::Rfc3339, Azure::DateTime::TimeFractionFormat::Truncate);
std::string signedStartsOnStr = userDelegationKey.SignedStartsOn.ToString(
Azure::DateTime::DateFormat::Rfc3339, Azure::DateTime::TimeFractionFormat::Truncate);
std::string signedExpiresOnStr = userDelegationKey.SignedExpiresOn.ToString(
Azure::DateTime::DateFormat::Rfc3339, Azure::DateTime::TimeFractionFormat::Truncate);

return Permissions + "\n" + startsOnStr + "\n" + expiresOnStr + "\n" + canonicalName + "\n"
+ userDelegationKey.SignedObjectId + "\n" + userDelegationKey.SignedTenantId + "\n"
+ signedStartsOnStr + "\n" + signedExpiresOnStr + "\n" + userDelegationKey.SignedService
+ "\n" + userDelegationKey.SignedVersion + "\n\n\n\n"
+ (IPRange.HasValue() ? IPRange.Value() : "") + "\n" + protocol + "\n" + SasVersion + "\n"
+ resource + "\n" + snapshotVersion + "\n" + EncryptionScope + "\n" + CacheControl + "\n"
+ ContentDisposition + "\n" + ContentEncoding + "\n" + ContentLanguage + "\n" + ContentType;
}

}}} // namespace Azure::Storage::Sas
2 changes: 1 addition & 1 deletion sdk/storage/azure-storage-blobs/swagger/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ package-name: azure-storage-blobs
namespace: Azure::Storage::Blobs
output-folder: generated
clear-output-folder: true
input-file: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/storage/data-plane/Microsoft.BlobStorage/stable/2021-12-02/blob.json
input-file: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/storage/data-plane/Microsoft.BlobStorage/stable/2024-08-04/blob.json
```

## ModelFour Options
Expand Down
78 changes: 78 additions & 0 deletions sdk/storage/azure-storage-blobs/test/ut/blob_sas_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -771,4 +771,82 @@ namespace Azure { namespace Storage { namespace Test {
EXPECT_TRUE(e.AdditionalInformation.count("ExtendedErrorDetail") != 0);
}
}

TEST(SasStringToSignTest, GenerateStringToSign)
{
std::string accountName = "testAccountName";
std::string accountKey = "dGVzdEFjY291bnRLZXk=";
std::string blobUrl = "https://testAccountName.blob.core.windows.net/container/blob";
auto keyCredential = std::make_shared<StorageSharedKeyCredential>(accountName, accountKey);
auto sasStartsOn = std::chrono::system_clock::now() - std::chrono::minutes(5);
auto sasExpiresOn = std::chrono::system_clock::now() + std::chrono::minutes(60);

// Account Sas
{
Sas::AccountSasBuilder accountSasBuilder;
accountSasBuilder.Protocol = Sas::SasProtocol::HttpsAndHttp;
accountSasBuilder.StartsOn = sasStartsOn;
accountSasBuilder.ExpiresOn = sasExpiresOn;
accountSasBuilder.Services = Sas::AccountSasServices::Blobs;
accountSasBuilder.ResourceTypes = Sas::AccountSasResource::All;
accountSasBuilder.SetPermissions(Sas::AccountSasPermissions::Read);
auto sasToken = accountSasBuilder.GenerateSasToken(*keyCredential);
auto signature = Azure::Core::Url::Decode(
Azure::Core::Url(blobUrl + sasToken).GetQueryParameters().find("sig")->second);
auto stringToSign = accountSasBuilder.GenerateSasStringToSign(*keyCredential);
auto signatureFromStringToSign = Azure::Core::Convert::Base64Encode(_internal::HmacSha256(
std::vector<uint8_t>(stringToSign.begin(), stringToSign.end()),
Azure::Core::Convert::Base64Decode(accountKey)));
EXPECT_EQ(signature, signatureFromStringToSign);
}

// Blob Sas
{
Sas::BlobSasBuilder blobSasBuilder;
blobSasBuilder.Protocol = Sas::SasProtocol::HttpsAndHttp;
blobSasBuilder.StartsOn = sasStartsOn;
blobSasBuilder.ExpiresOn = sasExpiresOn;
blobSasBuilder.BlobContainerName = "container";
blobSasBuilder.BlobName = "blob";
blobSasBuilder.Resource = Sas::BlobSasResource::Blob;
blobSasBuilder.SetPermissions(Sas::BlobSasPermissions::Read);
auto sasToken = blobSasBuilder.GenerateSasToken(*keyCredential);
auto signature = Azure::Core::Url::Decode(
Azure::Core::Url(blobUrl + sasToken).GetQueryParameters().find("sig")->second);
auto stringToSign = blobSasBuilder.GenerateSasStringToSign(*keyCredential);
auto signatureFromStringToSign = Azure::Core::Convert::Base64Encode(_internal::HmacSha256(
std::vector<uint8_t>(stringToSign.begin(), stringToSign.end()),
Azure::Core::Convert::Base64Decode(accountKey)));
EXPECT_EQ(signature, signatureFromStringToSign);
}

// Blob User Delegation Sas
{
Blobs::Models::UserDelegationKey userDelegationKey;
userDelegationKey.SignedObjectId = "testSignedObjectId";
userDelegationKey.SignedTenantId = "testSignedTenantId";
userDelegationKey.SignedStartsOn = sasStartsOn;
userDelegationKey.SignedExpiresOn = sasExpiresOn;
userDelegationKey.SignedService = "b";
userDelegationKey.SignedVersion = "2020-08-04";
userDelegationKey.Value = accountKey;

Sas::BlobSasBuilder blobSasBuilder;
blobSasBuilder.Protocol = Sas::SasProtocol::HttpsAndHttp;
blobSasBuilder.StartsOn = sasStartsOn;
blobSasBuilder.ExpiresOn = sasExpiresOn;
blobSasBuilder.BlobContainerName = "container";
blobSasBuilder.BlobName = "blob";
blobSasBuilder.Resource = Sas::BlobSasResource::Blob;
blobSasBuilder.SetPermissions(Sas::BlobSasPermissions::Read);
auto sasToken = blobSasBuilder.GenerateSasToken(userDelegationKey, accountName);
auto signature = Azure::Core::Url::Decode(
Azure::Core::Url(blobUrl + sasToken).GetQueryParameters().find("sig")->second);
auto stringToSign = blobSasBuilder.GenerateSasStringToSign(userDelegationKey, accountName);
auto signatureFromStringToSign = Azure::Core::Convert::Base64Encode(_internal::HmacSha256(
std::vector<uint8_t>(stringToSign.begin(), stringToSign.end()),
Azure::Core::Convert::Base64Decode(accountKey)));
EXPECT_EQ(signature, signatureFromStringToSign);
}
}
}}} // namespace Azure::Storage::Test
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,16 @@ namespace Azure { namespace Storage { namespace Sas {
*/
std::string GenerateSasToken(const StorageSharedKeyCredential& credential);

/**
* @brief For debugging purposes only.
*
* @param credential
* The storage account's shared key credential.
* @return Returns the string to sign that will be used to generate the signature for the SAS
* URL.
*/
std::string GenerateSasStringToSign(const StorageSharedKeyCredential& credential);

private:
std::string Permissions;
};
Expand Down
46 changes: 46 additions & 0 deletions sdk/storage/azure-storage-common/src/account_sas_builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,50 @@ namespace Azure { namespace Storage { namespace Sas {
return builder.GetAbsoluteUrl();
}

std::string AccountSasBuilder::GenerateSasStringToSign(
const StorageSharedKeyCredential& credential)
{
std::string protocol = _detail::SasProtocolToString(Protocol);

std::string services;
if ((Services & AccountSasServices::Blobs) == AccountSasServices::Blobs)
{
services += "b";
}
if ((Services & AccountSasServices::Queue) == AccountSasServices::Queue)
{
services += "q";
}
if ((Services & AccountSasServices::Files) == AccountSasServices::Files)
{
services += "f";
}

std::string resourceTypes;
if ((ResourceTypes & AccountSasResource::Service) == AccountSasResource::Service)
{
resourceTypes += "s";
}
if ((ResourceTypes & AccountSasResource::Container) == AccountSasResource::Container)
{
resourceTypes += "c";
}
if ((ResourceTypes & AccountSasResource::Object) == AccountSasResource::Object)
{
resourceTypes += "o";
}

std::string startsOnStr = StartsOn.HasValue()
? StartsOn.Value().ToString(
Azure::DateTime::DateFormat::Rfc3339, Azure::DateTime::TimeFractionFormat::Truncate)
: "";
std::string expiresOnStr = ExpiresOn.ToString(
Azure::DateTime::DateFormat::Rfc3339, Azure::DateTime::TimeFractionFormat::Truncate);

return credential.AccountName + "\n" + Permissions + "\n" + services + "\n" + resourceTypes
+ "\n" + startsOnStr + "\n" + expiresOnStr + "\n"
+ (IPRange.HasValue() ? IPRange.Value() : "") + "\n" + protocol + "\n" + SasVersion + "\n"
+ EncryptionScope + "\n";
}

}}} // namespace Azure::Storage::Sas
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,29 @@ namespace Azure { namespace Storage { namespace Sas {
const Files::DataLake::Models::UserDelegationKey& userDelegationKey,
const std::string& accountName);

/**
* @brief For debugging purposes only.
*
* @param credential
* The storage account's shared key credential.
* @return Returns the string to sign that will be used to generate the signature for the SAS
* URL.
*/
std::string GenerateSasStringToSign(const StorageSharedKeyCredential& credential);

/**
* @brief For debugging purposes only.
*
* @param userDelegationKey UserDelegationKey returned from
* BlobServiceClient.GetUserDelegationKey.
* @param accountName The name of the storage account.
* @return Returns the string to sign that will be used to generate the signature for the SAS
* URL.
*/
std::string GenerateSasStringToSign(
const Blobs::Models::UserDelegationKey& userDelegationKey,
const std::string& accountName);

private:
std::string Permissions;
};
Expand Down
Loading