I am trying to generate Shared Access signature for the Azure service bus queue. Here is the code snippnet through which I am generating SAS token.
using System;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Web;
namespace SASTokenGeneration
{
class Program
{
static void Main(string[] args)
{
string resourceUri = "sb://xxxservicequeue.servicebus.windows.net/;SharedAccessKeyName=xxxservicequeue;SharedAccessKey=xxxxxx";
string key = "token";
string keyName = "xxxservicequeue";
try
{
TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
var week = 60 * 60 * 24 * 7;
var expiry = Convert.ToString((int)sinceEpoch.TotalSeconds + week);
string stringToSign = HttpUtility.UrlEncode(resourceUri) + "\n" + expiry;
HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
var sasToken = String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",
HttpUtility.UrlEncode(resourceUri), HttpUtility.UrlEncode(signature), expiry, keyName);
}
catch (Exception ex)
{
}
}
}
}
When I use this token in postman then sometime it gives
MalformedToken: Failed to parse simple web token. azure sevice Bus
and Now I am getting
401 40103: Invalid authorization token signature
From error, I know that I am doing something wrong with connection string but cannot figure out what. Can you please point me in right direction.
Thanks
Problems may locate at
string resourceUri = "sb://xxxservicequeue.servicebus.windows.net/;SharedAccessKeyName=xxxservicequeue;SharedAccessKey=xxxxxx";
string key = "token";
string keyName = "xxxservicequeue";
The resourceUri is the full URI of the Service Bus resource to which access is claimed, in the format of sb://xxxservicequeue.servicebus.windows.net/ or with specific entity you need to operate like sb://xxxservicequeue.servicebus.windows.net/queueName.
The key should be the value of SharedAccessKeyKey and keyName is SAS policy name like the default RootManageSharedAccessKey.
Have a look at doc for more details.
Related
I'm trying to send telemetry to a device in Azure IoT Central with an HTTP POST request.
Similar Rest API is available for Azure IoT Hub - https://learn.microsoft.com/en-us/rest/api/iothub/device/send-device-event
I was able to extract the IoT Hub resource URL behind the Azure IoT Central using this website - https://dpsgen.z8.web.core.windows.net/
It takes Scope Id, Device Id and Device Primary Key that we get from Azure IoT Central. It gives you the IoT Hub connection string,
HostName=iotc-<<unique-iot-hub-id>>.azure-devices.net;DeviceId=<<device-id>>;SharedAccessKey=<<device-primary-key>>
Using the above IoT Hub host-name, I tried IoT Hub send device event Rest API. It is failing with an Unauthorized error.
I am using SAS token generated from the below path within the Azure IoT Central application
Azure IoT Central -> Permissions -> API tokens -> "App Administrator" Role
Any help will be useful.
Have a look at my answer where is described in details how to generate a connection info for sending a telemetry data to the Azure IoT Central App using a REST Post request.
The following is an updated azure function to generate a requested device connection info:
#r "Newtonsoft.Json"
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
int retryCounter = 10;
int pollingTimeInSeconds = 3;
string deviceId = req.Query["deviceid"];
string mid = req.Query["modelid"];
log.LogInformation($"DeviceId = {deviceId}, ModelId = {mid}");
if (!Regex.IsMatch(deviceId, #"^[a-z0-9\-]+$"))
throw new Exception($"Invalid format: DeviceID must be alphanumeric, lowercase, and may contain hyphens");
string iotcScopeId = System.Environment.GetEnvironmentVariable("AzureIoTC_scopeId");
string iotcSasToken = System.Environment.GetEnvironmentVariable("AzureIoTC_sasToken");
if(string.IsNullOrEmpty(iotcScopeId) || string.IsNullOrEmpty(iotcSasToken))
throw new ArgumentNullException($"Missing the scopeId and/or sasToken of the IoT Central App");
string deviceKey = SharedAccessSignatureBuilder.ComputeSignature(iotcSasToken, deviceId);
string address = $"https://global.azure-devices-provisioning.net/{iotcScopeId}/registrations/{deviceId}/register?api-version=2021-06-01";
string sas = SharedAccessSignatureBuilder.GetSASToken($"{iotcScopeId}/registrations/{deviceId}", deviceKey, "registration");
log.LogInformation($"sas_token: {sas}");
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", sas);
client.DefaultRequestHeaders.Add("accept", "application/json");
string jsontext = string.IsNullOrEmpty(mid) ? null : $"{{ \"modelId\": \"{mid}\" }}";
var response = await client.PutAsync(address, new StringContent(JsonConvert.SerializeObject(new { registrationId = deviceId, payload = jsontext }), Encoding.UTF8, "application/json"));
var atype = new { errorCode = "", message = "", operationId = "", status = "", registrationState = new JObject() };
do
{
dynamic operationStatus = JsonConvert.DeserializeAnonymousType(await response.Content.ReadAsStringAsync(), atype);
if (!string.IsNullOrEmpty(operationStatus.errorCode))
{
throw new Exception($"{operationStatus.errorCode} - {operationStatus.message}");
}
response.EnsureSuccessStatusCode();
if (operationStatus.status == "assigning")
{
Task.Delay(TimeSpan.FromSeconds(pollingTimeInSeconds)).Wait();
address = $"https://global.azure-devices-provisioning.net/{iotcScopeId}/registrations/{deviceId}/operations/{operationStatus.operationId}?api-version=2021-06-01";
response = await client.GetAsync(address);
}
else if (operationStatus.status == "assigned")
{
log.LogInformation($"{JsonConvert.SerializeObject(operationStatus, Formatting.Indented)}");
string assignedHub = operationStatus.registrationState.assignedHub;
string cstr = $"HostName={assignedHub};DeviceId={operationStatus.registrationState.deviceId};SharedAccessKey={deviceKey}"; // + (string.IsNullOrEmpty(mid) ? "" : $";modelId={mid.Replace(";","#")}");
string requestUrl = $"https://{assignedHub}/devices/{operationStatus.registrationState.deviceId}/messages/events?api-version=2021-04-12";
string deviceSasToken = SharedAccessSignatureBuilder.GetSASToken($"{assignedHub}/{operationStatus.registrationState.deviceId}", deviceKey);
log.LogInformation($"IoTC DeviceConnectionString:\n\t{cstr}");
return new OkObjectResult(JObject.FromObject(new { iotHub = assignedHub, iotFireUrl = requestUrl, deviceSasToken = deviceSasToken, deviceConnectionString = cstr }));
}
else
{
throw new Exception($"{operationStatus.registrationState.status}: {operationStatus.registrationState.errorCode} - {operationStatus.registrationState.errorMessage}");
}
} while (--retryCounter > 0);
throw new Exception("Registration device status retry timeout exprired, try again.");
}
}
public sealed class SharedAccessSignatureBuilder
{
public static string GetHostNameNamespaceFromConnectionString(string connectionString)
{
return GetPartsFromConnectionString(connectionString)["HostName"].Split('.').FirstOrDefault();
}
public static string GetSASTokenFromConnectionString(string connectionString, uint hours = 24)
{
var parts = GetPartsFromConnectionString(connectionString);
if (parts.ContainsKey("HostName") && parts.ContainsKey("SharedAccessKey"))
return GetSASToken(parts["HostName"], parts["SharedAccessKey"], parts.Keys.Contains("SharedAccessKeyName") ? parts["SharedAccessKeyName"] : null, hours);
else
return string.Empty;
}
public static string GetSASToken(string resourceUri, string key, string keyName = null, uint hours = 24)
{
try
{
var expiry = GetExpiry(hours);
string stringToSign = System.Web.HttpUtility.UrlEncode(resourceUri) + "\n" + expiry;
var signature = SharedAccessSignatureBuilder.ComputeSignature(key, stringToSign);
var sasToken = keyName == null ?
String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}", HttpUtility.UrlEncode(resourceUri), HttpUtility.UrlEncode(signature), expiry) :
String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}", HttpUtility.UrlEncode(resourceUri), HttpUtility.UrlEncode(signature), expiry, keyName);
return sasToken;
}
catch
{
return string.Empty;
}
}
#region Helpers
public static string ComputeSignature(string key, string stringToSign)
{
using (HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(key)))
{
return Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
}
}
public static Dictionary<string, string> GetPartsFromConnectionString(string connectionString)
{
return connectionString.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Split(new[] { '=' }, 2)).ToDictionary(x => x[0].Trim(), x => x[1].Trim(), StringComparer.OrdinalIgnoreCase);
}
// default expiring = 24 hours
private static string GetExpiry(uint hours = 24)
{
TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
return Convert.ToString((ulong)sinceEpoch.TotalSeconds + 3600 * hours);
}
public static DateTime GetDateTimeUtcFromExpiry(ulong expiry)
{
return (new DateTime(1970, 1, 1)).AddSeconds(expiry);
}
public static bool IsValidExpiry(ulong expiry, ulong toleranceInSeconds = 0)
{
return GetDateTimeUtcFromExpiry(expiry) - TimeSpan.FromSeconds(toleranceInSeconds) > DateTime.UtcNow;
}
public static string CreateSHA256Key(string secret)
{
using (var provider = new SHA256CryptoServiceProvider())
{
byte[] keyArray = provider.ComputeHash(UTF8Encoding.UTF8.GetBytes(secret));
provider.Clear();
return Convert.ToBase64String(keyArray);
}
}
public static string CreateRNGKey(int keySize = 32)
{
byte[] keyArray = new byte[keySize];
using (var provider = new RNGCryptoServiceProvider())
{
provider.GetNonZeroBytes(keyArray);
}
return Convert.ToBase64String(keyArray);
}
#endregion
}
The IoT Central API token is to manage the application functionality and cannot be used by the device. Select the device in IoT Central and click on the "Connect" menu at the top, use the primary key shown for that device.
As a side note, https although supported for devices is not well suited for IoT due to its polling nature and does not support device twin desired or reported properties. https://learn.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-d2c-guidance
IoT Central provides built-in high availability, the underlying IoTHub name can change, so manually getting the IoTHub name is not recommended. Always make call DPS to retrieve IoTHub name, the first time and periodically or error conditions.
I would like to use the Microsft Graph API to get informations from the azure active directory. For this I need an access token, which is issued based on a secret or certificate.
In the azure portal the use of a certificate is recommended.
What are the reasons for using a certificate? Is the use of a certificate more secure than a secret?
What type of certificate can and should be stored? How can it be generated?
Many thanks in advance!
What are the reasons for using a certificate? Is the use of a
certificate more secure than a secret?
Yes, Certificate is more secure than Secret, thus it is more complex and more expensive.
You could refer to the link in silent's comment.
What type of certificate can and should be stored? How can it be
generated?
File Type: .cer, .pem, .crt
Read details about the format of certificate. Here is how to craft a signed client assertion:
string Encode(byte[] arg)
{
char Base64PadCharacter = '=';
char Base64Character62 = '+';
char Base64Character63 = '/';
char Base64UrlCharacter62 = '-';
char Base64UrlCharacter63 = '_';
string s = Convert.ToBase64String(arg);
s = s.Split(Base64PadCharacter)[0]; // RemoveAccount any trailing padding
s = s.Replace(Base64Character62, Base64UrlCharacter62); // 62nd char of encoding
s = s.Replace(Base64Character63, Base64UrlCharacter63); // 63rd char of encoding
return s;
}
string GetSignedClientAssertion()
{
//Signing with SHA-256
string rsaSha256Signature = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
X509Certificate2 certificate = new X509Certificate2("Certificate.pfx", "Password", X509KeyStorageFlags.EphemeralKeySet);
//Create RSACryptoServiceProvider
var x509Key = new X509AsymmetricSecurityKey(certificate);
var privateKeyXmlParams = certificate.PrivateKey.ToXmlString(true);
var rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(privateKeyXmlParams);
//alg represents the desired signing algorithm, which is SHA-256 in this case
//kid represents the certificate thumbprint
var header = new Dictionary<string, string>()
{
{ "alg", "RS256"},
{ "kid", Encode(Certificate.GetCertHash()) }
};
//Please see the previous code snippet on how to craft claims for the GetClaims() method
string token = Encode(Encoding.UTF8.GetBytes(JObject.FromObject(header).ToString())) + "." + Encode(Encoding.UTF8.GetBytes(JObject.FromObject(GetClaims())));
string signature = Encode(rsa.SignData(Encoding.UTF8.GetBytes(token), new SHA256Cng()));
string signedClientAssertion = string.Concat(token, ".", signature);
return signedClientAssertion;
}
private static IDictionary<string, string> GetClaims()
{
//aud = https://login.microsoftonline.com/ + Tenant ID + /v2.0
string aud = $"https://login.microsoftonline.com/{tenantId}/v2.0";
string ConfidentialClientID = "00000000-0000-0000-0000-000000000000" //client id
const uint JwtToAadLifetimeInSeconds = 60 * 10; // Ten minutes
DateTime validFrom = DateTime.UtcNow;
var nbf = ConvertToTimeT(validFrom);
var exp = ConvertToTimeT(validFrom + TimeSpan.FromSeconds(JwtToAadLifetimeInSeconds));
return new Dictionary<string, string>()
{
{ "aud", aud },
{ "exp", exp.ToString() },
{ "iss", ConfidentialClientID },
{ "jti", Guid.NewGuid().ToString() },
{ "nbf", nbf.ToString() },
{ "sub", ConfidentialClientID }
};
}
Or using Powershell: https://learn.microsoft.com/en-us/azure/cosmos-db/certificate-based-authentication#create-a-self-signed-certificate
I am using this great method, found in this article https://codingstill.com/2016/01/verify-jwt-token-signed-with-rs256-using-the-public-key/#comment-3232, to validate my Azure idToken, which is signed with RS256.
I noticed that Azure AD changes the public key in a period of time. By reading this article, https://nicksnettravels.builttoroam.com/post-2017-01-24-verifying-azure-active-directory-jwt-tokens-aspx. Therefore, I used HttpClient (c#) to obtain the public key from the URL below, every time when I need to validate the token.
https://login.microsoftonline.com/{TenantId}/discovery/v2.0/keys?appid={AppId}
I did get the result of the public key, which is a string array in x5c key. Below is my code:
public static string GetPublicKeyAsync(string kid)
{
using (var client = new HttpClient())
{
HttpResponseMessage response = client.GetAsync("https://login.microsoftonline.com/{TenantId}/discovery/v2.0/keys?appid={AppId}").Result;
if (response.IsSuccessStatusCode)
{
var responseContent = response.Content;
// by calling .Result you are synchronously reading the result
string responseBody = responseContent.ReadAsStringAsync().Result;
JObject json = JObject.Parse(responseBody);
string c = json["keys"].ToString();
List<ADPublic> allPublicKeys = JsonConvert.DeserializeObject<List<ADPublic>>(json["keys"].ToString());
foreach (ADPublic key in allPublicKeys)
{
if (key.kid == kid)
{
string certificateString = key.x5c[0];
var certificate = new X509Certificate2(Convert.FromBase64String(certificateString));
string pkey = Convert.ToBase64String(certificate.PublicKey.EncodedKeyValue.RawData);
var x509SecurityKey = new X509SecurityKey(certificate)
{
KeyId = key.kid
};
return pkey;
}
}
}
return null;
}
}
When I pass my public key over to the validation method. I always get an error at the line (PublicKeyFactory.CreateKey(keyBytes) method ):
Please refer to the verify method I mentioned in the beginning of this question for the code below:
// call my get public key function...
string key = GetPublicKeyAsync(headerData["kid"].ToString());
var keyBytes = Convert.FromBase64String(key); // your key here
**AsymmetricKeyParameter asymmetricKeyParameter = PublicKeyFactory.CreateKey(keyBytes);**
Unknown object in GetInstance: Org.BouncyCastle.Asn1.DerInteger Parameter name: obj
I think I am almost there, but missing the last part, could you please help? Thank you very much!
I got the same error when using certificate.PublicKey.EncodedKeyValue.RawData to get the public key. I referred to this issue and finally succeeded.
The code shows getting AsymmetricKeyParameter from x5c string. Decode(...) function is referred to in the article.
string token = "<the token that you want to verify>";
string certificateString = "<the first x5c of the first key from https://login.microsoftonline.com/{TenantId}/discovery/v2.0/keys?appid={AppId}>";
byte[] buffer = Convert.FromBase64String(certificateString);
X509CertificateParser parser = new X509CertificateParser();
var _certificate = parser.ReadCertificate(buffer);
AsymmetricKeyParameter publicKey = _certificate.GetPublicKey();
string result = Decode(token, publicKey);
Console.WriteLine("result: " + result);
I have to register a device on IoT hub using DPS service. I cannot use the .net SDK as device firmware does not support so we decided to use the REST based API's to do the same.
With C# SDK all I need are the .PFX file with password, DPS_IDSCOPE and device endpoint something like this (xyz.azure-devices-provisioning.net).
Now How I can use above information to do the same with azure rest API.For Authentication I have seen below link which says I have to use SAS token for the same as Azure AD access token wont work.
https://social.msdn.microsoft.com/Forums/en-US/19183e82-437e-4d6f-8498-ed33ba18a3fa/creating-iot-device-with-azure-dps-via-rest?forum=azureiothub
Now If I trust on above link (However I do not think it will work ) then where is the use of certificate .PFX file ?
I have found this official API to register the device .
https://learn.microsoft.com/en-us/rest/api/iot-dps/runtimeregistration/registerdevice
I have not understand how to pass the body information like structure of JSON.I know I have to use x509 as Attestation type but how I will form is it like
var pairs = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("registrationId", "device1"),
new KeyValuePair<string, string>("type", "x509"),
};
Or if its a json then what will be the name of attribute ?
Now below are the sample code that I tried to use and getting same error.
Way-1 (Used .PFX as authentication)
public static void RegisterDeviceWithEnrollementGroup()
{
try
{
var handler = new WebRequestHandler();
var certFile = Path.Combine(#"C:\IoT\", "device1.pfx");
handler.ClientCertificates.Add(new X509Certificate2(certFile, "certificatepassword"));
HttpClient client4 = new HttpClient(handler);
client4.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client4.BaseAddress = new Uri("https://XYZ.azure-devices-provisioning.net/scopeid/registrations/device1/register?api-version=2018-11-01");
string content = Newtonsoft.Json.JsonConvert.SerializeObject(null);
var httpContent3 = new StringContent(content, Encoding.UTF8, "application/json");
var pairs = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("registrationId", "device1"),
new KeyValuePair<string, string>("type", "x509"),
};
var content2 = new FormUrlEncodedContent(pairs);
HttpResponseMessage response4 = client4.PutAsync(client4.BaseAddress.ToString(), content2).Result;
var commandResult = string.Empty;
if (response4.IsSuccessStatusCode)
{
commandResult = response4.Content.ReadAsStringAsync().Result;
}
else
{
commandResult = response4.Content.ReadAsStringAsync().Result;
}
Console.WriteLine("IoT hub API call result - " + commandResult);
}
catch (Exception)
{
throw;
}
}
Way-2 - Using SAS token :
public static void RegisterDeviceWithEnrollementGroup()
{
try
{
HttpClient client4 = new HttpClient();
var sas = generateSasToken("XYZ.azure-devices-provisioning.net", "key", "provisioningserviceowner");
client4.DefaultRequestHeaders.Add("Authorization", sas);
client4.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client4.BaseAddress = new Uri("https://XYZ.azure-devices-provisioning.net/scopeid/registrations/device1/register?api-version=2018-11-01");
string content = Newtonsoft.Json.JsonConvert.SerializeObject(null);
var httpContent3 = new StringContent(content, Encoding.UTF8, "application/json");
var pairs = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("registrationId", "device1"),
new KeyValuePair<string, string>("type", "x509"),
};
var content2 = new FormUrlEncodedContent(pairs);
HttpResponseMessage response4 = client4.PutAsync(client4.BaseAddress.ToString(), content2).Result;
var commandResult = string.Empty;
if (response4.IsSuccessStatusCode)
{
commandResult = response4.Content.ReadAsStringAsync().Result;
}
else
{
commandResult = response4.Content.ReadAsStringAsync().Result;
}
Console.WriteLine("IoT hub API call result - " + commandResult);
}
catch (Exception)
{
throw;
}
}
Helper method :
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;
}
Now Please answer some body whether I am doing correct or wrong here as I am getting exception.
{StatusCode: 415, ReasonPhrase: 'Unsupported Media Type', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
x-ms-request-id: 6475343d-5a2e-407a-9e7f-896e0c489307
Strict-Transport-Security: max-age=31536000; includeSubDomains
Date: Thu, 28 Feb 2019 11:42:59 GMT
Content-Length: 0
}}
Looking forward for the help ...
Please follow the steps outlined here:
https://learn.microsoft.com/en-us/azure/iot-dps/tutorial-net-provision-device-to-hub
to first create an Enrollment in DPS for this device using X.509 (no need to use EnrollmentGroup for a single device).
When registering a device with DPS, use the global endpoint - global.azure-devices-provisioning.net. Configure the HTTP client to include the device client certificate. Do not provide a SAStoken from the device.
You can setup the HTTP message content for device registration as follows:
httpRequest.Content = new StringContent("{\"registrationId\": \"device1\"}", Encoding.UTF8);
httpRequest.Content.Headers.ContentType=MediaTypeHeaderValue.Parse("application/json; charset=utf-8");
Note that the JSON content does NOT include "type" : "x509".
The device endpoint would be as follows:
PUT https://global.azure-devices-provisioning.net/{idScope}/registrations/device1/register?api-version=2018-11-01 (replace idScope with the one from your DPS. A sample idScope would be 0ne00000012)
This POST explains it quite well: http://busbyland.com/azure-iot-device-provisioning-service-via-rest-part-1/
Using the endpoint documented here: https://learn.microsoft.com/es-es/rest/api/iot-dps/runtimeregistration/registerdevice
You can manage to register your devices with a HTTPRequest see the CURL Example above:
curl -L -i -X PUT --cert ./chain.pem --key ./iot-device-1.key.pem -H 'Content-Type: application/json' -H 'Content-Encoding: utf-8' -d '{"registrationId": "iot-device-1", "payload": {"CustomProperty": "CustomValue"}}' https://global.azure-devices-provisioning.net/XXXXXXXXXXX/registrations/iot-device-1/register?api-version=2019-03-31
You could replicate that request in your development environment.
Note that the global.azure-devices-provisioning.net is used which is public and you do not need any authentication/authorization.
I wrote a simply java program (jdk 1.7) that lists all my service bus topics and prints out the name of each topic to stdout:
try {
String namespace = "myservicebus"; // from azure portal
String issuer = "owner"; // from azure portal
String key = "asdjklasdjklasdjklasdjklasdjk"; // from azure portal
Configuration config = ServiceBusConfiguration.configureWithWrapAuthentication(
namespace,
issuer,
key,
".servicebus.windows.net",
"-sb.accesscontrol.windows.net/WRAPv0.9");
ServiceBusContract service = ServiceBusService.create(config);
ListTopicsResult result = service.listTopics();
List<TopicInfo> infoList = result.getItems();
for(TopicInfo info : infoList) {
System.out.println( info.getPath());
}
} catch (Exception e) {
e.printStackTrace();
}
Now, i am trying to run this example in a simple android project (Android 4.2) but it wont work.
The runtime always throws following error:
java.lang.RuntimeException: Service or property not registered: com.microsoft.windowsazure.services.serviceBus.ServiceBusContract
Has anyone successfully established a connection from an android device (or emulator) to azure service bus?
Does the Microsoft Azure-Java-SDK not support android projects?
Thanks in advance
This error is due to the fact that apks generated do not include (remove) the ServiceLoader information (under META-INF/services). You can test yourself deleting it from the jar generated and see that the same error appears. In the documentation it is said that it is now supported, but i found problems to use it.
http://developer.android.com/reference/java/util/ServiceLoader.html
You can include manually the data in the apk using ant
Keep 'META-INF/services'-files in apk
After 10h debugging, removing classes manually, including META-INF/services, etc, I found that the Azure SDK uses some classes not supported by Android (javax.ws.*) and any woraround works for me.
So I would recommend using the REST API in Android environments, find below the source code i used to sebd messages to the topic.
private static String generateSasToken(URI uri) {
String targetUri;
try {
targetUri = URLEncoder
.encode(uri.toString().toLowerCase(), "UTF-8")
.toLowerCase();
long expiresOnDate = System.currentTimeMillis();
int expiresInMins = 20; // 1 hour
expiresOnDate += expiresInMins * 60 * 1000;
long expires = expiresOnDate / 1000;
String toSign = targetUri + "\n" + expires;
// Get an hmac_sha1 key from the raw key bytes
byte[] keyBytes = sasKey.getBytes("UTF-8");
SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA256");
// Get an hmac_sha1 Mac instance and initialize with the signing key
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);
// Compute the hmac on input data bytes
byte[] rawHmac = mac.doFinal(toSign.getBytes("UTF-8"));
// using Apache commons codec for base64
// String signature = URLEncoder.encode(
// Base64.encodeBase64String(rawHmac), "UTF-8");
String rawHmacStr = new String(Base64.encodeBase64(rawHmac, false),"UTF-8");
String signature = URLEncoder.encode(rawHmacStr, "UTF-8");
// construct authorization string
String token = "SharedAccessSignature sr=" + targetUri + "&sig="
+ signature + "&se=" + expires + "&skn=" + sasKeyName;
return token;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void Send(String topic, String subscription, String msgToSend) throws Exception {
String url = uri+topic+"/messages";
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost(url);
// Add header
String token = generateSasToken(new URI(uri));
post.setHeader("Authorization", token);
post.setHeader("Content-Type", "text/plain");
post.setHeader(subscription, subscription);
StringEntity input = new StringEntity(msgToSend);
post.setEntity(input);
System.out.println("Llamando al post");
HttpResponse response = client.execute(post);
System.out.println("Response Code : "
+ response.getStatusLine().getStatusCode());
if (response.getStatusLine().getStatusCode() != 201)
throw new Exception(response.getStatusLine().getReasonPhrase());
}
Further info at REST API Azure information.