Azure Event Grid MQTT-Certificates

When configuring Root, Intermediate and Device certificates for my Azure Event Grid Devices using the smallstep step-ca or OpenSSL I made mistakes/typos which broke my deployment and in the end I was copying and pasting commands from Windows Notepad.

For one test deployment it took me an hour to generate the Root, Intermediate and a number of Devices certificates which was a waste of time. At this point I decided investigate writing some applications to simplify the process.

After some searching I stumbled across CREATING CERTIFICATES FOR X.509 SECURITY IN AZURE IOT HUB USING .NET CORE by Damien Bod which showed how to generate certificates for Azure IoT Hub solutions using his NuGet package Certificate Manager.

The AzureIoTHubDps repository had a sample showing how to generate the certificate chain for Azure IoT Hub devices. It worked really well but I accidentally overwrote my Root and Intermediate certificates, there were some “magic numbers” and hard coded passwords (it was a sample) so I decided to “chop” the sample up into three command line applications.

static void Main(string[] args)
{
   var serviceProvider = new ServiceCollection()
         .AddCertificateManager()
         .BuildServiceProvider();

   // load the app settings into configuration
   var configuration = new ConfigurationBuilder()
         .AddJsonFile("appsettings.json", false, true)
         .AddUserSecrets<Program>()
   .Build();

   _applicationSettings = configuration.GetSection("ApplicationSettings").Get<Model.ApplicationSettings>();
//------
   Console.WriteLine($"validFrom:{validFrom} ValidTo:{validTo}");

   var serverRootCertificate = serviceProvider.GetService<CreateCertificatesClientServerAuth>();

   var root = serverRootCertificate.NewRootCertificate(
         new DistinguishedName { 
               CommonName = _applicationSettings.CommonName,
               Organisation = _applicationSettings.Organisation,
               OrganisationUnit = _applicationSettings.OrganisationUnit,
               Locality = _applicationSettings.Locality,
               StateProvince  = _applicationSettings.StateProvince,
               Country = _applicationSettings.Country
         },
         new ValidityPeriod { 
         ValidFrom = validFrom,
         ValidTo = validTo,
         },
         _applicationSettings.PathLengthConstraint,
         _applicationSettings.DnsName);
   root.FriendlyName = _applicationSettings.FriendlyName;

   Console.Write("PFX Password:");
   string password = Console.ReadLine();
   if ( String.IsNullOrEmpty(password))
   {
      Console.WriteLine("PFX Password invalid");
      return;
   }

   var exportCertificate = serviceProvider.GetService<ImportExportCertificate>();

   var rootCertificatePfxBytes = exportCertificate.ExportRootPfx(password, root);
   File.WriteAllBytes(_applicationSettings.RootCertificateFilePath, rootCertificatePfxBytes);

   Console.WriteLine($"Root certificate file:{_applicationSettings.RootCertificateFilePath}");
   Console.WriteLine("press enter to exit");
   Console.ReadLine();
}

The application’s configuration was split between application settings file(certificate file paths, validity periods, Organisation etc.) or entered at runtime ( certificate filenames, passwords etc.) The first application generates a Root Certificate using the distinguished name information from the application settings, plus file names and passwords entered by the user.

Root Certificate generation application output

The second application generates an Intermediate Certificate using the Root Certificate, the distinguished name information from the application settings, plus file names and passwords entered by the user.

