I need help getting my headers correct for an azure table storage request from an iPhone app.
I’ve been using these two posts to try and get the encryption of the key right but I’m still having trouble:
iPhone and HMAC-SHA-1 encoding
Objective-C sample code for HMAC-SHA1
The service is returning an error for my request:
Server failed to authenticate the request with this error:
Make sure the value of Authorization header is formed correctly including the signature.
I am using the following code to make the request:
NSDate *now = [[NSDate alloc] init];
NSString *dateString = [self rfc1123String:now];
NSString *messageToSign = [NSString stringWithFormat:@"%@\n/%@/%@", dateString, AZURE_ACCOUNT_NAME, table];
[Base64 initialize];
//xxx in my code is my primary access shared key
NSString *key = @"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const char *cKey = [key cStringUsingEncoding:NSUTF8StringEncoding];
const char *cData = [messageToSign cStringUsingEncoding:NSUTF8StringEncoding];
unsigned char cHMAC[CC_SHA256_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA256, cKey, strlen(cKey), cData, strlen(cData), cHMAC);
NSData *HMAC = [[NSData alloc] initWithBytes:cHMAC length:sizeof(cHMAC)];
NSString *hash = [Base64 encode:HMAC];
NSLog(@"Encoded hash: %@", hash);
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request addValue:[NSString stringWithFormat:@"SharedKeyLite %@:%@", AZURE_ACCOUNT_NAME, hash] forHTTPHeaderField:@"Authorization"];
[request addValue:dateString forHTTPHeaderField:@"x-ms-date"];
[request addValue:@"application/atom+xml, application/xml" forHTTPHeaderField:@"Accept"];
[request addValue:@"UTF-8" forHTTPHeaderField:@"Accept-Charset"];
NSLog(@"Headers: %@", [request allHTTPHeaderFields]);
NSLog(@"URL: %@", [[request URL] absoluteString]);
return request;
Which results in these headers being generated for the request:
Accept = "application/atom+xml, application/xml";
"Accept-Charset" = "UTF-8";
Authorization = "SharedKeyLite powderdayalarm:xwT1purDtREtxauVr6Bhvdz/2ObLh2J0lMw/prBBQBE=";
"X-Ms-Date" = "Fri, 05 Nov 2010 18:26:00 GMT";
The specification for the request is here: http://msdn.microsoft.com/en-us/library/dd179428.aspx
Which refers to the following:
This format supports Shared Key and
Shared Key Lite for all versions of
the Table service, and Shared Key Lite
for the 2009-09-19 version of the Blob
and Queue services. This format is
identical to that used with previous
versions of the storage services.
Construct the CanonicalizedResource
string in this format as follows:
Beginning with an empty string (“”), append a forward slash (/),
followed by the name of the account
that owns the resource being accessed.Append the resource’s encoded URI path. If the request URI addresses
a component of the resource, append
the appropriate query string. The
query string should include the
question mark and the comp parameter
(for example, ?comp=metadata). No
other parameters should be included on
the query string.Encoding the Signature
To encode the signature, call the
HMAC-SHA256 algorithm on the
UTF-8-encoded signature string and
encode the result as Base64. Use the
following format (shown as
pseudocode): CopySignature=Base64(HMAC-SHA256(UTF8(StringToSign)))
I can’t seem to get to the root of this one. And it doesn’t seem like too many people are making azure requests form the iphone :).
Thanks
Scott
Smarx explained the best practice to me on the Azure forum. Here is what he wrote:
There are two secure ways to access storage from the client:
1.Use Shared Access Signatures with blobs. Server-side, you can generate the signature (basically a signed URL), and that signature is limited in scope (maybe to a particular blob, particular permissions, and a limited amount of time).
2.Only access a web service from the client, and have the web service access storage.
In both cases, there’s a server in between, much like in a web app. (The browser never talks to your SQL database… it talks to your website, which then talks to the database.)