0
votes

I pieced together code to authorize to Microsoft Azure for a Storage Account Table operation.

function TAzureStorageAPI.GetAuthHeader(RequestMethod,Ressource,Time:UTF8String): String;

Var
  KeyBytes:TBytes;
  DataBytes:TBytes;
  TimeString,
  StringtoSign:UTF8String;

begin
  StringtoSign:=Uppercase(RequestMethod)+LF+                                  //RequestMethod
                ''+LF+                                                        //contentMD5
                'application/json; charset=ISO-8859-1'+LF+                                //contentType
                TimeString+LF+                                                //requestDate
                Ressource;                                                    //Ressource
  keyBytes:=TNetEncoding.Base64.DecodeStringToBytes(FAccessKey);
  dataBytes:=TEncoding.UTF8.GetBytes(StringToSign);
  result:= (TNetEncoding.Base64.EncodeBytesToString(THashSHA2.GetHMACAsBytes(dataBytes, keyBytes)));
end;

function TAzureStorageAPI.Insert(PartitionKey,RowKey:String; Data:tlkJSONObject):tlkJSONObject;

Var
  PostHeaders:TStringlist;
  Time:TDateTime;
  TimeString:String;

begin
  Socket.Request.Accept:='application/json;odata=minimalmetadata';
  PostHeaders:=TStringlist.Create;
  PostHeaders.Add('x-ms-version:2019-07-07');
  Time:=TTimeZone.Local.ToUniversalTime(Now);
  TimeString:=FormatDateTime('ddd, dd mmm yyyy hh:nn:ss',Time)+' UTC';
  PostHeaders.Add('Date:'+TimeString);
  PostHeaders.Add('MaxDataServiceVersion:3.0;NetFx');
  PostHeaders.Add('DataServiceVersion:3.0;NetFx');
  PostHeaders.Add('Authorization:SharedKey '+FStorageAccount+':'+GetAuthHeader('post','/'+FStorageAccount+'/'+FTable,TimeString));
  Host:=FStorageAccount+'.table.core.windows.net';
  Data.Add('PartitionKey',PartitionKey);
  Data.add('RowKey',RowKey);
  result:=WebPostData('/'+FTable,PostHeaders,Data)as tlkJSONObject;
  PostHeaders.Free;
end;
  • StorageAccount is the name I get from the SharedKey-Options
  • Table is the name of the ressource from the Table Service
  • and AccessKey is Key1 from the SharedKey-Options

The StringToSign is

'POST'#$A#$A'application/json; charset=ISO-8859-1'#$A'Mo, 27 Apr 2020 18:02:33 UTC'#$A'/smartflatlog/Log'

I am not using any access policy on the resource.

After fixing a bug in the passing of the headers I see the following transmit on the wire:

Ges 27.04.2020 20:02:48: POST /Log HTTP/1.1<EOL>Content-Type: application/json; charset=ISO-8859-1<EOL>Content-Length: 104<EOL>x-ms-version: 2019-07-07<EOL>Date: Mo, 27 Apr 2020 18:02:33 UTC<EOL>MaxDataServiceVersion: 3.0;NetFx<EOL>DataServiceVersion: 3.0;NetFx<EOL>Authorization: SharedKey smartflatlog:KVtJ*********************************A5zOME=<EOL>Host: smartflatlog.table.core.windows.net<EOL>Accept: application/json;odata=minimalmetadata<EOL>User-Agent: Demo<EOL><EOL>
Ges 27.04.2020 20:02:48: {"Level":"Debug","LogText":"something to note","Application":"Demo","PartitionKey":"Demo","RowKey":"13"}
Erh 27.04.2020 20:02:48: HTTP/1.1 403 Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.<EOL>Content-Length: 299<EOL>Content-Type: application/json<EOL>Server: Microsoft-HTTPAPI/2.0<EOL>x-ms-request-id: 86f7fd8d-2002-0021-63be-1c5d47000000<EOL>x-ms-error-code: AuthenticationFailed<EOL>Date: Mon, 27 Apr 2020 18:02:49 GMT<EOL><EOL>{"odata.error":{"code":"AuthenticationFailed","message":{"lang":"en-US","value":"Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.\nRequestId:86f7fd8d-2002-0021-63be-1c5d47000000\nTime:2020-04-27T18:02:49.6860540Z"}}}
1
It seems you're trying to insert an entity in the table. Can you trace the request/response through Fiddler? That should give you more details about the error.Gaurav Mantri
ah, there was a bug deep down in the send-socket method, failing to add the headers :-( Thx for the hint, now I get "authorization header invalid", but I will open a new post for this.Wolfgang Bures
No need to open a new question :). Just update the title of the question and provide the most current code.Gaurav Mantri
Again, please check the Fiddler response. Usually it should tell you the data used by the Storage Service to calculate StringtoSign. Please include that as well in your question.Gaurav Mantri
Also, please include your StringtoSign variable's value.Gaurav Mantri

1 Answers

0
votes

If you want to insert an entity with Sharekey auth, the stringtosign should be like

StringToSign = VERB + "\n" +
               Content-MD5 + "\n" +
               Content-Type + "\n" +  
               Date + "\n" +  
               CanonicalizedResource;  

For example

 RequestMethod:= 'GET';

  dateInRfc1123Format:= TTimeZone.Local.ToUniversalTime(Now);
  TheDate:= formatdatetime('ddd, dd mmm yyyy hh:nn:ss "GMT"',dateInRfc1123Format);
  contentType:='application/json;odata=nometadata'
canonicalizedResource:= "/yourAccount/yourTable"
 stringToSign:= format('%s\n'+ // request method

                        '\n' + // content md5
                        '%s\n' + // content type
                        '%s\n' + // date
                        '%s', // canonicalized ResourceL,
                        [RequestMethod,
                        contentType,
                        TheDate,
                        canonicalizedResource]);

For more details, please refer to the document and the document