Registering Devices With IoT Hub Device Provisioning Service

Introduction

In this article, we'll learn about the capabilities that Microsoft Azure provides to help us get our devices connected to the cloud. We'll start things off by looking at the device provisioning process at a high level. We'll learn about the roles involved, as well as the steps that need to be performed. We'll move on to looking at the solution Microsoft Azure provides for dealing with device provisioning challenges, Azure IoT Hub Device Provisioning Service. We'll learn about the core capabilities that this service provides, and see how these capabilities can help us streamline our device provisioning process. For a device to connect to our hub, our device needs some way to uniquely, securely identify itself. We'll look at the various methods that the Device Provisioning Service supports. Finally, we'll work together to create, configure, and use an instance of the Device Provisioning Service using a virtual device application that I've already created for you. Let's get started.

Road map

  1. Azure IoT hub device provisioning system setup
  2. Configuring Azure IoT Hub DPS
  3. Creating an individual enrollment
  4. Auto-provisioning a device

Prerequisites

An existing Azure IoT Hub service in the same resource group as our Device Provisioning Service. You can find the method of creating an IoT hub here.

Azure IoT hub DPS setup

I'm going to start by going to the Azure portal to create a new instance of the device provisioning service. Let's start by going to our resource group and let's add a new resource. We want to add a device provisioning service. Let's quickly create and now we can configure our service.

IoT hub device provisioning service

Click on create and you will be directed to the service creation screen. Enter all the relevant information for the service. Make sure the resource group is the same as that of our IoT Hub Click. Click on Create.

Click on create

Configuring Azure IoT hub DPS

Now that we have our service, we need to tell it about which IoT hub or hubs we want to use. We just finished creating our device provisioning service. If you don't see it on your dashboard, you can get to it by going to your resource groups and drilling into the resource group that contains our service. Now what we need to do is link it to the IoT that we have that we already created. We can do that by going to (Our DPS ) > Linked IoT Hubs.

Linked IOT hubs

After that click the add button and then we can choose our existing hub.

Add link to IOT hub

I do want to point out that we can link any hub from our subscription or any subscription we have access to you. It doesn't even have to be in the same region we chose for our device provisioning service. We also need to choose an access policy. We could define a custom policy, but the built-in IoT owner policy will work just fine. Our IoT device provisioning service and our hub are now linked. That means it's time to enroll a device.

Creating an individual enrollment

Now that we have a hub, and we have an instance of the Device Provisioning Service, that means we're ready to enroll a device. We are going to enroll a device manually. In a real solution, we would either enroll a trusted root certificate, which would allow any device signed by that certificate to be enrolled, or we would provide our device manufacturer with some automated mechanism for uploading device identities once they've been extracted from the physical hardware. You will need the. NET Core SDK version 2 or greater if you want to implement the code.

You will need to create a .Net Core project, which will have a Program.cs, and add 2 files, name one Device.cs and the other CertificateFactory.cs.

Program.cs code

using System;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
namespace DeviceProvisioningSample
{
    class Program
    {
        static async Task Main(string[] args)
        {
            if (args == null || args.Length < 1)
            {
                Console.WriteLine("You must specify an action!");
                return;
            }
            if (args[0] == "setup")
            {
                CertificateFactory.CreateTestCert();
            }
            else
            {
                var scopeId = args[0];
                var device = new Device(scopeId);
                await device.Provision();
                await device.ConnectAndRun();
            }
        }
    }
}

Device.cs code

using System;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.Devices.Provisioning.Client;
using Microsoft.Azure.Devices.Provisioning.Client.Transport;
using Microsoft.Azure.Devices.Shared;
using Microsoft.Azure.Devices.Client;
namespace DeviceProvisioningSample
{
    public class Device
    {
        private string scopeId;
        private string assignedHub;
        private DeviceAuthenticationWithX509Certificate auth;
        public Device(string scopeId)
        {
            this.scopeId = scopeId;
        }
        public async Task Provision()
        {
            var certificate = LoadPrivateKey("key.pfx");
            using (var securityProvider = new SecurityProviderX509Certificate(certificate))
            using (var transport = new ProvisioningTransportHandlerAmqp(TransportFallbackType.TcpOnly))
            {
                var client = ProvisioningDeviceClient.Create("global.azure-devices-provisioning.net", this.scopeId, securityProvider, transport);
                var result = await client.RegisterAsync();
                Console.WriteLine($"Provisioning result: {result.Status}");
                if (result.Status != ProvisioningRegistrationStatusType.Assigned)
                {
                    throw new InvalidOperationException("Something went wrong while trying to provision.");
                }
                this.assignedHub = result.AssignedHub;
                this.auth = new DeviceAuthenticationWithX509Certificate(result.DeviceId, securityProvider.GetAuthenticationCertificate());
            }
        }
        private static X509Certificate2 LoadPrivateKey(string key)
        {
            if (!File.Exists(key))
            {
                Console.WriteLine("No private key found.  Execute `dotnet run setup` first.");
                Environment.Exit(-1);
            }
            var certificateCollection = new X509Certificate2Collection();
            certificateCollection.Import(key);
            foreach (var element in certificateCollection)
            {
                if (element.HasPrivateKey)
                {
                    return element;
                }
                else
                {
                    element.Dispose();
                }
            }
            throw new InvalidOperationException("No private key found.  Execute `dotnet run setup` first.");
        }
        internal async Task ConnectAndRun()
        {
            using (var client = DeviceClient.Create(this.assignedHub, this.auth, TransportType.Amqp))
            {
                Console.Write($"Connecting to hub: {this.assignedHub}... ");
                await client.OpenAsync();
                Console.WriteLine("Connected!");
                Console.Write("Sending D2C message... ");
                await client.SendEventAsync(new Message(Encoding.UTF8.GetBytes("Hello from a provisioned device!")));
                Console.WriteLine("Sent!");
                Console.Write("Closing connection... ");
                await client.CloseAsync();
                Console.WriteLine("Connection Closed!");
            }
        }
    }
}

