From 311d9ae9ea5eb80cd3543ce98d4df94482fb26bf Mon Sep 17 00:00:00 2001 From: David Negstad Date: Wed, 7 Jan 2026 12:06:45 -0800 Subject: [PATCH 1/5] 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 b1d64f05f901bd1342be0f204675fd3d2be2407d Mon Sep 17 00:00:00 2001 From: David Negstad Date: Wed, 7 Jan 2026 12:13:39 -0800 Subject: [PATCH 2/5] 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 39b104e6acc1ff6ac03b59879be685862b65766c 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/5] 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 ce3d4d5d94b331484ecd8d0b8087dd2643a4c91f 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/5] 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 7e395e3af9187fd2bf6cdc5230a16625845794ee Mon Sep 17 00:00:00 2001 From: David Negstad Date: Wed, 7 Jan 2026 16:22:59 -0800 Subject: [PATCH 5/5] 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