Search code examples
c#azure-functionsazure-storagestorage-access-api

Azure Storage Queue via REST API c# using Shared Key Authentication


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();
    

Solution

  • 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.