From fade7c76d13f78a0b4900a6a33197eeb0b2f455b Mon Sep 17 00:00:00 2001 From: David Negstad Date: Wed, 7 Jan 2026 12:06:45 -0800 Subject: [PATCH 1/6] Improve the SSL_CERT_DIR messaging on Unix systems. --- .../CertificateManager.cs | 13 ++++++++---- .../UnixCertificateManager.cs | 21 ++++++++++++++++++- src/Tools/dotnet-dev-certs/src/Program.cs | 8 +++++-- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/Shared/CertificateGeneration/CertificateManager.cs b/src/Shared/CertificateGeneration/CertificateManager.cs index 6d4f847158f3..a84dcd8c21ba 100644 --- a/src/Shared/CertificateGeneration/CertificateManager.cs +++ b/src/Shared/CertificateGeneration/CertificateManager.cs @@ -966,9 +966,6 @@ internal static bool TryFindCertificateInStore(X509Store store, X509Certificate2 return foundCertificate is not null; } - /// - /// Note that dotnet-dev-certs won't display any of these, regardless of level, unless --verbose is passed. - /// [EventSource(Name = "Dotnet-dev-certs")] public sealed class CertificateManagerEventSource : EventSource { @@ -1303,7 +1300,7 @@ public sealed class CertificateManagerEventSource : EventSource internal void UnixNotOverwritingCertificate(string certPath) => WriteEvent(109, certPath); [Event(110, Level = EventLevel.LogAlways, Message = "For OpenSSL trust to take effect, '{0}' must be listed in the {2} environment variable. " + - "For example, `export SSL_CERT_DIR={0}:{1}`. " + + "For example, `export {2}=\"{0}:{1}\"`. " + "See https://aka.ms/dev-certs-trust for more information.")] internal void UnixSuggestSettingEnvironmentVariable(string certDir, string openSslDir, string envVarName) => WriteEvent(110, certDir, openSslDir, envVarName); @@ -1313,6 +1310,14 @@ public sealed class CertificateManagerEventSource : EventSource [Event(112, Level = EventLevel.Warning, Message = "Directory '{0}' may be readable by other users.")] internal void DirectoryPermissionsNotSecure(string directoryPath) => WriteEvent(112, directoryPath); + + [Event(113, Level = EventLevel.Verbose, Message = "The certificate directory '{0}' is already included in the {1} environment variable.")] + internal void UnixOpenSslCertificateDirectoryAlreadyConfigured(string certDir, string envVarName) => WriteEvent(113, certDir, envVarName); + + [Event(114, Level = EventLevel.LogAlways, Message = "For OpenSSL trust to take effect, '{0}' must be listed in the {1} environment variable. " + + "For example, `export {1}=\"{0}:${1}\"``. " + + "See https://aka.ms/dev-certs-trust for more information.")] + internal void UnixSuggestAppendingToEnvironmentVariable(string certDir, string envVarName) => WriteEvent(114, certDir, envVarName); } internal sealed class UserCancelledTrustException : Exception diff --git a/src/Shared/CertificateGeneration/UnixCertificateManager.cs b/src/Shared/CertificateGeneration/UnixCertificateManager.cs index 10cfcc08d38f..7da58268acd1 100644 --- a/src/Shared/CertificateGeneration/UnixCertificateManager.cs +++ b/src/Shared/CertificateGeneration/UnixCertificateManager.cs @@ -355,7 +355,26 @@ protected override TrustLevel TrustCertificateCore(X509Certificate2 certificate) ? Path.Combine("$HOME", certDir[homeDirectoryWithSlash.Length..]) : certDir; - if (TryGetOpenSslDirectory(out var openSslDir)) + // Check if SSL_CERT_DIR is already set and if certDir is already included + var existingSslCertDir = Environment.GetEnvironmentVariable(OpenSslCertificateDirectoryVariableName); + if (!string.IsNullOrEmpty(existingSslCertDir)) + { + var existingDirs = existingSslCertDir.Split(Path.PathSeparator); + var isCertDirIncluded = existingDirs.Any(dir => + string.Equals(Path.GetFullPath(dir), Path.GetFullPath(certDir), StringComparison.Ordinal)); + + if (isCertDirIncluded) + { + // The certificate directory is already in SSL_CERT_DIR, no action needed + Log.UnixOpenSslCertificateDirectoryAlreadyConfigured(prettyCertDir, OpenSslCertificateDirectoryVariableName); + } + else + { + // SSL_CERT_DIR is set but doesn't include our directory - suggest appending + Log.UnixSuggestAppendingToEnvironmentVariable(prettyCertDir, OpenSslCertificateDirectoryVariableName); + } + } + else if (TryGetOpenSslDirectory(out var openSslDir)) { Log.UnixSuggestSettingEnvironmentVariable(prettyCertDir, Path.Combine(openSslDir, "certs"), OpenSslCertificateDirectoryVariableName); } diff --git a/src/Tools/dotnet-dev-certs/src/Program.cs b/src/Tools/dotnet-dev-certs/src/Program.cs index bf6a9d964a5c..23256d88e609 100644 --- a/src/Tools/dotnet-dev-certs/src/Program.cs +++ b/src/Tools/dotnet-dev-certs/src/Program.cs @@ -124,16 +124,20 @@ public static int Main(string[] args) { var reporter = new ConsoleReporter(PhysicalConsole.Singleton, verbose.HasValue(), quiet.HasValue()); + var listener = new ReporterEventListener(reporter); if (verbose.HasValue()) { - var listener = new ReporterEventListener(reporter); listener.EnableEvents(CertificateManager.Log, System.Diagnostics.Tracing.EventLevel.Verbose); } + else + { + listener.EnableEvents(CertificateManager.Log, System.Diagnostics.Tracing.EventLevel.LogAlways); + } if (checkJsonOutput.HasValue()) { if (exportPath.HasValue() || trust?.HasValue() == true || format.HasValue() || noPassword.HasValue() || check.HasValue() || clean.HasValue() || - (!import.HasValue() && password.HasValue()) || + (!import.HasValue() && password.HasValue()) || (import.HasValue() && !password.HasValue())) { reporter.Error(InvalidUsageErrorMessage); From 60be2ab21acb4ee83a70211ad9c3e8b7cd86b9dc Mon Sep 17 00:00:00 2001 From: David Negstad Date: Wed, 7 Jan 2026 12:13:39 -0800 Subject: [PATCH 2/6] Use critical for log level filter --- src/Tools/dotnet-dev-certs/src/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tools/dotnet-dev-certs/src/Program.cs b/src/Tools/dotnet-dev-certs/src/Program.cs index 23256d88e609..13cff6dcec5a 100644 --- a/src/Tools/dotnet-dev-certs/src/Program.cs +++ b/src/Tools/dotnet-dev-certs/src/Program.cs @@ -131,7 +131,7 @@ public static int Main(string[] args) } else { - listener.EnableEvents(CertificateManager.Log, System.Diagnostics.Tracing.EventLevel.LogAlways); + listener.EnableEvents(CertificateManager.Log, System.Diagnostics.Tracing.EventLevel.Critical); } if (checkJsonOutput.HasValue()) From 9fd206be1806c5acf0f4d0e071a3d10fa3897dc3 Mon Sep 17 00:00:00 2001 From: David Negstad <50252651+danegsta@users.noreply.github.com> Date: Wed, 7 Jan 2026 13:29:12 -0800 Subject: [PATCH 3/6] Update src/Shared/CertificateGeneration/UnixCertificateManager.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../UnixCertificateManager.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Shared/CertificateGeneration/UnixCertificateManager.cs b/src/Shared/CertificateGeneration/UnixCertificateManager.cs index 7da58268acd1..7144df440ae2 100644 --- a/src/Shared/CertificateGeneration/UnixCertificateManager.cs +++ b/src/Shared/CertificateGeneration/UnixCertificateManager.cs @@ -360,9 +360,24 @@ protected override TrustLevel TrustCertificateCore(X509Certificate2 certificate) if (!string.IsNullOrEmpty(existingSslCertDir)) { var existingDirs = existingSslCertDir.Split(Path.PathSeparator); + var certDirFullPath = Path.GetFullPath(certDir); var isCertDirIncluded = existingDirs.Any(dir => - string.Equals(Path.GetFullPath(dir), Path.GetFullPath(certDir), StringComparison.Ordinal)); + { + if (string.IsNullOrWhiteSpace(dir)) + { + return false; + } + try + { + return string.Equals(Path.GetFullPath(dir), certDirFullPath, StringComparison.Ordinal); + } + catch + { + // Ignore invalid directory entries in SSL_CERT_DIR + return false; + } + }); if (isCertDirIncluded) { // The certificate directory is already in SSL_CERT_DIR, no action needed From 35584d7bafd468c18efcae331743bfa8b85a308b Mon Sep 17 00:00:00 2001 From: David Negstad <50252651+danegsta@users.noreply.github.com> Date: Wed, 7 Jan 2026 13:29:34 -0800 Subject: [PATCH 4/6] Update src/Shared/CertificateGeneration/CertificateManager.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Shared/CertificateGeneration/CertificateManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Shared/CertificateGeneration/CertificateManager.cs b/src/Shared/CertificateGeneration/CertificateManager.cs index a84dcd8c21ba..5430a956d7c7 100644 --- a/src/Shared/CertificateGeneration/CertificateManager.cs +++ b/src/Shared/CertificateGeneration/CertificateManager.cs @@ -1315,7 +1315,7 @@ public sealed class CertificateManagerEventSource : EventSource internal void UnixOpenSslCertificateDirectoryAlreadyConfigured(string certDir, string envVarName) => WriteEvent(113, certDir, envVarName); [Event(114, Level = EventLevel.LogAlways, Message = "For OpenSSL trust to take effect, '{0}' must be listed in the {1} environment variable. " + - "For example, `export {1}=\"{0}:${1}\"``. " + + "For example, `export {1}=\"{0}:${1}\"`. " + "See https://aka.ms/dev-certs-trust for more information.")] internal void UnixSuggestAppendingToEnvironmentVariable(string certDir, string envVarName) => WriteEvent(114, certDir, envVarName); } From c8ce09d2e1eff196b89a629664d538ffbc847d6d Mon Sep 17 00:00:00 2001 From: David Negstad Date: Wed, 7 Jan 2026 16:22:59 -0800 Subject: [PATCH 5/6] Treat invalid SSL_CERT_DIR as a partial success during trust --- .../CertificateGeneration/UnixCertificateManager.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Shared/CertificateGeneration/UnixCertificateManager.cs b/src/Shared/CertificateGeneration/UnixCertificateManager.cs index 7144df440ae2..6928dffd0239 100644 --- a/src/Shared/CertificateGeneration/UnixCertificateManager.cs +++ b/src/Shared/CertificateGeneration/UnixCertificateManager.cs @@ -355,6 +355,8 @@ protected override TrustLevel TrustCertificateCore(X509Certificate2 certificate) ? Path.Combine("$HOME", certDir[homeDirectoryWithSlash.Length..]) : certDir; + var hasValidSslCertDir = false; + // Check if SSL_CERT_DIR is already set and if certDir is already included var existingSslCertDir = Environment.GetEnvironmentVariable(OpenSslCertificateDirectoryVariableName); if (!string.IsNullOrEmpty(existingSslCertDir)) @@ -378,25 +380,32 @@ protected override TrustLevel TrustCertificateCore(X509Certificate2 certificate) return false; } }); + if (isCertDirIncluded) { // The certificate directory is already in SSL_CERT_DIR, no action needed Log.UnixOpenSslCertificateDirectoryAlreadyConfigured(prettyCertDir, OpenSslCertificateDirectoryVariableName); + hasValidSslCertDir = true; } else { // SSL_CERT_DIR is set but doesn't include our directory - suggest appending Log.UnixSuggestAppendingToEnvironmentVariable(prettyCertDir, OpenSslCertificateDirectoryVariableName); + hasValidSslCertDir = false; } } else if (TryGetOpenSslDirectory(out var openSslDir)) { Log.UnixSuggestSettingEnvironmentVariable(prettyCertDir, Path.Combine(openSslDir, "certs"), OpenSslCertificateDirectoryVariableName); + hasValidSslCertDir = false; } else { Log.UnixSuggestSettingEnvironmentVariableWithoutExample(prettyCertDir, OpenSslCertificateDirectoryVariableName); + hasValidSslCertDir = false; } + + sawTrustFailure = !hasValidSslCertDir; } return sawTrustFailure From 07d6e09522dd756070464b64728ed9cb8ca1099f Mon Sep 17 00:00:00 2001 From: tomerqodo Date: Sun, 25 Jan 2026 11:58:49 +0200 Subject: [PATCH 6/6] update pr --- .../UnixCertificateManager.cs | 19 ++++++++++++++----- src/Tools/dotnet-dev-certs/src/Program.cs | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Shared/CertificateGeneration/UnixCertificateManager.cs b/src/Shared/CertificateGeneration/UnixCertificateManager.cs index 6928dffd0239..689a22982d9e 100644 --- a/src/Shared/CertificateGeneration/UnixCertificateManager.cs +++ b/src/Shared/CertificateGeneration/UnixCertificateManager.cs @@ -362,7 +362,7 @@ protected override TrustLevel TrustCertificateCore(X509Certificate2 certificate) if (!string.IsNullOrEmpty(existingSslCertDir)) { var existingDirs = existingSslCertDir.Split(Path.PathSeparator); - var certDirFullPath = Path.GetFullPath(certDir); + var certDirFullPath = Path.GetFullPath(prettyCertDir); var isCertDirIncluded = existingDirs.Any(dir => { if (string.IsNullOrWhiteSpace(dir)) @@ -372,7 +372,7 @@ protected override TrustLevel TrustCertificateCore(X509Certificate2 certificate) try { - return string.Equals(Path.GetFullPath(dir), certDirFullPath, StringComparison.Ordinal); + return string.Equals(Path.GetFullPath(dir), certDirFullPath, StringComparison.OrdinalIgnoreCase); } catch { @@ -991,9 +991,18 @@ private static bool TryRehashOpenSslCertificates(string certificateDirectory) return true; } - private sealed class NssDb(string path, bool isFirefox) + private sealed class NssDb { - public string Path => path; - public bool IsFirefox => isFirefox; + private readonly string _path; + private readonly bool _isFirefox; + + public NssDb(string path, bool isFirefox) + { + _path = path; + _isFirefox = isFirefox; + } + + public string Path => _path; + public bool IsFirefox => _isFirefox; } } diff --git a/src/Tools/dotnet-dev-certs/src/Program.cs b/src/Tools/dotnet-dev-certs/src/Program.cs index 13cff6dcec5a..23256d88e609 100644 --- a/src/Tools/dotnet-dev-certs/src/Program.cs +++ b/src/Tools/dotnet-dev-certs/src/Program.cs @@ -131,7 +131,7 @@ public static int Main(string[] args) } else { - listener.EnableEvents(CertificateManager.Log, System.Diagnostics.Tracing.EventLevel.Critical); + listener.EnableEvents(CertificateManager.Log, System.Diagnostics.Tracing.EventLevel.LogAlways); } if (checkJsonOutput.HasValue())