diff --git a/src/Backend/AutopilotMonitor.Functions.Tests/AutopilotMonitor.Functions.Tests.csproj b/src/Backend/AutopilotMonitor.Functions.Tests/AutopilotMonitor.Functions.Tests.csproj
index 71d5f2a6..85ae563f 100644
--- a/src/Backend/AutopilotMonitor.Functions.Tests/AutopilotMonitor.Functions.Tests.csproj
+++ b/src/Backend/AutopilotMonitor.Functions.Tests/AutopilotMonitor.Functions.Tests.csproj
@@ -26,4 +26,10 @@
+
+
+ PreserveNewest
+
+
+
diff --git a/src/Backend/AutopilotMonitor.Functions.Tests/CertificateValidatorTests.cs b/src/Backend/AutopilotMonitor.Functions.Tests/CertificateValidatorTests.cs
new file mode 100644
index 00000000..31374e04
--- /dev/null
+++ b/src/Backend/AutopilotMonitor.Functions.Tests/CertificateValidatorTests.cs
@@ -0,0 +1,155 @@
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using AutopilotMonitor.Functions.Security;
+
+namespace AutopilotMonitor.Functions.Tests;
+
+///
+/// Tests for CertificateValidator chain-trust enforcement.
+///
+/// The validator pins trust to embedded Intune root certs via X509ChainTrustMode.CustomRootTrust.
+/// Two layers of coverage:
+/// - Happy path: a real Intune-issued device cert (public-key only, copied from a test VM)
+/// must validate successfully. This guards against rollouts where the embedded root/intermediate
+/// bundle drifts from the chain Microsoft is actually issuing.
+/// - Negative path: self-signed leaf certs (with or without Intune-shaped DNs) must NOT validate.
+///
+public class CertificateValidatorTests
+{
+ private const string DeviceSamplePem = "device-cert-sample.pem";
+
+ [Fact]
+ public void ValidateCertificate_WithRealIntuneDeviceCert_ReturnsValid()
+ {
+ // Real device cert (public-key only) issued by "Microsoft Intune MDM Device CA".
+ // This is the regression test that would have caught the previous outage where the
+ // embedded root bundle didn't match the chain Microsoft uses for current devices.
+ var pemPath = ResolveSamplePath();
+ Assert.True(File.Exists(pemPath), $"Sample cert not found at {pemPath}");
+
+ var pem = File.ReadAllText(pemPath);
+ using var cert = X509Certificate2.CreateFromPem(pem);
+ var b64 = Convert.ToBase64String(cert.Export(X509ContentType.Cert));
+
+ var result = CertificateValidator.ValidateCertificate(b64);
+
+ Assert.True(result.IsValid, $"Real device cert rejected: {result.ErrorMessage}");
+ Assert.False(string.IsNullOrEmpty(result.Thumbprint));
+ }
+
+ [Fact]
+ public void ValidateCertificate_WithEmptyBase64_ReturnsInvalid()
+ {
+ var result = CertificateValidator.ValidateCertificate("");
+
+ Assert.False(result.IsValid);
+ Assert.Contains("No certificate", result.ErrorMessage, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Fact]
+ public void ValidateCertificate_WithNull_ReturnsInvalid()
+ {
+ var result = CertificateValidator.ValidateCertificate(null);
+
+ Assert.False(result.IsValid);
+ }
+
+ [Fact]
+ public void ValidateCertificate_WithMalformedBase64_ReturnsInvalid()
+ {
+ var result = CertificateValidator.ValidateCertificate("not-base64!!!");
+
+ Assert.False(result.IsValid);
+ }
+
+ [Fact]
+ public void ValidateCertificate_WithSelfSignedCertImpersonatingIntune_ReturnsInvalid()
+ {
+ // Regression test: a self-signed leaf with Subject CN="Microsoft Intune MDM Device CA"
+ // and Client-Auth EKU previously passed validation under
+ // AllowUnknownCertificateAuthority + DN substring match. Under CustomRootTrust the
+ // chain has no signature link to a pinned Intune root and must be rejected.
+ var b64 = CreateSelfSignedCertBase64(
+ subjectCn: "Microsoft Intune MDM Device CA",
+ includeClientAuthEku: true);
+
+ var result = CertificateValidator.ValidateCertificate(b64);
+
+ Assert.False(result.IsValid);
+ Assert.Contains("chain validation failed", result.ErrorMessage, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Fact]
+ public void ValidateCertificate_WithSelfSignedCertImpersonatingMdmDeviceCa_ReturnsInvalid()
+ {
+ var b64 = CreateSelfSignedCertBase64(
+ subjectCn: "MDM Device CA",
+ includeClientAuthEku: true);
+
+ var result = CertificateValidator.ValidateCertificate(b64);
+
+ Assert.False(result.IsValid);
+ }
+
+ [Fact]
+ public void ValidateCertificate_WithSelfSignedNonIntuneCert_ReturnsInvalid()
+ {
+ var b64 = CreateSelfSignedCertBase64(
+ subjectCn: "test-device.contoso.example",
+ includeClientAuthEku: true);
+
+ var result = CertificateValidator.ValidateCertificate(b64);
+
+ Assert.False(result.IsValid);
+ }
+
+ [Fact]
+ public void ValidateCertificate_WithExpiredSelfSignedCert_ReturnsInvalid()
+ {
+ var b64 = CreateSelfSignedCertBase64(
+ subjectCn: "Microsoft Intune MDM Device CA",
+ includeClientAuthEku: true,
+ notBefore: DateTimeOffset.UtcNow.AddYears(-2),
+ notAfter: DateTimeOffset.UtcNow.AddYears(-1));
+
+ var result = CertificateValidator.ValidateCertificate(b64);
+
+ Assert.False(result.IsValid);
+ Assert.Contains("expired", result.ErrorMessage, StringComparison.OrdinalIgnoreCase);
+ }
+
+ private static string ResolveSamplePath()
+ {
+ var assemblyDir = Path.GetDirectoryName(typeof(CertificateValidatorTests).Assembly.Location)!;
+ return Path.Combine(assemblyDir, DeviceSamplePem);
+ }
+
+ private static string CreateSelfSignedCertBase64(
+ string subjectCn,
+ bool includeClientAuthEku,
+ DateTimeOffset? notBefore = null,
+ DateTimeOffset? notAfter = null)
+ {
+ using var rsa = RSA.Create(2048);
+ var req = new CertificateRequest(
+ $"CN={subjectCn}",
+ rsa,
+ HashAlgorithmName.SHA256,
+ RSASignaturePadding.Pkcs1);
+
+ if (includeClientAuthEku)
+ {
+ // 1.3.6.1.5.5.7.3.2 = Client Authentication
+ req.CertificateExtensions.Add(
+ new X509EnhancedKeyUsageExtension(
+ new OidCollection { new Oid("1.3.6.1.5.5.7.3.2") },
+ critical: false));
+ }
+
+ var nb = notBefore ?? DateTimeOffset.UtcNow.AddDays(-1);
+ var na = notAfter ?? DateTimeOffset.UtcNow.AddYears(1);
+ using var cert = req.CreateSelfSigned(nb, na);
+
+ return Convert.ToBase64String(cert.Export(X509ContentType.Cert));
+ }
+}
diff --git a/src/Backend/AutopilotMonitor.Functions.Tests/device-cert-sample.pem b/src/Backend/AutopilotMonitor.Functions.Tests/device-cert-sample.pem
new file mode 100644
index 00000000..1ee81902
--- /dev/null
+++ b/src/Backend/AutopilotMonitor.Functions.Tests/device-cert-sample.pem
@@ -0,0 +1,26 @@
+-----BEGIN CERTIFICATE-----
+MIIEeTCCA2GgAwIBAgIQ3CvOdsqKrJFKrCZd9mi7RjANBgkqhkiG9w0BAQsFADAp
+MScwJQYDVQQDEx5NaWNyb3NvZnQgSW50dW5lIE1ETSBEZXZpY2UgQ0EwHhcNMjYw
+NTA4MTIxNjQ5WhcNMjcwNTA4MDAxNjIyWjAvMS0wKwYDVQQDEyQwN2JjMDE2Ny05
+MDYxLTQzYjctYjI0My0wYmI2YWVkYTczNmUwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQDQR7R0I80KYq0j/ix1RxPmCgiHFr3a6cUVfcMtJnrGYQUg/Ngh
+PGigNyV/CxQ2qfExAjXh1OJ112NYo+Hhrwgl901r42s1bvU8JCXiXWERG87pnCv8
+LQ4sRLAW91bz3phhKT5I8gk4uPCJZqTjpZ+/cpdU3CrK5bS8lb23M+s5IizcNAtR
+DCTOBXc8My9Yy5ExZZ41uo+xOcP+RQ4yp5bxTzzT2gx5BVsc1QA9f4mNzdMQ8dyC
+ejygU+UwiCJsVTKIFtqhLIrgi5Bszuq2611wma2nKjfRUlJQITGCGCccCegKTQNO
+eaiYUoS/Hj9lh+gAej6OmCJCoomKWtB6lgUTAgMBAAGjggGVMIIBkTAdBgNVHQ4E
+FgQU762JgixoNCU2IZEnzm7sMy3ZSTAwHwYDVR0jBBgwFoAUjrJY+zZQjryKKzg8
+w0YEkXJJJRowHgYIKoZIhvcUBQsEEgQQarzWQl5UmUGVKayL/NSeijAeBggqhkiG
+9xQFDgQSBBCvwU21IFNgT7XUgh4M8qNZMA8GCCqGSIb3FAUPBAMCAQIwDwYIKoZI
+hvcUBRAEAwIBDTAPBggqhkiG9xQFEQQDAgEOMA8GCCqGSIb3FAUSBAMCAQEwDwYI
+KoZIhvcUBRMEAwIBADAWBggqhkiG9xQFFwQKAghAAAAAAAABEDAuBggqhkiG9xQF
+GAQiBCCVN3VWIg9eIC3z9Bjd96ocQSWOMgtwdKFx2nalSqHDvzAcBggqhkiG9xQF
+BAQQZwG8B2GQt0OyQwu2rtpzbjAeBggqhkiG9xQFBgQSBBA9hd7edOJrQJ0NRrb+
+ZbRAMA4GA1UdDwEB/wQEAwIFoDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoG
+CCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQBzgrcC8ky3A/U5iWPJApoH9T99
+izrA1SZQ6KNBpSfZPCeRAOtL+H8kxnFf5kIX//9qp00TcBlXAAIw9kXwSeCCWrfx
+76eFDJrbffyG11TN8L/LOwBaaFAnkOD+yKe/gvMYUyEdQFhDF9pPRr2uUV5L7RYe
+jZ8RojKl8oid6dlp4ZcY7k25WaZOI/UR03+I4PKl8/m8fkKWPcHY07uPxsdwWyIe
+Y1VQMborLHe8df2ZxS0wEfAtfjdahk8qLV6uw2VzJI0/S5HZDCAc9agGEKUh8KGC
+xCKndl1riMHjZZ6sNP/fzVZDI+6cM4yYV6yxraAyEOK5iyq/2QIGQa3NjScl
+-----END CERTIFICATE-----
diff --git a/src/Backend/AutopilotMonitor.Functions/AutopilotMonitor.Functions.csproj b/src/Backend/AutopilotMonitor.Functions/AutopilotMonitor.Functions.csproj
index 6207a881..14e926d5 100644
--- a/src/Backend/AutopilotMonitor.Functions/AutopilotMonitor.Functions.csproj
+++ b/src/Backend/AutopilotMonitor.Functions/AutopilotMonitor.Functions.csproj
@@ -59,8 +59,8 @@
-
-
+
+
diff --git a/src/Backend/AutopilotMonitor.Functions/Security/CertificateValidator.cs b/src/Backend/AutopilotMonitor.Functions/Security/CertificateValidator.cs
index ebab0cac..83b94db5 100644
--- a/src/Backend/AutopilotMonitor.Functions/Security/CertificateValidator.cs
+++ b/src/Backend/AutopilotMonitor.Functions/Security/CertificateValidator.cs
@@ -15,9 +15,9 @@ namespace AutopilotMonitor.Functions.Security
///
///
/// Security measures:
- /// - Full X.509 chain validation using X509Chain.Build()
+ /// - X.509 chain trust pinned to embedded Intune root certificate(s) via
+ /// X509ChainTrustMode.CustomRootTrust (OS trust store is ignored)
/// - No revocation checking (Intune certificates have no CRL/OCSP endpoints)
- /// - Validates entire chain contains Microsoft Intune CA
/// - Prevents self-signed certificate attacks
/// - Validates Enhanced Key Usage for Client Authentication
/// - In-memory cache for validated certificates (5 minute TTL)
@@ -29,8 +29,16 @@ namespace AutopilotMonitor.Functions.Security
private static readonly ConcurrentDictionary _validationCache = new();
private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(5);
- // Lazily loaded Intune CA certs from embedded resources
- private static readonly Lazy _intuneCaCerts = new(LoadIntuneCaCertificates);
+ // Lazily loaded Intune CA certs from embedded PEM resources, split by role:
+ // roots -> pinned trust anchors (chain.ChainPolicy.CustomTrustStore)
+ // intermediates -> bridge candidates (chain.ChainPolicy.ExtraStore)
+ // Files are picked up by name prefix from Security/Certificates/, so adding a
+ // future Intune root or intermediate is a drop-in (no code change).
+ // Duplicates (same thumbprint) are deduped at load time.
+ private static readonly Lazy _intuneRootCerts =
+ new(() => LoadIntuneCerts("intune-root-"));
+ private static readonly Lazy _intuneIntermediateCerts =
+ new(() => LoadIntuneCerts("intune-intermediate-"));
private class CachedValidationResult
{
@@ -89,43 +97,66 @@ namespace AutopilotMonitor.Functions.Security
logger?.LogDebug($"Certificate validation cache MISS for thumbprint {thumbprint}, performing full validation");
- // 1. Check expiry
+ // 1. Check expiry. X509Certificate2.NotBefore/NotAfter return Local time on .NET;
+ // normalize to UTC before comparing to DateTime.UtcNow or the result is wrong on
+ // hosts whose TZ != UTC (e.g. test runs on a CEST dev box).
var now = DateTime.UtcNow;
- if (now < certificate.NotBefore || now > certificate.NotAfter)
+ var notBeforeUtc = certificate.NotBefore.ToUniversalTime();
+ var notAfterUtc = certificate.NotAfter.ToUniversalTime();
+ if (now < notBeforeUtc || now > notAfterUtc)
{
return new CertificateValidationResult
{
IsValid = false,
- ErrorMessage = $"Certificate expired or not yet valid (NotBefore={certificate.NotBefore}, NotAfter={certificate.NotAfter})",
+ ErrorMessage = $"Certificate expired or not yet valid (NotBefore={notBeforeUtc:u}, NotAfter={notAfterUtc:u})",
Thumbprint = thumbprint
};
}
- // 2. Validate certificate chain (without revocation check - Intune certs have no CRL/OCSP)
+ // 2. Validate certificate chain against pinned Intune roots (no revocation - no CRL/OCSP).
//
- // On Linux (Azure Functions flex consumption plan) the OS trust store does not contain
- // Microsoft Intune CAs. We load them from embedded PEM resources into ExtraStore and set
- // AllowUnknownCertificateAuthority so chain.Build() can resolve the issuer without the OS
- // store. The issuer identity is still enforced in step 3.
+ // CustomTrustStore + CustomRootTrust pins trust to the embedded Intune root cert(s)
+ // only and ignores the OS trust store entirely - works on Linux/Flex Consumption
+ // (no machine store) and replaces a previous AllowUnknownCertificateAuthority
+ // workaround which was bypassable by self-signed leaf certs whose Subject contained
+ // "Microsoft Intune".
+ var roots = _intuneRootCerts.Value;
+ var intermediates = _intuneIntermediateCerts.Value;
+
+ if (roots.Length == 0)
+ {
+ logger?.LogError("No embedded Intune root certificates loaded - failing closed");
+ return new CertificateValidationResult
+ {
+ IsValid = false,
+ ErrorMessage = "Server misconfiguration: no Intune trust anchors available",
+ Thumbprint = thumbprint
+ };
+ }
+
using var chain = new X509Chain();
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
- chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
+ chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
chain.ChainPolicy.VerificationTime = DateTime.UtcNow;
chain.ChainPolicy.UrlRetrievalTimeout = TimeSpan.FromSeconds(10);
+ chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
- // Load embedded Intune CA certs into ExtraStore
- var caCerts = _intuneCaCerts.Value;
- logger?.LogDebug("Loading {Count} embedded Intune CA certificate(s) into chain ExtraStore", caCerts.Length);
- foreach (var caCert in caCerts)
- chain.ChainPolicy.ExtraStore.Add(caCert);
+ foreach (var root in roots)
+ chain.ChainPolicy.CustomTrustStore.Add(root);
+ foreach (var intermediate in intermediates)
+ chain.ChainPolicy.ExtraStore.Add(intermediate);
+
+ logger?.LogDebug(
+ "Chain build with {RootCount} pinned Intune root(s) + {IntermediateCount} intermediate(s)",
+ roots.Length, intermediates.Length);
var chainValid = chain.Build(certificate);
if (!chainValid)
{
var chainErrors = string.Join(", ", chain.ChainStatus.Select(s => s.StatusInformation));
- logger?.LogWarning($"Certificate chain validation failed: {chainErrors}");
+ logger?.LogWarning("Certificate chain validation failed: {ChainErrors}", chainErrors);
return new CertificateValidationResult
{
@@ -135,29 +166,12 @@ namespace AutopilotMonitor.Functions.Security
};
}
- // 3. Verify issuer chain contains Microsoft Intune CA
- var hasValidIssuer = chain.ChainElements
- .Cast()
- .Any(element =>
- element.Certificate.Issuer.Contains("Microsoft Intune", StringComparison.OrdinalIgnoreCase) ||
- element.Certificate.Issuer.Contains("MDM Device CA", StringComparison.OrdinalIgnoreCase) ||
- element.Certificate.Subject.Contains("Microsoft Intune", StringComparison.OrdinalIgnoreCase) ||
- element.Certificate.Subject.Contains("MDM Device CA", StringComparison.OrdinalIgnoreCase));
+ // chainValid == true under CustomRootTrust proves the leaf is transitively signed
+ // by one of the pinned Intune roots. The previous Subject/Issuer DN substring check
+ // is intentionally removed - it added no security on top of root pinning and would
+ // false-negative if Microsoft renames the CA.
- if (!hasValidIssuer)
- {
- var chainInfo = string.Join(" -> ", chain.ChainElements.Cast().Select(e => e.Certificate.Subject));
- logger?.LogWarning($"Certificate chain does not contain Microsoft Intune CA: {chainInfo}");
-
- return new CertificateValidationResult
- {
- IsValid = false,
- ErrorMessage = $"Certificate not issued by Microsoft Intune CA",
- Thumbprint = thumbprint
- };
- }
-
- // 4. Check Enhanced Key Usage for Client Authentication (1.3.6.1.5.5.7.3.2)
+ // 3. Check Enhanced Key Usage for Client Authentication (1.3.6.1.5.5.7.3.2)
var hasClientAuth = certificate.Extensions
.OfType()
.Any(ext => ext.EnhancedKeyUsages
@@ -207,45 +221,59 @@ namespace AutopilotMonitor.Functions.Security
}
///
- /// Loads Intune CA certificates from embedded PEM resources.
- /// Files: Security/Certificates/intune-intermediate-ca.pem
- /// Security/Certificates/intune-root-ca.pem
+ /// Loads all certs whose embedded resource filename starts with the given prefix.
+ /// Multiple PEM blocks per file are supported (cross-signed bundles). Duplicates
+ /// are deduped by thumbprint, so a file accidentally duplicated under another name
+ /// is harmless. Roles are filename-segregated:
+ /// intune-root-*.pem -> pinned trust anchors (CustomTrustStore)
+ /// intune-intermediate-*.pem -> chain bridge candidates (ExtraStore)
///
- private static X509Certificate2[] LoadIntuneCaCertificates()
+ private static X509Certificate2[] LoadIntuneCerts(string fileNamePrefix)
{
+ const string resourceNamespace = "AutopilotMonitor.Functions.Security.Certificates.";
var assembly = Assembly.GetExecutingAssembly();
- var resourceNames = new[]
- {
- "AutopilotMonitor.Functions.Security.Certificates.intune-intermediate-ca.pem",
- "AutopilotMonitor.Functions.Security.Certificates.intune-root-ca.pem"
- };
+ var resourcePrefix = resourceNamespace + fileNamePrefix;
- List certs = [];
+ var resourceNames = assembly.GetManifestResourceNames()
+ .Where(n => n.StartsWith(resourcePrefix, StringComparison.Ordinal)
+ && n.EndsWith(".pem", StringComparison.OrdinalIgnoreCase))
+ .OrderBy(n => n, StringComparer.Ordinal);
+
+ const string begin = "-----BEGIN CERTIFICATE-----";
+ const string end = "-----END CERTIFICATE-----";
+ var seenThumbprints = new HashSet(StringComparer.OrdinalIgnoreCase);
+ var certs = new List();
foreach (var resourceName in resourceNames)
{
using var stream = assembly.GetManifestResourceStream(resourceName);
- if (stream == null)
- {
- // Resource not found - not fatal, chain validation will fall back to OS store
- continue;
- }
+ if (stream == null) continue;
using var reader = new StreamReader(stream);
var pem = reader.ReadToEnd();
- // Skip placeholder files that haven't been filled with a real cert yet
if (pem.Contains("PLACEHOLDER_REPLACE_WITH_BASE64"))
continue;
- try
+ int idx = 0;
+ while ((idx = pem.IndexOf(begin, idx, StringComparison.Ordinal)) >= 0)
{
- var cert = X509Certificate2.CreateFromPem(pem);
- certs.Add(cert);
- }
- catch
- {
- // Malformed PEM - skip silently, chain validation continues with OS store
+ var endIdx = pem.IndexOf(end, idx, StringComparison.Ordinal);
+ if (endIdx < 0) break;
+ var block = pem.Substring(idx, endIdx + end.Length - idx);
+ try
+ {
+ var cert = X509Certificate2.CreateFromPem(block);
+ if (seenThumbprints.Add(cert.Thumbprint))
+ certs.Add(cert);
+ else
+ cert.Dispose();
+ }
+ catch
+ {
+ // Malformed block - skip; chain.Build() will fail closed if no roots remain.
+ }
+ idx = endIdx + end.Length;
}
}
diff --git a/src/Backend/AutopilotMonitor.Functions/Security/Certificates/intune-intermediate-mdm-2026.pem b/src/Backend/AutopilotMonitor.Functions/Security/Certificates/intune-intermediate-mdm-2026.pem
new file mode 100644
index 00000000..7b741527
--- /dev/null
+++ b/src/Backend/AutopilotMonitor.Functions/Security/Certificates/intune-intermediate-mdm-2026.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEfTCCAmWgAwIBAgIQQt4O1ZNyWaBPqw1mUq1F8jANBgkqhkiG9w0BAQsFADA4
+MTYwNAYDVQQDEy1NaWNyb3NvZnQgSW50dW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBB
+dXRob3JpdHkwHhcNMjMwNTA1MDAwMDAwWhcNMjYwNTA1MDAwMDAwWjApMScwJQYD
+VQQDEx5NaWNyb3NvZnQgSW50dW5lIE1ETSBEZXZpY2UgQ0EwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQDYOy/IV8f+BmH5y9/zJUv5oROn3MEQua39r03K
+7fgAqwEsAFtU/4ZWnFbriFSx0xZsDkZxxUy9BlOQZ+2sr/eJ2j6uI6CocR4woRkt
+JR6pQ5EXdsJb9lZOfV9+q6PwLnAIBQTTpUq+XtE8T1MwwGQ5NWW1JqQMDPo3nR0T
+h14tDQNrLtCPe7c2f+qaG2x+uRvWGMgTmxtGpYiipQB8upnInAZZ5wVdhu+/GMZg
+TSYJvn+3Px33NOB+0mpVanq7CwzyXA+dqGio8sZJDfOS1fPYDKqPj52l3rct3doa
+2o98gPHWaDs7Rj+GiAKeidUkN3uhQUyNfi8+YJEHb8OVGjEFAgMBAAGjgZEwgY4w
+HQYDVR0OBBYEFC5aTr/lv8IEGDqYYmHaaj3AsHw4MB8GA1UdIwQYMBaAFCtJzrEH
+JUyd5gqT85C5MPfmRJ+SMBAGCSsGAQQBgjcVAQQDAgEAMA4GA1UdDwEB/wQEAwIB
+hjAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMA0G
+CSqGSIb3DQEBCwUAA4ICAQCoci1EyidjS6xqi3S37/TJdIGqM0OcvTsi12WPIVxA
+MFRaF638dgu9FrRpTDCquc06lBHtIfBWCU7VFkGIGGIZ54L4en/dGi0ZBXjv4vfp
+RQ+LsoJZnYtCF3MbvlPXfQr3nk1k6m7c1utsuSzL0VgeEJJ++5KXlMRHrFdlXXm5
+N8MJNw78I/e7sbwNZxvA4UdxnEpqHeKDj1+nppANJDKrobIp9WzmYCFA8Qu3bX/N
+g9OnxLnpGi8mCIvPCkPu3oo1RtHxjf/tQtdSE4V1/8mggYLNNftjWk6ElqA+gRGY
+gJCssJrfG/bRYk+9ZQjavu3oG0B+SD7j3UeAOQqoOaPi8faF95tpgpVfJqQ6bm9w
+qBdODWfE4TYIPd0QG5nsYr86JThEtsL36U9j74wC33+4YHV4UviUiycyQiQQCSIF
+xSY2u9cLpFO4rjM+Qe/n8KWQURTW8i3wUFReVrMp9l+y70GklU/HHkXv8SDtFi6Q
+0+aK2duUhbHr+8P2nPuPNetfryVsdE+RVL4pFWEfIvbOKOI5EQj3Bh4/XkbhQtZk
++FR7VkUIm5A9Kp+OzsQADkYhWE1N4xL/ysfRWmOcjB1MT1m7Zfie3YsR7j+rY4NL
+b+snuHqqpYRDSZpngZAJgRYHI1xSUxOk+zJ8XyHG+eyeZdfwlrWLKuMJEFw4pczs
+0Q==
+-----END CERTIFICATE-----
diff --git a/src/Backend/AutopilotMonitor.Functions/Security/Certificates/intune-intermediate-mdm-2028.pem b/src/Backend/AutopilotMonitor.Functions/Security/Certificates/intune-intermediate-mdm-2028.pem
new file mode 100644
index 00000000..184c49e7
--- /dev/null
+++ b/src/Backend/AutopilotMonitor.Functions/Security/Certificates/intune-intermediate-mdm-2028.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEfTCCAmWgAwIBAgIQ6w2J4bS8K69IHzmhs1IQ2TANBgkqhkiG9w0BAQsFADA4
+MTYwNAYDVQQDEy1NaWNyb3NvZnQgSW50dW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBB
+dXRob3JpdHkwHhcNMjUwOTE1MDAwMDAwWhcNMjgwOTE1MDAwMDAwWjApMScwJQYD
+VQQDEx5NaWNyb3NvZnQgSW50dW5lIE1ETSBEZXZpY2UgQ0EwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQDQJdsb4/U/bHNQZxCYN+9luarjMFhbBsBgGUnf
+SZw3IzfvXuwvg/hyctwPDGjNh83L+sVDMXuHJ7yRtCtCcI/bUutR8eplET9GZJjt
+yINcf373QkCJRn5NwRU7UNuy19LbCQmYWxbgEORgrTV/pQAMFULc5hjEtyXnMe+e
+VNocKsjXn0FnJCT5IOqZZpTIa6Lb6T52jWX3JAU//4DaVl8JWMkZ8r4UptYUfWQ0
+uCdvfEpb3+M3tlWT0LWfoSQ7EpLxrW1zCCEHsEAnhC4m7Dvk9rw9GiYwl+UCSU03
+RkjXXSssHOqcIkhaBigzLCIOKy0apa+7iWc/2rnnTf73PGVxAgMBAAGjgZEwgY4w
+HQYDVR0OBBYEFI6yWPs2UI68iis4PMNGBJFySSUaMB8GA1UdIwQYMBaAFLI/34iA
+ekb9SNronyGSCloICRftMBAGCSsGAQQBgjcVAQQDAgEAMA4GA1UdDwEB/wQEAwIB
+hjAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMA0G
+CSqGSIb3DQEBCwUAA4ICAQApSLtPpgvRt/wu1gRll2PHYXm984XTGVePkfcZd7Sh
+FfG3G21SnAoVYL8jRwgIwqOKpdE07dq+09tELdP2EBJff4uhdHyBJxC40lzJeOK5
+ClzHMhdKq/HQ0dnbdfqqr5b54mkEuQubcZFCtd2e+3hcMi0gaHbKhUO53RhU32yt
+Hy980mcnlfOzrhTyzY7hDZIkWO5mTqp0Ygxlb4w1nh4/PVXWnZPFKd6d2Nbgj34n
+olNMa53R7xFGKj2o1NMTS2ITjTSo0wzdifo41keNIIIjGrPokaINIuDJyvUNDZuR
+vcpNXSpQ83/hAo5F9kmgnetGXxJYUoTJRw1WAGE/33lXxKD9/5BN1CJjwZlBVt6j
+7gVaoELMn64t3pw1zrd+iycCmXW0RmZpLDUEO99SlXKp2N2+qPizh+3Z8RsQBp+u
+obiPbOcpBRTGdj9j90+JvGJSTrohLn6ViRIuiqYKjzFPEKehmFV/yOt40i0MEQW0
+kXoApaYPOhlEY2pdDZARsRcJf8Qa3cVJPqbfgb2gg5kcyjtgZYibEnsYCQJcY0+J
+lemmBQCSEIPqYV33ydz8MUKBweaAREediHGhdQVMmEmuxUTIqxTv2ADcxUSjgQHE
+JXxiQKCWL91HVoetR9NDWHFZBNCdBcZ3TGPX/BoH/a8duNW1RHficqDVOm9pDdDU
+Bw==
+-----END CERTIFICATE-----
diff --git a/src/Backend/AutopilotMonitor.Functions/Security/Certificates/intune-root-2026.pem b/src/Backend/AutopilotMonitor.Functions/Security/Certificates/intune-root-2026.pem
new file mode 100644
index 00000000..9ef17acf
--- /dev/null
+++ b/src/Backend/AutopilotMonitor.Functions/Security/Certificates/intune-root-2026.pem
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFaTCCA1GgAwIBAgIQWhvaqZFB9KVNpmVRc71GWjANBgkqhkiG9w0BAQsFADA4
+MTYwNAYDVQQDEy1NaWNyb3NvZnQgSW50dW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBB
+dXRob3JpdHkwHhcNMjEwODEyMDAwMDAwWhcNMjYwODEyMDAwMDAwWjA4MTYwNAYD
+VQQDEy1NaWNyb3NvZnQgSW50dW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDQL63MwZDlW02yCipf
+dAUGn5Q8sSIc6zjdTkdCRh2SZGVFVLCgFr/EULGw3q7xaOS/mTA2I+koZM+95JvE
+xfsIy+S7I7rLQjYNuJZVXl0s1xj4hfewF1jk8nWjdEwYfZkbZ+/6pYTOtHg/U3tV
+seT63tg6Vc94dkUpOmN4tlyuie2qbMbc1VmAyyITcoIFtRspdE9qW1NnwgzGJz/Q
+KaL2H7LtGyNGwXhH4jSwZa8ZNcwKjJ7wdaG2SNxOgrZsCHv272vugNiGEy4yYwGU
+CyGOAksOaHEobW87Y92s/q4/5beBa979ZCeH4VSpRo2LcQFH9ZM9r05VpFimg8Pu
+pcOl2g1WcuBj1MBBjT/YPaVgbZUp9wvqqefhW6m/JGTYaPJ4YrnMnrzem6kwGP5K
+3xwHWL/5ANTjWDx6+b7dZ5f1AKf2DQFaxslMk4CqTElABI8Te1QzTG/QOmSzewtV
+cwcWGo4F7yJyBbnYcqvN5XJWURFjecuoTvyxPG3R2ljfTKSyJhrTkP0hw3Zxr3cn
+pY0ZtK80mKE0A9pFG597Qy3q/9TJNRtDjeQrr7DCPwQ8cvfeyT568J+hUyAeN7PV
+hOV/OXkWXZND9bbkYKNE4ebfiNTGCeJX9iIGjZfbzz86eDqEVpgHcJstnWN6VWbR
+qkrK/p6m5fqYNZCMAN+g4OyHZQIDAQABo28wbTAdBgNVHQ4EFgQUK0nOsQclTJ3m
+CpPzkLkw9+ZEn5IwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQEw
+FgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZI
+hvcNAQELBQADggIBAGrkO1h+8wKHJHCMf/jRz5DhvigyDOyohx2LlbhL/i/0uSka
+/u/m9uhi0UXXYX9Y15tpRUjZDqtQAjpnFePNcxe3/RRp5kuqVHaMyOHShjTTJ6YD
+hKhlZP37qSBuWv+x2RsUrPzVdpFyljJWyJ1yruBJqgJ2sq3lskb8cO94fhcc2StT
+uO9aB+0YY2ce5OHtOgj39Enx+PRCpweIodhAKdZuTVAX1M4qeBJD87Gg/7b4VfaS
+aM6Frzrh9VuylCM258Xx1CYGgzTYJCcvCYOHA74nS3XigalsKonbdVHUEac+4D7i
+P33JSlV1wlxYPPJqayiBam21YtSHmdJKV3pwkFbvlX2+pNioX86E48YaNz4faq3v
+Cl4xHqMfVfOOG8QLiOnNlHsBKsffD420CKi2SJaKETPJnOLG61265jiT4Yr1mUeW
+G+tQTquFeFdTTSGfToyXE58IMLhI19hQtf/2HU9aZK/vJsjWPYKCucPCXwQZA2Kk
+Z/RT8HsSPPet3GyP3gL0nzfacohJ7RKwClE82exXgiGK/UAFqvEL9pwbLJtX4Hx/
++OhT4zQ9CSppKjSBDIRR8gV6G2HY5gWKXq9K+/Dv+m1APPWsR1kTsqy+tAQPRxlK
+7bDsXt5GawcCP+FM0vJwd5O+ZPB3x5VLG5OLv48cCRUxf1alMQSfsJz/r2L4
+-----END CERTIFICATE-----
diff --git a/src/Backend/AutopilotMonitor.Functions/Security/Certificates/intune-root-2030.pem b/src/Backend/AutopilotMonitor.Functions/Security/Certificates/intune-root-2030.pem
new file mode 100644
index 00000000..97d86dfa
--- /dev/null
+++ b/src/Backend/AutopilotMonitor.Functions/Security/Certificates/intune-root-2030.pem
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFaTCCA1GgAwIBAgIQWDldDibkr6lFMz6rqUVDRDANBgkqhkiG9w0BAQsFADA4
+MTYwNAYDVQQDEy1NaWNyb3NvZnQgSW50dW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBB
+dXRob3JpdHkwHhcNMjUwOTE1MDAwMDAwWhcNMzAwOTE1MDAwMDAwWjA4MTYwNAYD
+VQQDEy1NaWNyb3NvZnQgSW50dW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDpcRMWOOO5oDbbLnOV
+2SnaqN5NuQoEaE7nIUuH/3dKtqL/ZSRqvpNgKUyISfM/hpuq8V8JlYW/cutwg+B8
+0WGXLkFHMgYhJGYxgDiwNbfZ0tk64zUpidzYgj1PeNWkKOVIspvVBiMj3co92LQl
+yK8CL0V4oSeD1LpR8fncdiZtYyWKZssDE1fSG/TH/wTAQHywZJ4cwKV2itgVr1Qw
+F5yShozUerrwmQ+QnEb/5gthJzKleb8lUW1FtHCaMWiEVhflUpQCDJzYgvZJOhFQ
+6l0CD+eR1Oz6scDj/UxJPBEvNKB4G5m1b93gYKZ10mtK0a3Qi4df5A+8P44AhDI6
+zaG2GrmF7EX6vLFOVdPC1d3MKFoApJ3ylMeknwqRlzZj0HB/SjhzyvVbLI+BPVDy
+8AvM+w8cJAtV3Etyrflpmc+dEzS8I1RTO1soEuMZ6/8osg2iPAH7rV6KwbLitQvo
+4thwVSw0x+hERl+M4vKnq7Vs789yC5Nb1zZ6MGTXp3tRiuG40BBOY+6FdBS0aQAQ
++UmsnRxSMTrOC9CKCYFuFYxwDn9J+VvzMNhJ4VlR6e/R7KoJNVV7KWF+OfDcsxxI
+qLw3RcD1dsKKW+kl33K5XW7Q0Ra7KcMiLsvqHffhrYsgmllTGtpCgDLGl1H1vP9/
+TM31bLEc6udQX5xOdcht0VmewQIDAQABo28wbTAdBgNVHQ4EFgQUsj/fiIB6Rv1I
+2uifIZIKWggJF+0wDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQEw
+FgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZI
+hvcNAQELBQADggIBAJjQPngeOpKjhd0eouyDSlWzYMyTLEgrQwKc3XGL4el/AjB1
+bOTaSPs8Y7jLUQ62Za51qyijacWSHBbrOh77We5kUf4UvFcCOKl7KdgBfgqEKJKM
+3/r6SIFMouc0DhWO82Yjs2IOwOuEoKXagZRHfhHGMHFvnj6ebb1uGaPRvxdMVi70
+kEoPysCavQbDahVTKuFySo1iK2BtRDYKJYUZRLTON3NfuVb6AaIUCQpAB8+0qDdR
+VGV0nblmecLJ+pyCmfrWKFoRHiqV69WuAweiCX6qUxpokzwpM78fuaFnDFzihxzo
+FuLbv+79MiupYSsC73OrF9ABNyc215jhmkM1NNNYtEH7Dj0JZz5o5GQ/ROH4Q2jU
+2mSbeB0EOpiW1tiUzt7/R/wRcYvg6XSlwfapmE0EjrW2iUwywkU2+qGHvNQeuoIw
+ILZ2eSBiaYZW97P49GNBpsliPGmpv9N5I35132b8CfwXpSBmqTTcN0UMBUKgZAV2
+AUqjNml8B7bZw8h++CybzMdHOy+8uNnwO/MWsozqXz5SIeRW6YeWqyHTwbfmkJzM
+VKuSBFrSgP9peUmzmpMFiqZ0yb9Rp5AhKx+kBEBAJAq4UEIV9Dxcw1hgiz00g7Ne
+wBokEfeVKpYAyVLPiMvkT/HNZ0mnKeTES1xYONSV27UVzQHhQ6CxAKXiPS8Y
+-----END CERTIFICATE-----