CertificateFactory.cs code

using System;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
namespace DeviceProvisioningSample
{
    public static class CertificateFactory
    {
        public static void CreateTestCert()
        {
            var ecdsa = ECDsa.Create();
            var request = new CertificateRequest("cn=globomantics-prov-dev-01", ecdsa, HashAlgorithmName.SHA256);
            var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(1));
            // Create PFX (PKCS #12) with private key
            Console.WriteLine("Writing private key to key.pfx...");
            File.WriteAllBytes("key.pfx", certificate.Export(X509ContentType.Pfx));
            // Create Base 64 encoded CER (public key only)
            Console.WriteLine("Writing public certificate to cert.cer...");
            File.WriteAllText("cert.cer", "-----BEGIN CERTIFICATE-----\r\n"
                + Convert.ToBase64String(certificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks)
                + "\r\n-----END CERTIFICATE-----");
        }
    }
}

We're going to add a new device enrollment through the Azure portal.

We just need to go to our Device Provisioning Service and then into Manage Enrollments.

Individual enrollments

There are two tabs here, one for group enrollments and one for individual enrollments. We haven't enrolled anything yet, so both are empty. We're going to add an individual enrollment.

Upload certificate

We're going to go with X. 509 for our attestation mechanism, but we could switch to another option if we wanted. Since we're using X. 509 attestation, we have to upload a certificate, and that's where the .Net Core application that we’ve created comes in.

We're going to use the. NET Core CLI to run this project. So let's go ahead and navigate into the folder with our project. Now let's go over to our terminal and let's change to that directory. We can do a cd, and then we can paste in the path that we copied. There we go. Now let's execute this command to generate an X. 509 certificate for us to test with, the dotnet run setup.

Command to generate

And as we can see from the output, we now have a private key and a certificate.

Private key and a certificate key

Let's choose that certificate and click Open, and now our certificate has been uploaded.

Certificate uploaded

There is no need to upload a secondary certificate for the sake of this article.

Further down is where we could assign a device ID. If we don't do this, it will use the common name from the X. 509 certificate. I'm going to go ahead and give mine a name, dps-sample-01.

DSP sample

We can also choose how we want this device to be assigned to a hub. We only have a single hub, so it doesn't matter what we select, but if we had more than one hub we could choose a different strategy to control how it was assigned to one of the available linked hubs. Here select the “Evenly weighted distribution” option.

Evenly weighted distribution

The final set of options deals with how this device should be configured if we're using device twins. We're not going to cover device twins in this article. For now, everything is configured the way we want, so all that's left to do is click this Save button, which is up at the top. And there we go, our device is now enrolled. All that's left is to provision it, which we'll do in the next.

Auto-provisioning a device

So far, we've configured Azure IoT Hub Device Provisioning Service, we've enrolled our device, and we're now ready for the moment of truth, auto-provisioning a device. We don't have a real device for us to use, but that's okay. The sample application we created can act as a virtual device too. So let's get to it. Let's use this virtual device to see auto-provisioning in action. We've already enrolled our virtual devices certificate with our Device Provisioning Service, but there's still one more thing we need to do before we can test this out. We need to tell our device how to connect to the Device Provisioning Service. For that, we'll need to go back over to the portal. Most of the heavy lifting is done for us because I used the Azure IoT Hub SDK, so all we need to do is go into our Device Provisioning Service and then copy this value here, the ID Scope.

DPS

Now I can go back over to my terminal, and now we’re going to run our app again, but this time I'm going to pass in that ID Scope like this, donet run, and then the ID Scope that I copied from the Device Provisioning Service.

Device provisioning

Let's go ahead and run this command, and then we'll watch the output to see what's happening. As you can see, our device has now been assigned to a hub, and it's able to send data to the hub. So everything looks good here, but let's double-check and make sure. If the Device Provisioning Service truly did its thing, then we should see that our device has been added to our hub. So let's go back over to the Azure portal, let's go back over to our dashboard, and then let's go into our IoT hub, not the Device Provisioning Service. We want to see our list of devices, so let's scroll down a bit and go into IoT devices here under Explorers. And sure enough, there's our device.

IOT devices

If you'll recall, when we enrolled our device I named my device dps-sample-01, and that's what the Device Provisioning Service assigned as the device ID. So everything here looks good.

Summary

This article was all about the Azure IoT Hub Device Provisioning Service. We learned about the device provisioning process, its challenges, and how Microsoft's solution addresses those challenges. We learned how to create and configure a new instance of the Device Provisioning Service through the Azure portal, and we saw how to enroll a device using X. 509 certificates. We also discussed the other methods of device enrollment that the Device Provisioning Service supports, including Trusted Platform Modules and symmetric keys. We wrapped things up by putting all the pieces together. We enrolled the device and then watched as a virtual device connected to the Device Provisioning Service and our hub. We can use IoT Hub to enable two-way communication with the devices, and now with the Device Provisioning Service, we can easily scale to the high volumes of devices.