Reputation: 743
so we are using MQTT to connect device/server. I have everything working using a mock client using the M2Mqtt library. What im really struggling with is how to in code generate the signature used in the password field.
I followed this https://azure.microsoft.com/en-us/documentation/articles/iot-hub-sas-tokens/ however im battling around the HMAC side of things. What is the "** signingKey**" they talk of? Is that the devices shared access key? For now just getting the mock client to create its own signature in code (not through the device explorer) is essential before we even worry if our products in the field can compute this (Finding this really over complicated for field devices). Is there a C# example somewhere I can follow other than the node.js - what does this line mean "hmac.update(toSign);"
Is there any simpler way to authenticate a device to the server? maybe just using its shared access key?
Sorry for all the questions :/ Probably I just need a step by step guide on what/when to do URI encode/Base64 encode/decode, HMAC 256 etc as I believe the documentation is far from sufficient.
"{signature} An HMAC-SHA256 signature string of the form: {URL-encoded-resourceURI} + "\n" + expiry. Important: The key is decoded from base64 and used as key to perform the HMAC-SHA256 computation."
Upvotes: 1
Views: 4414
Reputation: 647
Here is how the SAS token can be generated in Java:
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Date;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class AzureSasTokenCreator
{
public static void main(String[] args) throws InvalidKeyException, UnsupportedEncodingException,
MalformedURLException, NoSuchAlgorithmException
{
String token = generateSasTokenForIotDevice("myiothub.azure-devices.net/devices/mydevice",
"ZNILSsz4ke0r5DQ8rfB/PBWf6QqWGV7aaT/iICi9WTc=", 3600);
System.out.println(token);
}
private static String generateSasTokenForIotDevice(String uri, String devicePrimaryKey, int validtySeconds)
throws UnsupportedEncodingException, MalformedURLException, NoSuchAlgorithmException,
InvalidKeyException
{
Date now = new Date();
Date previousDate = new Date(1970);
long tokenExpirationTime = ((now.getTime() - previousDate.getTime()) / 1000) + validtySeconds;
String signature = getSignature(uri, tokenExpirationTime, devicePrimaryKey);
String token = String.format("SharedAccessSignature sr=%s&sig=%s&se=%s", uri, signature,
String.valueOf(tokenExpirationTime));
return token;
}
private static String getSignature(String resourceUri, long expiryTime, String devicePrimaryKey)
throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException
{
byte[] textToSign = new String(resourceUri + "\n" + expiryTime).getBytes();
byte[] decodedDeviceKey = Base64.getDecoder().decode(devicePrimaryKey);
byte[] signature = encryptHmacSha256(textToSign, decodedDeviceKey);
byte[] encryptedSignature = Base64.getEncoder().encode(signature);
String encryptedSignatureUtf8 = new String(encryptedSignature, StandardCharsets.UTF_8);
return URLEncoder.encode(encryptedSignatureUtf8, "utf-8");
}
private static byte[] encryptHmacSha256(byte[] textToSign, byte[] key)
throws NoSuchAlgorithmException, InvalidKeyException
{
SecretKeySpec secretKey = new SecretKeySpec(key, "HmacSHA256");
Mac hMacSha256 = Mac.getInstance("HmacSHA256");
hMacSha256.init(secretKey);
return hMacSha256.doFinal(textToSign);
}
}
See also: https://github.com/Breitmann/AzureSasTokenCreator
Upvotes: 0
Reputation: 26414
This will be helpful for someone someday:
https://github.com/snobu/Azure-IoT-Hub/blob/master/make-token.sh
#!/usr/bin/env bash
#
# GitHub repo:
# https://github.com/snobu/Azure-IoT-Hub
#
# Construct authorization header for Azure IoT Hub
# https://azure.microsoft.com/en-us/documentation/articles/iot-hub-devguide/#security
#
# The security token has the following format:
# SharedAccessSignature sig={signature-string}&se={expiry}&skn={policyName}&sr={URL-encoded-resourceURI}
#
# Author:
# Adrian Calinescu (a-adcali@microsoft.com), Twitter: @evilSnobu, github.com/snobu
#
# Many things borrowed from:
# http://stackoverflow.com/questions/20103258/accessing-azure-blob-storage-using-bash-curl
#
# Prereq:
# OpenSSL
# npm install underscore -g (for the tidy JSON colorized output) - OPTIONAL
# Python 2.6 (Might work with 2.5 too)
# curl (a build from this century should do)
urlencodesafe() {
# Use urllib to safely urlencode stuff
python -c "import urllib, sys; print urllib.quote_plus(sys.argv[1])" $1
}
iothub_name="heresthething"
apiversion="2015-08-15-preview"
req_url="${iothub_name}.azure-devices.net/devices?top=100&api-version=${apiversion}"
sas_key="eU2XXXXXXXXXXXXXXXXXXXXXXXXXXXXX="
sas_name="iothubowner"
authorization="SharedAccessSignature"
# 259200 seconds = 72h (Signature is good for the next 72h)
expiry=$(echo $(date +%s)+259200 | bc)
req_url_encoded=$(urlencodesafe $req_url)
string_to_sign="$req_url_encoded\\n$expiry"
# Create the HMAC signature for the Authorization header
#
# In pseudocode:
# BASE64_ENCODE(HMAC_SHA256($string_to_sign))
#
# With OpenSSL it's a little more work (StackOverflow thread at the top for details)
decoded_hex_key=$(printf %b "$sas_key" | base64 -d -w0 | xxd -p -c256)
signature=$(printf %b "$string_to_sign" | openssl dgst -sha256 -mac HMAC -macopt "hexkey:$decoded_hex_key" -binary | base64 -w0)
# URLencode computed HMAC signature
sig_urlencoded=$(urlencodesafe $signature)
# Print Authorization header
authorization_header="Authorization: $authorization sr=$req_url_encoded&sig=$sig_urlencoded&se=$expiry&skn=$sas_name"
echo -e "\n$authorization_header\n"
# We're ready to make the GET request against azure-devices.net REST API
curl -s -H "$authorization_header" "https://$req_url" | underscore print --color
echo -e "\n"
And a sample MQTT user/pass combo for Azure IoT Hub (yes the password is brutal and includes a whitespace):
https://github.com/Azure/azure-content/blob/master/articles/iot-hub/iot-hub-devguide.md#example
Username (DeviceId is case sensitive): iothubname.azure-devices.net/DeviceId
Password (Generate SAS with Device Explorer): SharedAccessSignature sr=iothubname.azure-devices.net%2fdevices%2fDeviceId&sig=kPszxZZZZZZZZZZZZZZZZZAhLT%2bV7o%3d&se=1487709501
Upvotes: 1
Reputation: 743
Finally got it :)
public static string getSaSToken()
{
TimeSpan fromEpochStart = DateTime.UtcNow - new DateTime(1970, 1, 1);
string expiry = Convert.ToString((int)fromEpochStart.TotalSeconds + 3600);
string baseAddress = "XYZABCBLAH.azure-devices.net/devices/12345".ToLower();
string stringToSign = WebUtility.UrlEncode(baseAddress).ToLower() + "\n" + expiry;
byte[] data = Convert.FromBase64String("y2moreblahblahblah=");
HMACSHA256 hmac = new HMACSHA256(data);
byte[] poo = hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign));
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(baseAddress).ToLower(), WebUtility.UrlEncode(signature), expiry);
return token;
}
"12345" is our device's serial number. the key of y2z.... will be a base64 combination of our serial with something else fancy (as long as its in the base64 format to make the hub happy ;) )
Upvotes: 0
Reputation: 2331
The page https://azure.microsoft.com/en-us/documentation/articles/iot-hub-sas-tokens/ includes a Node.js function that generates a SAS token from the given inputs. From what you have said, you're using the token to enable a device to connect to your IoT Hub, so the inputs to the Node function should be:
Upvotes: 0