1

I'm working on a project that uses Azure blob storage.

My code send the following in order to upload a jpeg image to an Azure storage blob:

PUT https://<storageAccount>.blob.core.windows.net/<container>/ca13bd3a-d805-46ce-994e-e70560a54cda.jpg HTTP/1.1
x-ms-date: Tue, 08 Aug 2017 10:05:24 GMT
Date: Tue, 08 Aug 2017 10:05:24 GMT
x-ms-version: 2009-09-19
Content-Type: image/jpeg
x-ms-blob-type: BlockBlob
Accept-Charset: UTF-8
Authorization: SharedKey <storageAccount>:<computed hash>
Host: <storageAccount>.blob.core.windows.net
Content-Length: 498732
Expect: 100-continue
Connection: Keep-Alive

<jpeg image>

The Autorization Header is created with the following code :

    byte[] SignatureBytes = System.Text.Encoding.UTF8.GetBytes(MessageSignature);
    System.Security.Cryptography.HMACSHA256 SHA256 = new System.Security.Cryptography.HMACSHA256(Convert.FromBase64String(StorageKey));
    String AuthorizationHeaderRes = "SharedKey " + StorageAccount + ":" + Convert.ToBase64String(SHA256.ComputeHash(SignatureBytes));

But as a result, I get a 403 error with the following message :

The MAC signature found in the HTTP request '<request id>' is not the same as any computed signature. Server used following string to sign: 'PUT


498732

image/jpeg






x-ms-blob-type:BlockBlob
x-ms-date:Tue, 08 Aug 2017 10:05:24 GMT
x-ms-version:2009-09-19
/<storageAccount>/<storageAccount>/ca13bd3a-d805-46ce-994e-e70560a54cda.jpg

I tried a lot of solution I've fount on the web, thus I know the following :

  • DateTime is OK
  • I can get the list of the blobs in the container

EDIT 1 :

MessageSignature :

  string MessageSignature;

        if (IsTableStorage)
        {
            MessageSignature = String.Format("{0}\n\n{1}\n{2}\n{3}",
                method,
                "application/atom+xml",
                now.ToString("R", System.Globalization.CultureInfo.InvariantCulture),
                GetCanonicalizedResource(request.RequestUri, StorageAccount)
                );
        }
        else
        {
            MessageSignature = String.Format("{0}\n\n\n{1}\n{5}\n\n\n\n{2}\n\n\n\n{3}{4}",
                method,
                (method == "GET" || method == "HEAD") ? String.Empty : request.ContentLength.ToString(),
                ifMatch,
                GetCanonicalizedHeaders(request),
                GetCanonicalizedResource(request.RequestUri, StorageAccount),
                md5
                );
        }

EDIT 2 :

GetCanonicalizedHeaders :

        public string GetCanonicalizedHeaders(HttpWebRequest request)
    {
        ArrayList headerNameList = new ArrayList();
        StringBuilder sb = new StringBuilder();
        foreach (string headerName in request.Headers.Keys)
        {
            if (headerName.ToLowerInvariant().StartsWith("x-ms-", StringComparison.Ordinal))
            {
                headerNameList.Add(headerName.ToLowerInvariant());
            }
        }
        headerNameList.Sort();
        foreach (string headerName in headerNameList)
        {
            StringBuilder builder = new StringBuilder(headerName);
            string separator = ":";
            foreach (string headerValue in GetHeaderValues(request.Headers, headerName))
            {
                string trimmedValue = headerValue.Replace("\r\n", String.Empty);
                builder.Append(separator);
                builder.Append(trimmedValue);
                separator = ",";
            }
            sb.Append(builder.ToString());
            sb.Append("\n");
        }
        return sb.ToString();
    }

GetCanonicalizedResource :

