From 410707f501d55297c66ac9c091daa7356591fdc4 Mon Sep 17 00:00:00 2001 From: Robert E Date: Thu, 26 Sep 2024 21:44:11 +1000 Subject: [PATCH 1/2] Remove as much of the WINDOWS_CERT compiler directives as possible --- .../Calamari.Common/CalamariFlavourProgram.cs | 2 +- .../PrivateKeyAccessRuleExtensionMethods.cs | 7 +- .../WindowsNative/CertificatePal.cs | 9 +- .../WindowsNative/SafeCertContextHandle.cs | 6 +- .../SafeCertContextHandleExtensions.cs | 5 +- .../WindowsNative/SafeCertStoreHandle.cs | 6 +- .../WindowsNative/SafeCspHandle.cs | 4 +- .../WindowsNative/WindowsX509Native.cs | 26 +-- .../WindowsX509CertificateStore.cs | 173 ++++++++++-------- .../ImportCertificateCommandFixture.cs | 16 +- .../WindowsX509CertificateStoreFixture.cs | 104 ++++++----- .../DeployWebPackageToIISFixture.cs | 10 +- .../Helpers/Certificates/SampleCertificate.cs | 7 +- .../Commands/ImportCertificateCommand.cs | 14 +- source/Calamari/Program.cs | 6 - 15 files changed, 205 insertions(+), 190 deletions(-) diff --git a/source/Calamari.Common/CalamariFlavourProgram.cs b/source/Calamari.Common/CalamariFlavourProgram.cs index d6b50122b..3d83f44ce 100644 --- a/source/Calamari.Common/CalamariFlavourProgram.cs +++ b/source/Calamari.Common/CalamariFlavourProgram.cs @@ -81,7 +81,7 @@ protected virtual int Run(string[] args) } catch (Exception ex) { - return ConsoleFormatter.PrintError(ConsoleLog.Instance, ex); + return ConsoleFormatter.PrintError(Log, ex); } } diff --git a/source/Calamari.Shared/Integration/Certificates/PrivateKeyAccessRuleExtensionMethods.cs b/source/Calamari.Shared/Integration/Certificates/PrivateKeyAccessRuleExtensionMethods.cs index 7d19d537a..58eaf94e0 100644 --- a/source/Calamari.Shared/Integration/Certificates/PrivateKeyAccessRuleExtensionMethods.cs +++ b/source/Calamari.Shared/Integration/Certificates/PrivateKeyAccessRuleExtensionMethods.cs @@ -1,6 +1,4 @@ -#if WINDOWS_CERTIFICATE_STORE_SUPPORT - -using System; +using System; using System.Security.Principal; namespace Calamari.Integration.Certificates @@ -20,5 +18,4 @@ public static IdentityReference GetIdentityReference(this PrivateKeyAccessRule p } } } -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/source/Calamari.Shared/Integration/Certificates/WindowsNative/CertificatePal.cs b/source/Calamari.Shared/Integration/Certificates/WindowsNative/CertificatePal.cs index 52d80c6b2..7964cff11 100644 --- a/source/Calamari.Shared/Integration/Certificates/WindowsNative/CertificatePal.cs +++ b/source/Calamari.Shared/Integration/Certificates/WindowsNative/CertificatePal.cs @@ -1,5 +1,4 @@ -#if WINDOWS_CERTIFICATE_STORE_SUPPORT -using System; +using System; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; @@ -115,6 +114,8 @@ public static byte[] GetCspPrivateKeySecurity(SafeCspHandle cspHandle) return buffer; } + // At the moment these models are not in the Calamari Solution. This is the next step. +#if WINDOWS_CERTIFICATE_STORE_SUPPORT public static byte[] GetCngPrivateKeySecurity(SafeNCryptKeyHandle hObject) { int bufferSize = 0; @@ -179,6 +180,7 @@ public static void DeleteCngKey(SafeNCryptKeyHandle key) throw new CryptographicException(errorCode); } +#endif public static string GetSubjectName(SafeCertContextHandle certificate) { var flags = CertNameFlags.None; @@ -194,5 +196,4 @@ public static string GetSubjectName(SafeCertContextHandle certificate) return sb.ToString(); } } -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/source/Calamari.Shared/Integration/Certificates/WindowsNative/SafeCertContextHandle.cs b/source/Calamari.Shared/Integration/Certificates/WindowsNative/SafeCertContextHandle.cs index dceca3a52..75b8684e7 100644 --- a/source/Calamari.Shared/Integration/Certificates/WindowsNative/SafeCertContextHandle.cs +++ b/source/Calamari.Shared/Integration/Certificates/WindowsNative/SafeCertContextHandle.cs @@ -1,5 +1,4 @@ -#if WINDOWS_CERTIFICATE_STORE_SUPPORT -using System; +using System; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; @@ -57,5 +56,4 @@ public IntPtr Disconnect() return ptr; } } -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/source/Calamari.Shared/Integration/Certificates/WindowsNative/SafeCertContextHandleExtensions.cs b/source/Calamari.Shared/Integration/Certificates/WindowsNative/SafeCertContextHandleExtensions.cs index be043a017..907313fa6 100644 --- a/source/Calamari.Shared/Integration/Certificates/WindowsNative/SafeCertContextHandleExtensions.cs +++ b/source/Calamari.Shared/Integration/Certificates/WindowsNative/SafeCertContextHandleExtensions.cs @@ -1,6 +1,4 @@ -#if WINDOWS_CERTIFICATE_STORE_SUPPORT - -namespace Calamari.Integration.Certificates.WindowsNative +namespace Calamari.Integration.Certificates.WindowsNative { internal static class SafeCertContextHandleExtensions { @@ -25,4 +23,3 @@ public static T GetCertificateProperty(this SafeCertContextHandle certificate } } } -#endif \ No newline at end of file diff --git a/source/Calamari.Shared/Integration/Certificates/WindowsNative/SafeCertStoreHandle.cs b/source/Calamari.Shared/Integration/Certificates/WindowsNative/SafeCertStoreHandle.cs index c73120911..b8277c364 100644 --- a/source/Calamari.Shared/Integration/Certificates/WindowsNative/SafeCertStoreHandle.cs +++ b/source/Calamari.Shared/Integration/Certificates/WindowsNative/SafeCertStoreHandle.cs @@ -1,5 +1,4 @@ -#if WINDOWS_CERTIFICATE_STORE_SUPPORT -using System; +using System; using Microsoft.Win32.SafeHandles; namespace Calamari.Integration.Certificates.WindowsNative @@ -28,5 +27,4 @@ protected override bool ReleaseHandle() public static SafeCertStoreHandle InvalidHandle => new SafeCertStoreHandle(IntPtr.Zero); } -} -#endif +} \ No newline at end of file diff --git a/source/Calamari.Shared/Integration/Certificates/WindowsNative/SafeCspHandle.cs b/source/Calamari.Shared/Integration/Certificates/WindowsNative/SafeCspHandle.cs index 71d79a32a..60b92f4cb 100644 --- a/source/Calamari.Shared/Integration/Certificates/WindowsNative/SafeCspHandle.cs +++ b/source/Calamari.Shared/Integration/Certificates/WindowsNative/SafeCspHandle.cs @@ -1,5 +1,4 @@ -#if WINDOWS_CERTIFICATE_STORE_SUPPORT -using System; +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Cryptography; @@ -63,4 +62,3 @@ protected override bool ReleaseHandle() } } } -#endif \ No newline at end of file diff --git a/source/Calamari.Shared/Integration/Certificates/WindowsNative/WindowsX509Native.cs b/source/Calamari.Shared/Integration/Certificates/WindowsNative/WindowsX509Native.cs index aebacc338..05083b090 100644 --- a/source/Calamari.Shared/Integration/Certificates/WindowsNative/WindowsX509Native.cs +++ b/source/Calamari.Shared/Integration/Certificates/WindowsNative/WindowsX509Native.cs @@ -1,5 +1,4 @@ -#if WINDOWS_CERTIFICATE_STORE_SUPPORT -#nullable disable +#nullable disable using System; using System.Runtime.InteropServices; using System.Text; @@ -69,14 +68,7 @@ internal static extern bool CryptAcquireCertificatePrivateKey(SafeCertContextHan [Out] out int dwKeySpec, [Out, MarshalAs(UnmanagedType.Bool)] out bool pfCallerFreeProvOrNCryptKey); - [DllImport("Crypt32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool CryptAcquireCertificatePrivateKey(SafeCertContextHandle pCert, - AcquireCertificateKeyOptions dwFlags, - IntPtr pvReserved, // void * - [Out] out SafeNCryptKeyHandle phCryptProvOrNCryptKey, - [Out] out int dwKeySpec, - [Out, MarshalAs(UnmanagedType.Bool)] out bool pfCallerFreeProvOrNCryptKey); + [DllImport("Crypt32.dll", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern @@ -91,6 +83,7 @@ bool CertEnumSystemStoreCallBackProto( [MarshalAs(UnmanagedType.LPWStr)] string storeName, uint dwFlagsNotUsed, IntPtr notUsed1, IntPtr notUsed2, IntPtr notUsed3); +#if WINDOWS_CERTIFICATE_STORE_SUPPORT [DllImport("Ncrypt.dll", SetLastError = true, ExactSpelling = true)] internal static extern int NCryptGetProperty(SafeNCryptHandle hObject, [MarshalAs(UnmanagedType.LPWStr)] string szProperty, [Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbOutput, int cbOutput, ref int pcbResult, int flags); @@ -100,6 +93,16 @@ bool CertEnumSystemStoreCallBackProto( [DllImport("Ncrypt.dll")] internal static extern int NCryptDeleteKey(SafeNCryptKeyHandle hKey, int flags); + [DllImport("Crypt32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CryptAcquireCertificatePrivateKey(SafeCertContextHandle pCert, + AcquireCertificateKeyOptions dwFlags, + IntPtr pvReserved, // void * + [Out] out SafeNCryptKeyHandle phCryptProvOrNCryptKey, + [Out] out int dwKeySpec, + [Out, MarshalAs(UnmanagedType.Bool)] out bool pfCallerFreeProvOrNCryptKey); +#endif + [Flags] internal enum CertStoreProviders { @@ -333,5 +336,4 @@ internal struct CRYPT_BIT_BLOB public int cUnusedBits; } } -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/source/Calamari.Shared/Integration/Certificates/WindowsX509CertificateStore.cs b/source/Calamari.Shared/Integration/Certificates/WindowsX509CertificateStore.cs index dfd2923fc..03f71723a 100644 --- a/source/Calamari.Shared/Integration/Certificates/WindowsX509CertificateStore.cs +++ b/source/Calamari.Shared/Integration/Certificates/WindowsX509CertificateStore.cs @@ -1,5 +1,4 @@ -#if WINDOWS_CERTIFICATE_STORE_SUPPORT -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -18,14 +17,26 @@ namespace Calamari.Integration.Certificates { - public class WindowsX509CertificateStore: IWindowsX509CertificateStore + public class WindowsX509CertificateStore : IWindowsX509CertificateStore { + readonly ILog log; public static readonly ISemaphoreFactory Semaphores = new SystemSemaphoreManager(); public static readonly string SemaphoreName = nameof(WindowsX509CertificateStore); const string IntermediateAuthorityStoreName = "CA"; public static readonly string RootAuthorityStoreName = "Root"; + public WindowsX509CertificateStore(ILog log) + { + this.log = log; + } + + // This should only be used in tests + public WindowsX509CertificateStore(): this(ConsoleLog.Instance) + { + + } + private static IDisposable AcquireSemaphore() { return Semaphores.Acquire(SemaphoreName, "Another process is working with the certificate store, please wait..."); @@ -96,7 +107,7 @@ public void ImportCertificateToStore(byte[] pfxBytes, string password, string us { // Because we have to store the private-key in the machine key-store, we must grant the user access to it var keySecurity = new[] {new PrivateKeyAccessRule(account.Value, PrivateKeyAccess.FullControl)}; - AddPrivateKeyAccessRules(keySecurity, certificate); + CryptoKeySecurityAccessRules.AddPrivateKeyAccessRules(keySecurity, certificate); } } } @@ -126,41 +137,12 @@ public void AddPrivateKeyAccessRules(string thumbprint, StoreLocation storeLocat if (!certificate.HasPrivateKey()) throw new Exception("Certificate does not have a private-key"); - AddPrivateKeyAccessRules(privateKeyAccessRules, certificate); + CryptoKeySecurityAccessRules.AddPrivateKeyAccessRules(privateKeyAccessRules, certificate); store.Close(); } } - public CryptoKeySecurity GetPrivateKeySecurity(string thumbprint, StoreLocation storeLocation, string storeName) - { - using (AcquireSemaphore()) - { - var store = new X509Store(storeName, storeLocation); - store.Open(OpenFlags.ReadOnly); - - var found = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false); - store.Close(); - - if (found.Count == 0) - throw new Exception( - $"Could not find certificate with thumbprint '{thumbprint}' in store Cert:\\{storeLocation}\\{storeName}"); - - var certificate = new SafeCertContextHandle(found[0].Handle, false); - - if (!certificate.HasPrivateKey()) - throw new Exception("Certificate does not have a private-key"); - - var keyProvInfo = - certificate.GetCertificateProperty(CertificateProperty.KeyProviderInfo); - - // If it is a CNG key - return keyProvInfo.dwProvType == 0 - ? GetCngPrivateKeySecurity(certificate) - : GetCspPrivateKeySecurity(certificate); - } - } - /// /// Unlike X509Store.Remove() this function also cleans up private-keys /// @@ -188,6 +170,7 @@ public void RemoveCertificateFromStore(string thumbprint, StoreLocation storeLoc // If it is a CNG key if (keyProvInfo.dwProvType == 0) { +#if WINDOWS_CERTIFICATE_STORE_SUPPORT try { var key = CertificatePal.GetCngPrivateKey(certificateHandle); @@ -197,6 +180,7 @@ public void RemoveCertificateFromStore(string thumbprint, StoreLocation storeLoc { throw new Exception("Exception while deleting CNG private key", ex); } +#endif } else // CAPI key { @@ -258,7 +242,7 @@ public static ICollection GetStoreNames(StoreLocation location) return names; } - static SafeCertContextHandle ImportPfxToStore(CertificateSystemStoreLocation storeLocation, string storeName, byte[] pfxBytes, string password, + SafeCertContextHandle ImportPfxToStore(CertificateSystemStoreLocation storeLocation, string storeName, byte[] pfxBytes, string password, bool useUserKeyStore, bool privateKeyExportable) { var pfxImportFlags = useUserKeyStore @@ -314,7 +298,7 @@ internal static bool CertEnumSystemStoreCallBack(string storeName, uint dwFlagsN return true; } - static void AddCertificateToStore(CertificateSystemStoreLocation storeLocation, string storeName, SafeCertContextHandle certificate) + void AddCertificateToStore(CertificateSystemStoreLocation storeLocation, string storeName, SafeCertContextHandle certificate) { try { @@ -331,14 +315,14 @@ static void AddCertificateToStore(CertificateSystemStoreLocation storeLocation, if (error == (int) CapiErrorCode.CRYPT_E_EXISTS) { - Log.Info($"Certificate '{subjectName}' already exists in store '{storeName}'."); + log.Info($"Certificate '{subjectName}' already exists in store '{storeName}'."); return; } throw new CryptographicException(error); } - Log.Info($"Imported certificate '{subjectName}' into store '{storeName}'"); + log.Info($"Imported certificate '{subjectName}' into store '{storeName}'"); } } catch (Exception ex) @@ -406,7 +390,55 @@ static IList GetCertificatesFromPfx(byte[] pfxBytes, stri } } - static void AddPrivateKeyAccessRules(ICollection accessRules, SafeCertContextHandle certificate) + static IList GetCertificatesToImport(byte[] pfxBytes, string? password) + { + using (var memoryStream = new MemoryStream(pfxBytes)) + { + // The latest version of BouncyCastle fails if the cert doesn't require a key, but we pass an empty array key. + // Will issue a PR to make this configurable at least in a way that doesn't require writing environment variables. + Environment.SetEnvironmentVariable(Org.BouncyCastle.Pkcs.Pkcs12Store.IgnoreUselessPasswordProperty, "true"); + var pkcs12Store = new Pkcs12StoreBuilder().Build(); + pkcs12Store.Load(memoryStream, password?.ToCharArray() ?? "".ToCharArray()); + + if (pkcs12Store.Count < 1) + throw new Exception("No certificates were found in PFX"); + + var aliases = pkcs12Store.Aliases.Cast().ToList(); + + // Find the first bag which contains a private-key + var keyAlias = aliases.FirstOrDefault(alias => pkcs12Store.IsKeyEntry(alias)); + + if (keyAlias != null) + { + return pkcs12Store.GetCertificateChain(keyAlias).Select(x => x.Certificate).ToList(); + + } + + return new List + { + pkcs12Store.GetCertificate(aliases.First()).Certificate + }; + } + } + + static bool IsSelfSigned(SafeCertContextHandle certificate) + { + var certificateInfo = (CERT_INFO)Marshal.PtrToStructure(certificate.CertificateContext.pCertInfo, typeof(CERT_INFO)); + return CertCompareCertificateName(CertificateEncodingType.Pkcs7OrX509AsnEncoding, ref certificateInfo.Subject, ref certificateInfo.Issuer); + } + + static byte[] CalculateThumbprint(Org.BouncyCastle.X509.X509Certificate certificate) + { + var der = certificate.GetEncoded(); + return DigestUtilities.CalculateDigest("SHA1", der); + } + } + +#if WINDOWS_CERTIFICATE_STORE_SUPPORT + public static class CryptoKeySecurityAccessRules + { + + internal static void AddPrivateKeyAccessRules(ICollection accessRules, SafeCertContextHandle certificate) { try { @@ -515,50 +547,41 @@ static CryptoKeySecurity GetCspPrivateKeySecurity(SafeCertContextHandle certific return security; } } - - static IList GetCertificatesToImport(byte[] pfxBytes, string? password) + + + public static CryptoKeySecurity GetPrivateKeySecurity(string thumbprint, StoreLocation storeLocation, string storeName) { - using (var memoryStream = new MemoryStream(pfxBytes)) - { - // The latest version of BouncyCastle fails if the cert doesn't require a key, but we pass an empty array key. - // Will issue a PR to make this configurable at least in a way that doesn't require writing environment variables. - Environment.SetEnvironmentVariable(Org.BouncyCastle.Pkcs.Pkcs12Store.IgnoreUselessPasswordProperty, "true"); - var pkcs12Store = new Pkcs12StoreBuilder().Build(); - pkcs12Store.Load(memoryStream, password?.ToCharArray() ?? "".ToCharArray()); - - if (pkcs12Store.Count < 1) - throw new Exception("No certificates were found in PFX"); + var store = new X509Store(storeName, storeLocation); + store.Open(OpenFlags.ReadOnly); - var aliases = pkcs12Store.Aliases.Cast().ToList(); + var found = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false); + store.Close(); - // Find the first bag which contains a private-key - var keyAlias = aliases.FirstOrDefault(alias => pkcs12Store.IsKeyEntry(alias)); + if (found.Count == 0) + throw new Exception( + $"Could not find certificate with thumbprint '{thumbprint}' in store Cert:\\{storeLocation}\\{storeName}"); - if (keyAlias != null) - { - return pkcs12Store.GetCertificateChain(keyAlias).Select(x => x.Certificate).ToList(); + var certificate = new SafeCertContextHandle(found[0].Handle, false); - } + if (!certificate.HasPrivateKey()) + throw new Exception("Certificate does not have a private-key"); - return new List - { - pkcs12Store.GetCertificate(aliases.First()).Certificate - }; - } - } + var keyProvInfo = + certificate.GetCertificateProperty(CertificateProperty.KeyProviderInfo); - static bool IsSelfSigned(SafeCertContextHandle certificate) - { - var certificateInfo = (CERT_INFO)Marshal.PtrToStructure(certificate.CertificateContext.pCertInfo, typeof(CERT_INFO)); - return CertCompareCertificateName(CertificateEncodingType.Pkcs7OrX509AsnEncoding, ref certificateInfo.Subject, ref certificateInfo.Issuer); + // If it is a CNG key + return keyProvInfo.dwProvType == 0 + ? GetCngPrivateKeySecurity(certificate) + : GetCspPrivateKeySecurity(certificate); } - - static byte[] CalculateThumbprint(Org.BouncyCastle.X509.X509Certificate certificate) + } +#else + public static class CryptoKeySecurityAccessRules + { + internal static void AddPrivateKeyAccessRules(ICollection accessRules, SafeCertContextHandle certificate) { - var der = certificate.GetEncoded(); - return DigestUtilities.CalculateDigest("SHA1", der); + throw new NotImplementedException("Not Yet Available For NetCore"); } } -} - -#endif \ No newline at end of file +#endif +} \ No newline at end of file diff --git a/source/Calamari.Tests/Fixtures/Certificates/ImportCertificateCommandFixture.cs b/source/Calamari.Tests/Fixtures/Certificates/ImportCertificateCommandFixture.cs index 95e1ff6a5..668d27fda 100644 --- a/source/Calamari.Tests/Fixtures/Certificates/ImportCertificateCommandFixture.cs +++ b/source/Calamari.Tests/Fixtures/Certificates/ImportCertificateCommandFixture.cs @@ -1,11 +1,11 @@ -#if WINDOWS_CERTIFICATE_STORE_SUPPORT -using System; +using System; using System.IO; using System.Security.Cryptography.X509Certificates; using Calamari.Common.Plumbing.FileSystem; using Calamari.Common.Plumbing.Variables; using Calamari.Deployment; using Calamari.Testing.Helpers; +using Calamari.Testing.Requirements; using Calamari.Tests.Helpers; using Calamari.Tests.Helpers.Certificates; using NUnit.Framework; @@ -13,6 +13,7 @@ namespace Calamari.Tests.Fixtures.Certificates { + [Category(TestCategory.CompatibleOS.OnlyWindows)] public class ImportCertificateCommandFixture : CalamariFixture { readonly string certificateVariable = "FooCert"; @@ -53,12 +54,15 @@ public void AddingCertToStore_AddsCert() cert.EnsureCertificateNotInStore(storeName, certificateStoreLocation); } +#if WINDOWS_CERTIFICATE_STORE_SUPPORT [Test] public void NoStoreLocationProvided_StoresInUserName() { var storeName = StoreName.My.ToString(); var storeLocation = StoreLocation.CurrentUser; +#pragma warning disable CA1416 var userName = System.Security.Principal.WindowsIdentity.GetCurrent().Name; +#pragma warning restore CA1416 var variables = CreateInitialVariables(); variables.Add(SpecialVariables.Action.Certificate.StoreName, storeName); variables.Add(SpecialVariables.Action.Certificate.StoreUser, userName); @@ -74,13 +78,14 @@ public void NoStoreLocationProvided_StoresInUserName() // Hygiene Cleanup cert.EnsureCertificateNotInStore(storeName, storeLocation); } - +#endif + CalamariResult Invoke(VariableDictionary variables) { using (var variablesFile = new TemporaryFile(Path.GetTempFileName())) { variables.Save(variablesFile.FilePath); - return Invoke(Calamari() + return InvokeInProcess(Calamari() .Action("import-certificate") .Argument("variables", variablesFile.FilePath)); } @@ -99,5 +104,4 @@ VariableDictionary CreateInitialVariables() return variables; } } -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/source/Calamari.Tests/Fixtures/Certificates/WindowsX509CertificateStoreFixture.cs b/source/Calamari.Tests/Fixtures/Certificates/WindowsX509CertificateStoreFixture.cs index cc2521da3..d47f27cb6 100644 --- a/source/Calamari.Tests/Fixtures/Certificates/WindowsX509CertificateStoreFixture.cs +++ b/source/Calamari.Tests/Fixtures/Certificates/WindowsX509CertificateStoreFixture.cs @@ -1,5 +1,4 @@ -#if WINDOWS_CERTIFICATE_STORE_SUPPORT -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; @@ -17,6 +16,7 @@ namespace Calamari.Tests.Fixtures.Certificates { [TestFixture] [Category(TestCategory.CompatibleOS.OnlyWindows)] +#pragma warning disable CA1416 public class WindowsX509CertificateStoreFixture { [Test] @@ -24,7 +24,9 @@ public class WindowsX509CertificateStoreFixture [TestCase(SampleCertificate.CngPrivateKeyId, StoreLocation.CurrentUser, "My")] [TestCase(SampleCertificate.CngPrivateKeyId, StoreLocation.LocalMachine, "Foo")] [TestCase(SampleCertificate.CapiWithPrivateKeyId, StoreLocation.LocalMachine, "My")] +#if WINDOWS_CERTIFICATE_STORE_SUPPORT [TestCase(SampleCertificate.CapiWithPrivateKeyId, StoreLocation.CurrentUser, "My")] +#endif [TestCase(SampleCertificate.CapiWithPrivateKeyId, StoreLocation.CurrentUser, "Foo")] [TestCase(SampleCertificate.CapiWithPrivateKeyNoPasswordId, StoreLocation.LocalMachine, "My")] public void CanImportCertificate(string sampleCertificateId, StoreLocation storeLocation, string storeName) @@ -47,6 +49,44 @@ public void CanImportCertificate(string sampleCertificateId, StoreLocation store sampleCertificate.EnsureCertificateNotInStore(storeName, storeLocation); } + [Test] + public void CanImportCertificateWithNoPrivateKeyForSpecificUser() + { + // This test cheats a little bit, using the current user + var user = WindowsIdentity.GetCurrent().Name; + var storeName = "My"; + var sampleCertificate = SampleCertificate.CertWithNoPrivateKey; + + sampleCertificate.EnsureCertificateNotInStore(storeName, StoreLocation.CurrentUser); + + new WindowsX509CertificateStore().ImportCertificateToStore(Convert.FromBase64String(sampleCertificate.Base64Bytes()), sampleCertificate.Password, + user, storeName, sampleCertificate.HasPrivateKey); + + sampleCertificate.AssertCertificateIsInStore(storeName, StoreLocation.CurrentUser); + + sampleCertificate.EnsureCertificateNotInStore(storeName, StoreLocation.CurrentUser); + } + +#if WINDOWS_CERTIFICATE_STORE_SUPPORT + [Test] + public void CanImportCertificateForSpecificUser() + { + // This test cheats a little bit, using the current user + + var user = WindowsIdentity.GetCurrent().Name; + var storeName = "My"; + var sampleCertificate = SampleCertificate.CapiWithPrivateKey; + + sampleCertificate.EnsureCertificateNotInStore(storeName, StoreLocation.CurrentUser); + + new WindowsX509CertificateStore().ImportCertificateToStore(Convert.FromBase64String(sampleCertificate.Base64Bytes()), sampleCertificate.Password, + user, storeName, sampleCertificate.HasPrivateKey); + + sampleCertificate.AssertCertificateIsInStore(storeName, StoreLocation.CurrentUser); + + sampleCertificate.EnsureCertificateNotInStore(storeName, StoreLocation.CurrentUser); + } + [Test(Description = "This test proves, to a degree of certainty, the WindowsX509CertificateStore is safe for concurrent operations. We were seeing exceptions when multiple processes attempted to get/set private key ACLs at the same time.")] public void SafeForConcurrentOperations() { @@ -113,7 +153,7 @@ Thread[] CreateThreads(int number, string name, Action action) => Enumerable.Ran })) .Concat(CreateThreads(numThreads, "GetPrivateKeySecurity", () => { - var unused = new WindowsX509CertificateStore().GetPrivateKeySecurity( + var unused = CryptoKeySecurityAccessRules.GetPrivateKeySecurity( sampleCertificate.Thumbprint, StoreLocation.LocalMachine, "My"); })).ToArray(); @@ -159,42 +199,8 @@ Thread[] CreateThreads(int number, string name, Action action) => Enumerable.Ran } } - [Test] - public void CanImportCertificateForSpecificUser() - { - // This test cheats a little bit, using the current user - var user = WindowsIdentity.GetCurrent().Name; - var storeName = "My"; - var sampleCertificate = SampleCertificate.CapiWithPrivateKey; - - sampleCertificate.EnsureCertificateNotInStore(storeName, StoreLocation.CurrentUser); - - new WindowsX509CertificateStore().ImportCertificateToStore(Convert.FromBase64String(sampleCertificate.Base64Bytes()), sampleCertificate.Password, - user, storeName, sampleCertificate.HasPrivateKey); - sampleCertificate.AssertCertificateIsInStore(storeName, StoreLocation.CurrentUser); - - sampleCertificate.EnsureCertificateNotInStore(storeName, StoreLocation.CurrentUser); - } - [Test] - public void CanImportCertificateWithNoPrivateKeyForSpecificUser() - { - // This test cheats a little bit, using the current user - var user = WindowsIdentity.GetCurrent().Name; - var storeName = "My"; - var sampleCertificate = SampleCertificate.CertWithNoPrivateKey; - - sampleCertificate.EnsureCertificateNotInStore(storeName, StoreLocation.CurrentUser); - - new WindowsX509CertificateStore().ImportCertificateToStore(Convert.FromBase64String(sampleCertificate.Base64Bytes()), sampleCertificate.Password, - user, storeName, sampleCertificate.HasPrivateKey); - - sampleCertificate.AssertCertificateIsInStore(storeName, StoreLocation.CurrentUser); - - sampleCertificate.EnsureCertificateNotInStore(storeName, StoreLocation.CurrentUser); - } - [Test] [TestCase(SampleCertificate.CngPrivateKeyId, StoreLocation.LocalMachine, "My")] [TestCase(SampleCertificate.CngPrivateKeyId, StoreLocation.LocalMachine, "Foo")] @@ -221,12 +227,22 @@ public void ImportExistingCertificateShouldNotOverwriteExistingPrivateKeyRights( Convert.FromBase64String(sampleCertificate.Base64Bytes()), sampleCertificate.Password, storeLocation, storeName, sampleCertificate.HasPrivateKey); - var privateKeySecurity = new WindowsX509CertificateStore().GetPrivateKeySecurity(sampleCertificate.Thumbprint, - storeLocation, storeName); + var privateKeySecurity = CryptoKeySecurityAccessRules.GetPrivateKeySecurity(sampleCertificate.Thumbprint, + storeLocation, storeName); AssertHasPrivateKeyRights(privateKeySecurity, "BUILTIN\\Users", CryptoKeyRights.GenericAll); sampleCertificate.EnsureCertificateNotInStore(storeName, storeLocation); } + void AssertHasPrivateKeyRights(CryptoKeySecurity privateKeySecurity, string identifier, CryptoKeyRights right) + { + var accessRules = privateKeySecurity.GetAccessRules(true, false, typeof(NTAccount)); + + var found = accessRules.Cast() + .Any(x => x.IdentityReference.Value == identifier && x.CryptoKeyRights.HasFlag(right)); + + Assert.True(found, "Private-Key right was not set"); + } +#endif [Test] [TestCase(SampleCertificate.CertificateChainId, "2E5DEC036985A4028351FD8DF3532E49D7B34049", "CC7ED077F0F292595A8166B01709E20C0884A5F8", StoreLocation.LocalMachine, "My")] @@ -282,15 +298,7 @@ private static void AssertCertificateInStore(X509Store store, string thumbprint) Assert.AreEqual(1, found.Count); } - void AssertHasPrivateKeyRights(CryptoKeySecurity privateKeySecurity, string identifier, CryptoKeyRights right) - { - var accessRules = privateKeySecurity.GetAccessRules(true, false, typeof(NTAccount)); - - var found = accessRules.Cast() - .Any(x => x.IdentityReference.Value == identifier && x.CryptoKeyRights.HasFlag(right)); - Assert.True(found, "Private-Key right was not set"); - } } +#pragma warning restore CA1416 } -#endif \ No newline at end of file diff --git a/source/Calamari.Tests/Fixtures/Deployment/DeployWebPackageToIISFixture.cs b/source/Calamari.Tests/Fixtures/Deployment/DeployWebPackageToIISFixture.cs index ccccadfd6..06be7e0d0 100644 --- a/source/Calamari.Tests/Fixtures/Deployment/DeployWebPackageToIISFixture.cs +++ b/source/Calamari.Tests/Fixtures/Deployment/DeployWebPackageToIISFixture.cs @@ -9,9 +9,7 @@ using Calamari.Integration.Iis; using Calamari.Testing.Helpers; using Calamari.Tests.Fixtures.Deployment.Packages; -#if WINDOWS_CERTIFICATE_STORE_SUPPORT using Calamari.Tests.Helpers.Certificates; -#endif using Microsoft.Web.Administration; using NUnit.Framework; using Polly; @@ -20,6 +18,7 @@ namespace Calamari.Tests.Fixtures.Deployment { [TestFixture] + [Category(TestCategory.CompatibleOS.OnlyWindows)] public class DeployWebPackageToIISFixture : DeployPackageFixture { TemporaryFile packageV1; @@ -47,9 +46,7 @@ public override void SetUp() iis = new WebServerSevenSupport(); uniqueValue = "Test_" + Guid.NewGuid().ToString("N"); -#if WINDOWS_CERTIFICATE_STORE_SUPPORT SampleCertificate.CapiWithPrivateKey.EnsureCertificateNotInStore(StoreName.My, StoreLocation.LocalMachine); -#endif base.SetUp(); } @@ -60,9 +57,7 @@ public override void CleanUp() if (iis.WebSiteExists(uniqueValue)) iis.DeleteWebSite(uniqueValue); if (iis.ApplicationPoolExists(uniqueValue)) iis.DeleteApplicationPool(uniqueValue); -#if WINDOWS_CERTIFICATE_STORE_SUPPORT SampleCertificate.CapiWithPrivateKey.EnsureCertificateNotInStore(StoreName.My, StoreLocation.LocalMachine); -#endif base.CleanUp(); } @@ -387,7 +382,6 @@ public void ShouldDeployWhenVirtualPathAlreadyExistsAndPointsToPhysicalDirectory } } - #if WINDOWS_CERTIFICATE_STORE_SUPPORT [Test] [Category(TestCategory.CompatibleOS.OnlyWindows)] @@ -432,6 +426,7 @@ public void ShouldCreateHttpsBindingUsingCertificatePassedAsVariable() Assert.AreEqual(ObjectState.Started, website.State); } +#endif [Test] [Category(TestCategory.CompatibleOS.OnlyWindows)] @@ -587,7 +582,6 @@ public void ShouldNotFailIfDisabledBindingUsesUnavailableCertificateVariable() result.AssertSuccess(); } -#endif private string ToFirstLevelPath(string value) { diff --git a/source/Calamari.Tests/Helpers/Certificates/SampleCertificate.cs b/source/Calamari.Tests/Helpers/Certificates/SampleCertificate.cs index 31b9dc79e..089be0337 100644 --- a/source/Calamari.Tests/Helpers/Certificates/SampleCertificate.cs +++ b/source/Calamari.Tests/Helpers/Certificates/SampleCertificate.cs @@ -1,4 +1,4 @@ -#if WINDOWS_CERTIFICATE_STORE_SUPPORT + using System; using System.Collections.Generic; using System.IO; @@ -123,6 +123,7 @@ public X509Certificate2 GetCertificateFromStore(string storeName, StoreLocation : null; } +#if WINDOWS_CERTIFICATE_STORE_SUPPORT public static void AssertIdentityHasPrivateKeyAccess(X509Certificate2 certificate, IdentityReference identity, CryptoKeyRights rights) { if (!certificate.HasPrivateKey) @@ -143,6 +144,7 @@ public static void AssertIdentityHasPrivateKeyAccess(X509Certificate2 certificat throw new Exception($"Identity '{identity.ToString()}' does not have access right '{rights}' to private-key"); } +#endif X509Certificate2 LoadAsX509Certificate2() { @@ -153,5 +155,4 @@ X509Certificate2 LoadAsX509Certificate2() string FilePath => TestEnvironment.GetTestPath("Helpers", "Certificates", "SampleCertificateFiles", fileName); } -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/source/Calamari/Commands/ImportCertificateCommand.cs b/source/Calamari/Commands/ImportCertificateCommand.cs index 02b8cd0d7..c7bd5a3a7 100644 --- a/source/Calamari/Commands/ImportCertificateCommand.cs +++ b/source/Calamari/Commands/ImportCertificateCommand.cs @@ -1,5 +1,4 @@ -#if WINDOWS_CERTIFICATE_STORE_SUPPORT -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography.X509Certificates; @@ -17,11 +16,13 @@ public class ImportCertificateCommand : Command { readonly IVariables variables; readonly IWindowsX509CertificateStore windowsX509CertificateStore; + readonly ILog log; - public ImportCertificateCommand(IVariables variables, IWindowsX509CertificateStore windowsX509CertificateStore) + public ImportCertificateCommand(IVariables variables, IWindowsX509CertificateStore windowsX509CertificateStore, ILog log) { this.variables = variables; this.windowsX509CertificateStore = windowsX509CertificateStore; + this.log = log; } public override int Execute(string[] commandLineArguments) @@ -50,7 +51,7 @@ void ImportCertificate() { if (locationSpecified) { - Log.Info( + log.Info( $"Importing certificate '{variables.Get($"{certificateVariable}.{CertificateVariables.Properties.Subject}")}' with thumbprint '{thumbprint}' into store '{storeLocation}\\{storeName}'"); windowsX509CertificateStore.ImportCertificateToStore(pfxBytes, password, storeLocation, storeName, privateKeyExportable); @@ -80,7 +81,7 @@ void ImportCertificate() } catch (Exception) { - Log.Error("There was an error importing the certificate into the store"); + log.Error("There was an error importing the certificate into the store"); throw; } } @@ -128,5 +129,4 @@ static void ValidateStore(StoreLocation? storeLocation, string storeName) } } } -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/source/Calamari/Program.cs b/source/Calamari/Program.cs index c719dcbf1..06d1ec2bb 100644 --- a/source/Calamari/Program.cs +++ b/source/Calamari/Program.cs @@ -80,13 +80,7 @@ protected override void ConfigureContainer(ContainerBuilder builder, CommonOptio builder.RegisterType().AsSelf().As().InstancePerLifetimeScope(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().AsSelf().SingleInstance(); - - -#if WINDOWS_CERTIFICATE_STORE_SUPPORT builder.RegisterType().As().SingleInstance(); -#else - builder.RegisterType().As().SingleInstance(); -#endif builder.RegisterType() .As() From e6193a92e0d375ba6103f22e726cb9593fc8bfd7 Mon Sep 17 00:00:00 2001 From: Robert E Date: Tue, 1 Oct 2024 21:42:25 +1000 Subject: [PATCH 2/2] Move `CryptoKeySecurityAccessRules` to its own file --- .../CryptoKeySecurityAccessRules.cs | 166 ++++++++++++++++++ .../WindowsX509CertificateStore.cs | 152 ---------------- .../Commands/ImportCertificateCommand.cs | 2 +- 3 files changed, 167 insertions(+), 153 deletions(-) create mode 100644 source/Calamari.Shared/Integration/Certificates/CryptoKeySecurityAccessRules.cs diff --git a/source/Calamari.Shared/Integration/Certificates/CryptoKeySecurityAccessRules.cs b/source/Calamari.Shared/Integration/Certificates/CryptoKeySecurityAccessRules.cs new file mode 100644 index 000000000..5e4d2a1dc --- /dev/null +++ b/source/Calamari.Shared/Integration/Certificates/CryptoKeySecurityAccessRules.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using Calamari.Integration.Certificates.WindowsNative; +#if WINDOWS_CERTIFICATE_STORE_SUPPORT +using System.Linq; +using System.Runtime.InteropServices; +using System.Security.AccessControl; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using static Calamari.Integration.Certificates.WindowsNative.WindowsX509Native; +#endif + +namespace Calamari.Integration.Certificates +{ +#if WINDOWS_CERTIFICATE_STORE_SUPPORT + public static class CryptoKeySecurityAccessRules + { + + internal static void AddPrivateKeyAccessRules(ICollection accessRules, SafeCertContextHandle certificate) + { + try + { + var keyProvInfo = certificate.GetCertificateProperty(CertificateProperty.KeyProviderInfo); + + // If it is a CNG key + if (keyProvInfo.dwProvType == 0) + { + SetCngPrivateKeySecurity(certificate, accessRules); + } + else + { + SetCspPrivateKeySecurity(certificate, accessRules); + } + } + catch (Exception ex) + { + throw new Exception("Could not set security on private-key", ex); + } + } + + static void SetCngPrivateKeySecurity(SafeCertContextHandle certificate, ICollection accessRules) + { + using (var key = CertificatePal.GetCngPrivateKey(certificate)) + { + var security = GetCngPrivateKeySecurity(certificate); + + foreach (var cryptoKeyAccessRule in accessRules.Select(ToCryptoKeyAccessRule)) + { + security.AddAccessRule(cryptoKeyAccessRule); + } + + var securityDescriptorBytes = security.GetSecurityDescriptorBinaryForm(); + var gcHandle = GCHandle.Alloc(securityDescriptorBytes, GCHandleType.Pinned); + + var errorCode = NCryptSetProperty(key, + WindowsX509Native.NCryptProperties.SecurityDescriptor, + gcHandle.AddrOfPinnedObject(), + securityDescriptorBytes.Length, + (int)WindowsX509Native.NCryptFlags.Silent | (int)WindowsX509Native.SecurityDesciptorParts.DACL_SECURITY_INFORMATION); + + gcHandle.Free(); + + if (errorCode != 0) + { + throw new CryptographicException(errorCode); + } + } + } + + static void SetCspPrivateKeySecurity(SafeCertContextHandle certificate, ICollection accessRules) + { + using (var cspHandle = CertificatePal.GetCspPrivateKey(certificate)) + { + var security = GetCspPrivateKeySecurity(certificate); + + foreach (var cryptoKeyAccessRule in accessRules.Select(ToCryptoKeyAccessRule)) + { + security.AddAccessRule(cryptoKeyAccessRule); + } + + var securityDescriptorBytes = security.GetSecurityDescriptorBinaryForm(); + + if (!CryptSetProvParam(cspHandle, + WindowsX509Native.CspProperties.SecurityDescriptor, + securityDescriptorBytes, + WindowsX509Native.SecurityDesciptorParts.DACL_SECURITY_INFORMATION)) + { + throw new CryptographicException(Marshal.GetLastWin32Error()); + } + } + } + + static CryptoKeyAccessRule ToCryptoKeyAccessRule(PrivateKeyAccessRule privateKeyAccessRule) + { + switch (privateKeyAccessRule.Access) + { + case PrivateKeyAccess.ReadOnly: + return new CryptoKeyAccessRule(privateKeyAccessRule.GetIdentityReference(), CryptoKeyRights.GenericRead, AccessControlType.Allow); + + case PrivateKeyAccess.FullControl: + // We use 'GenericAll' here rather than 'FullControl' as 'FullControl' doesn't correctly set the access for CNG keys + return new CryptoKeyAccessRule(privateKeyAccessRule.GetIdentityReference(), CryptoKeyRights.GenericAll, AccessControlType.Allow); + + default: + throw new ArgumentOutOfRangeException(nameof(privateKeyAccessRule.Access)); + } + } + + static CryptoKeySecurity GetCngPrivateKeySecurity(SafeCertContextHandle certificate) + { + using (var key = CertificatePal.GetCngPrivateKey(certificate)) + { + var security = new CryptoKeySecurity(); + security.SetSecurityDescriptorBinaryForm(CertificatePal.GetCngPrivateKeySecurity(key), + AccessControlSections.Access); + return security; + } + } + + static CryptoKeySecurity GetCspPrivateKeySecurity(SafeCertContextHandle certificate) + { + using (var cspHandle = CertificatePal.GetCspPrivateKey(certificate)) + { + var security = new CryptoKeySecurity(); + security.SetSecurityDescriptorBinaryForm(CertificatePal.GetCspPrivateKeySecurity(cspHandle), AccessControlSections.Access); + return security; + } + } + + + public static CryptoKeySecurity GetPrivateKeySecurity(string thumbprint, StoreLocation storeLocation, string storeName) + { + var store = new X509Store(storeName, storeLocation); + store.Open(OpenFlags.ReadOnly); + + var found = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false); + store.Close(); + + if (found.Count == 0) + throw new Exception( + $"Could not find certificate with thumbprint '{thumbprint}' in store Cert:\\{storeLocation}\\{storeName}"); + + var certificate = new SafeCertContextHandle(found[0].Handle, false); + + if (!certificate.HasPrivateKey()) + throw new Exception("Certificate does not have a private-key"); + + var keyProvInfo = + certificate.GetCertificateProperty(WindowsX509Native.CertificateProperty.KeyProviderInfo); + + // If it is a CNG key + return keyProvInfo.dwProvType == 0 + ? GetCngPrivateKeySecurity(certificate) + : GetCspPrivateKeySecurity(certificate); + } + } +#else + public static class CryptoKeySecurityAccessRules + { + internal static void AddPrivateKeyAccessRules(ICollection accessRules, SafeCertContextHandle certificate) + { + throw new NotImplementedException("Not Yet Available For NetCore"); + } + } +#endif +} \ No newline at end of file diff --git a/source/Calamari.Shared/Integration/Certificates/WindowsX509CertificateStore.cs b/source/Calamari.Shared/Integration/Certificates/WindowsX509CertificateStore.cs index 03f71723a..b51fd85b4 100644 --- a/source/Calamari.Shared/Integration/Certificates/WindowsX509CertificateStore.cs +++ b/source/Calamari.Shared/Integration/Certificates/WindowsX509CertificateStore.cs @@ -3,7 +3,6 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; -using System.Security.AccessControl; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Security.Principal; @@ -433,155 +432,4 @@ static byte[] CalculateThumbprint(Org.BouncyCastle.X509.X509Certificate certific return DigestUtilities.CalculateDigest("SHA1", der); } } - -#if WINDOWS_CERTIFICATE_STORE_SUPPORT - public static class CryptoKeySecurityAccessRules - { - - internal static void AddPrivateKeyAccessRules(ICollection accessRules, SafeCertContextHandle certificate) - { - try - { - var keyProvInfo = certificate.GetCertificateProperty( - CertificateProperty.KeyProviderInfo); - - // If it is a CNG key - if (keyProvInfo.dwProvType == 0) - { - SetCngPrivateKeySecurity(certificate, accessRules); - } - else - { - SetCspPrivateKeySecurity(certificate, accessRules); - } - } - catch (Exception ex) - { - throw new Exception("Could not set security on private-key", ex); - } - } - - static void SetCngPrivateKeySecurity(SafeCertContextHandle certificate, ICollection accessRules) - { - using (var key = CertificatePal.GetCngPrivateKey(certificate)) - { - var security = GetCngPrivateKeySecurity(certificate); - - foreach (var cryptoKeyAccessRule in accessRules.Select(ToCryptoKeyAccessRule)) - { - security.AddAccessRule(cryptoKeyAccessRule); - } - - var securityDescriptorBytes = security.GetSecurityDescriptorBinaryForm(); - var gcHandle = GCHandle.Alloc(securityDescriptorBytes, GCHandleType.Pinned); - - var errorCode = NCryptSetProperty(key, - NCryptProperties.SecurityDescriptor, - gcHandle.AddrOfPinnedObject(), securityDescriptorBytes.Length, - (int)NCryptFlags.Silent | - (int)SecurityDesciptorParts.DACL_SECURITY_INFORMATION); - - gcHandle.Free(); - - if (errorCode != 0) - { - throw new CryptographicException(errorCode); - } - } - } - - static void SetCspPrivateKeySecurity(SafeCertContextHandle certificate, ICollection accessRules) - { - using (var cspHandle = CertificatePal.GetCspPrivateKey(certificate)) - { - var security = GetCspPrivateKeySecurity(certificate); - - foreach (var cryptoKeyAccessRule in accessRules.Select(ToCryptoKeyAccessRule)) - { - security.AddAccessRule(cryptoKeyAccessRule); - } - - var securityDescriptorBytes = security.GetSecurityDescriptorBinaryForm(); - - if (!CryptSetProvParam(cspHandle, CspProperties.SecurityDescriptor, - securityDescriptorBytes, SecurityDesciptorParts.DACL_SECURITY_INFORMATION)) - { - throw new CryptographicException(Marshal.GetLastWin32Error()); - } - } - } - - static CryptoKeyAccessRule ToCryptoKeyAccessRule(PrivateKeyAccessRule privateKeyAccessRule) - { - switch (privateKeyAccessRule.Access) - { - case PrivateKeyAccess.ReadOnly: - return new CryptoKeyAccessRule(privateKeyAccessRule.GetIdentityReference(), CryptoKeyRights.GenericRead, AccessControlType.Allow); - - case PrivateKeyAccess.FullControl: - // We use 'GenericAll' here rather than 'FullControl' as 'FullControl' doesn't correctly set the access for CNG keys - return new CryptoKeyAccessRule(privateKeyAccessRule.GetIdentityReference(), CryptoKeyRights.GenericAll, AccessControlType.Allow); - - default: - throw new ArgumentOutOfRangeException(nameof(privateKeyAccessRule.Access)); - } - } - - static CryptoKeySecurity GetCngPrivateKeySecurity(SafeCertContextHandle certificate) - { - using (var key = CertificatePal.GetCngPrivateKey(certificate)) - { - var security = new CryptoKeySecurity(); - security.SetSecurityDescriptorBinaryForm(CertificatePal.GetCngPrivateKeySecurity(key), - AccessControlSections.Access); - return security; - } - } - - static CryptoKeySecurity GetCspPrivateKeySecurity(SafeCertContextHandle certificate) - { - using (var cspHandle = CertificatePal.GetCspPrivateKey(certificate)) - { - var security = new CryptoKeySecurity(); - security.SetSecurityDescriptorBinaryForm(CertificatePal.GetCspPrivateKeySecurity(cspHandle), AccessControlSections.Access); - return security; - } - } - - - public static CryptoKeySecurity GetPrivateKeySecurity(string thumbprint, StoreLocation storeLocation, string storeName) - { - var store = new X509Store(storeName, storeLocation); - store.Open(OpenFlags.ReadOnly); - - var found = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false); - store.Close(); - - if (found.Count == 0) - throw new Exception( - $"Could not find certificate with thumbprint '{thumbprint}' in store Cert:\\{storeLocation}\\{storeName}"); - - var certificate = new SafeCertContextHandle(found[0].Handle, false); - - if (!certificate.HasPrivateKey()) - throw new Exception("Certificate does not have a private-key"); - - var keyProvInfo = - certificate.GetCertificateProperty(CertificateProperty.KeyProviderInfo); - - // If it is a CNG key - return keyProvInfo.dwProvType == 0 - ? GetCngPrivateKeySecurity(certificate) - : GetCspPrivateKeySecurity(certificate); - } - } -#else - public static class CryptoKeySecurityAccessRules - { - internal static void AddPrivateKeyAccessRules(ICollection accessRules, SafeCertContextHandle certificate) - { - throw new NotImplementedException("Not Yet Available For NetCore"); - } - } -#endif } \ No newline at end of file diff --git a/source/Calamari/Commands/ImportCertificateCommand.cs b/source/Calamari/Commands/ImportCertificateCommand.cs index c7bd5a3a7..54326fb07 100644 --- a/source/Calamari/Commands/ImportCertificateCommand.cs +++ b/source/Calamari/Commands/ImportCertificateCommand.cs @@ -73,7 +73,7 @@ void ImportCertificate() $"Either '{SpecialVariables.Action.Certificate.StoreLocation}' or '{SpecialVariables.Action.Certificate.StoreUser}' must be supplied"); } - Log.Info( + log.Info( $"Importing certificate '{variables.Get($"{certificateVariable}.{CertificateVariables.Properties.Subject}")}' with thumbprint '{thumbprint}' into store '{storeName}' for user '{storeUser}'"); windowsX509CertificateStore.ImportCertificateToStore(pfxBytes, password, storeUser, storeName, privateKeyExportable); }