locked
Windows Azure REST API forbidden RRS feed

  • Question

  • I am getting a 403 forbidden when calling the blob storage services API from my Windows Store App.

    Yes, there are lots of examples out there, but I can only find ones that use the non-store app apis. I have converted the HMAC SHA256 stuff, but I assume it is something in that code that isn't quite right. I was originally using the MacAlgorithmProvider's ConvertStringToBinary on the primary access key, but instead have moved to DecodeFromBase64String. This may be the problem - perhaps I need to generate the binary in a different format (?)

    I have tried both SharedKey and SharedKeyLite - the lite version is below.

     

    Code for generating the Signature:

    StringBuilder sb = new StringBuilder();
    sb.Append(method + "\n"); 
    sb.Append('\n'); 
    sb.Append('\n'); 
    sb.Append('\n');
    sb.Append("x-ms-date:" + date + '\n'); 
    sb.Append("x-ms-version:" + version + "\n");
    sb.Append("/" + account + "/\ncomp:list");
    
    MacAlgorithmProvider macAlgorithmProvider = MacAlgorithmProvider.OpenAlgorithm("HMAC_SHA256");
    
    var binaryMessage = CryptographicBuffer.ConvertStringToBinary(sb.ToString(), BinaryStringEncoding.Utf8);
    //var binaryKeyMaterial =  CryptographicBuffer.ConvertStringToBinary(key, BinaryStringEncoding.Utf8);
    
    var binaryKeyMaterial = CryptographicBuffer.DecodeFromBase64String(key); 
    var hmacKey = macAlgorithmProvider.CreateKey(binaryKeyMaterial);
    var binarySignedMessage = CryptographicEngine.Sign(hmacKey, binaryMessage);
    var signature = CryptographicBuffer.EncodeToBase64String(binarySignedMessage);

    Wonderful, so when building the request I get the signature with the storage uri and the headers:

    HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, String.Format("{0}?{1}", BlobEndPoint, "comp=list"));
    
    var date = DateTime.UtcNow.ToString("R");
    var signature = GetSignature("GET", date, "2009-09-19", Account, AzurePrimaryKey, request); //this is the code from above
    request.Headers.Add("x-ms-version", "2009-09-19");
    request.Headers.Add("x-ms-date", date);
    request.Headers.Add("Authorization", "SharedKeyLite " + Account + ":" + signature);

    And then submitting the request:

    var result = await client.SendAsync(request); 

    And here is where the 403 happens. Any help greatly appreciated. Feel free to flick me a link to an example of this actually working as well. I haven't had any luck finding any yet.


    EDIT: Ok, so I have also tried this using good ol' .NET and I get the same result. i.e.

    HMACSHA256 signer = new HMACSHA256(Convert.FromBase64String(key));
    Byte[] dataToHmac = Encoding.UTF8.GetBytes(sb.ToString());
    var signature = Convert.ToBase64String(signer.ComputeHash(dataToHmac));


    ANOTHER EDIT: I can use the Azure StorageClient in the normal .NET app (obviously not in the Windows Store App) so the settings are right at least!

    StorageCredentialsAccountAndKey creds = new StorageCredentialsAccountAndKey(account, Convert.FromBase64String(key));
    CloudStorageAccount account = new CloudStorageAccount(creds, false);
    
    CloudBlobClient client = account.CreateCloudBlobClient();
    var conts = client.ListContainers().ToList();

    Monday, October 22, 2012 12:53 AM

Answers

  • I have managed to get it sorted. I figured I would leave the post here, just so there is an example around.

    Basically I had just failed to format the string to be passed to the hasher correctly (I believe).

    So the full example is below (which, by the way, will be refactored to read the request to ensure everything in the string for encoding is the same as the request)

    private string ReadHeader(HttpRequestMessage request, string headerName)
    {
     var header = request.Headers.SingleOrDefault(kvp => kvp.Key == headerName);
     if (header.Key == null)
      return String.Empty;
     var value = header.Value.FirstOrDefault();
     if (value == null)
      return String.Empty;
     return value;
    }
    
    private string GetSignature(string method, string date, string version, string account, string key, HttpRequestMessage request)
    {
     string format = "";
     for (int i = 0; i < 15; i++)
     {
      format += "{" + i + "}";
     }
      var stringToEncrypt = String.Format(format,  method + "\n",
      ReadHeader(request, "Content-Encoding") + "\n",
      ReadHeader(request, "Content-Language") + "\n",
      ReadHeader(request, "Content-Length") + "\n",
      ReadHeader(request, "Content-MD5") + "\n",
      ReadHeader(request, "Content-Type") + "\n",
      "\n",
      ReadHeader(request, "If-Modified-Since") + "\n",
      ReadHeader(request, "If-Match") + "\n",
      ReadHeader(request, "If-None-Match") + "\n",
      ReadHeader(request, "If-Unmodified-Since") + "\n",
      ReadHeader(request, "Range") + "\n",
      "x-ms-date:" + date + "\n",  "x-ms-version:" + version + "\n",
      "/" + account + "/" + "\n" + "comp:list\ntimeout:90"); //refactor this to use the request parameters!
    
     MacAlgorithmProvider macAlgorithmProvider = MacAlgorithmProvider.OpenAlgorithm("HMAC_SHA256");
     var binaryMessage = CryptographicBuffer.ConvertStringToBinary(stringToEncrypt, BinaryStringEncoding.Utf8);
     var binaryKeyMaterial = CryptographicBuffer.DecodeFromBase64String(key);
     var hmacKey = macAlgorithmProvider.CreateKey(binaryKeyMaterial);
     var binarySignedMessage = CryptographicEngine.Sign(hmacKey, binaryMessage);
     var signedMessage = CryptographicBuffer.EncodeToBase64String(binarySignedMessage);
    
     return signedMessage;
    }
    
    public async Task<IEnumerable<string>> GetContainers()
    {
     var client = new HttpClient();
     var date = DateTime.UtcNow.ToString("R");
     var version = "2011-08-18";
    
     HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, String.Format("{0}?{1}&timeout=90", BlobEndPoint, GetContainersCommand));
    
     var signature = GetSignature("GET", date, version, Account, base64key, request);
    
     request.Headers.Add("x-ms-version", version);
     request.Headers.Add("x-ms-date", date);
     request.Headers.Add("Authorization", "SharedKey whitecloudworlds:" + signature);
    
     var result = await client.SendAsync(request);
    
      //... I leave it up to the reader to deserialise the response as required
     return new List<string>();
    }

    • Marked as answer by Jono Stewart Monday, October 22, 2012 2:28 AM
    Monday, October 22, 2012 2:27 AM