Skip to content

Commit

Permalink
fix #2329
Browse files Browse the repository at this point in the history
  • Loading branch information
WouterTinus committed Mar 13, 2023
1 parent 48f1b32 commit 8fdb649
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 76 deletions.
Expand Up @@ -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;
Expand All @@ -43,7 +42,6 @@ internal class CertificateStore : IStorePlugin, IDisposable
_log = log;
_iisClient = iisClient;
_options = options;
_settings = settings;
_keyFinder = keyFinder;
_storeName = options.StoreName ?? DefaultStore(settings, iisClient);
if (string.Equals(_storeName, "Personal", StringComparison.InvariantCultureIgnoreCase) ||
Expand All @@ -53,7 +51,7 @@ internal class CertificateStore : IStorePlugin, IDisposable
// 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;
}

Expand Down Expand Up @@ -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))
Expand Down
Expand Up @@ -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));
Expand All @@ -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
Expand Down Expand Up @@ -137,7 +149,7 @@ public void UninstallCertificate(X509Certificate2 certificate)
/// <param name="original"></param>
/// <param name="flags"></param>
/// <returns></returns>
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.
Expand All @@ -148,6 +160,47 @@ public X509Certificate2 ApplyFlags(X509Certificate2 original, X509KeyStorageFlag
};
}

/// <summary>
/// Apply certificate flags and convert private key provider if asked
/// </summary>
/// <param name="input"></param>
/// <param name="baseFlags"></param>
/// <returns></returns>
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;
}


/// <summary>
/// Set the right flags on the certificate and
/// convert the private key to the right cryptographic
Expand All @@ -156,7 +209,7 @@ public X509Certificate2 ApplyFlags(X509Certificate2 original, X509KeyStorageFlag
/// <param name="original"></param>
/// <param name="flags"></param>
/// <returns></returns>
public X509Certificate2? ConvertCertificate(X509Certificate2 original, X509KeyStorageFlags flags)
private X509Certificate2? ConvertCertificate(X509Certificate2 original, X509KeyStorageFlags flags)
{
try
{
Expand Down Expand Up @@ -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;
}
}

/// <summary>
/// Find certificate in the store
/// </summary>
/// <param name="filter"></param>
/// <returns></returns>
public X509Certificate2? GetCertificate(Func<X509Certificate2, bool> filter)
{
var possibles = new List<X509Certificate2>();
Expand Down
Expand Up @@ -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;
}
Expand Down
38 changes: 2 additions & 36 deletions src/plugin.store.userstore/UserStore.cs
Expand Up @@ -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<StoreInfo?> Save(ICertificateInfo input)
Expand All @@ -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<StoreInfo?>(new StoreInfo() {
Name = Name,
Expand Down

0 comments on commit 8fdb649

Please sign in to comment.