Generate Azure Service Bus SAS Token

martonnmartonn Member Posts: 29
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:
AzureServiceBusQueue@1000000001 : Record 54000;
HttpUtility@1000000005 : DotNet "'System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.System.Web.HttpUtility";
HMACSHA256@1000000007 : DotNet "'mscorlib'.System.Security.Cryptography.HMACSHA256";
Convert@1000000008 : DotNet "'mscorlib'.System.Convert";
Encoding@1000000009 : DotNet "'mscorlib'.System.Text.Encoding";
CultureInfo@1000000012 : DotNet "'mscorlib'.System.Globalization.CultureInfo";
Environment@1000000014 : 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 Member Posts: 3
    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 Member Posts: 3
    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 Member Posts: 3
    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 Member Posts: 29
    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...
  • andnilandnil Member Posts: 3
    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);
    


  • martonnmartonn Member Posts: 29
    Many thanks! I've tried with CRLF but haven't tried with LF only!
Sign In or Register to comment.