1

I am trying to call Azure Storage queue using REST API, but I am getting an error

The MAC signature found in the HTTP request 'UCiypkoySXueF4scXt+EqQESf5VXmAVLJUA93+3W10M=' is not the same as any computed signature. The server used following string to sign: 'POST text/plain

My C# Code is

  var Client = new HttpClient();
        var RequestDateString = DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture);

        if (Client.DefaultRequestHeaders.Contains("x-ms-date"))
            Client.DefaultRequestHeaders.Remove("x-ms-date");
        Client.DefaultRequestHeaders.Add("x-ms-date", RequestDateString);


        var StorageAccountName = "storaxxxxxxxsnd";
        var StorageKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx==";
        String urlPath = String.Format("{0}/messages", "splitator");
        Uri uri = new Uri(string.Format("https://{0}.queue.core.windows.net/", StorageAccountName) + urlPath);

        if (Client.DefaultRequestHeaders.Contains("Authorization"))
            Client.DefaultRequestHeaders.Remove("Authorization");

        var canonicalizedStringToBuild = string.Format("{0}\n{1}", RequestDateString, $"/{StorageAccountName}/{uri.AbsolutePath.TrimStart('/')}");
        string signature;
        using (var hmac = new HMACSHA256(Convert.FromBase64String(StorageKey)))
        {
            byte[] dataToHmac = Encoding.UTF8.GetBytes(canonicalizedStringToBuild);
            signature = Convert.ToBase64String(hmac.ComputeHash(dataToHmac));
        }

        string authorizationHeader = string.Format($"{StorageAccountName}:" + signature);
        Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("SharedKey", authorizationHeader);
        Client.DefaultRequestHeaders.Accept.Clear();
        
        Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/plain"));
        if (Client.DefaultRequestHeaders.Contains("x-ms-version"))
            Client.DefaultRequestHeaders.Remove("x-ms-version");

        Client.DefaultRequestHeaders.Add("x-ms-version", "2015-12-11");

        // if (httpMethod == HttpMethod.Delete || httpMethod == HttpMethod.Put)
        //  {
        //    if (Client.DefaultRequestHeaders.Contains("If-Match"))
        //       Client.DefaultRequestHeaders.Remove("If-Match");
        // Currently I'm not using optimistic concurrency :-(
        try
        {
            //Client.DefaultRequestHeaders.Add("If-Match", "*");
            var stringContent = new StringContent("TESTAUTH", Encoding.UTF8, "text/plain");
           var response= Client.PostAsync(uri, stringContent);
            var resu=response.Result;
            
        }
        catch(Exception ex)
        {

        }

I am not sure what I am missing. I tried various combination but its failing.

I tried Microsoft recommended stringToSign formula too

I tried  using canonical headers too


        string signature;
     
        var stringTosign =  "POST\n" + "\n" + "\n" + "1024" + "\n" + "\n" + "text/plain\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + dateInRfc1123Format + "/xxxxxx/splitator/messages";

       var hmac = new HMACSHA256(Convert.FromBase64String(accountKey));
       var headerval= accountName + ":" + Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringTosign)));
        Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("SharedKey", headerval);
        Client.DefaultRequestHeaders.Accept.Clear();
    
Dinesh Tripathi
  • 196
  • 3
  • 17
  • The issue is how you're building your `canonicalizedStringToBuild`. Please see instructions here: https://learn.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key#blob-queue-and-file-services-shared-key-authorization. Also, why are you not using the SDK? – Gaurav Mantri Apr 14 '21 at 15:58
  • I require to use the REST API without adding any additional NuGet package or azure based librairies. – Dinesh Tripathi Apr 14 '21 at 16:19
  • Few things: 1) Can you update your question with the complete most recent code used by you. That would make things easier and 2) Check the response body of the error response. That should tell you the string to sign used by Storage Service. You can compare that with your string to sign value to identify where you're making mistake. – Gaurav Mantri Apr 15 '21 at 04:57

1 Answers1

1

I fixed the issue in your code, and now it's working. Please give it a try:

namespace ConsoleApp25
{
    class Program
    {
        static void Main(string[] args)
        {
            var Client = new HttpClient();
           
            var StorageAccountName = "yy1";
            var StorageKey = "xxxx";
            var apiversion = "2020-02-10";
            var queue_name = "myqueue2";
            String urlPath = String.Format("{0}/messages", queue_name);
            Uri uri = new Uri(string.Format("https://{0}.queue.core.windows.net/{1}", StorageAccountName, urlPath));

            //define a message to send
            string raw_message = "TESTAUTH is ok";

            //to send the message to the queue storage, the raw message must be formatted as below
            string queue_message = $"<QueueMessage><MessageText>{raw_message}</MessageText></QueueMessage>";

            //define the content type
            string content_type = "text/plain; charset=utf-8";

            //define date
            var RequestDateString = DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture);

            string StringToSign = String.Format("POST\n"
                + "\n" // content encoding
                + "\n" // content language
                + queue_message.Length + "\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-date:" + RequestDateString + "\nx-ms-version:" + apiversion + "\n" // headers
                + "/{0}/{1}/{2}", StorageAccountName, queue_name, "messages"); //url

            string auth = SignThis(StringToSign, StorageKey, StorageAccountName);

            //define authorization header
            if (Client.DefaultRequestHeaders.Contains("Authorization"))
                Client.DefaultRequestHeaders.Remove("Authorization");

            Client.DefaultRequestHeaders.Add("Authorization", auth);

            Client.DefaultRequestHeaders.Accept.Clear();

            //define x-ms-version header
            Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/plain"));
            if (Client.DefaultRequestHeaders.Contains("x-ms-version"))
                Client.DefaultRequestHeaders.Remove("x-ms-version");

            Client.DefaultRequestHeaders.Add("x-ms-version", apiversion);

            //define the x-ms-date header
            if (Client.DefaultRequestHeaders.Contains("x-ms-date"))
                Client.DefaultRequestHeaders.Remove("x-ms-date");
            Client.DefaultRequestHeaders.Add("x-ms-date", RequestDateString);

            try
            {
                var stringContent = new StringContent(queue_message, Encoding.UTF8, "text/plain");
                var response = Client.PostAsync(uri, stringContent);
                var resu = response.Result;
               
            }
            catch (Exception ex)
            {

            }

            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;
        }

    }
}

And if you don't want to generate the shared key since it's not easy, you can use sas token for authentication in the rest api.

Ivan Glasenberg
  • 29,865
  • 2
  • 44
  • 60