From d0d5bbea7f32650bb0115e3233e5c650e3b05787 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Wed, 15 Jul 2026 14:43:41 +0200 Subject: [PATCH] Fixed custom root certificate validation on Linux (#861) --- .../Tools/ExternalHttpClientTimeout.cs | 44 +++++++++++++++++-- .../wwwroot/changelog/v26.7.3.md | 1 + 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/app/MindWork AI Studio/Tools/ExternalHttpClientTimeout.cs b/app/MindWork AI Studio/Tools/ExternalHttpClientTimeout.cs index 1181cb40..f697b938 100644 --- a/app/MindWork AI Studio/Tools/ExternalHttpClientTimeout.cs +++ b/app/MindWork AI Studio/Tools/ExternalHttpClientTimeout.cs @@ -359,10 +359,19 @@ public static class ExternalHttpClientTimeout if (sslPolicyErrors is SslPolicyErrors.None) return true; - if (sslPolicyErrors is not SslPolicyErrors.RemoteCertificateChainErrors || certificate is null) - return false; - var host = ReadRequestHost(request); + if (certificate is null) + { + LOGGER.Value.LogError($"Rejected external HTTPS certificate for '{HostForLog(host)}' because the TLS stack did not provide a server certificate. TLS policy errors: {sslPolicyErrors}."); + return false; + } + + if (sslPolicyErrors is not SslPolicyErrors.RemoteCertificateChainErrors) + { + LOGGER.Value.LogError($"Rejected external HTTPS certificate for '{HostForLog(host)}' because custom root certificates can only resolve certificate chain trust errors. TLS policy errors: {sslPolicyErrors}."); + return false; + } + if (trustPolicy is ExternalHttpTrustPolicy.SYSTEM_TRUST_ONLY) { LOGGER.Value.LogError($"Rejected external HTTPS certificate for '{HostForLog(host)}' because this request requires system trust only. Configured custom root certificates are not allowed for this request."); @@ -383,6 +392,10 @@ public static class ExternalHttpClientTimeout customChain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; customChain.ChainPolicy.CustomTrustStore.AddRange(customRootCertificateCache.Certificates); customChain.ChainPolicy.ApplicationPolicy.Add(new Oid(TLS_SERVER_AUTHENTICATION_EKU_OID)); + + // Match the .NET 9 HttpClient default used for the initial system-trust validation. + // Hostname, signature, validity, EKU, and root trust checks remain enabled. + customChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; if (originalChain is not null) { @@ -398,6 +411,8 @@ public static class ExternalHttpClientTimeout var isValid = customChain.Build(serverCertificate); if (isValid) LogCustomRootCertificateAccepted(request); + else + LogCustomRootCertificateValidationFailure(request, sslPolicyErrors, customChain); return isValid; } @@ -459,6 +474,27 @@ public static class ExternalHttpClientTimeout LOGGER.Value.LogWarning($"Accepted an external HTTPS certificate for '{host}' using configured custom root certificates."); } + private static void LogCustomRootCertificateValidationFailure(HttpRequestMessage request, SslPolicyErrors sslPolicyErrors, X509Chain chain) + { + var chainStatuses = FormatChainStatusesForLog(chain.ChainStatus); + var elementStatuses = chain.ChainElements + .Cast() + .Select((element, index) => $"element {index}: {FormatChainStatusesForLog(element.ChainElementStatus)}") + .ToList(); + var host = ReadRequestHost(request); + LOGGER.Value.LogError($"Rejected external HTTPS certificate for '{HostForLog(host)}' after validation with configured custom root certificates. TLS policy errors: {sslPolicyErrors}. Chain statuses: {chainStatuses}. Chain element statuses: {string.Join("; ", elementStatuses)}"); + } + + private static string FormatChainStatusesForLog(IEnumerable statuses) + { + var formattedStatuses = statuses + .Select(status => $"{status.Status} ({status.StatusInformation.Trim()})") + .ToList(); + return formattedStatuses.Count == 0 + ? "none" + : string.Join(", ", formattedStatuses); + } + private static string ReadRequestHost(HttpRequestMessage request) { var host = request.RequestUri?.IdnHost; @@ -484,4 +520,4 @@ public static class ExternalHttpClientTimeout string CacheKey, X509Certificate2Collection Certificates, ExternalHttpCustomRootCertificateState State); -} \ No newline at end of file +} diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.7.3.md b/app/MindWork AI Studio/wwwroot/changelog/v26.7.3.md index a467a4e4..d075dd45 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.7.3.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.7.3.md @@ -5,6 +5,7 @@ - Improved the "My Tasks Assistant": you can now provide one or more documents in addition to text or use documents alone when asking to identify tasks. - Improved update guidance for Flatpak installations and added an enterprise option that lets organizations manage updates entirely through their IT department. - Fixed an issue that could leave AI Studio unresponsive after waking the computer from sleep. Yes, we know this was an annoying bug, and we apologize for the inconvenience. +- Fixed connections to internal HTTPS services and enterprise configuration servers that use organization-provided root certificates on Linux. - Fixed enterprise configuration plugins from Windows-created ZIP files may not load correctly on Linux when the ZIP contained plugin files inside a folder. - Fixed voice recording and transcription on Linux. - Fixed being able to switch document analysis policies while an analysis or media transcription was still in progress.