0
votes

I am trying to create a file in Azure File Service: https://docs.microsoft.com/en-us/rest/api/storageservices/create-file

I've seen and read multiple threads about this issue, but I can't figure out what might be the problem in my case... Most likely I am missing some small thing in the request which I cannot find.

The error I am getting: The MAC signature found in the HTTP request (...) is not the same as any computed signature.

The signature string I've used:

PUT
\n
\n
\n
\n
text/plain // Content-Type
\n
\n
\n
\n
\n
\n
x-ms-content-length:1000
x-ms-date:Tue, 20 Apr 2021 19:23:30 GMT
x-ms-type:file
x-ms-version:2015-02-21
/account/share/test

Authorization header:

SharedKey account:XXXXXXXXXX

I've used the HMACSHA256 for hashing the authorization header and signature string, it all works when I use other endpoint (List Shares: https://docs.microsoft.com/en-us/rest/api/storageservices/list-shares), but I can't make it work for the file. I've seen this topic AZURE File Service - Upload PDF through REST API and I believe I am using very similar request, but with no success...

I appreciate any help : )

Edit: I am not sure if I correctly set the content headers. For example, does the x-ms-content-length should be placed in the CanonicalizedHeaders string?

Edit2: With regard to what Ivan Yang wrote, I made the code to work, but only when my CanonicalizedHeaders are built like that:

        CanonicalizedHeaders := 'x-ms-content-length:1200'
                + LF + 'x-ms-date:' + UTCDateTimeText
                + LF + 'x-ms-file-attributes:Archive' + LF + 'x-ms-file-creation-time:Now' + LF + 'x-ms-file-last-write-time:Now' + LF + 'x-ms-file-permission:Inherit'
                + LF + 'x-ms-type:file' + LF + 'x-ms-version:2019-02-02';

If I have them in different order, then it crashes:

     CanonicalizedHeaders := 'x-ms-date:' + UTCDateTimeText + LF +
                                'x-ms-content-length:1200' + LF +
                                'x-ms-version:2019-02-02' + LF +
                                'x-ms-file-attributes:Archive' + LF +
                                'x-ms-file-creation-time:Now' + LF +
                                'x-ms-file-last-write-time:Now' + LF +
                                'x-ms-file-permission:Inherit' + LF +
                                'x-ms-type:file';

How come does this make a difference?

1
2 things: Can you share the code where you're making the request? and 2) Please check the error response stream. It will have the signature string used by Azure Service. Compare that with your signature string and that should give you an idea about why your request is failing. Please edit your question and include these in the question itself. - Gaurav Mantri
In "Edit2" you can find which CanonicalHeaders work for me and which not. Not sure why it makes the difference ; ) - MatDDev
The reason it would make the difference is because your headers in your CanonicalizedHeaders must be lexicographically sorted. Please see this link: docs.microsoft.com/en-us/rest/api/storageservices/… (point #2). - Gaurav Mantri
I was just about to write that : ) Thanks for pointing that out in the documentation. - MatDDev

1 Answers

0
votes

Your signature string is incorrect(you're missing some "\n"). I'm using the x-ms-version:2019-02-02 instead of the older one x-ms-version:2015-02-21, and the correct signature string should look like this one:

           "PUT\n"
            + "\n" // content encoding
            + "\n" // content language
            + "\n" // content length
            + "\n" // content md5
            + content_type  + "\n" // content type
            + "\n" // date
            + "\n" // if modified since
            + "\n" // if match
            + "\n" // if none match
            + "\n" // if unmodified since
            + "\n" // range
            + "x-ms-content-length:" + content_length
            + "\nx-ms-date:" + dt.ToString("R")
            + "\nx-ms-file-attributes:Archive" + "\nx-ms-file-creation-time:Now" + "\nx-ms-file-last-write-time:Now" + "\nx-ms-file-permission:Inherit"
            + "\nx-ms-type:file" + "\nx-ms-version:" + apiversion + "\n" // headers
            + "/{0}/{1}/{2}", Account, FileShare, FileName);

Here is the c# code using create file api:

using System;
using System.Globalization;
using System.Net;
using System.Security.Cryptography;

namespace ConsoleApp25
{
    class Program
    {
        static void Main(string[] args)
        {
            string Account = "storage_account_name";
            string Key = "storage_account_key";
            string FileShare = "file_share_name";
            string FileName = "test555.txt";
            string apiversion = "2019-02-02";
            int content_length = 1200;
            string content_type = "text/plain";

            DateTime dt = DateTime.UtcNow;
            string StringToSign = String.Format("PUT\n"
                + "\n" // content encoding
                + "\n" // content language
                + "\n" // content length
                + "\n" // content md5
                + content_type  + "\n" // content type
                + "\n" // date
                + "\n" // if modified since
                + "\n" // if match
                + "\n" // if none match
                + "\n" // if unmodified since
                + "\n" // range
                + "x-ms-content-length:" + content_length
                + "\nx-ms-date:" + dt.ToString("R")
                + "\nx-ms-file-attributes:Archive" + "\nx-ms-file-creation-time:Now" + "\nx-ms-file-last-write-time:Now" + "\nx-ms-file-permission:Inherit"
                + "\nx-ms-type:file" + "\nx-ms-version:" + apiversion + "\n" // headers
                + "/{0}/{1}/{2}", Account, FileShare, FileName);

            string auth = SignThis(StringToSign, Key, Account);
            string method = "PUT";
            string urlPath = string.Format("https://{0}.file.core.windows.net/{1}/{2}", Account, FileShare, FileName);
            Uri uri = new Uri(urlPath);
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
            request.Method = method;
            request.ContentLength = 0;

            request.Headers.Add("x-ms-content-length", $"{content_length}");
            request.Headers.Add("Content-Type", content_type);
            request.Headers.Add("x-ms-type", "file");
            request.Headers.Add("x-ms-date", dt.ToString("R"));
            request.Headers.Add("x-ms-version", apiversion);
            request.Headers.Add("x-ms-file-attributes", "Archive"); //note it is case-sensitive.
            request.Headers.Add("x-ms-file-permission", "Inherit");
            request.Headers.Add("x-ms-file-creation-time", "Now");
            request.Headers.Add("x-ms-file-last-write-time", "Now");
            request.Headers.Add("Authorization", auth);
           

            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                //read the response code
                Console.WriteLine("the response is:" + response.StatusCode);
            }

            Console.WriteLine("**completed**");
            Console.ReadLine();
        }

        private static String SignThis(String StringToSign, string Key, string Account)
        {
            String signature = string.Empty;
            byte[] unicodeKey = Convert.FromBase64String(Key);
            using (HMACSHA256 hmacSha256 = new HMACSHA256(unicodeKey))
            {
                Byte[] dataToHmac = System.Text.Encoding.UTF8.GetBytes(StringToSign);
                signature = Convert.ToBase64String(hmacSha256.ComputeHash(dataToHmac));
            }

            String authorizationHeader = String.Format(
                  CultureInfo.InvariantCulture,
                  "{0} {1}:{2}",
                  "SharedKey",
                  Account,
                  signature);

            return authorizationHeader;
        }

    }
}