Using Azure Device Provisioning Service with Self Signed X.509 Certificates – Part 3

Creating X.509 Certificates with OpenSSL

Contents:

  1. A Primer on DPS
  2. Creating DPS and IoT Hub Instances and Linking Them
  3. Creating X.509 Certificates with OpenSSL (This Post)
  4. Uploading to the Cloud
  5. Verifying X.509 Certificates (Proof of Possession)
  6. Creating X.509 Enrollment Groups
  7. Setting up a Simulated IoT Device (C#)

Certificates: The Security Foundation

Before we run any OpenSSL commands, let’s talk about why we use certificates and why we use three of them instead of just one. This architecture is industry standard and makes your IoT deployment more secure and easier to manage.

Why a Certificate Hierarchy?

Before we jump into commands, let’s understand why we need THREE certificates instead of just one.

The Problem with a Single Certificate

If you create just one certificate for your device:

  • ❌ If compromised, you must replace it everywhere
  • ❌ Hard to scale to many devices
  • ❌ No way to revoke subset of devices
  • ❌ Root key is constantly used (higher risk)

The Solution: 3-Tier PKI Hierarchy

┌────────────────┐
│   Root CA      │  Trust anchor, kept offline
│  (10 years)    │  Verified once in DPS
└───────┬────────┘
        │ signs

┌────────────────┐
│ Intermediate   │  Day-to-day signing
│    CA          │  Can be rotated
│  (5 years)     │  Used in enrollment group
└───────┬────────┘
        │ signs

┌────────────────┐
│ Device Cert    │  Identifies this device
│  (1 year)      │  Easy to renew
└────────────────┘

Why This Approach?

Root CA:

  • Long-lived (10 years)
  • Private key stays in secure storage (HSM or offline)
  • Only used to sign intermediate CAs
  • If compromised, entire PKI must be rebuilt (rare operation)

Intermediate CA:

  • Medium-lived (5 years)
  • Used for day-to-day certificate signing
  • Can be revoked and replaced without touching root
  • Uploaded and verified in DPS

Device Certificate:

  • Short-lived (1 year)
  • Easy to renew before expiration
  • If compromised, only affects one device
  • Signed by intermediate (not root)

Prerequisites

Install OpenSSL

If you already have Git installed on your local machine, then it’s recommended you use that. We can include it in the path for any openssl commands by running the following;

PowerShell
env:Path = "C:\Program Files\OpenSSL-Win64\bin;$env:Path"

If you don’t have Git installed, you can also use WinGet;

PowerShell
winget install ShiningLight.OpenSSL.Full

Alternatively, depending on your requirements, the following methods will work too;

Windows:

  • Download from Win32OpenSSL
  • Or use Git Bash (includes OpenSSL)
  • Or use WSL (Windows Subsystem for Linux)

macOS:

Bash
brew install openssl

Linux:

Bash
sudo apt-get install openssl  # Debian/Ubuntu
sudo yum install openssl      # RHEL/CentOS

Verify installation:

PowerShell
openssl version
# Expected: OpenSSL 3.0.x or higher

Step 1: Create Directory Structure

First we’ll setup the folders to store our certs.

PowerShell
# Create organized directory structure
New-Item -ItemType Directory -Force -Path certs/root, certs/intermediate, certs/device, certs/issued

# Verify structure
Get-ChildItem certs/ -Directory
# Output:
# Directory: certs
# Mode    Name
# ----    ----
# d----   device
# d----   intermediate
# d----   issued
# d----   root

Step 2: Generate Root CA (Self-Signed)

Next we’ll generate the Root CA Certificate what all our other certificates will be based on… The root CA is the trust anchor for our entire certificate chain.

PowerShell
openssl req -x509 -new -nodes `
  -newkey rsa:4096 `
  -keyout certs/root/root.key `
  -out certs/root/root.pem `
  -days 3650 `
  -sha256 `
  -subj "/CN=My-Root-CA" `
  -addext "basicConstraints=critical,CA:true,pathlen:1" `
  -addext "keyUsage=critical,keyCertSign,cRLSign" `
  -addext "subjectKeyIdentifier=hash" `
  -addext "authorityKeyIdentifier=keyid:always"

What this command does:

  • -x509: Create self-signed certificate
  • -newkey rsa:4096: Generate 4096-bit RSA key pair
  • -days 3650: Valid for 10 years
  • -subj "/CN=My-Root-CA": Certificate common name
  • pathlen:1: Can sign 1 level down (intermediate)
  • keyCertSign: Can sign other certificates
Verify that the Root CA was created correctly:
PowerShell
openssl x509 -in certs/root/root.pem -text -noout

Look for:

  • ✅ Issuer: CN = My-Root-CA
  • ✅ Subject: CN = My-Root-CA (self-signed)
  • ✅ CA:TRUE, pathlen:1

Step 3: Generate Intermediate CA CSR

Now we create an intermediate CA that will be signed by the Root and used to create our Leaf, or Device Certificate.

PowerShell
# Generate intermediate CA private key and CSR
openssl req -new -nodes `
  -newkey rsa:4096 `
  -keyout certs/intermediate/intermediate.key `
  -out certs/intermediate/intermediate.csr `
  -subj "/CN=My-Intermediate-CA"

Create an extension file for the Intermediate Certificate:

This next Powershell command writes a block of OpenSSL extension settings into intermediate-ext.cnf.
Those settings define the certificate as an intermediate CA with the correct key‑usage and constraints.

PowerShell
@"
[v3_intermediate]
basicConstraints = critical,CA:true,pathlen:0
keyUsage = critical,keyCertSign,cRLSign
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
"@ | Set-Content -Path certs/intermediate/intermediate-ext.cnf -Encoding ASCII

Sign the Intermediate Certificate with the Root Certificate:

Now we use our Root CA to sign the Intermediate CSR, producing a fully trusted Intermediate Certificate we can use to create Leaf Certificates.

PowerShell
openssl x509 -req `
  -in certs/intermediate/intermediate.csr `
  -CA certs/root/root.pem `
  -CAkey certs/root/root.key `
  -CAcreateserial `
  -out certs/intermediate/intermediate.pem `
  -days 1825 `
  -sha256 `
  -extfile certs/intermediate/intermediate-ext.cnf `
  -extensions v3_intermediate

What’s different:

  • pathlen:0: Cannot sign more CAs (end of chain)
  • days 1825: Valid for 5 years
  • Signed by root (not self-signed)

Verify that the Intermediate Certificate was created successfully:

We can now verify that the Intermediate Certificate was correctly signed by the Root, then inspect its full decoded contents.

PowerShell
# Verify signature
openssl verify -CAfile certs/root/root.pem certs/intermediate/intermediate.pem
# Expected: certs/intermediate/intermediate.pem: OK

# View certificate
openssl x509 -in certs/intermediate/intermediate.pem -text -noout

Look for:

  • ✅ Issuer: CN = My-Root-CA (signed by root)
  • ✅ Subject: CN = My-Intermediate-CA
  • ✅ CA:TRUE, pathlen:0

Step 4: Generate Leaf (Device) Certificate

Now we create a Device Certificate signed by the Intermediate CA.

PowerShell
# Generate device private key and CSR
openssl req -new -nodes `
  -newkey rsa:2048 `
  -keyout certs/device/device.key `
  -out certs/device/device.csr `
  -subj "/CN=my-device-001"

⚠️ Important: The CN (Common Name) my-device-001 must match your device’s Registration ID in code that we’ll set later.

Create an extension file for our Device Certificate:

In the same way as the previous stages, we create an extension file to specify the usage and identity fields for our Device Certificate.

PowerShell
@"
[v3_req]
basicConstraints = CA:FALSE
keyUsage = critical,digitalSignature,keyEncipherment
extendedKeyUsage = clientAuth
subjectAltName = DNS:my-device-001
authorityKeyIdentifier = keyid:always,issuer
"@ | Set-Content -Path certs/device/device-ext.cnf -Encoding ASCII

Sign Device Certificate with the Intermediate Certificate:

And now we can sign our Device Certificate with the Intermediate Certificate to add it to the Chain, producing a fully trusted Device Certificate.

PowerShell
openssl x509 -req `
  -in certs/device/device.csr `
  -CA certs/intermediate/intermediate.pem `
  -CAkey certs/intermediate/intermediate.key `
  -CAcreateserial `
  -out certs/device/device.pem `
  -days 365 `
  -sha256 `
  -extfile certs/device/device-ext.cnf `
  -extensions v3_req

What’s different:

  • CA:FALSE: This is NOT a CA certificate
  • clientAuth: Used for TLS client authentication
  • days 365: Valid for 1 year (easier to rotate)
  • -newkey rsa:2048: Smaller key for devices (faster)

Verify device certificate:

Let’s verify that our Device Certificate was create successfully.

PowerShell
# Verify signature
openssl verify `
  -CAfile certs/root/root.pem `
  -untrusted certs/intermediate/intermediate.pem `
  certs/device/device.pem
# Expected: certs/device/device.pem: OK

# View certificate
openssl x509 -in certs/device/device.pem -text -noout

Look for:

  • ✅ Issuer: CN = My-Intermediate-CA
  • ✅ Subject: CN = my-device-001
  • ✅ CA:FALSE
  • ✅ X509v3 Extended Key Usage: TLS Web Client Authentication

Step 5: Create Certificate Chains

Devices need to present the full certificate chain during TLS handshake.

PowerShell
# Chain without root (device + intermediate)
Get-Content certs/device/device.pem, certs/intermediate/intermediate.pem |
  Set-Content -Path certs/device/device-chain.pem -Encoding ASCII

# Full chain (device + intermediate + root)
Get-Content certs/device/device.pem, certs/intermediate/intermediate.pem, certs/root/root.pem |
  Set-Content -Path certs/device/device-full-chain.pem -Encoding ASCII

When to use which:

  • device.pem: Device certificate only (sometimes needed)
  • chain.pem: Intermediate + root (for DPS TLS)
  • device-full-chain.pem: Complete chain (most common)

Step 6: View Certificate Details

Finally we can run a few local sanity checks on the device certificate, such as confirming its fingerprint, validity dates, and ensuring the chain is cryptographically sound.

This isn’t the same as verifying a certificate in DPS – We’ll come to that later… But it does verify that the certificate and its chain are structurally correct before you hand it over to DPS for the real enrolment checks.

PowerShell
# Get certificate thumbprint (fingerprint)
openssl x509 -in certs/device/device.pem -fingerprint -noout
# SHA256 Fingerprint=AB:CD:EF:...

# Check expiration dates
openssl x509 -in certs/device/device.pem -noout -dates
# notBefore=Jan 21 10:00:00 2026 GMT
# notAfter=Jan 21 10:00:00 2027 GMT

# Verify the entire chain
openssl verify -CAfile certs/root/root.pem `
  -untrusted certs/intermediate/intermediate.pem `
  certs/device/device.pem
# certs/device/device.pem: OK

Understanding PEM Format

All our certificates are in PEM (Privacy-Enhanced Mail) format:

-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAKJ3mE...
(Base64 encoded certificate data)
...
-----END CERTIFICATE-----

Why PEM?

  • ✅ Text format (easy to view, copy, paste)
  • ✅ Standard format for Azure and most systems
  • ✅ Can concatenate multiple certs in one file

File Summary

After completing this section, you should have the following files and folders:

certs/
├── root/
│   ├── root.key              # Root private key (KEEP SECURE!)
│   ├── root.pem              # Root certificate (upload to DPS)
│   └── root.pem.srl          # Serial number file
├── intermediate/
│   ├── intermediate.key      # Intermediate private key (KEEP SECURE!)
│   ├── intermediate.pem      # Intermediate certificate (upload to DPS)
│   ├── intermediate.csr      # CSR (can delete)
│   ├── intermediate-ext.cnf  # Extension file (can delete)
│   └── intermediate.pem.srl  # Serial number file
└── device/
    ├── device.key            # Device private key (deploy to device)
    ├── device.pem            # Device certificate (deploy to device)
    ├── device.csr            # CSR (can delete)
    ├── device-ext.cnf        # Extension file (can delete)
    ├── chain.pem             # Intermediate + root chain
    └── device-full-chain.pem # Full chain (deploy to device)

Security Best Practices

🔒 Root CA private key (root.key):

  • Most sensitive file
  • Should be stored offline or in HSM
  • Never deploy to devices
  • Only used for signing intermediate CAs

🔒 Intermediate CA private key (intermediate.key):

  • Used for day-to-day signing
  • Store securely (not on public servers)
  • Never deploy to devices

🔒 Device private key (device.key):

  • Deploy to device securely
  • Unique per device
  • Never share between devices

⚠️ Never commit private keys to source control!

Git Ignore File

If you’re using git to source control perhaps a parent folder, then you must make sure not to check your certificates in to your repo, otherwise you’re certificates and any devices could be comprimised.

Add the following to an .gitignore file:

certs/**/*.key
certs/**/*.csr

Or create/update via PowerShell:

PowerShell
Add-Content .gitignore "`ncerts/**/*.key`ncerts/**/*.csr"

Next Steps

Now that we have our certificate hierarchy, we’ll upload the CA certificates to DPS and verify them.

Next: Uploading to the Cloud >