static void Main(string[] args)
{
   var serviceProvider = new ServiceCollection()
         .AddCertificateManager()
         .BuildServiceProvider();

   // load the app settings into configuration
   var configuration = new ConfigurationBuilder()
         .AddJsonFile("appsettings.json", false, true)
         .AddUserSecrets<Program>()
   .Build();

   _applicationSettings = configuration.GetSection("ApplicationSettings").Get<Model.ApplicationSettings>();
//------
   Console.WriteLine($"validFrom:{validFrom} be after ValidTo:{validTo}");

   Console.WriteLine($"Root Certificate file:{_applicationSettings.RootCertificateFilePath}");

   Console.Write("Root Certificate Password:");
   string rootPassword = Console.ReadLine();
   if (String.IsNullOrEmpty(rootPassword))
   {
      Console.WriteLine("Fail");
      return;
   }
   var rootCertificate = new X509Certificate2(_applicationSettings.RootCertificateFilePath, rootPassword);

   var intermediateCertificateCreate = serviceProvider.GetService<CreateCertificatesClientServerAuth>();

   var intermediateCertificate = intermediateCertificateCreate.NewIntermediateChainedCertificate(
         new DistinguishedName
         {
            CommonName = _applicationSettings.CommonName,
            Organisation = _applicationSettings.Organisation,
            OrganisationUnit = _applicationSettings.OrganisationUnit,
            Locality = _applicationSettings.Locality,
            StateProvince = _applicationSettings.StateProvince,
            Country = _applicationSettings.Country
         },
      new ValidityPeriod
      {
         ValidFrom = validFrom,
         ValidTo = validTo,
      },
            _applicationSettings.PathLengthConstraint,
            _applicationSettings.DnsName, rootCertificate);
      intermediateCertificate.FriendlyName = _applicationSettings.FriendlyName;

   Console.Write("Intermediate certificate Password:");
   string intermediatePassword = Console.ReadLine();
   if (String.IsNullOrEmpty(intermediatePassword))
   {
      Console.WriteLine("Fail");
      return;
   }

   var importExportCertificate = serviceProvider.GetService<ImportExportCertificate>();

   Console.WriteLine($"Intermediate PFX file:{_applicationSettings.IntermediateCertificatePfxFilePath}");
   var intermediateCertificatePfxBtyes = importExportCertificate.ExportChainedCertificatePfx(intermediatePassword, intermediateCertificate, rootCertificate);
   File.WriteAllBytes(_applicationSettings.IntermediateCertificatePfxFilePath, intermediateCertificatePfxBtyes);

   Console.WriteLine($"Intermediate CER file:{_applicationSettings.IntermediateCertificateCerFilePath}");
   var intermediateCertificatePemText = importExportCertificate.PemExportPublicKeyCertificate(intermediateCertificate);
   File.WriteAllText(_applicationSettings.IntermediateCertificateCerFilePath, intermediateCertificatePemText);

   Console.WriteLine("press enter to exit");
   Console.ReadLine();
}
Intermediate Certificate generation application output
Uploading the Intermediate certificate to Azure Event Grid

The third application generates Device Certificates using the Intermediate Certificate, distinguished name information from the application settings, plus device id, file names and passwords entered by the user.

static void Main(string[] args)
{
   var serviceProvider = new ServiceCollection()
         .AddCertificateManager()
         .BuildServiceProvider();

   // load the app settings into configuration
   var configuration = new ConfigurationBuilder()
         .AddJsonFile("appsettings.json", false, true)
         .AddUserSecrets<Program>()
   .Build();

   _applicationSettings = configuration.GetSection("ApplicationSettings").Get<Model.ApplicationSettings>();
//------
   Console.WriteLine($"validFrom:{validFrom} ValidTo:{validTo}");

   Console.WriteLine($"Intermediate PFX file:{_applicationSettings.IntermediateCertificateFilePath}");

   Console.Write("Intermediate PFX Password:");
   string intermediatePassword = Console.ReadLine();
   if (String.IsNullOrEmpty(intermediatePassword))
   {
      Console.WriteLine("Intermediate PFX Password invalid");
      return;
   }
   var intermediate = new X509Certificate2(_applicationSettings.IntermediateCertificateFilePath, intermediatePassword);

   Console.Write("Device ID:");
   string deviceId = Console.ReadLine();
   if (String.IsNullOrEmpty(deviceId))
   {
      Console.WriteLine("Device ID invalid");
      return;
   }

   var createClientServerAuthCerts = serviceProvider.GetService<CreateCertificatesClientServerAuth>();

   var device = createClientServerAuthCerts.NewDeviceChainedCertificate(
         new DistinguishedName
         {
            CommonName = deviceId,
            Organisation = _applicationSettings.Organisation,
            OrganisationUnit = _applicationSettings.OrganisationUnit,
            Locality = _applicationSettings.Locality,
            StateProvince = _applicationSettings.StateProvince,
            Country = _applicationSettings.Country
         },
      new ValidityPeriod
      {
         ValidFrom = validFrom,
         ValidTo = validTo,
      },
      deviceId, intermediate);
   device.FriendlyName = deviceId;

   Console.Write("Device PFX Password:");
   string devicePassword = Console.ReadLine();
   if (String.IsNullOrEmpty(devicePassword))
   {
      Console.WriteLine("Fail");
      return;
   }

   var importExportCertificate = serviceProvider.GetService<ImportExportCertificate>();

   string devicePfxPath = string.Format(_applicationSettings.DeviceCertificatePfxFilePath, deviceId);

   Console.WriteLine($"Device PFX file:{devicePfxPath}");
   var deviceCertificatePath = importExportCertificate.ExportChainedCertificatePfx(devicePassword, device, intermediate);
   File.WriteAllBytes(devicePfxPath,  deviceCertificatePath);

   Console.WriteLine("press enter to exit");
   Console.ReadLine();
}
Device Certificate generation application output
Uploading the Intermediate certificate to Azure Event Grid

These applications wouldn’t have been possible without Damien Bod’s CREATING CERTIFICATES FOR X.509 SECURITY IN AZURE IOT HUB USING .NET CORE blog post, and his Certificate Manager NuGet package.