X509 Certificates are the most secure way for IoT devices to authenticate with Azure. In this tutorial, we will show you how to use EZCA to connect your IoT devices to Azure in a secure and compliant way.
For this tutorial we only need an Azure IoT hub.
After creating the Azure resource, the next step is to establish a trust between Azure and our Certificate Authority. This trust tells Azure to trust any certificate issued by this Certificate Authority as a valid credential for this IoT Hub.
This can be done with our one click trust in the EZCA portal as well as manually in the Azure portal. First we will demonstrate how to do it with EZCA’s one click trust an then Manually in Azure.
Now that we have created our Azure IoT Hub and established trust with the EZCA CA, we will download a sample repository that will: register the domain in EZCA, create a certificate, use the certificate to authenticate to Azure. This sample is a great starting point to manually connect IoT devices to Azure. If you want to automatically add IoT devices to Azure, look at our guide using Azure’s Device Provisioning Service.
SimulateIoTHubDevice.csproj
project located in AzureIoTSamples -> SimulateIoTHubDevice.This project has to be run on Linux or Mac due to a limitation that .NET in Windows requires you to use the windows cert store to use X509 Certificates. If you are in a Windows workstation, we recommend running this in VS Code WSL. Alternatively, you can modify the sample with our Windows Cert Store Sample to use the Windows store to create the certificates (not recommended since your IoT devices will not have a Windows store and you will have to rewrite the code again).
string _iotHubEndpoint = "YOURENDPOINT.azure-devices.net";
This CA has to be one of the CAs you added to IoT Hub in the “Establish Trust Between IoT Hub and Your EZCA CA” step.
This step can be automated with Azure Device Provisioning Service, check out our guide on how to get started with automatic provisioning with Azure DPS.
While having a working code sample is great, we recommend going in and understanding the details of what the code is doing. For this I am going to open the solution in Visual Studio to also see the EZCASharedLibrary.csproj
library that this project depends on to create the certificates.
SimulateIoTHubDevice.sln
solution in Visual Studio or your preferred text editor.EZCASharedLibrary.csproj
This project is a library to call EZCA and manage all the cryptographic operationsSimulateIoTHubDevice.csproj
The project used for this tutorial (It depends on EZCASharedLibrary for cryptographic operations)SimulateDeviceProvisioning.csproj
This Project is covered in our guide for using Azure’s Device Provisioning Service. It is basically this project + the code to automatically provision devices.SimulateIoTHubDevice.csproj
project.
This part of the code is for usability of the sample, in real production scenario, the CA would have been selected in advance and passed as a setting to the registration program.
ezManager.GetAvailableCAsAsync()
if we deep dive into this function, we can see that it gets a token and then calls an API to get the available CAs. Let’s dive into the get token function.private async Task<string> GetTokenAsync()
{
if (_token == null || _token.Value.ExpiresOn < DateTime.UtcNow)
{
//Create Azure Credential
//This example includes Interactive for development purposes.
//For production you should remove interactive since there is no human involved
DefaultAzureCredential credential = new(includeInteractiveCredentials: true);
TokenRequestContext authContext = new(
new string[] { "https://management.core.windows.net/.default" });
_token = await credential.GetTokenAsync(authContext);
}
return _token.Value.Token;
}
ezManager.GetAvailableCAsAsync()
will call the EZCA api to get the available SSL CAs that that user can request from. HttpResponseMessage response = await _httpService.GetAPIAsync($"{_portalURL}api/CA/GetAvailableSSLCAs", await GetTokenAsync());
Once the CA is selected, we have to create a fake DeviceID and register it as a domain for the CA en EZCA. This is all done in the RegisterDomainAsync
function.
NewDomainRegistrationRequest
object, this object will then be send as a post request to EZCA. Below you can see an explanation of each of the fields. public class NewDomainRegistrationRequest
{
[JsonPropertyName("CAID")]
public string? CAID { get; set; } //This is the CA ID used by EZCA to know which CA you are requesting from. We got this value from the CA Information.
[JsonPropertyName("TemplateID")]
public string? TemplateID { get; set; } // This is another ID Value that allows EZCA to know which CA you are requesting from. We got this value from the CA Information.
[JsonPropertyName("Domain")]
public string? Domain { get; set; } // In this case the domain is the device ID of the Device
[JsonPropertyName("Owners")]
public List<AADObjectModel> Owners { get; set; } = new List<AADObjectModel>(); //This are the AAD Objects that are allowed to make changes to this domain.
[JsonPropertyName("Requesters")]
public List<AADObjectModel> Requesters { get; set; } = new List<AADObjectModel>(); //This are the AAD objects allowed to request certificates for this domain. For this field we recommend keeping it to your enrollment agent only. After the first certificate is issued, your IoT device can use that certificate to renew its certificate.
}
NewDomainRegistrationRequest
object, we send the post request to EZCA. HttpResponseMessage response = await _httpService.PostAPIAsync($"{_portalURL}api/CA/RegisterNewDomain", JsonSerializer.Serialize(request), await GetTokenAsync());
Once the “domain” for this device ID is registered in EZCA, we can go ahead and request the certificate. To create the certificate, we call ezManager.RequestCertificateAsync(selectedCA, deviceID);
This function will create a certificate signing request, request the certificate and return the newly created X509 certificate.
//create a 4096 RSA key
RSA key = RSA.Create(4096);
//create Certificate Signing Request
X500DistinguishedName x500DistinguishedName = new("CN=" + domain);
CertificateRequest certificateRequest = new(x500DistinguishedName, key,
HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
string csr = PemEncodeSigningRequest(certificateRequest);
List<string> subjectAlternateNames = new()
{
domain
};
int certificateValidityDays = 10; // setting the lifetime of the certificate to 10 days
CertificateCreateRequestModel
object, this object will then be send as a post request to EZCA. Below you can see an explanation of each of the fields. public class CertificateCreateRequestModel
{
[JsonPropertyName("SubjectName")]
public string SubjectName { get; set; } //This is the subject name of the certificate, for this case we set CN=DeviceID where DeviceID is your device ID. This is used by Azure to know which device is calling.
[JsonPropertyName("SubjectAltNames")]
public List<string> SubjectAltNames { get; set; } = new List<string>(); //This are alternative names for the subject name, this was introduced to have multiple domains in one certificate. Since some authentication standards use subject alternate names for authentication, we add the device ID as a subject alternate name.
[JsonPropertyName("CAID")]
public string? CAID { get; set; } //This is the CA ID used by EZCA to know which CA you are requesting from. We got this value from the CA Information.
[JsonPropertyName("TemplateID")]
public string? TemplateID { get; set; } // This is another ID Value that allows EZCA to know which CA you are requesting from. We got this value from the CA Information.
[JsonPropertyName("CSR")]
public string CSR { get; set; } //this is the signing request we created to prove we own the private key.
[JsonPropertyName("ValidityInDays")]
public int ValidityInDays { get; set; } //this is the number of days the certificate will be valid
[JsonPropertyName("EKUs")]
public string[] EKUs { get; set; } = new string[] { "1.3.6.1.5.5.7.3.1", "1.3.6.1.5.5.7.3.2" }; //this is what the certificate can be used for. By default we add Client authentication and Server authentication
[JsonPropertyName("KeyUsages")]
public string[] KeyUsages { get; set; } = new string[] { "Key Encipherment", "Digital Signature" }; // this is what the certificate's key can be used for.
//The following fields are used to give visibility to your administrators how you are using the certificate
[JsonPropertyName("SelectedLocation")]
public string SelectedLocation { get; set; } = "IoT Device"; // where the device is being stored.
[JsonPropertyName("ResourceID")]
public string ResourceID { get; set; } = string.Empty; //This is only used when the certificate is being stored in Azure
[JsonPropertyName("SecretName")]
public string SecretName { get; set; } = string.Empty; //This is only used when the certificate is being stored in Azure
[JsonPropertyName("AKVName")]
public string AKVName { get; set; } = string.Empty;//This is only used when the certificate is being stored in Azure
[JsonPropertyName("AutoRenew")]
public bool AutoRenew { get; set; } = false; //This indicates if you want EZCA to automatically renew your certificate. This feature is only available when the certificate is being stored in Azure
[JsonPropertyName("AutoRenewPercentage")]
public int AutoRenewPercentage { get; set; } = 80; //This feature is only available when the certificate is being stored in Azure
}
CertificateCreateRequestModel
object we send the certificate request to Azure. //Request Certificate from EZCA
HttpResponseMessage response = await _httpService.PostAPIAsync($"{_portalURL}api/CA/RequestSSLCertificate",
JsonSerializer.Serialize(request), await GetTokenAsync());
APIResultModel? result = JsonSerializer.Deserialize<APIResultModel>(await response.Content.ReadAsStringAsync());
if (result != null)
{
if(result.Success)
{
X509Certificate2 certificate = ImportCertFromPEMString(result.Message);
return certificate.CopyWithPrivateKey(key);
}
}
Now that we have created the certificate, all we have to do is authenticate to Azure and send some messages to test that it is working.
To Authenticate with Azure we will need the Microsoft.Azure.Devices.Client
NuGet package.
DeviceAuthenticationWithX509Certificate
with the device ID and the certificate issued for that device. DeviceAuthenticationWithX509Certificate auth = new (deviceID, deviceCertificate);
var deviceClient = DeviceClient.Create(_iotHubEndpoint, auth, TransportType.Mqtt);
static async Task SendEventAsync(DeviceClient deviceClient, string deviceId)
{
//ref https://docs.microsoft.com/en-us/azure/iot-hub/tutorial-x509-test-certificate
string dataBuffer;
int MESSAGE_COUNT = 5;
Random rnd = new Random();
float temperature;
float humidity;
int TEMPERATURE_THRESHOLD = 30;
Console.WriteLine("Device sending {0} messages to IoTHub...\n", MESSAGE_COUNT);
// Iterate MESSAGE_COUNT times to set random temperature and humidity values.
for (int count = 0; count < MESSAGE_COUNT; count++)
{
// Set random values for temperature and humidity.
temperature = rnd.Next(20, 35);
humidity = rnd.Next(60, 80);
dataBuffer = string.Format("{{\"deviceId\":\"{0}\",\"messageId\":{1},\"temperature\":{2},\"humidity\":{3}}}",
deviceId, count, temperature, humidity);
Message eventMessage = new Message(Encoding.UTF8.GetBytes(dataBuffer));
eventMessage.Properties.Add("temperatureAlert",
(temperature > TEMPERATURE_THRESHOLD) ? "true" : "false");
Console.WriteLine("\t{0}> Sending message: {1}, Data: [{2}]",
DateTime.Now.ToLocalTime(), count, dataBuffer);
// Send to IoT Hub.
await deviceClient.SendEventAsync(eventMessage);
}
}
In this tutorial, we were able to manually connect a device to Azure and send events. While this is great for testing, it cannot scale to the millions of IoT devices that are registered every day. Check our guide Automatically Provision IoT Devices in Azure IoT With a Trusted CA to learn how you can automate all the manual steps on this tutorial.