0
votes

I'm trying to migrate an existing solution with Azure IoT Hub to use Azure IoT Hub Device Provisioning Service (DPS).

The devices authenticate themself using a X.509 Self-Signed certificate.

For testing purposes I can generate a certificate using

openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out certificate.pem
  • Common Name set to mything1
  • All other fields blank

I can then combine those files into one file using

openssl pkcs12 -inkey key.pem -in certificate.pem -export -out certificate.p12

Choosing password as the password.

I then have 3 files.

  • certificate.p12 containing both certificate and private key
  • certificate.pem containing the certificate
  • key.pem containing the private key

I can then register this device directly in the IoT Hub using

string ThingId = "mything1";

// Register device directly in IoT hub.
{
    var certificate = new X509Certificate2(File.ReadAllBytes("certificate.pem"));
    string thumb1 = certificate.Thumbprint;

    using (var hasher = SHA256.Create())
    {
        using (var registryManager = RegistryManager.CreateFromConnectionString(iotHubConnectionString))
        {
            await registryManager.AddDeviceAsync(new Device(ThingId)
            {
                Authentication = new AuthenticationMechanism()
                {
                    X509Thumbprint = new X509Thumbprint()
                    {
                        PrimaryThumbprint = thumb1,
                        SecondaryThumbprint = thumb1,
                    }
                },
            });
        }
    }
}

Then after the device is registered I can connect to the IoT Hub as the device using the certificate as device authentication.

// Vertifying that certificate is ok etc
// by connecting to an IoT Hub
{
    var auth = new DeviceAuthenticationWithX509Certificate(ThingId, new X509Certificate2(File.ReadAllBytes("certificate.p12"), "password"));

    var client = DeviceClient.Create(iotHubHostname, auth,
        new ITransportSettings[] { new AmqpTransportSettings(Microsoft.Azure.Devices.Client.TransportType.Amqp_WebSocket_Only) });

    // Report a value just to make sure it works.
    TwinCollection props = new TwinCollection();
    props["Hello"] = "World";
    await client.UpdateReportedPropertiesAsync(props, new CancellationTokenSource(1000 * 30).Token);
}

And this works and is the solution I have today. Now trying to do the same thing but using DPS.

First create an individual enrollment for the device:

// Create an individual enrollment for device
using (var provisioningServiceClient = 
    ProvisioningServiceClient.CreateFromConnectionString(provisioningServiceConnectionString))
{
    var attestation = X509Attestation.CreateFromClientCertificates(new X509Certificate2(File.ReadAllBytes("certificate.pem")));

    IndividualEnrollment individualEnrollment =
        new IndividualEnrollment(
                ThingId,
                attestation);

    IndividualEnrollment individualEnrollmentResult =
        await provisioningServiceClient.CreateOrUpdateIndividualEnrollmentAsync(individualEnrollment);
}

Then after the enrollment is created, I should be able to provison myself as a device.

string globalDeviceEndpoint = "global.azure-devices-provisioning.net";
            
using (var security = new SecurityProviderX509Certificate(new X509Certificate2(File.ReadAllBytes("certificate.p12"), "password")))
{
    ProvisioningDeviceClient provClient = ProvisioningDeviceClient.Create(globalDeviceEndpoint, 
        s_idScope, 
        security, 
        new ProvisioningTransportHandlerHttp());
    
    DeviceRegistrationResult result = await provClient.RegisterAsync(); // Throws exception

    Console.WriteLine($"ProvisioningClient AssignedHub: {result.AssignedHub}; DeviceId: {result.DeviceId}");
}

But this throws an unauthorized exception:

Unhandled exception. Microsoft.Azure.Devices.Provisioning.Client.ProvisioningTransportException: {"errorCode":401002,"trackingId":"f7ec27c8-dbad-4600-8b47-f46aaa0160de","message":"Unauthorized","timestampUtc":"2021-05-20T14:09:00.8491407Z"}
 ---> Microsoft.Rest.HttpOperationException: Operation returned an invalid status code 'Unauthorized'
   at Microsoft.Azure.Devices.Provisioning.Client.Transport.RuntimeRegistration.RegisterDeviceWithHttpMessagesAsync(String registrationId, String idScope, DeviceRegistration deviceRegistration, Nullable`1 forceRegistration, Dictionary`2 customHeaders, CancellationToken cancellationToken)
   at Microsoft.Azure.Devices.Provisioning.Client.Transport.RuntimeRegistrationExtensions.RegisterDeviceAsync(IRuntimeRegistration operations, String registrationId, String idScope, DeviceRegistration deviceRegistration, Nullable`1 forceRegistration, CancellationToken cancellationToken)
   at Microsoft.Azure.Devices.Provisioning.Client.Transport.ProvisioningTransportHandlerHttp.RegisterAsync(ProvisioningTransportRegisterMessage message, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at Microsoft.Azure.Devices.Provisioning.Client.Transport.ProvisioningTransportHandlerHttp.RegisterAsync(ProvisioningTransportRegisterMessage message, CancellationToken cancellationToken)
   at DPSWorkShop.Program.Main(String[] args) in C:\Work\Elux\DPSWorkShop\DPSWorkShop\Program.cs:line 91
   at DPSWorkShop.Program.<Main>(String[] args)

What am I doing wrong here?

EDIT: As answered by Rajeev here: https://stackoverflow.com/a/67641996/6877590 The problem is the basic constraint: CA:true.

To solve this (for testing)

  1. edit the file /usr/lib/ssl/openssl.cnf
  2. Find [ v3_ca ]
  3. Change CA:true to CA:false
1

1 Answers

0
votes

The leaf certificate presented by the device should not contain the 'CA' Basic constraint. See RFC 5280. This is the reason DPS is throwing an 'Unauthorized' exception.