Reputation: 89
I am struggling with this issue for 3 days now.. I would really appreciate the help.
I am trying to create SAS tokens for accessing Azure storage blobs dynamically, the token generates but I keep getting this error:
Signature did not match. String to sign used was ...
Creating the SAS token from azure portal works fine, but I have tried generating it in the code using many different ways, Micrtosoft.Azure.Storage library:
private static string GetBlobSasUri(CloudBlobContainer container, string blobName)
{
string sasBlobToken;
// Get a reference to a blob within the container.
// Note that the blob may not exist yet, but a SAS can still be created for it.
CloudBlockBlob blob = container.GetBlockBlobReference(blobName);
// Create a new access policy and define its constraints.
// Note that the SharedAccessBlobPolicy class is used both to define the parameters of an ad hoc SAS, and
// to construct a shared access policy that is saved to the container's shared access policies.
SharedAccessBlobPolicy adHocSAS = new SharedAccessBlobPolicy()
{
// When the start time for the SAS is omitted, the start time is assumed to be the time when the storage service receives the request.
// Omitting the start time for a SAS that is effective immediately helps to avoid clock skew.
SharedAccessStartTime = DateTime.UtcNow.AddHours(-24),
SharedAccessExpiryTime = DateTime.UtcNow.AddHours(24),
Permissions = SharedAccessBlobPermissions.Read //| SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Create
};
// Generate the shared access signature on the blob, setting the constraints directly on the signature.
sasBlobToken = blob.GetSharedAccessSignature(adHocSAS);
Console.WriteLine("SAS for blob (ad hoc): {0}", sasBlobToken);
Console.WriteLine();
// Return the URI string for the container, including the SAS token.
return blob.Uri + sasBlobToken;
}
This has worked for a for few tries, the next day is stopped functioning.. I tested it on my local IIS and on azure as well, locally it never worked.
I have also tried creating the token myself, both generating and signing the token, but the same issue persists.. here is the code for the signing:
public static string generateSAS(Uri resourceUri, string accountKey, string accountName, StorageFile f)
{
string
sr = "b",
sp = "r",
st = DateTime.Now.AddHours(-50).ToString("yyyy-MM-ddTH:MM:ssZ"),
se = DateTime.Now.AddHours(50).ToString("yyyy-MM-ddTH:MM:ssZ"),
sv = "2016-05-31"; //"2019-07-07"
string blobUri = $"https://{accountName}.blob.core.windows.net/{f.Model.Container}/{f.Model.FileName}";
string stringToSign = string.Format("{0}\n{1}\n{2}\n{3}\n{4}\n{5}\n{6}\n{7}\n{8}\n{9}\n{10}\n{11}\n{12}", new object[]
{
"r",
st,
se,
GetCanonicalizedResource(new Uri(blobUri), accountName),
string.Empty,
string.Empty,
string.Empty,
sv,
sr,
string.Empty,
string.Empty,
string.Empty,
string.Empty
});
var sas = GetHash(stringToSign, accountKey);
var token =
$"?sv={sv}&sr=b&sig={HttpUtility.UrlEncode(sas)}&st={HttpUtility.UrlEncode(st)}&se={HttpUtility.UrlEncode(se)}&sp=r";
return blobUri + token;
}
private static string GetHash(string stringToSign, string key)
{
byte[] keyValue = Convert.FromBase64String(key);
using (HMACSHA256 hmac = new HMACSHA256(keyValue))
{
return Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
}
}
private static string GetCanonicalizedResource(Uri address, string storageAccountName)
{
// The absolute path is "/" because for we're getting a list of containers.
StringBuilder sb = new StringBuilder("/").Append("blob").Append("/").Append(storageAccountName).Append(address.AbsolutePath);
return sb.ToString();
}
I have read the documentation and thought the issue might be a version issue, so I tried using the latest build and some versions prior to it, but to no avail.
I have tried not using and adding other parameters, nothing worked at all..
I also have been lead to believe that the issue might be due to clock, but I have adjusted my clock to every hour of the day so far, also it has been tested on an app service on Azure which is located in "UK South", in there the C# code worked for less than a single day.
I would appreciate you help very much! thank you
EDIT Using the RESTful version of the code where I self sign it manually, this is the "toSignString":
r
2020-02-17T14:02:56Z
2020-02-21T18:02:56Z
/blob/<accountName>/<contaioner1>/<subContainer2>/<subContainer3>/file.v
2016-05-31
b
and this is the full Uri:
https://<account>.blob.core.windows.net/<contaioner1>/<subContainer2>/<subContainer3>/file.v?
sv=2016-05-31&sr=b&sig=hI7MH0pT05cDpga2bMkanR%2b77CeocgttygDJbkRw6EM%3d&st=2020-02-17T14%3a02%3a56Z&se=2020-02-21T18%3a02%3a56Z&sp=r
Upvotes: 1
Views: 1342
Reputation: 136136
This has worked for a single day, then it stopped working.. I tested it on my local IIS and on azure as well, locally it never worked.
This is expected behavior because you're setting the SAS Token expiry to be 24 hours from the current date/time (in UTC):
SharedAccessExpiryTime = DateTime.UtcNow.AddHours(24),
If you want your SAS URL to work for a longer duration, you will need to specify an expiry date much out in future.
Regarding the 2nd issue (manual creation of SAS Token), I believe there's an issue with your GetCanonicalizedResource
method. Per the documentation
, it should be in the following format:
/blob/<account-name>/<container-name>/<blob-name>
The canonicalizedresource portion of the string is a canonical path to the signed resource. It must include the service name (blob, table, queue or file) for version 2015-02-21 or later, the storage account name, and the resource name, and must be URL-decoded.
Based on this, please try the following code:
private static string GetCanonicalizedResource(Uri address, string storageAccountName)
{
// The absolute path is "/" because for we're getting a list of containers.
StringBuilder sb = new StringBuilder("/").Append("blob").Append("/").Append(storageAccountName).Append("/").Append(address.AbsolutePath);
return sb.ToString();
}
Took me a while to figure out what's wrong :) but finally was able to do so. There are a number of issues I found:
yyyy-MM-ddTHH:MM:ssZ
but you're formatting it in yyyy-MM-ddTH:MM:ssZ
format. If you notice, it's missing an extra H
.2016-05-31
signed version and that does not include the signed resource (b
in your case) in stringToSign
but you're including that. It should be included in your SAS URL though.content-language (rscl)
parameter.According to the documentation, this is how you should construct your stringToSign
:
StringToSign = signedpermissions + "\n" +
signedstart + "\n" +
signedexpiry + "\n" +
canonicalizedresource + "\n" +
signedidentifier + "\n" +
signedIP + "\n" +
signedProtocol + "\n" +
signedversion + "\n" +
rscc + "\n" +
rscd + "\n" +
rsce + "\n" +
rscl + "\n" +
rsct
Based on this, here should be your code:
string stringToSign = string.Format("{0}\n{1}\n{2}\n{3}\n{4}\n{5}\n{6}\n{7}\n{8}\n{9}\n{10}\n{11}\n{12}", new object[]
{
"r",//permission
st,//start
se,//end
GetCanonicalizedResource(new Uri(blobUri), accountName),//canonical resource
string.Empty,//signed identifier
string.Empty,//signed ip
string.Empty,//signed protocol
sv,//signed version
string.Empty,//cache control
string.Empty,//content disposition
string.Empty,//content encoding
string.Empty,//content language
string.Empty//content type
});
Give it a try and it should work.
Upvotes: 2