4
votes

I am using Azure Blob Storage to store my application files. I already done it without difficulty for public containers, but I am finding a little trouble making them private.

To access the files in my private containters I am using the following method to retrieve the final url:

public static string GetBlobSasUri(string containerLocation, string blobName, string policyName = null)
        {
            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.
            CloudStorageAccount storageAccount = CloudStorageAccount.Parse(ConfigurationManager.
             ConnectionStrings["AzureConnection"].ConnectionString);
            CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
            CloudBlobContainer container = blobClient.GetContainerReference(containerLocation);
            CloudBlockBlob blob = container.GetBlockBlobReference(blobName);

            if (policyName == null)
            {
                // 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.
                    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();
            }
            else
            {
                // Generate the shared access signature on the blob. In this case, all of the constraints for the
                // shared access signature are specified on the container's stored access policy.
                sasBlobToken = blob.GetSharedAccessSignature(null, policyName);

                Console.WriteLine("SAS for blob (stored access policy): {0}", sasBlobToken);
                Console.WriteLine();
            }

            // Return the URI string for the container, including the SAS token.
            return blob.Uri + sasBlobToken;
        }

But then when I try to past the final URI in my browser I always get this error:

<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:96d18e26-0001-008e-7eb3-c88fcd000000 Time:2017-05-09T11:01:24.9128128Z
</Message>
<AuthenticationErrorDetail>
Signature did not match. String to sign used was rcw 2017-05-10T11:01:06Z /blob/mystorage/profiles/317ce29e-86c9-46d8-8579-8cbdaf385971pic.png 2016-05-31
</AuthenticationErrorDetail>
</Error>

Any ideia why?

Update: Connection string:

<add name="AzureConnection" connectionString="DefaultEndpointsProtocol=https;AccountName=mystorage;AccountKey=5Z+lqPDdRDdlVwSW6kA3iOIb8jUjJkib6A/gAQ8UeqcgPcsW1Do0NbypYsUguTOpb41cO0S0k2wdBEeqvtxaXQ==;EndpointSuffix=core.windows.net" />

Url generated by Azure Storage Explorer:

https://mystorage.blob.core.windows.net/profiles/169ae1c0-3307-401f-b45c-a50fa86cb1c6pic.png?sv=2016-05-31&ss=b&srt=sco&sp=r&se=2017-05-11T19:19:43Z&st=2017-05-11T09:19:43Z&spr=https&sig=ZpdhWPHZaUwECoorBwWidaresP50jannOx6ECUdd2Vw%3D

sv=2016-05-31
ss=b
srt=sco
sp=r
se=2017-05-11T19:19:43Z
st=2017-05-11T09:19:43Z
spr=https
sig=ZpdhWPHZaUwECoorBwWidaresP50jannOx6ECUdd2Vw%3D

Url generated by Code:

https://mystorage.blob.core.windows.net/profiles/169ae1c0-3307-401f-b45c-a50fa86cb1c6pic.png?sv=2016-05-31&sr=c&sig=JQgK68ycbhyq1xQ%2BQb6UDrUCcy3jwA8FEZVYununZVE%3D&st=2017-05-11T08%3A19%3A47Z&se=2017-05-11T12%3A19%3A47Z&sp=r

sv=2016-05-31
sr=c
sig=JQgK68ycbhyq1xQ%2BQb6UDrUCcy3jwA8FEZVYununZVE%3D
st=2017-05-11T08%3A19%3A47Z
se=2017-05-11T12%3A19%3A47Z
sp=r

New update 29/05/2017:

I decided to change my method and try this:

    public static string GetBlobSasUri(string containerLocation, string blobName, string policyName = null)
    {
        string sasBlobToken;
        CloudStorageAccount storageAccount = CloudStorageAccount.Parse(ConfigurationManager.
         ConnectionStrings["AzureStoreConnection"].ConnectionString);
        CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
        CloudBlobContainer container = blobClient.GetContainerReference(containerLocation);
        container.CreateIfNotExists();

        if (policyName == null)
        {
            SharedAccessBlobPolicy adHocSAS = new SharedAccessBlobPolicy()
            {
                SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes(120),
                SharedAccessStartTime = DateTime.UtcNow.AddMinutes(-120),
                Permissions = SharedAccessBlobPermissions.Read

            };

            CloudBlockBlob blob = container.GetBlockBlobReference(blobName);
            sasBlobToken = blob.GetSharedAccessSignature(adHocSAS);
            Console.WriteLine("SAS for blob (ad hoc): {0}", sasBlobToken);
            Console.WriteLine();

            return blob.Uri + HttpUtility.UrlEncode(sasBlobToken);
        }
        return null;
    }

The error change, now I am getting the following error: The specified resource does not exist.

2
Are you trying to get a list of blobs in a container?Gaurav Mantri
no. I am trying to get the url of a blob inside a container.ruizing
Have you tried setting the SAS start time to a few minutes in the past? Sometimes, clock-drift can be the issue. (though I doubt that's it, given the error; looks more like malformed signature / header, based on error)David Makogon
@DavidMakogon just tried it, did not workruizing
Your code looks all right to me. Can you please check if the account key is correct?Gaurav Mantri

2 Answers

1
votes

The error change, now I am getting the following error: The specified resource does not exist.

The final URL is as following if you encoded the token. It is not a well formatted blob URL with SAS.

https://accountname.blob.core.windows.net/containername/blobname.jpg%3fsv%3d2016-05-31%26sr%3db%26sig%3dNGZzNbKvnR%252FRCnz1Dsk%252FJrSn8lVdqszyCF8MuLsdo0I%253D%26st%3d2017-05-31T05%253A32%253A00Z%26se%3d2017-05-31T09%253A32%253A00Z%26sp%3dr

Here is a method which could generate SAS without using the GetSharedAccessSignature method. Please try it and check whether it can work on your side.

private static string GetSharedAccessSignature(
       string accountName,
       string accountkey,
       string blobContainer,
       string blobName,
       DateTimeOffset sharedAccessStartTime,
       DateTimeOffset sharedAccessExpiryTime)
{
    var canonicalNameFormat = $"/blob/{accountName}/{blobContainer}/{blobName}";
    var st = sharedAccessStartTime.UtcDateTime.ToString("yyyy-MM-ddTHH:mm:ssZ");
    var se = sharedAccessExpiryTime.UtcDateTime.ToString("yyyy-MM-ddTHH:mm:ssZ");
    var sasVersion = "2016-05-31";

    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,
        canonicalNameFormat,
        string.Empty,
        string.Empty,
        string.Empty,
        sasVersion,
        string.Empty,
        string.Empty,
        string.Empty,
        string.Empty,
        string.Empty
    });

    var sas = GetHash(stringToSign, accountkey);

    var credentials =
        $"?sv={sasVersion}&sr=b&sig={UrlEncoder.Default.Encode(sas)}&st={UrlEncoder.Default.Encode(st)}&se={UrlEncoder.Default.Encode(se)}&sp=r";

    string blobUri = $"https://{accountName}.blob.core.windows.net/{blobContainer}/{blobName}";
    return blobUri + credentials;
}

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)));
    }
}
0
votes

You need to return 'blob.Uri.AbsoluteUri + sasBlobToken'.

Also, it is recommended to set SharedAccessStartTime to (Now - 5m) to overcome clock differences between machines.