From 2a8a4501d5f2717313e631c742033ffba10360fd Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Sat, 4 Jul 2026 03:30:53 -0400 Subject: [PATCH] fix(dev-mode): avoid certificate store rescans Signed-off-by: Yordis Prieto --- src/EventStore.ClusterNode/Program.cs | 29 +++++++------- .../DevCertificates/CertificateManager.cs | 39 +++++++++++++++++-- .../DevCertificates/UnixCertificateManager.cs | 37 ++++++++++++++---- 3 files changed, 81 insertions(+), 24 deletions(-) diff --git a/src/EventStore.ClusterNode/Program.cs b/src/EventStore.ClusterNode/Program.cs index a08368ccde..e8f181a341 100644 --- a/src/EventStore.ClusterNode/Program.cs +++ b/src/EventStore.ClusterNode/Program.cs @@ -141,8 +141,10 @@ public static async Task Main(string[] args) "DEV MODE WILL GENERATE AND TRUST DEV CERTIFICATES FOR RUNNING A SINGLE SECURE NODE ON LOCALHOST.\n" + "==============================================================================================================\n"); var manager = CertificateManager.Instance; - var result = - manager.EnsureDevelopmentCertificate(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddMonths(1)); + var result = manager.EnsureDevelopmentCertificate( + DateTimeOffset.UtcNow, + DateTimeOffset.UtcNow.AddMonths(1), + out var devCertificate); if (result is not (EnsureCertificateResult.Succeeded or EnsureCertificateResult.ValidCertificatePresent)) { @@ -150,29 +152,30 @@ public static async Task Main(string[] args) return 1; } - var userCerts = manager.ListCertificates(StoreName.My, StoreLocation.CurrentUser, true); - var machineCerts = manager.ListCertificates(StoreName.My, StoreLocation.LocalMachine, true); - var certs = userCerts.Concat(machineCerts).ToList(); - - if (!certs.Any()) + if (devCertificate == null) { Log.Fatal("Could not create dev certificate."); return 1; } - if (!manager.IsTrusted(certs[0]) && RuntimeInformation.IsWindows) + if (manager.IsTrusted(devCertificate)) + { + Log.Information("Dev certificate {cert} is already trusted.", devCertificate); + } + else if (RuntimeInformation.IsWindows) { - Log.Information("Dev certificate {cert} is not trusted. Adding it to the trusted store.", certs[0]); - manager.TrustCertificate(certs[0]); + Log.Information("Dev certificate {cert} is not trusted. Adding it to the trusted store.", + devCertificate); + manager.TrustCertificate(devCertificate); } else { Log.Warning("Automatically trusting dev certs is only supported on Windows.\n" + - "Please trust certificate {cert} if it's not trusted already.", certs[0]); + "Please trust certificate {cert} if it's not trusted already.", devCertificate); } - Log.Information("Running in dev mode using certificate '{cert}'", certs[0]); - certificateProvider = new DevCertificateProvider(certs[0]); + Log.Information("Running in dev mode using certificate '{cert}'", devCertificate); + certificateProvider = new DevCertificateProvider(devCertificate); } else { diff --git a/src/EventStore.Common/DevCertificates/CertificateManager.cs b/src/EventStore.Common/DevCertificates/CertificateManager.cs index fc52e6fa35..514f1b77ee 100644 --- a/src/EventStore.Common/DevCertificates/CertificateManager.cs +++ b/src/EventStore.Common/DevCertificates/CertificateManager.cs @@ -65,6 +65,13 @@ public IList ListCertificates( bool isValid, bool requireExportable = true) { + if (location == StoreLocation.LocalMachine && + storeName is not (StoreName.Root or StoreName.CertificateAuthority) && + !RuntimeInformation.IsWindows && !RuntimeInformation.IsOSX) + { + return []; + } + Log.ListCertificatesStart(location, storeName); var certificates = new List(); try @@ -169,6 +176,32 @@ public EnsureCertificateResult EnsureDevelopmentCertificate( CertificateKeyExportFormat keyExportFormat = CertificateKeyExportFormat.Pfx, bool isInteractive = true) { + var result = EnsureDevelopmentCertificate( + notBefore, + notAfter, + out var certificate, + path, + trust, + includePrivateKey, + password, + keyExportFormat, + isInteractive); + DisposeCertificates(certificate == null ? [] : [certificate]); + return result; + } + + public EnsureCertificateResult EnsureDevelopmentCertificate( + DateTimeOffset notBefore, + DateTimeOffset notAfter, + out X509Certificate2 certificate, + string path = null, + bool trust = false, + bool includePrivateKey = false, + string password = null, + CertificateKeyExportFormat keyExportFormat = CertificateKeyExportFormat.Pfx, + bool isInteractive = true) + { + certificate = null; var result = EnsureCertificateResult.Succeeded; var currentUserCertificates = ListCertificates(StoreName.My, StoreLocation.CurrentUser, isValid: true, @@ -188,8 +221,6 @@ public EnsureCertificateResult EnsureDevelopmentCertificate( certificates = filteredCertificates; - X509Certificate2 certificate = null; - var isNewCertificate = false; if (certificates.Any()) { certificate = certificates.First(); @@ -252,7 +283,6 @@ public EnsureCertificateResult EnsureDevelopmentCertificate( try { Log.CreateDevelopmentCertificateStart(); - isNewCertificate = true; certificate = CreateDevelopmentCertificate(notBefore, notAfter); } catch (Exception e) @@ -347,7 +377,8 @@ public EnsureCertificateResult EnsureDevelopmentCertificate( } } - DisposeCertificates(!isNewCertificate ? certificates : certificates.Append(certificate)); + var returnedCertificate = certificate; + DisposeCertificates(certificates.Where(c => !ReferenceEquals(c, returnedCertificate))); return result; } diff --git a/src/EventStore.Common/DevCertificates/UnixCertificateManager.cs b/src/EventStore.Common/DevCertificates/UnixCertificateManager.cs index e57b7168c5..53227f6551 100644 --- a/src/EventStore.Common/DevCertificates/UnixCertificateManager.cs +++ b/src/EventStore.Common/DevCertificates/UnixCertificateManager.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using EventStore.Common.Utils; @@ -24,18 +26,26 @@ protected override X509Certificate2 SaveCertificateCore(X509Certificate2 certifi { var export = certificate.ExportToPkcs12(string.Empty); certificate.Dispose(); - certificate = X509CertificateLoader.LoadPkcs12(export, "", - X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); - Array.Clear(export, 0, export.Length); - - using (var store = new X509Store(storeName, storeLocation)) + try { + certificate = X509CertificateLoader.LoadPkcs12(export, "", + X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); + + using var store = new X509Store(storeName, storeLocation); store.Open(OpenFlags.ReadWrite); store.Add(certificate); store.Close(); } - - ; + catch (Exception ex) when (IsHomeDirectoryAccessError(ex)) + { + certificate.Dispose(); + certificate = X509CertificateLoader.LoadPkcs12(export, "", + X509KeyStorageFlags.EphemeralKeySet | X509KeyStorageFlags.Exportable); + } + finally + { + Array.Clear(export, 0, export.Length); + } return certificate; } @@ -67,4 +77,17 @@ protected override IList GetCertificatesToRemove(StoreName sto { return ListCertificates(StoreName.My, StoreLocation.CurrentUser, isValid: false, requireExportable: false); } + + static bool IsHomeDirectoryAccessError(Exception ex) + { + for (var current = ex; current != null; current = current.InnerException) + { + if (current is UnauthorizedAccessException or DirectoryNotFoundException) + { + return true; + } + } + + return false; + } }