Generate Azure Service Bus SAS Token

martonnmartonn Posts: 29Member
I'm trying to generate a SAS Token from Dynamics NAV 2018 using DotNet. I have a working code in PowerShell what I've been using for testing:
$epoch = Get-Date -Date "1970-01-01 00:00:00Z"
$epoch.ToUniversalTime() | Out-Null

$utcNow = Get-Date
$utcNow.ToUniversalTime() | Out-Null

$sinceEpoch = New-TimeSpan -Start $epoch -End $utcNow
$expiry = [System.Convert]::ToString([int32]$sinceEpoch.TotalSeconds + 3600)
$stringToSign = [System.Web.HttpUtility]::UrlEncode($resourceUri) + "`n" + $expiry
$hamcsha = New-Object System.Security.Cryptography.HMACSHA256
$hamcsha.Key = [Text.Encoding]::UTF8.GetBytes($key);
$signature = [System.Convert]::ToBase64String($hamcsha.ComputeHash([Text.Encoding]::UTF8.GetBytes($stringToSign)))
$token = [System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture,"SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",[System.Web.HttpUtility]::UrlEncode($resourceUri),[System.Web.HttpUtility]::UrlEncode($signature),$expiry,$keyName);

here is the C/AL code but I get (401) Unauthorized and the token looks a bit different
Epoch := CREATEDATETIME(DMY2DATE(1,1,1970),000000T);
SinceEpoch := ( CURRENTDATETIME - Epoch ) / 1000;
Expiry := Convert.ToString(ROUND((SinceEpoch + 3600),1,'<'));
StringToSign := HttpUtility.UrlEncode(AzureServiceBusQueue.URL) + Environment.NewLine + Expiry;
HMACSHA256 := HMACSHA256.HMACSHA256(Encoding.UTF8.GetBytes(AzureServiceBusQueue.Key));
Signature := Convert.ToBase64String(HMACSHA256.ComputeHash(Encoding.UTF8.GetBytes(StringToSign)));
Token := Convert.ToString(STRSUBSTNO('SharedAccessSignature sr=%1&sig=%2&se=%3&skn=%4',
                                  HttpUtility.UrlEncode(AzureServiceBusQueue.URL),
                                  HttpUtility.UrlEncode(Signature),
                                  Expiry,
                                  AzureServiceBusQueue."Key Name"),
                               CultureInfo.InvariantCulture);

the DotNet variables are:
[email protected] : Record 54000;
[email protected] : DotNet "'System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.System.Web.HttpUtility";
[email protected] : DotNet "'mscorlib'.System.Security.Cryptography.HMACSHA256";
[email protected] : DotNet "'mscorlib'.System.Convert";
[email protected] : DotNet "'mscorlib'.System.Text.Encoding";
[email protected] : DotNet "'mscorlib'.System.Globalization.CultureInfo";
[email protected] : DotNet "'mscorlib'.System.Environment";

If I try the token generated by the C/AL code in the PowerShell script I get the same result : (401) Unauthorized
Any ideas?

Best Answer

  • andnilandnil Posts: 3
    Accepted Answer
    I´m sorry, I was way to quick to jump to conclusions. It was not the timestamp that was the missing part, it was indeed the new line character as you suspected.

    The Environment.NewLine property in C# is just "\r\n" and it isn´t a new line in a utf8-buffer.
    I created a text variable with a length of 1, and assigned the value 10 to it, and used that as a new line char wich seems to work!

    Here is my working code:
    Epoch := CREATEDATETIME(DMY2DATE(1,1,1970),000000T);
    SinceEpoch := ( CURRENTDATETIME - Epoch ) / 1000;
    Expiry := Convert.ToString(ROUND((SinceEpoch + 3600),1,'<'));
    
    newline[1] := 10;
    StringToSign := HttpUtility.UrlEncode(namespace) + newline + Expiry;
    HMACSHA256 := HMACSHA256.HMACSHA256(Encoding.UTF8.GetBytes(key));
    Signature := Convert.ToBase64String(HMACSHA256.ComputeHash(Encoding.UTF8.GetBytes(StringToSign)));
    
    Token := Convert.ToString(STRSUBSTNO('SharedAccessSignature sr=%1&sig=%2&se=%3&skn=%4',
                                      HttpUtility.UrlEncode(namespace),
                                      HttpUtility.UrlEncode(Signature),
                                      Expiry,
                                      keyname),
                                   CultureInfo.InvariantCulture);
    


Answers

  • andnilandnil Posts: 3Member
    Hi, I´m trying to solve the same problem.
    I´ve debugged your code and it seems to me that the expiry timestamp actually is in the year 1970 (I created a Date object in javascript with the expiry timestamp).

    If that is the case, you get a 401 response simply because your token expired in 1970.
  • andnilandnil Posts: 3Member
    Hmm, the powershell script also is a timestamp in then 1970´s when using the javascript Date object to parse the timestamp.
    However, something is wrong with the generated timestamp in the C/AL code. If you generate an expiry timestamp in powershell, 'hard code' it into you c/al codeunit it will probably work for you. It did for me.
    I was able to generate a valid sas-token with your c/al code by using an expiry data generated from your powershell script
  • martonnmartonn Posts: 29Member
    Hi andnil,

    cool, thanks for having a look.
    So you mean that the Epoch in the C/AL code is the wrong timestamp?
    And how can I hardcode it? I have to calculate it anyway to get the unix timestamp...
    But it's a good idea anyway to separate the code bit which is generating the unix timestamp and check that that it's valid.
    My first suspicion was the NewLine character...

    I have a workout btw - I'm simply running the Powershell Script from NAV using waldo's solution. I know it's not "elegant" but...
  • martonnmartonn Posts: 29Member
    Many thanks! I've tried with CRLF but haven't tried with LF only!
Sign In or Register to comment.