This post has been edited (2019-11-24) my original assumption about how DateTime.Kind unspecified was handled were incorrect.
As I was testing my Azure MQTT Test Client I noticed some oddness with MQTT connection timeouts and this got me wondering about token expiry times. So, I went searching again and found this Azure IoT Hub specific sample code
public static string generateSasToken(string resourceUri, string key, string policyName, int expiryInSeconds = 3600)
{
TimeSpan fromEpochStart = DateTime.UtcNow - new DateTime(1970, 1, 1);
string expiry = Convert.ToString((int)fromEpochStart.TotalSeconds + expiryInSeconds);
string stringToSign = WebUtility.UrlEncode(resourceUri) + "\n" + expiry;
HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(key));
string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
string token = String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}", WebUtility.UrlEncode(resourceUri), WebUtility.UrlEncode(signature), expiry);
if (!String.IsNullOrEmpty(policyName))
{
token += "&skn=" + policyName;
}
return token;
}
This code worked first time and was more flexible than mine which was a bonus. Though while running my MQTTNet based client I noticed the connection would drop after approximately 10mins (EDIT this was probably an unrelated networking issue).
A long time ago (25 years) I had issues sharing a Unix time value between an applications written with Borland C and Microsoft Visual C which made me wonder about Unix epoch base offsets.
So to test my theory I built a Unix epoch test harness console application
using System;
namespace UnixEpocTest
{
class Program
{
static void Main(string[] args)
{
TimeSpan ttl = new TimeSpan(0, 0, 0);
Console.WriteLine("Current time");
Console.WriteLine($"Local {DateTime.Now} {DateTime.Now.Kind}");
Console.WriteLine($"UTC {DateTime.UtcNow} {DateTime.UtcNow.Kind}");
Console.WriteLine($"Unix DIY {new DateTime(1970, 1, 1)} {new DateTime(1970, 1, 1).Kind}");
Console.WriteLine($"Unix DIY+ {new DateTime(1970, 1, 1).ToUniversalTime()} {new DateTime(1970, 1, 1).ToUniversalTime().Kind}");
Console.WriteLine($"Unix DIY {new DateTime(1970, 1, 1, 0,0,0, DateTimeKind.Utc)}");
Console.WriteLine($"Unix {DateTime.UnixEpoch} {DateTime.UnixEpoch.Kind}");
Console.WriteLine();
TimeSpan fromEpochStart = DateTime.UtcNow - new DateTime(1970, 1, 1);
TimeSpan fromEpochStartUtc = DateTime.UtcNow - new DateTime(1970, 1, 1,0,0,0, DateTimeKind.Utc);
TimeSpan fromEpochStartUnixEpoch = DateTime.UtcNow - DateTime.UnixEpoch;
Console.WriteLine("Epoch comparison");
Console.WriteLine($"Local {fromEpochStart} {fromEpochStart.TotalSeconds.ToString("f0")} sec");
Console.WriteLine($"UTC {fromEpochStartUtc} {fromEpochStartUtc.TotalSeconds.ToString("f0")} sec");
Console.WriteLine($"Epoc {fromEpochStartUnixEpoch} {fromEpochStartUnixEpoch.TotalSeconds.ToString("f0")} sec");
Console.WriteLine();
TimeSpan afterEpoch = DateTime.UtcNow.Add(ttl) - new DateTime(1970, 1, 1);
TimeSpan afterEpochUtC = DateTime.UtcNow.Add(ttl) - new DateTime(1970, 1, 1).ToUniversalTime();
TimeSpan afterEpochEpoch = DateTime.UtcNow.Add(ttl) - DateTime.UnixEpoch;
Console.WriteLine("Epoch calculation");
Console.WriteLine($"Local {afterEpoch}");
Console.WriteLine($"UTC {afterEpochUtC}");
Console.WriteLine($"Epoch {afterEpochEpoch}");
Console.WriteLine();
Console.WriteLine("Epoch DateTime");
Console.WriteLine($"Local :{new DateTime(1970, 1, 1)}");
Console.WriteLine($"UTC :{ new DateTime(1970, 1, 1).ToUniversalTime()}");
Console.WriteLine("Press ENTER to exit");
Console.ReadLine();
Console.WriteLine("Hello World!");
}
}
}
EDIT: I now think the UtcNow to “unspecified” kind mathematics was being handled correctly. I have updated the code to use the DateTime.UnixEpoch constant so the code is more readable.

public static string generateSasToken(string resourceUri, string key, string policyName, int expiryInSeconds = 900)
{
TimeSpan fromEpochStart = DateTime.UtcNow - DateTime.UnixEpoch;
string expiry = Convert.ToString((int)fromEpochStart.TotalSeconds + expiryInSeconds);
string stringToSign = WebUtility.UrlEncode(resourceUri) + "\n" + expiry;
HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(key));
string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
string token = String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}", WebUtility.UrlEncode(resourceUri), WebUtility.UrlEncode(signature), expiry);
if (!String.IsNullOrEmpty(policyName))
{
token += "&skn=" + policyName;
}
return token;
}
I need to test the expiry of my SAS Tokens some more especially with the client running on my development machine (NZT which is currently UTC+13) and in Azure (UTC timezone)
Pingback: Azure IoT Hub SAS Tokens revisited yet again | devMobile's blog