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-----