public string GetCanonicalizedResource(Uri address, string accountName)
    {
        StringBuilder str = new StringBuilder();
        StringBuilder builder = new StringBuilder("/");
        builder.Append(accountName);
        builder.Append(address.AbsolutePath);
        str.Append(builder.ToString());
        NameValueCollection values2 = new NameValueCollection();
        if (!IsTableStorage)
        {
            //NameValueCollection values = System.Web.HttpUtility.ParseQueryString(address.Query);
            NameValueCollection values = this.ParseQueryString(address.Query);
            foreach (string str2 in values.Keys)
            {
                ArrayList list = new ArrayList(values.GetValues(str2));
                list.Sort();
                StringBuilder builder2 = new StringBuilder();
                foreach (object obj2 in list)
                {
                    if (builder2.Length > 0)
                    {
                        builder2.Append(",");
                    }
                    builder2.Append(obj2.ToString());
                }
                values2.Add((str2 == null) ? str2 : str2.ToLowerInvariant(), builder2.ToString());
            }
        }
        ArrayList list2 = new ArrayList(values2.AllKeys);
        list2.Sort();
        foreach (string str3 in list2)
        {
            StringBuilder builder3 = new StringBuilder(string.Empty);
            builder3.Append(str3);
            builder3.Append(":");
            builder3.Append(values2[str3]);
            str.Append("\n");
            str.Append(builder3.ToString());
        }
        return str.ToString();
    }

EDIT 3 :

MessageSignature content (note : date changed, but it is irrelevant):

PUT


498732








x-ms-blob-type:BlockBlob
x-ms-date:Tue, 08 Aug 2017 15:09:52 GMT
x-ms-version:2009-09-19
/<storageAccount>/<container>/ca13bd3a-d805-46ce-994e-e70560a54cda.jpg
Dremor
  • 789
  • 2
  • 8
  • 27
  • Please share the code for computing `MessageSignature`. – Gaurav Mantri Aug 08 '17 at 14:06
  • @GauravMantri Done – Dremor Aug 08 '17 at 14:30
  • I believe the problem is with this line of code: `builder.Append(address.AbsolutePath);` in `GetCanonicalizedResource` method. Essentially there's an extra `/` in your builder string (see this fiddle: https://dotnetfiddle.net/k2rWQ4). This will cause your `builder.ToString()` to produce a string like `/accountname//container/ca13bd3a-d805-46ce-994e-e70560a54cda.jpg` instead of `/accountname/container/ca13bd3a-d805-46ce-994e-e70560a54cda.jpg`. – Gaurav Mantri Aug 08 '17 at 14:52
  • I checked, this don't seem to be the case. There is only one slash. – Dremor Aug 08 '17 at 15:01
  • Aah....Can you edit your question and include 3 things: 1) Actual value of `MessageSignature`, 2) Updated error message for that request (so that we can compare your MessageSignature with what Azure is computing and 3) Request headers for that request. My guess is that you're missing out on one minor thing which is causing this error. – Gaurav Mantri Aug 08 '17 at 15:07
  • One more thing I noticed is that you're using both `Date` and `x-ms-date` request headers however in your `MessageSignature` computation, you're not including `Date` header. Please remove the `Date` header from your request and try again. – Gaurav Mantri Aug 08 '17 at 15:17
  • Initially, only `x-ms-date` was set, I added `Data` for debugging purpose. It not being in MessageSignature is a normal behaviour, as MS documentation say that `Date` should be removed if `x-ms-date` is defined. https://learn.microsoft.com/en-us/rest/api/storageservices/Authentication-for-the-Azure-Storage-Services – Dremor Aug 08 '17 at 15:21
  • 1
    But are you passing `Date` in request headers or not? Also, your `MessageSignature` content has `storageAccount` repeated (`///ca13bd3a-d805-46ce-994e-e70560a54cda.jpg`). I am guessing its a typo. Right? One more thing....Why is `content type` value missing from `MessageSignature` (in Edit 3)? – Gaurav Mantri Aug 08 '17 at 15:24
  • Yes, I am passing `Date` in the request header. I removed it, no change. The duplicated `` is a copy-paste error. Yes, indeed, I forgot to add the content type. Now it works, thank you. I added the "x-ms-blob-content-type" to my request in order to achieve it. – Dremor Aug 08 '17 at 15:44
  • Awesome! Will it be possible for you to edit your question (one last time :)) and include your solution (or provide that as an answer). I believe it will be helpful to others. – Gaurav Mantri Aug 08 '17 at 15:49
  • I will, unless you want to answer it yourself in order to get some sweet reputation ;) – Dremor Aug 08 '17 at 15:56
  • I'm cool :). You did all the hard work....I just looked at your code and asked some annoying questions :D. Please go ahead and add your answer. Thanks! – Gaurav Mantri Aug 08 '17 at 15:58

0 Answers0