This guide explains how to configure mutual TLS (mTLS) authentication for the Secure Boot Report Proxy Azure Function in a multi-tier PKI environment with multiple Issuing CAs. Certificate authentication provides defense-in-depth security by validating client identity through X.509 certificates in addition to API key authentication.
- PKI Architecture Overview
- Certificate Validation Layers
- Configuration Parameters
- Multi-Tier PKI Setup
- Step-by-Step Configuration
- Client Configuration
- Testing & Validation
- Troubleshooting
- Best Practices
- Security Considerations
┌─────────────────────────────────────┐
│ Root CA (Offline, Secure) │
│ CN=Contoso Root CA │
│ Validity: 20 years │
└─────────────────┬───────────────────┘
│
┌────────────┼────────────┬──────────────┐
│ │ │ │
┌────▼─────┐ ┌───▼──────┐ ┌──▼───────┐ ┌────▼─────┐
│Issuing │ │Issuing │ │Issuing │ │Issuing │
│CA 01 │ │CA 02 │ │CA 03 │ │CA 04 │
│(Devices) │ │(Servers) │ │(Users) │ │(IoT) │
│Validity: │ │Validity: │ │Validity: │ │Validity: │
│5 years │ │5 years │ │3 years │ │5 years │
└────┬─────┘ └───┬──────┘ └──┬───────┘ └────┬─────┘
│ │ │ │
┌──▼──┐ ┌─▼──┐ ┌──▼──┐ ┌──▼──┐
│Dev │ │Web │ │User │ │IoT │
│Certs│ │Svr │ │Certs│ │Certs│
└─────┘ └────┘ └─────┘ └─────┘
When a client presents a certificate, the certificate chain is validated:
┌──────────────────────────────────────┐
│ 1. End-Entity Certificate (Leaf) │ ← Client certificate
│ CN=DESKTOP-12345.contoso.com │
│ Issued by: Issuing CA 01 │
│ Thumbprint: ABC123DEF... │
└──────────────┬───────────────────────┘
│
┌──────────────▼───────────────────────┐
│ 2. Subordinate CA (Intermediate) │ ← Issuing CA
│ CN=Contoso Device Management CA │
│ Issued by: Root CA │
│ Thumbprint: 789GHI012... │
└──────────────┬───────────────────────┘
│
┌──────────────▼───────────────────────┐
│ 3. Root CA │ ← Trusted Root
│ CN=Contoso Root CA │
│ Self-signed │
│ Thumbprint: XYZ456ABC... │
└──────────────────────────────────────┘
The Azure Function performs four layers of certificate validation:
- Expiration check: Certificate is within validity period (NotBefore ≤ Now ≤ NotAfter)
- Revocation check (optional): Certificate is not revoked (CRL/OCSP)
- Chain building: Certificate chains to a trusted Root CA
- Root CA Subject validation: Ensures chain terminates at expected Root CA
- Root CA Thumbprint validation: Verifies Root CA fingerprint
- Intermediate CA presence: Validates expected Issuing CAs are in chain
- Issuing CA Subject matching: Ensures certificate issued by specific Subordinate CA
- Issuing CA Thumbprint matching: Verifies Subordinate CA fingerprint
- Thumbprint allowlist: Only specific client certificates are accepted
- Explicit authorization: Granular control per device/client
| Variable | Type | Required | Description |
|---|---|---|---|
RequireCertificateAuthentication |
bool | Yes | Enable mutual TLS authentication |
CertificateThumbprints |
string | No* | Comma-separated client cert thumbprints allowlist |
CertificateValidateExpiration |
bool | No | Validate cert expiration (default: true) |
CertificateValidateChain |
bool | No | Validate cert chain to root (default: true) |
CertificateCheckRevocation |
bool | No | Check CRL for revocation (default: false) |
CertificateExpectedCARootName |
string | No** | Expected Root CA Subject name |
CertificateExpectedCARootThumbprint |
string | No** | Expected Root CA thumbprint |
CertificateExpectedSubordinateCAsJson |
JSON | No** | Expected Subordinate CAs (JSON array) |
Notes:
- *If
CertificateThumbprintsis empty, any valid certificate is accepted (not recommended for production) - **At least one CA validation parameter should be configured for production security
[
{
"name": "CN=Contoso Device Management CA, O=Contoso, C=US",
"thumbprint": "ABC123DEF456789ABCDEF123456789ABCDEF1234"
},
{
"name": "CN=Contoso IoT Issuing CA, O=Contoso, C=US",
"thumbprint": "789GHI012JKL345MNO678PQR901STU234VWX567"
}
]Field Requirements:
name: Subject Distinguished Name (DN) of the Subordinate CAthumbprint: SHA-1 fingerprint (40 hex characters, no spaces/colons)- Both fields are optional, but at least one should be specified
- Matching logic:
(name matches OR name is empty) AND (thumbprint matches OR thumbprint is empty)
Business Requirement:
- Devices managed by MDM should use certificates from "Device Management CA"
- IoT devices should use certificates from "IoT Issuing CA"
- Azure Function should only accept device certificates, not IoT certificates
Contoso Root CA
├── Contoso Device Management CA (for managed devices) ← ✅ Accept
├── Contoso IoT Issuing CA (for IoT sensors) ← ❌ Reject
├── Contoso User CA (for user authentication) ← ❌ Reject
└── Contoso Server CA (for web servers) ← ❌ Reject
Option A: Subordinate CA Validation Only
- Accept any certificate issued by "Device Management CA"
- Automatically accepts new devices without updating Function config
- Suitable for: Dynamic device enrollment scenarios
Option B: Subordinate CA + Client Thumbprint Allowlist
- Accept certificates from "Device Management CA" AND in allowlist
- Explicit per-device authorization
- Suitable for: High-security environments with static device inventory
Option C: Full Stack Validation
- Root CA + Subordinate CA + Client Thumbprint
- Maximum security, defense-in-depth
- Suitable for: Critical infrastructure, compliance requirements
From Windows Certificate Store:
# Open Certificate Manager
certmgr.msc
# Navigate to: Trusted Root Certification Authorities → Certificates
# Right-click Root CA → All Tasks → Export...
# Save as: contoso-root-ca.cerUsing PowerShell:
# Load certificate
$rootCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("C:\Temp\contoso-root-ca.cer")
# Extract Subject name
$rootCert.Subject
# Output: CN=Contoso Root CA, O=Contoso Corporation, C=US
# Extract thumbprint
$rootCert.Thumbprint
# Output: A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0Using OpenSSL (Linux/macOS):
# Extract Subject
openssl x509 -in contoso-root-ca.cer -noout -subject
# Output: subject=CN=Contoso Root CA, O=Contoso Corporation, C=US
# Extract thumbprint (SHA-1)
openssl x509 -in contoso-root-ca.cer -noout -fingerprint -sha1
# Output: SHA1 Fingerprint=A1:B2:C3:D4:E5:F6:G7:H8:I9:J0:K1:L2:M3:N4:O5:P6:Q7:R8:S9:T0Repeat the same process for each Issuing CA:
# Example: Device Management CA
$issuingCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("C:\Temp\device-management-ca.cer")
# Subject
$issuingCert.Subject
# Output: CN=Contoso Device Management CA, O=Contoso, C=US
# Thumbprint
$issuingCert.Thumbprint
# Output: 789ABC012DEF345GHI678JKL901MNO234PQR567From device (PowerShell as Administrator):
# List all client certificates with private key
Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.HasPrivateKey } |
Select-Object Subject, Thumbprint, NotAfter, Issuer | Format-List
# Example output:
# Subject : CN=DESKTOP-12345.contoso.com
# Thumbprint : ABC123DEF456789012345678901234567890ABCD
# NotAfter : 12/15/2025 10:30:00 AM
# Issuer : CN=Contoso Device Management CA, O=Contoso, C=USTemplate:
[
{
"name": "<Subordinate CA Subject DN>",
"thumbprint": "<Subordinate CA SHA-1 Thumbprint>"
}
]Example (Single Issuing CA):
[
{
"name": "CN=Contoso Device Management CA, O=Contoso, C=US",
"thumbprint": "789ABC012DEF345GHI678JKL901MNO234PQR567"
}
]Example (Multiple Issuing CAs):
[
{
"name": "CN=Contoso Device Management CA 01, O=Contoso, C=US",
"thumbprint": "789ABC012DEF345GHI678JKL901MNO234PQR567"
},
{
"name": "CN=Contoso Device Management CA 02, O=Contoso, C=US",
"thumbprint": "890DEF123GHI456JKL789MNO012PQR345STU678"
}
]Note: Remove spaces and colons from thumbprints!
PowerShell:
# Minify JSON (remove whitespace)
$json = @"
[
{
"name": "CN=Contoso Device Management CA, O=Contoso, C=US",
"thumbprint": "789ABC012DEF345GHI678JKL901MNO234PQR567"
}
]
"@
$minified = ($json | ConvertFrom-Json | ConvertTo-Json -Compress)
Write-Output $minified
# Output: [{"name":"CN=Contoso Device Management CA, O=Contoso, C=US","thumbprint":"789ABC012DEF345GHI678JKL901MNO234PQR567"}]Azure Portal:
- Navigate to your Function App
- Go to Configuration → General settings
- Set Client certificate mode to Optional or Required
- Optional: Function code validates certificate (allows HTTP health checks)
- Required: App Service enforces certificate (blocks requests without cert)
- Click Save
Azure CLI:
az functionapp update \
--name <function-app-name> \
--resource-group <resource-group> \
--set clientCertEnabled=true \
--set clientCertMode=OptionalRecommendation: Use Optional mode for better logging and control. The Function code will validate certificates and reject invalid ones.
Azure Portal:
- Navigate to Configuration → Application settings
- Add the following settings:
| Name | Value | Example |
|---|---|---|
RequireCertificateAuthentication |
true |
true |
CertificateValidateChain |
true |
true |
CertificateValidateExpiration |
true |
true |
CertificateCheckRevocation |
false |
false ( |
CertificateExpectedCARootName |
Root CA Subject | CN=Contoso Root CA, O=Contoso Corporation, C=US |
CertificateExpectedCARootThumbprint |
Root CA SHA-1 | A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0 |
CertificateExpectedSubordinateCAsJson |
Minified JSON | [{"name":"CN=Contoso Device Management CA, O=Contoso, C=US","thumbprint":"789ABC..."}] |
CertificateThumbprints |
Client cert allowlist (optional) | ABC123...,DEF456...,GHI789... |
- Click Save and Continue (Function App will restart)
Azure CLI:
# Set mutual TLS settings
az functionapp config appsettings set \
--name <function-app-name> \
--resource-group <resource-group> \
--settings \
RequireCertificateAuthentication=true \
CertificateValidateChain=true \
CertificateValidateExpiration=true \
CertificateCheckRevocation=false \
CertificateExpectedCARootName="CN=Contoso Root CA, O=Contoso Corporation, C=US" \
CertificateExpectedCARootThumbprint="A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0" \
'CertificateExpectedSubordinateCAsJson=[{"name":"CN=Contoso Device Management CA, O=Contoso, C=US","thumbprint":"789ABC012DEF345GHI678JKL901MNO234PQR567"}]'appsettings.json (SecureBootWatcher.Client):
{
"SecureBootWatcher": {
"Sinks": {
"EnableAzureFunction": true,
"EnableAzureQueue": false,
"ExecutionStrategy": "StopOnFirstSuccess",
"SinkPriority": "AzureFunction",
"AzureFunction": {
"FunctionUrl": "https://your-function-app.azurewebsites.net/api/reports",
"ApiKey": "your-api-key-here",
"HttpTimeout": "00:00:30",
"UseApiKeyAsQueryParameter": false,
"UseCertificateAuth": true,
"CertificateThumbprint": "ABC123DEF456789012345678901234567890ABCD",
"CertificateStoreLocation": "LocalMachine",
"CertificateStoreName": "My"
}
}
}
}Key fields:
UseCertificateAuth:true(enable client certificate)CertificateThumbprint: Client certificate thumbprint (from Step 1.4)CertificateStoreLocation:LocalMachine(for device certs) orCurrentUserCertificateStoreName:My(Personal certificate store)
Option A: Group Policy (Domain-joined devices)
Computer Configuration
└── Policies
└── Windows Settings
└── Security Settings
└── Public Key Policies
└── Automatic Certificate Request Settings
Option B: Intune (MDM-enrolled devices)
- Create SCEP or PKCS certificate profile
- Deploy to device groups
- Certificate auto-enrolled to
LocalMachine\My
Option C: Manual Installation (Testing)
# Import PFX certificate
$certPath = "C:\Temp\device-cert.pfx"
$certPassword = ConvertTo-SecureString -String "YourPassword" -Force -AsPlainText
Import-PfxCertificate -FilePath $certPath -CertStoreLocation Cert:\LocalMachine\My -Password $certPasswordPowerShell:
# Load client certificate
$clientCert = Get-ChildItem -Path Cert:\LocalMachine\My |
Where-Object { $_.Thumbprint -eq "ABC123DEF456..." }
# Build chain
$chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain
$chain.ChainPolicy.RevocationMode = "NoCheck"
$buildResult = $chain.Build($clientCert)
if ($buildResult) {
Write-Host "✅ Certificate chain is valid" -ForegroundColor Green
# Display chain
foreach ($element in $chain.ChainElements) {
Write-Host " - $($element.Certificate.Subject)" -ForegroundColor Cyan
Write-Host " Thumbprint: $($element.Certificate.Thumbprint)" -ForegroundColor Gray
Write-Host " Issuer: $($element.Certificate.Issuer)" -ForegroundColor Gray
Write-Host ""
}
} else {
Write-Host "❌ Certificate chain validation failed" -ForegroundColor Red
$chain.ChainStatus | ForEach-Object {
Write-Host " - $($_.StatusInformation)" -ForegroundColor Yellow
}
}Expected Output:
✅ Certificate chain is valid
- CN=DESKTOP-12345.contoso.com
Thumbprint: ABC123DEF456789012345678901234567890ABCD
Issuer: CN=Contoso Device Management CA, O=Contoso, C=US
- CN=Contoso Device Management CA, O=Contoso, C=US
Thumbprint: 789ABC012DEF345GHI678JKL901MNO234PQR567
Issuer: CN=Contoso Root CA, O=Contoso Corporation, C=US
- CN=Contoso Root CA, O=Contoso Corporation, C=US
Thumbprint: A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0
Issuer: CN=Contoso Root CA, O=Contoso Corporation, C=US
PowerShell (Invoke-WebRequest):
# Load client certificate
$cert = Get-ChildItem -Path Cert:\LocalMachine\My |
Where-Object { $_.Thumbprint -eq "ABC123DEF456..." }
# Create test report JSON
$testReport = @{
Device = @{
MachineName = "TEST-DEVICE"
Domain = "CONTOSO"
}
ClientVersion = "1.13.0"
CollectedAtUtc = (Get-Date).ToUniversalTime().ToString("o")
} | ConvertTo-Json -Depth 10
# Send request with certificate
$response = Invoke-WebRequest `
-Uri "https://your-function-app.azurewebsites.net/api/reports" `
-Method POST `
-Certificate $cert `
-Headers @{ "X-API-Key" = "your-api-key" } `
-Body $testReport `
-ContentType "application/json"
Write-Host "Status: $($response.StatusCode)" -ForegroundColor Green
Write-Host "Response: $($response.Content)"Expected Response:
Status: 202
Response: {"status":"accepted","message":"Report queued for processing","correlationId":"abc-123-def"}
Azure Portal - Live Metrics:
- Navigate to Function App → Application Insights → Live Metrics
- Send test request
- Watch for log messages:
- ✅
"API key authentication successful" - ✅
"Client certificate present. Subject=..." - ✅
"Root CA name validated: CN=Contoso Root CA..." - ✅
"Subordinate CA found in chain: CN=Contoso Device Management CA..." - ✅
"All expected Subordinate CAs validated successfully" - ✅
"Report forwarded to Azure Queue successfully"
- ✅
Azure CLI (Stream logs):
az webapp log tail \
--name <function-app-name> \
--resource-group <resource-group>Symptom:
HTTP 403 Forbidden
"Invalid or missing client certificate"
Possible Causes & Solutions:
- Check: Verify
UseCertificateAuth = truein client config - Check: Confirm certificate exists in specified store location
- Fix: Install client certificate in correct store (
LocalMachine\My)
- Check: Function App → Configuration → General settings → Client certificate mode
- Fix: Set to "Optional" or "Required"
- Check:
NotBeforeandNotAfterdates - Fix: Renew certificate and deploy to clients
$cert = Get-ChildItem Cert:\LocalMachine\My\ABC123...
Write-Host "Valid from: $($cert.NotBefore)"
Write-Host "Valid until: $($cert.NotAfter)"
Write-Host "Expired: $($cert.NotAfter -lt (Get-Date))"Symptom (Function logs):
Root CA name mismatch. Expected=CN=Contoso Root CA, O=Contoso, C=US, Actual=CN=Other Root CA
Possible Causes:
- Client certificate issued by different PKI
- Typo in
CertificateExpectedCARootNameconfiguration - Certificate chain incomplete (missing intermediate CAs)
Solution:
# Verify Root CA in client certificate chain
$cert = Get-ChildItem Cert:\LocalMachine\My\ABC123...
$chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain
$chain.Build($cert) | Out-Null
$rootCert = $chain.ChainElements[$chain.ChainElements.Count - 1].Certificate
Write-Host "Root CA Subject: $($rootCert.Subject)"
# Compare with Function config: CertificateExpectedCARootName
# Update Function App setting if needed
az functionapp config appsettings set \
--name <function-app-name> \
--resource-group <resource-group> \
--settings CertificateExpectedCARootName="$($rootCert.Subject)"Symptom (Function logs):
Expected Subordinate CA not found in chain: Name=CN=Contoso Device Management CA, Thumbprint=789ABC...
Possible Causes:
- Client certificate issued by different Issuing CA (e.g., "User CA" instead of "Device CA")
- Subordinate CA certificate not installed on client machine
- Typo in Subordinate CA name or thumbprint
Solution:
# Check who issued the client certificate
$cert = Get-ChildItem Cert:\LocalMachine\My\ABC123...
Write-Host "Issued by: $($cert.Issuer)"
# Build full chain and list all CAs
$chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain
$chain.Build($cert) | Out-Null
Write-Host "`nCertificate Chain:"
for ($i = 0; $i -lt $chain.ChainElements.Count; $i++) {
$element = $chain.ChainElements[$i]
Write-Host "$i. Subject: $($element.Certificate.Subject)"
Write-Host " Thumbprint: $($element.Certificate.Thumbprint)"
}# Get current config
az functionapp config appsettings list \
--name <function-app-name> \
--resource-group <resource-group> \
--query "[?name=='CertificateExpectedSubordinateCAsJson'].value" -o tsv# Update to match actual Issuing CA
az functionapp config appsettings set \
--name <function-app-name> \
--resource-group <resource-group> \
--settings 'CertificateExpectedSubordinateCAsJson=[{"name":"CN=Actual Issuing CA Subject","thumbprint":"ActualThumbprint"}]'Symptom (Function logs):
Certificate chain validation failed. ChainStatus=UntrustedRoot, PartialChain
Possible Causes:
- Root CA not trusted on Function App (Azure App Service)
- Intermediate CA certificates missing
- Certificate chain incomplete
Solution:
Azure App Service uses the Windows Trusted Root Store. If your enterprise Root CA is not in the public trust store, you need to bundle intermediate certificates with the client certificate.
Export full chain from client:
# Export certificate with full chain (PFX)
$cert = Get-ChildItem Cert:\LocalMachine\My\ABC123...
$password = ConvertTo-SecureString -String "TempPassword" -Force -AsPlainText
Export-PfxCertificate -Cert $cert -FilePath "C:\Temp\client-with-chain.pfx" -Password $password -ChainOption BuildChainAlternatively, disable chain validation (
az functionapp config appsettings set \
--name <function-app-name> \
--resource-group <resource-group> \
--settings CertificateValidateChain=falseNote: If ValidateChain=false, you must configure ExpectedCARootThumbprint and ExpectedSubordinateCAsJson to maintain security!
Recommended Configuration:
{
"RequireCertificateAuthentication": true,
"CertificateThumbprints": "ABC123...,DEF456...", // Client allowlist
"CertificateValidateChain": true,
"CertificateExpectedCARootName": "CN=Contoso Root CA, ...",
"CertificateExpectedCARootThumbprint": "A1B2C3D4...",
"CertificateExpectedSubordinateCAsJson": "[{...}]"
}Security Levels:
- 🔴 Low: Only API key
- 🟡 Medium: API key + certificate (any valid cert)
- 🟢 High: API key + certificate + Root CA validation
- 🟢 Very High: API key + cert + Root CA + Subordinate CA validation
- 🟢 Maximum: Above + client thumbprint allowlist
# Check client certificates expiring in next 30 days
Get-ChildItem -Path Cert:\LocalMachine\My |
Where-Object { $_.NotAfter -lt (Get-Date).AddDays(30) -and $_.HasPrivateKey } |
Select-Object Subject, Thumbprint, NotAfter |
Format-Table -AutoSize- Use SCEP (Simple Certificate Enrollment Protocol) for auto-renewal
- Configure auto-enrollment via Group Policy or Intune
- Monitor Azure Monitor alerts for expiring certificates
Recommended PKI Structure:
Root CA
├── Device Management CA → Devices managed by IT
├── IoT CA → IoT sensors and embedded devices
├── User CA → User authentication certificates
└── Server CA → Web servers and internal services
Segregation Benefits:
- Different validation periods (devices: 5 years, users: 2 years)
- Different revocation policies
- Granular access control per device type
- Easier compliance auditing
When to enable CertificateCheckRevocation=true:
- ✅ High-security environments
- ✅ Compliance requirements (PCI-DSS, HIPAA)
- ✅ CRL server is reliable and fast
When to disable:
- ❌ CRL server unreachable (causes timeouts)
- ❌ Offline environments
- ❌ Performance-critical scenarios
Alternative: Use OCSP Stapling (handled by Azure App Service automatically)
Configure Application Insights:
az functionapp config appsettings set \
--name <function-app-name> \
--resource-group <resource-group> \
--settings APPINSIGHTS_INSTRUMENTATIONKEY="your-key"Monitor Key Metrics:
- Certificate validation failures (403 responses)
- Certificate expiration warnings
- Root CA/Subordinate CA mismatch errors
- Revocation check timeouts
Create Alert Rules:
# Alert on certificate validation failures
az monitor metrics alert create \
--name "Certificate Validation Failures" \
--resource-group <resource-group> \
--scopes /subscriptions/.../functionApps/your-function \
--condition "count requests where resultCode == 403 > 10" \
--window-size 5m \
--evaluation-frequency 1mClient Certificates:
- ✅ Store in Trusted Platform Module (TPM) if available
- ✅ Use non-exportable private keys
- ✅ Mark private keys as machine-only (not user-accessible)
- ❌ Never store certificates in user profiles for device auth
PowerShell - Request non-exportable certificate:
$certRequest = New-Object -ComObject X509Enrollment.CX509CertificateRequestPkcs10
$certRequest.InitializeFromPrivateKey(1, $privateKey, "")
$certRequest.PrivateKeyExportable = $falseImmediate Revocation:
- Revoke certificate in CA
- Publish updated CRL
- Wait for CRL propagation (can take hours)
- Alternative: Remove thumbprint from
CertificateThumbprintsallowlist (immediate)
Best Practice: Use both CRL and allowlist for faster revocation
Enable Diagnostic Settings:
az monitor diagnostic-settings create \
--name "CertAuthLogs" \
--resource /subscriptions/.../functionApps/your-function \
--logs '[{"category":"FunctionAppLogs","enabled":true}]' \
--workspace /subscriptions/.../workspaces/your-log-analyticsQuery Certificate Auth Events:
// Log Analytics query
FunctionAppLogs
| where Message contains "Certificate authentication"
| project TimeGenerated, Message, Level
| order by TimeGenerated descBackup Critical Data:
- Root CA certificate (export to secure storage)
- Subordinate CA certificates
- Function App configuration (export ARM template)
- Client certificate thumbprint inventory
Recovery Procedure:
- Restore Function App from ARM template
- Re-configure Application Settings
- Re-enable mutual TLS
- Validate with test certificate
# Load certificate by thumbprint
$cert = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Thumbprint -eq "ABC123..." }
# Basic info
$cert.Subject
$cert.Issuer
$cert.Thumbprint
$cert.NotBefore
$cert.NotAfter
# Build and display chain
$chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain
$chain.Build($cert) | Out-Null
$chain.ChainElements | ForEach-Object { $_.Certificate.Subject }
# Export certificate (no private key)
Export-Certificate -Cert $cert -FilePath "C:\Temp\cert.cer"
# Export with private key (PFX)
$pwd = ConvertTo-SecureString -String "Password123" -Force -AsPlainText
Export-PfxCertificate -Cert $cert -FilePath "C:\Temp\cert.pfx" -Password $pwd# Display certificate details
openssl x509 -in cert.pem -text -noout
# Extract Subject
openssl x509 -in cert.pem -noout -subject
# Extract Issuer
openssl x509 -in cert.pem -noout -issuer
# Extract Thumbprint (SHA-1)
openssl x509 -in cert.pem -noout -fingerprint -sha1
# Verify certificate chain
openssl verify -CAfile root.pem -untrusted intermediate.pem client.pem
# Display full chain
openssl s_client -connect your-function-app.azurewebsites.net:443 -showcerts{
"ApiKey": "@Microsoft.KeyVault(SecretUri=...)",
"QueueStorageUri": "https://prodstorageaccount.queue.core.windows.net",
"QueueName": "secureboot-reports",
"RequireCertificateAuthentication": "true",
"CertificateValidateChain": "true",
"CertificateValidateExpiration": "true",
"CertificateCheckRevocation": "false",
"CertificateExpectedCARootName": "CN=Contoso Root CA, O=Contoso Corporation, C=US",
"CertificateExpectedCARootThumbprint": "A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0",
"CertificateExpectedSubordinateCAsJson": "[{\"name\":\"CN=Contoso Device Management CA, O=Contoso, C=US\",\"thumbprint\":\"789ABC012DEF345GHI678JKL901MNO234PQR567\"}]"
}{
"ApiKey": "@Microsoft.KeyVault(SecretUri=...)",
"QueueStorageUri": "https://prodstorageaccount.queue.core.windows.net",
"QueueName": "secureboot-reports",
"RequireCertificateAuthentication": "true",
"CertificateValidateChain": "true",
"CertificateValidateExpiration": "true",
"CertificateCheckRevocation": "false",
"CertificateExpectedCARootName": "CN=Contoso Root CA, O=Contoso Corporation, C=US",
"CertificateExpectedCARootThumbprint": "A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0",
"CertificateExpectedSubordinateCAsJson": "[{\"name\":\"CN=Contoso Device CA US-East\",\"thumbprint\":\"789ABC...\"},{\"name\":\"CN=Contoso Device CA EU-West\",\"thumbprint\":\"890DEF...\"},{\"name\":\"CN=Contoso Device CA AP-South\",\"thumbprint\":\"901GHI...\"}]"
}{
"ApiKey": "@Microsoft.KeyVault(SecretUri=...)",
"QueueStorageUri": "https://prodstorageaccount.queue.core.windows.net",
"QueueName": "secureboot-reports",
"RequireCertificateAuthentication": "true",
"CertificateValidateChain": "true",
"CertificateValidateExpiration": "true",
"CertificateCheckRevocation": "true",
"CertificateExpectedCARootName": "CN=Contoso Root CA, O=Contoso Corporation, C=US",
"CertificateExpectedCARootThumbprint": "A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0",
"CertificateExpectedSubordinateCAsJson": "[{\"name\":\"CN=Contoso Device Management CA, O=Contoso, C=US\",\"thumbprint\":\"789ABC012DEF345GHI678JKL901MNO234PQR567\"}]",
"CertificateThumbprints": "ABC123DEF456789012345678901234567890ABCD,DEF456GHI789012345678901234567890ABCDE,GHI789JKL012345678901234567890ABCDEF01"
}- Azure App Service Mutual TLS
- X.509 Certificate Validation
- Azure Function Authentication
- PKI Best Practices
- Secure Boot Certificate Watcher - Main Documentation
For issues or questions:
- Check Troubleshooting section
- Review Function App logs in Application Insights
- Open an issue on GitHub: https://github.com/robgrame/Nimbus.BootCertWatcher/issues
Document Version: 1.0
Last Updated: 2025-01-XX
Author: Secure Boot Certificate Watcher Team