diff --git a/src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStore.cs b/src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStore.cs index 2c1b6d98..f7efcbfd 100644 --- a/src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStore.cs +++ b/src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStore.cs @@ -27,7 +27,6 @@ internal class CertificateStore : IStorePlugin, IDisposable internal const string Name = "CertificateStore"; private const string DefaultStoreName = nameof(StoreName.My); private readonly ILogService _log; - private readonly ISettingsService _settings; private readonly string _storeName; private readonly IIISClient _iisClient; private readonly CertificateStoreOptions _options; @@ -43,7 +42,6 @@ public CertificateStore( _log = log; _iisClient = iisClient; _options = options; - _settings = settings; _keyFinder = keyFinder; _storeName = options.StoreName ?? DefaultStore(settings, iisClient); if (string.Equals(_storeName, "Personal", StringComparison.InvariantCultureIgnoreCase) || @@ -53,7 +51,7 @@ public CertificateStore( // config files, because that's what the store is called in mmc _storeName = nameof(StoreName.My); } - _storeClient = new CertificateStoreClient(_storeName, StoreLocation.LocalMachine, _log); + _storeClient = new CertificateStoreClient(_storeName, StoreLocation.LocalMachine, _log, settings); _runLevel = runLevel; } @@ -95,37 +93,6 @@ public static string DefaultStore(ISettingsService settings, IIISClient client) } else { - var exportable = - _settings.Store.CertificateStore.PrivateKeyExportable == true || - #pragma warning disable CS0618 // Type or member is obsolete - (_settings.Store.CertificateStore.PrivateKeyExportable == null && _settings.Security.PrivateKeyExportable == true); - #pragma warning restore CS0618 // Type or member is obsolete - - var baseFlags = X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet; - var finalFlags = baseFlags; - if (exportable) - { - finalFlags |= X509KeyStorageFlags.Exportable; - } - _log.Debug("Storing certificate with flags {flags}", finalFlags); - - if (_settings.Store.CertificateStore.UseNextGenerationCryptoApi != true) - { - // Should always be exportable before we attempt to convert, - // because otherwise we won't be able to get to the private key - store = _storeClient.ApplyFlags(store, baseFlags | X509KeyStorageFlags.Exportable); - - // If the ConvertCertificate fails we fall back to the original - // input certificate wit the base flags applied - store = _storeClient.ConvertCertificate(store, finalFlags); - store ??= _storeClient.ApplyFlags(input.Certificate, baseFlags); - } - else - { - // Do not attempt conversion, just apply the final flags - store = _storeClient.ApplyFlags(store, finalFlags); - } - _log.Information("Installing certificate in the certificate store"); _storeClient.InstallCertificate(store); if (!_runLevel.HasFlag(RunLevel.Test)) diff --git a/src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStoreClient.cs b/src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStoreClient.cs index e31cd710..a6a0dccc 100644 --- a/src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStoreClient.cs +++ b/src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStoreClient.cs @@ -12,15 +12,17 @@ public class CertificateStoreClient : IDisposable private readonly X509Store _store; private X509Store? _imStore; private readonly ILogService _log; + private readonly ISettingsService _settings; private readonly StoreLocation _location; private bool disposedValue; - public CertificateStoreClient(string storeName, StoreLocation storeLocation, ILogService log) + public CertificateStoreClient(string storeName, StoreLocation storeLocation, ILogService log, ISettingsService settings) { _log = log; _location = storeLocation; _log.Debug("Certificate store name: {_storeName}", storeName); _store = new X509Store(storeName, storeLocation); + _settings = settings; } public X509Certificate2? FindByThumbprint(string thumbprint) => GetCertificate(x => string.Equals(x.Thumbprint, thumbprint)); @@ -42,6 +44,16 @@ public void InstallCertificate(X509Certificate2 certificate) { _log.Information(LogType.All, "Adding certificate {FriendlyName} to store {name}", certificate.FriendlyName, _store.Name); _log.Verbose("{sub} - {iss} ({thumb})", certificate.Subject, certificate.Issuer, certificate.Thumbprint); + var flags = X509KeyStorageFlags.PersistKeySet; + if (_location == StoreLocation.CurrentUser) + { + flags |= X509KeyStorageFlags.UserKeySet; + } + else + { + flags |= X509KeyStorageFlags.MachineKeySet; + } + certificate = ProcessCertificate(certificate, flags); _store.Add(certificate); } catch @@ -137,7 +149,7 @@ public void UninstallCertificate(X509Certificate2 certificate) /// /// /// - public X509Certificate2 ApplyFlags(X509Certificate2 original, X509KeyStorageFlags flags) + private static X509Certificate2 ApplyFlags(X509Certificate2 original, X509KeyStorageFlags flags) { // If no RSA key is present, we only export and re-fallback to // set the correct flags on the certificate. @@ -148,6 +160,47 @@ public X509Certificate2 ApplyFlags(X509Certificate2 original, X509KeyStorageFlag }; } + /// + /// Apply certificate flags and convert private key provider if asked + /// + /// + /// + /// + private X509Certificate2 ProcessCertificate(X509Certificate2 input, X509KeyStorageFlags baseFlags) + { + var exportable = + _settings.Store.CertificateStore.PrivateKeyExportable == true || + #pragma warning disable CS0618 // Type or member is obsolete + (_settings.Store.CertificateStore.PrivateKeyExportable == null && _settings.Security.PrivateKeyExportable == true); + + var finalFlags = baseFlags; + if (exportable) + { + finalFlags |= X509KeyStorageFlags.Exportable; + } + _log.Debug("Storing certificate with flags {flags}", finalFlags); + + X509Certificate2? store; + if (_settings.Store.CertificateStore.UseNextGenerationCryptoApi != true) + { + // Should always be exportable before we attempt to convert, + // because otherwise we won't be able to get to the private key + store = ApplyFlags(input, baseFlags | X509KeyStorageFlags.Exportable); + + // If the ConvertCertificate fails it returns null, and when that + // happend we apply the final flags to the original input instead + store = ConvertCertificate(store, finalFlags); + store ??= ApplyFlags(input, finalFlags); + } + else + { + // Do not attempt conversion, just apply the final flags + store = ApplyFlags(input, finalFlags); + } + return store; + } + + /// /// Set the right flags on the certificate and /// convert the private key to the right cryptographic @@ -156,7 +209,7 @@ public X509Certificate2 ApplyFlags(X509Certificate2 original, X509KeyStorageFlag /// /// /// - public X509Certificate2? ConvertCertificate(X509Certificate2 original, X509KeyStorageFlags flags) + private X509Certificate2? ConvertCertificate(X509Certificate2 original, X509KeyStorageFlags flags) { try { @@ -213,12 +266,17 @@ public X509Certificate2 ApplyFlags(X509Certificate2 original, X509KeyStorageFlag // means we're left with a pfx generated with the // 'wrong' Crypto provider therefor delete it to // make sure it's retried on the next run. - _log.Warning("Error converting private key to Microsoft RSA SChannel Cryptographic Provider"); + _log.Warning("Error converting key to legacy CryptoAPI, using CNG instead."); _log.Verbose("{ex}", ex); return null; } } + /// + /// Find certificate in the store + /// + /// + /// public X509Certificate2? GetCertificate(Func filter) { var possibles = new List(); diff --git a/src/main.lib/Plugins/StorePlugins/CertificateStore/FindPrivateKey.cs b/src/main.lib/Plugins/StorePlugins/CertificateStore/FindPrivateKey.cs index dae1f56b..1a186af1 100644 --- a/src/main.lib/Plugins/StorePlugins/CertificateStore/FindPrivateKey.cs +++ b/src/main.lib/Plugins/StorePlugins/CertificateStore/FindPrivateKey.cs @@ -46,12 +46,16 @@ partial class FindPrivateKey static string GetKeyFileName(X509Certificate2 cert) { var ecdsa = cert.GetECDsaPrivateKey(); - if (ecdsa is ECDsaCng ecdsaCng && !string.IsNullOrWhiteSpace(ecdsaCng.Key.UniqueName)) + if (ecdsa is ECDsaCng ecdsaCng && + ecdsaCng.Key != null && + !string.IsNullOrWhiteSpace(ecdsaCng.Key.UniqueName)) { return ecdsaCng.Key.UniqueName; } var rsa = cert.GetRSAPrivateKey(); - if (rsa is RSACng rsaCng && !string.IsNullOrWhiteSpace(rsaCng.Key.UniqueName)) + if (rsa is RSACng rsaCng && + rsaCng.Key != null && + !string.IsNullOrWhiteSpace(rsaCng.Key.UniqueName)) { return rsaCng.Key.UniqueName; } diff --git a/src/plugin.store.userstore/UserStore.cs b/src/plugin.store.userstore/UserStore.cs index 48421f96..0ad0afe7 100644 --- a/src/plugin.store.userstore/UserStore.cs +++ b/src/plugin.store.userstore/UserStore.cs @@ -22,14 +22,12 @@ internal class UserStore : IStorePlugin, IDisposable internal const string Name = "UserStore"; private const string DefaultStoreName = nameof(StoreName.My); private readonly ILogService _log; - private readonly ISettingsService _settings; private readonly CertificateStoreClient _storeClient; public UserStore(ILogService log, ISettingsService settings) { _log = log; - _settings = settings; - _storeClient = new CertificateStoreClient(DefaultStoreName, StoreLocation.CurrentUser, _log); + _storeClient = new CertificateStoreClient(DefaultStoreName, StoreLocation.CurrentUser, _log, settings); } public Task Save(ICertificateInfo input) @@ -41,40 +39,8 @@ public UserStore(ILogService log, ISettingsService settings) } else { - var exportable = - _settings.Store.CertificateStore.PrivateKeyExportable == true || - #pragma warning disable CS0618 // Type or member is obsolete - (_settings.Store.CertificateStore.PrivateKeyExportable == null && _settings.Security.PrivateKeyExportable == true); - #pragma warning restore CS0618 // Type or member is obsolete - - var baseFlags = X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.PersistKeySet; - var finalFlags = baseFlags; - if (exportable) - { - finalFlags |= X509KeyStorageFlags.Exportable; - } - _log.Debug("Storing certificate with flags {flags}", finalFlags); - - var store = input.Certificate; - if (_settings.Store.CertificateStore.UseNextGenerationCryptoApi != true) - { - // Should always be exportable before we attempt to convert, - // because otherwise we won't be able to get to the private key - store = _storeClient.ApplyFlags(store, baseFlags | X509KeyStorageFlags.Exportable); - - // If the ConvertCertificate fails we fall back to the original - // input certificate wit the base flags applied - store = _storeClient.ConvertCertificate(store, finalFlags); - store ??= _storeClient.ApplyFlags(input.Certificate, baseFlags); - } - else - { - // Do not attempt conversion, just apply the final flags - store = _storeClient.ApplyFlags(store, finalFlags); - } - _log.Information("Installing certificate in the certificate store"); - _storeClient.InstallCertificate(store); + _storeClient.InstallCertificate(input.Certificate); } return Task.FromResult(new StoreInfo() { Name = Name,