Quantcast
Channel: elastacloud = azurecoder + bareweb » hmac
Viewing all articles
Browse latest Browse all 2

Shared Key Signature Schemes for Storage Services

$
0
0

This is a tough one. Following my LWAUG earlier this month it was clear that I was greeted by a sea of blank faces when I talked about Shared Key signatures for accessing Storage Services. It is a pain this but unfortunately you need to use these Storage Services if you want to place your .cspkg file in Blob storage so get yourself a storage account and start playing!

Let’s start with a simple method which will send our REST request to Azure. We have headers x-ms-date and x-ms-version which control the REST API version you’re attempting to use but we also have an additional header. This is our Authorization header. This is what my post is all about. Later on I’ll provide the Microsoft links and you can choose to read that or me. Go on pick me!

private void SendWebRequest(string url, string requestMethod, string dateHeader, string versionHeader,
            string authHeader, byte[] fileBytes = null, int contentLength = 0)
        {
            Uri uri = new Uri(url);
            var request = (HttpWebRequest) WebRequest.Create(uri);
            request.Method = requestMethod;
            request.ContentLength = contentLength;
            request.Headers.Add("x-ms-date", dateHeader);
            request.Headers.Add("x-ms-version", versionHeader);
            request.Headers.Add("Authorization", authHeader);
            if (contentLength != 0)
            {
                request.Headers.Add("x-ms-blob-type", "BlockBlob");
                request.GetRequestStream().Write(fileBytes, 0, fileBytes.Length);
            }
            HttpWebResponse response;
            try
            {
                using (response = (HttpWebResponse)request.GetResponse())
                {
                    if (response.StatusCode == HttpStatusCode.Created)
                    {
                        Console.WriteLine("Container or blob has been created!");
                    }
                }
            }
            catch (WebException ex)
            {
                if (((HttpWebResponse)ex.Response).StatusCode == HttpStatusCode.Conflict)
                {
                    Console.WriteLine("container or blob already exists!");
                }
                if (((HttpWebResponse)ex.Response).StatusCode == HttpStatusCode.Forbidden)
                {
                    Console.WriteLine("problem with signature!");
                }
            }
        }

So, Storage Services also uses

"x-ms-blob-type", "BlockBlob"

when you want to write a Blob to a container. BlockBlob’s are what you want but if you haven’t got that far in your reading you have a choice of Block and Page Blobs. Big distinctions between the two but for us now, we’re using BlockBlobs. They have an upper limit in size, which means they need to be segmented past a point and written in chunks. Offhand you’ve got 64 Mb to play around with until you have to think about this.

So now we need to consume our method …

// 2nd step create a Blob container, by default only $root exists
public void CreateContainer(string name)
{
string accessContainer = String.Format("http://{0}.blob.core.windows.net/{1}?restype=container", _accountName, name);
const string versionHeader = "2011-08-18";
string dateHeader = DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture);
string canResource = String.Format("/{0}/{1}\nrestype:container", _accountName, name);
string toSign = String.Format("PUT\n\n\n0\n\n\n\n\n\n\n\n\nx-ms-date:{0}\nx-ms-version{1}\n{2}", dateHeader, versionHeader, canResource);
string authHeader = CreateAuthorizationHeader(toSign);
SendWebRequest(accessContainer, "PUT", dateHeader, versionHeader, authHeader);
}

This is where the chaos starts so let’s talk through it step-by-step. We need all of the headers in place, which includes a UTC formatted date and a version header for the REST API. These form the basis of our signature  or shared key scheme. In addition to this, however, we also have the canonical representation of our querystring, which in this case is restype=container. This is then appended to the signature scheme and string to sign.

No turn your attention to that big horrendous string that needs to be signed by a shared key (the primary or secondary key from your azure portal for that storage account – it’s your shared secret). We have about 9 \n characters. Each of these are values from HTTP headers which are documented on the Microsoft site as part of the scheme. The character in the middle of this string is the value for the content-length header which always has to be present.

Let’s take a look at the actual signing…

private string CreateAuthorizationHeader(String canonicalizedHeader)
{
      string signature;
      using (var hmacSha256 = new HMACSHA256(Convert.FromBase64String(_accountKey)))
      {
           Byte[] bytes = Encoding.UTF8.GetBytes(canonicalizedHeader);
           signature = Convert.ToBase64String(hmacSha256.ComputeHash(bytes));
      }
      return "SharedKey " + _accountName + ":" + signature;
}

This shouldn’t need a lot of explanation but from the top we take an HMAC which is a shared code that our server can calculate given the key and knowledge of the same signature scheme. Note the UTF-8 encoding for the web. The signature should be calculated as an Authorization header in the form of Authorization: SharedKey lwaug:signature where the signature will be displayed in the form of a Base64 string.

Lastly we should just take a look at the Upload method.

 // 3rd step upload a file cspack file to Blob Storage 
        public void Upload(string containerName, string blobName, string fileNamePath)
        {
            //string accessContainer = String.Format("http://{0}.blob.core.windows.net/{1}/{2}?comp=block&blockid=MyBlock&timeout=120", _accountName, containerName, blobName);
            string accessContainer = String.Format("http://{0}.blob.core.windows.net/{1}/{2}", _accountName, containerName, blobName);
            const string versionHeader = "2011-08-18";
            string dateHeader = DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture);
            int contentLength;
            var ms = GetPackageFileBytesAndLength(fileNamePath, out contentLength);
           
            string canResource = String.Format("/{0}/{1}/{2}", _accountName, containerName, blobName);
            string toSign = String.Format("PUT\n\n\n{0}\n\n\n\n\n\n\n\n\nx-ms-blob-type:BlockBlob\nx-ms-date:{1}\nx-ms-version:{2}\n{3}", contentLength, dateHeader, versionHeader, canResource);

            string authHeader = CreateAuthorizationHeader(toSign);
            SendWebRequest(accessContainer, "PUT", dateHeader, versionHeader, authHeader, ms, contentLength);
        }

The only real difference here is the BlockBlob header we’ve added to our HTTP request, the lack of canonical resources we’re signing and a real value for the content-length header. The method GetPackageFileBytesAndLength uses a FileStream writing back to a byte array without any attempt to try and encode the contents in anyway.



Viewing all articles
Browse latest Browse all 2

Latest Images

Trending Articles





Latest Images