diff --git a/README.md b/README.md index ddf46b04..9fbed908 100644 --- a/README.md +++ b/README.md @@ -57,11 +57,14 @@ Installs a certificate into the Windows certificate store from a file, and grant #### Properties - `source` - name attribute. The source file (for create and acl_add), thumbprint (for delete and acl_add) or subject (for delete). +- `pfx_exportable` - if false (default) then do not allow certificate to be exported if it is a pfx file +- `pfx_prefer_cng_ksp` - if false (default) then do not use CNG KSP if a provider has not been specified by certificate if it is a pfx file +- `pfx_always_cng_ksp` - if false (default) then do not override provider specified by certificate to use CNG KSP if it is a pfx file - `pfx_password` - the password to access the source if it is a pfx file. - `private_key_acl` - array of 'domain\account' entries to be granted read-only access to the certificate's private key. This is not idempotent. - `store_name` - the certificate store to manipulate. One of MY (default : personal store), CA (trusted intermediate store) or ROOT (trusted root store). - `user_store` - if false (default) then use the local machine store; if true then use the current user's store. -- `provider_name` - if `Default` (default) then use pfx provided provider name. Valid provider names are `Default`, `Microsoft Enhanced RSA and AES Cryptographic Provider`, `Microsoft RSA SChannel Cryptographic Provider`, `Microsoft Strong Cryptographic Provider` or `Microsoft Software Key Storage Provider` + #### Examples diff --git a/resources/certificate.rb b/resources/certificate.rb index 3c702a71..e9dae607 100644 --- a/resources/certificate.rb +++ b/resources/certificate.rb @@ -22,43 +22,18 @@ property :source, String, name_property: true, required: true property :pfx_password, String +property :pfx_exportable, [true, false], default: false +property :pfx_prefer_cng_ksp, [true, false], default: false +property :pfx_always_cng_ksp, [true, false], default: false property :private_key_acl, Array property :store_name, String, default: 'MY', regex: /^(?:MY|CA|ROOT|TrustedPublisher|TRUSTEDPEOPLE)$/ property :user_store, [true, false], default: false -property :provider_name, String, default: 'Default', regex: /^(?:Microsoft Enhanced RSA and AES Cryptographic Provider|Microsoft RSA SChannel Cryptographic Provider|Microsoft Strong Cryptographic Provider|Microsoft Software Key Storage Provider|Default)$/ action :create do hash = '$cert.GetCertHashString()' code_script = <<-EOH #{library_script} -#{cert_script(true)} - -$providerName = '#{provider_name}' - -if ($providerName -ne '' -and $providerName -ne 'Default'){ - $cspParameters = [Contoso.CryptoTriage]::GetCspParameters($cert) - $cspParameters.pwszProvName = $providerName - $cspParameters.dwKeySpec = [Contoso.CryptoTriage+ProviderKeySpecifier]::AT_KEYEXCHANGE -bor [Contoso.CryptoTriage+ProviderKeySpecifier]::AT_SIGNATURE - $cspParameters.cProvParam = 0 - $cspParameters.rgProvParam = 0 - - if ($providerName -eq 'Microsoft Enhanced RSA and AES Cryptographic Provider'){ - $cspParameters.dwProvType = [Contoso.CryptoTriage+ProviderType]::PROV_RSA_AES - } elseif ($providerName -eq 'Microsoft RSA SChannel Cryptographic Provider'){ - $cspParameters.dwProvType = [Contoso.CryptoTriage+ProviderType]::PROV_RSA_SCHANNEL - } elseif ($providerName -eq 'Microsoft Strong Cryptographic Provider'){ - $cspParameters.dwProvType = [Contoso.CryptoTriage+ProviderType]::PROV_RSA_FULL - } elseif ($providerName -eq 'Microsoft Software Key Storage Provider'){ - $cspParameters.dwProvType = [Contoso.CryptoTriage+ProviderType]::CNG - $cspParameters.dwKeySpec = 0 - $cspParameters.dwKeySpec = [Contoso.CryptoTriage+ProviderKeySpecifier]::CERT_NCRYPT_KEY_SPEC - } else { - throw "Unknown provider name - $providerName" - } - [Contoso.CryptoTriage]::SetCspParameters($cert, $cspParameters) -} - -#{within_store_script { |store| store + '.Add($cert)' }} +#{import_cert_script} #{acl_script(hash)} EOH @@ -166,8 +141,68 @@ def within_store_script #{inner_script} $store.Close() EOH +end + + def import_cert_script + file = win_friendly_path(new_resource.source) + pfxOptions = ['[Crypto.Win+PfxCertStoreFlags]::PKCS12_INCLUDE_EXTENDED_PROPERTIES', '[Crypto.Win+PfxCertStoreFlags]::PKCS12_ALLOW_OVERWRITE_KEY'] + if !new_resource.user_store + pfxOptions << '[Crypto.Win+PfxCertStoreFlags]::CRYPT_MACHINE_KEYSET' + end + if new_resource.pfx_exportable + pfxOptions << '[Crypto.Win+PfxCertStoreFlags]::CRYPT_EXPORTABLE' + end + if new_resource.pfx_prefer_cng_ksp + pfxOptions << '[Crypto.Win+PfxCertStoreFlags]::PKCS12_PREFER_CNG_KSP' + end + if new_resource.pfx_always_cng_ksp + pfxOptions << '[Crypto.Win+PfxCertStoreFlags]::PKCS12_ALLOW_OVERWRITE_KEY' + end + + if ::File.extname(file.downcase) == '.pfx' + set_import_cert_script = <<-EOH +$CertificatePath = '#{file}' +$Password = '#{new_resource.pfx_password}' +$Location = '#{cert_location}' +$StoreName = '#{new_resource.store_name}' +$pfxOptions = #{pfxOptions * " -bor "} + +[Crypto.PfxHelper]::ImportPfx($CertificatePath,$Password,$Location,$StoreName,$pfxOptions) + +#{cert_script(false)} +EOH + else + set_import_cert_script = <<-EOH2 +#{cert_script(true)} +#{within_store_script { |store| store + '.Add($cert)' }} +EOH2 + end + set_import_cert_script end + def acl_script(hash) + return '' if new_resource.private_key_acl.nil? || new_resource.private_key_acl.empty? + set_acl_script = <<-EOH + $hash = #{hash} + $Location = '#{cert_location}' + $StoreName = '#{new_resource.store_name}' + + $CertificatePath = "cert:\\$($Location)\\$($StoreName)\\$($hash)" + $Certificate = Get-ChildItem $CertificatePath + if ($Certificate -eq $null) { + throw 'no certificate exists for: $CertificatePath' + } + + $CertificateTriage = [Crypto.AclHelper]::TriageAcquireKeyHandle($Certificate) + + EOH + new_resource.private_key_acl.each do |name| + set_acl_script << "$CertificateTriage.Acl = $CertificateTriage.Acl | Add-AccessRule -UserAccount '#{name}' -FileSystemRights ReadAndExecute -AccessControlType Allow\n" + end + set_acl_script + end + + def library_script # Most of this PS came from https://www.powershellgallery.com/packages/GuardedFabricTools/0.2.0/Content/CertificateManagement.psm1 @@ -220,60 +255,60 @@ def library_script using System.Threading.Tasks; using Microsoft.Win32.SafeHandles; -namespace Contoso +namespace Crypto { - public static class CryptoTriage - { - private const string NCRYPT = "$ncryptDll"; - private const string CRYPT32 = "$crypt32Dll"; - private const string APIMSWINSECURITYBASE = "$securityDll"; - private const string APIMSWINSECURITYCRYPTOAPI = "$capiDll"; - - internal const string NCRYPT_SECURITY_DESCR_PROPERTY = "Security Descr"; - - [Flags] - public enum SECURITY_INFORMATION : uint - { - OWNER_SECURITY_INFORMATION = 0x00000001, - GROUP_SECURITY_INFORMATION = 0x00000002, - DACL_SECURITY_INFORMATION = 0x00000004, - SACL_SECURITY_INFORMATION = 0x00000008, - UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000, - UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000, - PROTECTED_SACL_SECURITY_INFORMATION = 0x40000000, - PROTECTED_DACL_SECURITY_INFORMATION = 0x80000000 - } - - [Flags] - private enum CryptAcquireKeyFlagControl : uint - { - CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG = 0x00010000, - CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG = 0x00020000, - CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG = 0x00040000, - } - - [Flags] - public enum CryptAcquireKeyFlags : uint - { - CRYPT_ACQUIRE_CACHE_FLAG = 0x00000001, - CRYPT_ACQUIRE_USE_PROV_INFO_FLAG = 0x00000002, - CRYPT_ACQUIRE_COMPARE_KEY_FLAG = 0x00000004, - CRYPT_ACQUIRE_NO_HEALING = 0x00000008, - CRYPT_ACQUIRE_SILENT_FLAG = 0x00000040, - } - - [Flags] - public enum CryptAcquireNCryptKeyFlags : uint - { - CRYPT_ACQUIRE_CACHE_FLAG = CryptAcquireKeyFlags.CRYPT_ACQUIRE_CACHE_FLAG | CryptAcquireKeyFlagControl.CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG, - CRYPT_ACQUIRE_USE_PROV_INFO_FLAG = CryptAcquireKeyFlags.CRYPT_ACQUIRE_USE_PROV_INFO_FLAG | CryptAcquireKeyFlagControl.CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG, - CRYPT_ACQUIRE_COMPARE_KEY_FLAG = CryptAcquireKeyFlags.CRYPT_ACQUIRE_COMPARE_KEY_FLAG | CryptAcquireKeyFlagControl.CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG, - CRYPT_ACQUIRE_NO_HEALING = CryptAcquireKeyFlags.CRYPT_ACQUIRE_NO_HEALING | CryptAcquireKeyFlagControl.CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG, - CRYPT_ACQUIRE_SILENT_FLAG = CryptAcquireKeyFlags.CRYPT_ACQUIRE_SILENT_FLAG | CryptAcquireKeyFlagControl.CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG, - } - - public enum ErrorCode - { + public static class Win + { + private const string NCRYPT = "$ncryptDll"; + private const string CRYPT32 = "$crypt32Dll"; + private const string APIMSWINSECURITYBASE = "$securityDll"; + private const string APIMSWINSECURITYCRYPTOAPI = "$capiDll"; + + internal const string NCRYPT_SECURITY_DESCR_PROPERTY = "Security Descr"; + + [Flags] + public enum SECURITY_INFORMATION : uint + { + OWNER_SECURITY_INFORMATION = 0x00000001, + GROUP_SECURITY_INFORMATION = 0x00000002, + DACL_SECURITY_INFORMATION = 0x00000004, + SACL_SECURITY_INFORMATION = 0x00000008, + UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000, + UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000, + PROTECTED_SACL_SECURITY_INFORMATION = 0x40000000, + PROTECTED_DACL_SECURITY_INFORMATION = 0x80000000 + } + + [Flags] + private enum CryptAcquireKeyFlagControl : uint + { + CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG = 0x00010000, + CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG = 0x00020000, + CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG = 0x00040000, + } + + [Flags] + public enum CryptAcquireKeyFlags : uint + { + CRYPT_ACQUIRE_CACHE_FLAG = 0x00000001, + CRYPT_ACQUIRE_USE_PROV_INFO_FLAG = 0x00000002, + CRYPT_ACQUIRE_COMPARE_KEY_FLAG = 0x00000004, + CRYPT_ACQUIRE_NO_HEALING = 0x00000008, + CRYPT_ACQUIRE_SILENT_FLAG = 0x00000040, + } + + [Flags] + public enum CryptAcquireNCryptKeyFlags : uint + { + CRYPT_ACQUIRE_CACHE_FLAG = CryptAcquireKeyFlags.CRYPT_ACQUIRE_CACHE_FLAG | CryptAcquireKeyFlagControl.CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG, + CRYPT_ACQUIRE_USE_PROV_INFO_FLAG = CryptAcquireKeyFlags.CRYPT_ACQUIRE_USE_PROV_INFO_FLAG | CryptAcquireKeyFlagControl.CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG, + CRYPT_ACQUIRE_COMPARE_KEY_FLAG = CryptAcquireKeyFlags.CRYPT_ACQUIRE_COMPARE_KEY_FLAG | CryptAcquireKeyFlagControl.CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG, + CRYPT_ACQUIRE_NO_HEALING = CryptAcquireKeyFlags.CRYPT_ACQUIRE_NO_HEALING | CryptAcquireKeyFlagControl.CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG, + CRYPT_ACQUIRE_SILENT_FLAG = CryptAcquireKeyFlags.CRYPT_ACQUIRE_SILENT_FLAG | CryptAcquireKeyFlagControl.CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG, + } + + public enum ErrorCode + { Success = 0, // ERROR_SUCCESS BadSignature = unchecked((int)0x80090006), // NTE_BAD_SIGNATURE NotFound = unchecked((int)0x80090011), // NTE_NOT_FOUND @@ -281,124 +316,114 @@ def library_script BufferTooSmall = unchecked((int)0x80090028), // NTE_BUFFER_TOO_SMALL NoMoreItems = unchecked((int)0x8009002a), // NTE_NO_MORE_ITEMS NotSupported = unchecked((int)0x80090029) // NTE_NOT_SUPPORTED - } - + } + [Flags] - public enum KeySpec : uint - { - NONE = 0x0, - AT_KEYEXCHANGE = 0x1, - AT_SIGNATURE = 2, - CERT_NCRYPT_KEY_SPEC = 0xFFFFFFFF - } - - public enum ProvParam : uint - { - PP_ENUMALGS = 1, - PP_ENUMCONTAINERS = 2, - PP_IMPTYPE = 3, - PP_NAME = 4, - PP_VERSION = 5, - PP_CONTAINER = 6, - PP_CHANGE_PASSWORD = 7, + public enum KeySpec : uint + { + NONE = 0x0, + AT_KEYEXCHANGE = 0x1, + AT_SIGNATURE = 2, + CERT_NCRYPT_KEY_SPEC = 0xFFFFFFFF + } + + public enum ProvParam : uint + { + PP_ENUMALGS = 1, + PP_ENUMCONTAINERS = 2, + PP_IMPTYPE = 3, + PP_NAME = 4, + PP_VERSION = 5, + PP_CONTAINER = 6, + PP_CHANGE_PASSWORD = 7, PP_KEYSET_SEC_DESCR = 8, // get/set security descriptor of keyset PP_CERTCHAIN = 9, // for retrieving certificates from tokens - PP_KEY_TYPE_SUBTYPE = 10, - PP_PROVTYPE = 16, - PP_KEYSTORAGE = 17, - PP_APPLI_CERT = 18, - PP_SYM_KEYSIZE = 19, - PP_SESSION_KEYSIZE = 20, - PP_UI_PROMPT = 21, - PP_ENUMALGS_EX = 22, - PP_ENUMMANDROOTS = 25, - PP_ENUMELECTROOTS = 26, - PP_KEYSET_TYPE = 27, - PP_ADMIN_PIN = 31, - PP_KEYEXCHANGE_PIN = 32, - PP_SIGNATURE_PIN = 33, - PP_SIG_KEYSIZE_INC = 34, - PP_KEYX_KEYSIZE_INC = 35, - PP_UNIQUE_CONTAINER = 36, - PP_SGC_INFO = 37, - PP_USE_HARDWARE_RNG = 38, - PP_KEYSPEC = 39, - PP_ENUMEX_SIGNING_PROT = 40, - PP_CRYPT_COUNT_KEY_USE = 41, - } - - [DllImport(APIMSWINSECURITYBASE, CharSet = CharSet.Unicode, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool GetSecurityDescriptorDacl( - IntPtr pSecurityDescriptor, - [MarshalAs(UnmanagedType.Bool)] out bool bDaclPresent, - ref IntPtr pDacl, - [MarshalAs(UnmanagedType.Bool)] out bool bDaclDefaulted); - - [DllImport(NCRYPT, CharSet = CharSet.Unicode, SetLastError = true)] - public static extern ErrorCode NCryptGetProperty( - SafeHandle hObject, - [MarshalAs(UnmanagedType.LPWStr)] string pszProperty, - SafeSecurityDescriptorPtr pbOutput, - uint cbOutput, - ref uint pcbResult, - SECURITY_INFORMATION dwFlags); - - [DllImport(NCRYPT, CharSet = CharSet.Unicode, SetLastError = true)] - public static extern ErrorCode NCryptSetProperty( - SafeHandle hObject, - [MarshalAs(UnmanagedType.LPWStr)] string pszProperty, - [MarshalAs(UnmanagedType.LPArray)] byte[] pbInput, - uint cbInput, - SECURITY_INFORMATION dwFlags); - - [DllImport(CRYPT32, CharSet = CharSet.Unicode, SetLastError = true)] - public static extern bool CryptAcquireCertificatePrivateKey( - IntPtr pCert, - CryptAcquireKeyFlags dwFlags, - IntPtr pvParameters, - out SafeCryptProviderHandle phCryptProvOrNCryptKey, - out KeySpec pdwKeySpec, - out bool pfCallerFreeProvOrNCryptKey); - - [DllImport(CRYPT32, CharSet = CharSet.Unicode, SetLastError = true)] - public static extern bool CryptAcquireCertificatePrivateKey( - IntPtr pCert, - CryptAcquireNCryptKeyFlags dwFlags, - IntPtr pvParameters, - out SafeNCryptKeyHandle phCryptProvOrNCryptKey, - out KeySpec pdwKeySpec, - out bool pfCallerFreeProvOrNCryptKey); - - [DllImport(APIMSWINSECURITYCRYPTOAPI, CharSet = CharSet.Unicode, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool CryptContextAddRef( - SafeCryptProviderHandle hProv, - IntPtr pdwReserved, - uint dwFlags); - - [DllImport(APIMSWINSECURITYCRYPTOAPI, CharSet = CharSet.Unicode, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool CryptReleaseContext( - IntPtr hProv, - uint dwFlags); - - [DllImport(APIMSWINSECURITYCRYPTOAPI, CharSet = CharSet.Unicode, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool CryptGetProvParam( - SafeHandle hProv, - ProvParam dwParam, - SafeSecurityDescriptorPtr pbData, - ref uint pdwDataLen, - SECURITY_INFORMATION dwFlags); - - [DllImport(APIMSWINSECURITYCRYPTOAPI, CharSet = CharSet.Unicode, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool CryptSetProvParam( - SafeHandle hProv, - ProvParam dwParam, - [MarshalAs(UnmanagedType.LPArray)] byte[] pbData, - SECURITY_INFORMATION dwFlags); + PP_KEY_TYPE_SUBTYPE = 10, + PP_PROVTYPE = 16, + PP_KEYSTORAGE = 17, + PP_APPLI_CERT = 18, + PP_SYM_KEYSIZE = 19, + PP_SESSION_KEYSIZE = 20, + PP_UI_PROMPT = 21, + PP_ENUMALGS_EX = 22, + PP_ENUMMANDROOTS = 25, + PP_ENUMELECTROOTS = 26, + PP_KEYSET_TYPE = 27, + PP_ADMIN_PIN = 31, + PP_KEYEXCHANGE_PIN = 32, + PP_SIGNATURE_PIN = 33, + PP_SIG_KEYSIZE_INC = 34, + PP_KEYX_KEYSIZE_INC = 35, + PP_UNIQUE_CONTAINER = 36, + PP_SGC_INFO = 37, + PP_USE_HARDWARE_RNG = 38, + PP_KEYSPEC = 39, + PP_ENUMEX_SIGNING_PROT = 40, + PP_CRYPT_COUNT_KEY_USE = 41, + } + + [Flags] + public enum StoreLocationFlags : uint + { + CERT_SYSTEM_STORE_UNPROTECTED_FLAG = 0x40000000, + CERT_SYSTEM_STORE_LOCATION_MASK = 0x00FF0000, + CERT_SYSTEM_STORE_LOCATION_SHIFT = 16, + CERT_SYSTEM_STORE_CURRENT_USER_ID = 1, + CERT_SYSTEM_STORE_LOCAL_MACHINE_ID = 2, + CERT_SYSTEM_STORE_CURRENT_SERVICE_ID = 4, + CERT_SYSTEM_STORE_SERVICES_ID = 5, + CERT_SYSTEM_STORE_USERS_ID = 6, + CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY_ID = 7, + CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY_ID = 8, + CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE_ID = 9, + CERT_SYSTEM_STORE_CURRENT_USER = ((int)CERT_SYSTEM_STORE_CURRENT_USER_ID << (int)CERT_SYSTEM_STORE_LOCATION_SHIFT), + CERT_SYSTEM_STORE_LOCAL_MACHINE = ((int)CERT_SYSTEM_STORE_LOCAL_MACHINE_ID << (int)CERT_SYSTEM_STORE_LOCATION_SHIFT), + CERT_SYSTEM_STORE_CURRENT_SERVICE = ((int)CERT_SYSTEM_STORE_CURRENT_SERVICE_ID << (int)CERT_SYSTEM_STORE_LOCATION_SHIFT), + CERT_SYSTEM_STORE_SERVICES = ((int)CERT_SYSTEM_STORE_SERVICES_ID << (int)CERT_SYSTEM_STORE_LOCATION_SHIFT), + CERT_SYSTEM_STORE_USERS = ((int)CERT_SYSTEM_STORE_USERS_ID << (int)CERT_SYSTEM_STORE_LOCATION_SHIFT), + CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY = ((int)CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY_ID << (int)CERT_SYSTEM_STORE_LOCATION_SHIFT), + CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY = ((int)CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY_ID << (int)CERT_SYSTEM_STORE_LOCATION_SHIFT), + CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE = ((int)CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE_ID << (int)CERT_SYSTEM_STORE_LOCATION_SHIFT) + } + + [Flags] + public enum PfxCertStoreFlags : uint + { + CRYPT_EXPORTABLE = 0x00000001, + CRYPT_USER_PROTECTED = 0x00000002, + CRYPT_MACHINE_KEYSET = 0x00000020, + CRYPT_USER_KEYSET = 0x00001000, + PKCS12_PREFER_CNG_KSP = 0x00000100, + PKCS12_ALWAYS_CNG_KSP = 0x00000200, + PKCS12_ALLOW_OVERWRITE_KEY = 0x00004000, + PKCS12_NO_PERSIST_KEY = 0x00008000, + PKCS12_INCLUDE_EXTENDED_PROPERTIES = 0x00000010, + None = 0x00000000, + } + + public enum CertStoreProvider : int + { + CERT_STORE_PROV_MEMORY = 2, + CERT_STORE_PROV_SYSTEM = 10, + } + + public enum CertStoreAddDisposition : int + { + CERT_STORE_ADD_NEW = 1, + CERT_STORE_ADD_USE_EXISTING = 2, + CERT_STORE_ADD_REPLACE_EXISTING = 3, + CERT_STORE_ADD_ALWAYS = 4, + CERT_STORE_ADD_REPLACE_EXISTING_INHERIT_PROPERTIES = 5, + CERT_STORE_ADD_NEWER = 6, + CERT_STORE_ADD_NEWER_INHERIT_PROPERTIES = 7, + } + + public enum CertStoreCloseDisposition : int + { + NONE = 0, + CERT_CLOSE_STORE_FORCE_FLAG = 0x1, + CERT_CLOSE_STORE_CHECK_FLAG = 0x2, + } public enum ProviderType : uint { @@ -426,18 +451,27 @@ def library_script [Flags] public enum ProviderKeyFlags : uint { - CERT_SET_KEY_PROV_HANDLE_PROP_ID_OR_CERT_SET_KEY_CONTEXT_PROP_ID = 1, - CRYPT_MACHINE_KEYSET_OR_NCRYPT_MACHINE_KEY_FLAG = 32, - CRYPT_SILENT_OR_NCRYPT_SILENT_FLAG = 64, + CERT_SET_KEY_PROV_HANDLE_PROP_ID_OR_CERT_SET_KEY_CONTEXT_PROP_ID = 1, + CRYPT_MACHINE_KEYSET_OR_NCRYPT_MACHINE_KEY_FLAG = 32, + CRYPT_SILENT_OR_NCRYPT_SILENT_FLAG = 64, } - [Flags] - public enum ProviderKeySpecifier : uint + [StructLayout(LayoutKind.Sequential)] + public struct CRYPT_DATA_BLOB + { + public int cbData; + public IntPtr pbData; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CERT_CONTEXT { - NONE = 0x0, - AT_KEYEXCHANGE = 0x1, - AT_SIGNATURE = 2, - CERT_NCRYPT_KEY_SPEC = 0xFFFFFFFF + public uint dwCertEncodingType; + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] + public byte[] pbCertEncoded; + public uint cbCertEncoded; + public IntPtr pCertInfo; + public IntPtr hCertStore; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] @@ -451,15 +485,122 @@ def library_script public ProviderKeyFlags dwFlags; public uint cProvParam; public IntPtr rgProvParam; - public ProviderKeySpecifier dwKeySpec; + public KeySpec dwKeySpec; } - [DllImport(CRYPT32, CharSet = CharSet.Auto, SetLastError = true)] + [DllImport(APIMSWINSECURITYBASE, CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetSecurityDescriptorDacl( + IntPtr pSecurityDescriptor, + [MarshalAs(UnmanagedType.Bool)] out bool bDaclPresent, + ref IntPtr pDacl, + [MarshalAs(UnmanagedType.Bool)] out bool bDaclDefaulted); + + [DllImport(NCRYPT, CharSet = CharSet.Unicode, SetLastError = true)] + public static extern ErrorCode NCryptGetProperty( + SafeHandle hObject, + [MarshalAs(UnmanagedType.LPWStr)] string pszProperty, + SafeSecurityDescriptorPtr pbOutput, + uint cbOutput, + ref uint pcbResult, + SECURITY_INFORMATION dwFlags); + + [DllImport(NCRYPT, CharSet = CharSet.Unicode, SetLastError = true)] + public static extern ErrorCode NCryptSetProperty( + SafeHandle hObject, + [MarshalAs(UnmanagedType.LPWStr)] string pszProperty, + [MarshalAs(UnmanagedType.LPArray)] byte[] pbInput, + uint cbInput, + SECURITY_INFORMATION dwFlags); + + [DllImport(CRYPT32, CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool CryptAcquireCertificatePrivateKey( + IntPtr pCert, + CryptAcquireKeyFlags dwFlags, + IntPtr pvParameters, + out SafeCryptProviderHandle phCryptProvOrNCryptKey, + out KeySpec pdwKeySpec, + out bool pfCallerFreeProvOrNCryptKey); + + [DllImport(CRYPT32, CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool CryptAcquireCertificatePrivateKey( + IntPtr pCert, + CryptAcquireNCryptKeyFlags dwFlags, + IntPtr pvParameters, + out SafeNCryptKeyHandle phCryptProvOrNCryptKey, + out KeySpec pdwKeySpec, + out bool pfCallerFreeProvOrNCryptKey); + + [DllImport(APIMSWINSECURITYCRYPTOAPI, CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CryptContextAddRef( + SafeCryptProviderHandle hProv, + IntPtr pdwReserved, + uint dwFlags); + + [DllImport(APIMSWINSECURITYCRYPTOAPI, CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CryptReleaseContext( + IntPtr hProv, + uint dwFlags); + + [DllImport(APIMSWINSECURITYCRYPTOAPI, CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CryptGetProvParam( + SafeHandle hProv, + ProvParam dwParam, + SafeSecurityDescriptorPtr pbData, + ref uint pdwDataLen, + SECURITY_INFORMATION dwFlags); + + [DllImport(APIMSWINSECURITYCRYPTOAPI, CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CryptSetProvParam( + SafeHandle hProv, + ProvParam dwParam, + [MarshalAs(UnmanagedType.LPArray)] byte[] pbData, + SECURITY_INFORMATION dwFlags); + + [DllImport(CRYPT32, CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr CertOpenStore( + CertStoreProvider storeProvider, + uint dwMsgAndCertEncodingType, + IntPtr hCryptProv, + StoreLocationFlags dwFlags, + string cchNameString); + + [DllImport(CRYPT32, SetLastError = true)] + public static extern IntPtr PFXImportCertStore( + ref CRYPT_DATA_BLOB pPfx, + [MarshalAs(UnmanagedType.LPWStr)] string szPassword, + PfxCertStoreFlags dwFlags); + + [DllImport(CRYPT32, SetLastError = true)] + public static extern bool CertAddCertificateContextToStore( + IntPtr hCertStore, + IntPtr pCertContext, + CertStoreAddDisposition dwAddDisposition, + ref IntPtr ppStoreContext + ); + + [DllImport(CRYPT32, SetLastError = true)] + public static extern IntPtr CertEnumCertificatesInStore( + IntPtr storeProvider, + IntPtr prevCertContext + ); + + [DllImport(CRYPT32, SetLastError = true)] + public static extern bool CertCloseStore( + IntPtr hCertStore, + CertStoreCloseDisposition dwFlags + ); + + [DllImport(CRYPT32, CharSet = CharSet.Auto, SetLastError = true)] public static extern bool CertGetCertificateContextProperty( - IntPtr pCertContext, - uint dwPropId, - IntPtr pvData, - ref uint pcbData); + IntPtr pCertContext, + uint dwPropId, + IntPtr pvData, + ref uint pcbData); [DllImport(CRYPT32, EntryPoint = "CertSetCertificateContextProperty", CharSet = CharSet.Auto, SetLastError = true)] internal static extern Boolean CertSetCertificateContextProperty( @@ -468,450 +609,563 @@ def library_script Int32 dwFlags, IntPtr pvData); - public static CRYPT_KEY_PROV_INFO GetCspParameters(X509Certificate2 certificate) { - const int CERT_KEY_PROV_INFO_PROP_ID = 2; - uint pcbData = 0; + public class SafeSecurityDescriptorPtr : SafeHandleZeroOrMinusOneIsInvalid + { + private static SafeSecurityDescriptorPtr nullHandle = new SafeSecurityDescriptorPtr(); + + private int size = -1; + + public SafeSecurityDescriptorPtr() + : base(true) + { + } - if (!CertGetCertificateContextProperty(certificate.Handle, CERT_KEY_PROV_INFO_PROP_ID, IntPtr.Zero, ref pcbData)) - { - throw new Win32Exception(Marshal.GetLastWin32Error()); + public SafeSecurityDescriptorPtr(uint size) + : base(true) + { + this.size = (int)size; + this.SetHandle(Marshal.AllocHGlobal(this.size)); } - IntPtr pvData = Marshal.AllocHGlobal((int)pcbData); - - try{ + public SafeSecurityDescriptorPtr(IntPtr handle) + : base(true) + { + this.SetHandle(handle); + } - if (!CertGetCertificateContextProperty(certificate.Handle, CERT_KEY_PROV_INFO_PROP_ID, pvData, ref pcbData)) - { + public static SafeSecurityDescriptorPtr Null + { + get + { + return nullHandle; + } + } + + public IntPtr GetDacl() + { + IntPtr pDacl = IntPtr.Zero; + bool daclPresent = false; + bool daclDefaulted = false; + + if (!GetSecurityDescriptorDacl( + this.handle, + out daclPresent, + ref pDacl, + out daclDefaulted)) + { throw new Win32Exception(Marshal.GetLastWin32Error()); } - var cryptoKeyProvInfo = (CRYPT_KEY_PROV_INFO)Marshal.PtrToStructure(pvData, typeof(CRYPT_KEY_PROV_INFO)); - return cryptoKeyProvInfo; - } finally { - Marshal.FreeHGlobal(pvData); + if (!daclPresent) + { + return IntPtr.Zero; + } + else + { + return pDacl; + } + } + + public byte[] GetBinaryForm() + { + if (size < 0) + { + throw new NotSupportedException(); + } + + byte[] buffer = new byte[size]; + Marshal.Copy(this.handle, buffer, 0, buffer.Length); + + return buffer; + } + + protected override bool ReleaseHandle() + { + try + { + Marshal.FreeHGlobal(this.handle); + return true; + } + catch + { + // semantics of this function are to never throw an exception so we must eat the underlying error and + // return false. + return false; + } } } - public static bool SetCspParameters(X509Certificate2 certificate, CRYPT_KEY_PROV_INFO cryptKeyProvInfo) + public class SafeCryptProviderHandle : SafeHandleZeroOrMinusOneIsInvalid { - const int CERT_KEY_PROV_INFO_PROP_ID = 2; - + private static SafeCryptProviderHandle nullHandle = new SafeCryptProviderHandle(); - IntPtr pvData = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(CRYPT_KEY_PROV_INFO))); - try + public SafeCryptProviderHandle() + : base(true) { - Marshal.StructureToPtr(cryptKeyProvInfo, pvData, false); - return CertSetCertificateContextProperty(certificate.Handle, CERT_KEY_PROV_INFO_PROP_ID, 0, pvData); - } finally { - Marshal.FreeHGlobal(pvData); } - } - public static TriageHandle TriageAcquireKeyHandle(X509Certificate2 certificate) - { - SafeNCryptKeyHandle ncryptKeyHandle = null; - SafeCryptProviderHandle cspHandle = null; - KeySpec keySpec; - bool ownHandle = true; - - if (!CryptAcquireCertificatePrivateKey( - certificate.Handle, - CryptAcquireKeyFlags.CRYPT_ACQUIRE_SILENT_FLAG, - IntPtr.Zero, - out cspHandle, - out keySpec, - out ownHandle)) - { - Win32Exception cspException = new Win32Exception(Marshal.GetLastWin32Error()); - - if (!CryptAcquireCertificatePrivateKey( - certificate.Handle, - CryptAcquireNCryptKeyFlags.CRYPT_ACQUIRE_SILENT_FLAG, - IntPtr.Zero, - out ncryptKeyHandle, - out keySpec, - out ownHandle)) - { - throw new AggregateException( - new Win32Exception(Marshal.GetLastWin32Error()), - cspException); - } - } - - if (!ownHandle) - { - throw new NotSupportedException("Must be able to take ownership of the certificate private key handle."); - } - - if (ncryptKeyHandle != null) - { - return new CngTriageHandle(ncryptKeyHandle); - } - else if (cspHandle != null) - { - if (keySpec != KeySpec.AT_KEYEXCHANGE && keySpec != KeySpec.AT_SIGNATURE) - { - throw new NotSupportedException("Only exchange or signature key pairs are supported."); - } - - return new CapiTriageHandle(cspHandle); - } - else - { - throw new NotSupportedException("The certificate private key cannot be accessed."); - } - } - - public static void AssertSuccess(this ErrorCode code) - { - if (code != ErrorCode.Success) - { - throw new Win32Exception((int)code); - } - } - - public class SafeCryptProviderHandle : SafeHandleZeroOrMinusOneIsInvalid - { - private static SafeCryptProviderHandle nullHandle = new SafeCryptProviderHandle(); - - public SafeCryptProviderHandle() - : base(true) - { - } - - public SafeCryptProviderHandle(IntPtr handle) - : base(true) - { - this.SetHandle(handle); - } - - public static SafeCryptProviderHandle Null - { - get - { - return nullHandle; - } - } - - internal SafeCryptProviderHandle Duplicate() - { - if (this.IsInvalid || this.IsClosed) - { - throw new InvalidOperationException(); - } - + public SafeCryptProviderHandle(IntPtr handle) + : base(true) + { + this.SetHandle(handle); + } + + public static SafeCryptProviderHandle Null + { + get + { + return nullHandle; + } + } + + internal SafeCryptProviderHandle Duplicate() + { + if (this.IsInvalid || this.IsClosed) + { + throw new InvalidOperationException(); + } + // in the window between the call to CryptContextAddRef and when the raw handle value is assigned // into the new safe handle, there's a second reference to the original safe handle that the CLR does // not know about, so we need to bump the reference count around this entire operation to ensure // that we don't have the original handle closed underneath us. - bool acquired = false; - try - { - this.DangerousAddRef(ref acquired); - IntPtr underlyingHandle = this.DangerousGetHandle(); - SafeCryptProviderHandle duplicate = new SafeCryptProviderHandle(); + bool acquired = false; + try + { + this.DangerousAddRef(ref acquired); + IntPtr underlyingHandle = this.DangerousGetHandle(); + SafeCryptProviderHandle duplicate = new SafeCryptProviderHandle(); int lastError = 0; // atomically add reference and set handle on the duplicate - $PrepareConstrainedRegions - try - { - } - finally - { - if (!CryptContextAddRef(this, IntPtr.Zero, 0)) - { - lastError = Marshal.GetLastWin32Error(); - } - else - { - duplicate.SetHandle(underlyingHandle); - } - } - - if (lastError != 0) - { - duplicate.Dispose(); - throw new Win32Exception(lastError); - } - - return duplicate; - } - finally - { - if (acquired) - { - this.DangerousRelease(); - } - } - } - - protected override bool ReleaseHandle() - { - return CryptReleaseContext(this.handle, 0); - } - } - - public class SafeSecurityDescriptorPtr : SafeHandleZeroOrMinusOneIsInvalid - { - private static SafeSecurityDescriptorPtr nullHandle = new SafeSecurityDescriptorPtr(); - - private int size = -1; - - public SafeSecurityDescriptorPtr() - : base(true) - { - } - - public SafeSecurityDescriptorPtr(uint size) - : base(true) - { - this.size = (int)size; - this.SetHandle(Marshal.AllocHGlobal(this.size)); - } - - public SafeSecurityDescriptorPtr(IntPtr handle) - : base(true) - { - this.SetHandle(handle); - } - - public static SafeSecurityDescriptorPtr Null - { - get - { - return nullHandle; - } - } - - public IntPtr GetDacl() - { - IntPtr pDacl = IntPtr.Zero; - bool daclPresent = false; - bool daclDefaulted = false; - - if (!GetSecurityDescriptorDacl( - this.handle, - out daclPresent, - ref pDacl, - out daclDefaulted)) - { - throw new Win32Exception(Marshal.GetLastWin32Error()); - } - - if (!daclPresent) - { - return IntPtr.Zero; - } - else - { - return pDacl; - } - } - - public byte[] GetBinaryForm() - { - if (size < 0) - { - throw new NotSupportedException(); - } - - byte[] buffer = new byte[size]; - Marshal.Copy(this.handle, buffer, 0, buffer.Length); - - return buffer; - } - - protected override bool ReleaseHandle() - { - try - { - Marshal.FreeHGlobal(this.handle); - return true; - } - catch - { - // semantics of this function are to never throw an exception so we must eat the underlying error and - // return false. - return false; - } - } - } - - public abstract class TriageHandle : IDisposable - { - public TriageHandle() - { - } - - public abstract FileSecurity Acl - { - get; - set; - } - - public abstract bool IsValid - { - get; - } - - public abstract void Dispose(); - } - - public class CapiTriageHandle : TriageHandle - { - public CapiTriageHandle(SafeCryptProviderHandle handle) : base() - { - this.Handle = handle; - } - - public override bool IsValid - { - get - { - return this.Handle != null && !this.Handle.IsInvalid && !this.Handle.IsClosed; - } - } - - public override FileSecurity Acl - { - get - { - uint securityDescriptorSize = 0; - if (!CryptGetProvParam( - this.Handle, - ProvParam.PP_KEYSET_SEC_DESCR, - SafeSecurityDescriptorPtr.Null, - ref securityDescriptorSize, - SECURITY_INFORMATION.DACL_SECURITY_INFORMATION)) - { - throw new Win32Exception(Marshal.GetLastWin32Error()); - } - - SafeSecurityDescriptorPtr securityDescriptorBuffer = new SafeSecurityDescriptorPtr(securityDescriptorSize); - - if (!CryptGetProvParam( - this.Handle, - ProvParam.PP_KEYSET_SEC_DESCR, - securityDescriptorBuffer, - ref securityDescriptorSize, - SECURITY_INFORMATION.DACL_SECURITY_INFORMATION)) - { - throw new Win32Exception(Marshal.GetLastWin32Error()); - } - - using (securityDescriptorBuffer) - { - FileSecurity acl = new FileSecurity(); - acl.SetSecurityDescriptorBinaryForm(securityDescriptorBuffer.GetBinaryForm()); - return acl; - } - } - - set - { - if (!CryptSetProvParam( - this.Handle, - ProvParam.PP_KEYSET_SEC_DESCR, - value.GetSecurityDescriptorBinaryForm(), - SECURITY_INFORMATION.DACL_SECURITY_INFORMATION)) - { - throw new Win32Exception(Marshal.GetLastWin32Error()); - } - } - } - - protected SafeCryptProviderHandle Handle - { - get; - set; - } - - public override void Dispose() - { - this.Handle.Dispose(); - } - } - - public class CngTriageHandle : TriageHandle - { - public CngTriageHandle(SafeNCryptHandle handle) : base() - { - this.Handle = handle; - } - - public override bool IsValid - { - get - { - return this.Handle != null && !this.Handle.IsInvalid && !this.Handle.IsClosed; - } - } - - public override FileSecurity Acl - { - get - { - uint securityDescriptorSize = 0; - NCryptGetProperty( - this.Handle, - NCRYPT_SECURITY_DESCR_PROPERTY, - SafeSecurityDescriptorPtr.Null, - 0, - ref securityDescriptorSize, - SECURITY_INFORMATION.DACL_SECURITY_INFORMATION).AssertSuccess(); - - SafeSecurityDescriptorPtr securityDescriptorBuffer = new SafeSecurityDescriptorPtr(securityDescriptorSize); - - NCryptGetProperty( - this.Handle, - NCRYPT_SECURITY_DESCR_PROPERTY, - securityDescriptorBuffer, - securityDescriptorSize, - ref securityDescriptorSize, - SECURITY_INFORMATION.DACL_SECURITY_INFORMATION).AssertSuccess(); - - using (securityDescriptorBuffer) - { - FileSecurity acl = new FileSecurity(); - acl.SetSecurityDescriptorBinaryForm(securityDescriptorBuffer.GetBinaryForm()); - return acl; - } - } - - set - { - byte[] sd = value.GetSecurityDescriptorBinaryForm(); - NCryptSetProperty( - this.Handle, - NCRYPT_SECURITY_DESCR_PROPERTY, - sd, - (uint)sd.Length, - SECURITY_INFORMATION.DACL_SECURITY_INFORMATION).AssertSuccess(); - } - } - - protected SafeNCryptHandle Handle - { - get; - set; - } - - public override void Dispose() - { - this.Handle.Dispose(); - } - } - } + $PrepareConstrainedRegions + try + { + } + finally + { + if (!CryptContextAddRef(this, IntPtr.Zero, 0)) + { + lastError = Marshal.GetLastWin32Error(); + } + else + { + duplicate.SetHandle(underlyingHandle); + } + } + + if (lastError != 0) + { + duplicate.Dispose(); + throw new Win32Exception(lastError); + } + + return duplicate; + } + finally + { + if (acquired) + { + this.DangerousRelease(); + } + } + } + + protected override bool ReleaseHandle() + { + return CryptReleaseContext(this.handle, 0); + } + } + } + + public static class PfxHelper + { + public static void ImportPfx(string pfxPath, string pfxPassword, StoreLocation storeLocation, + StoreName storeName, Win.PfxCertStoreFlags pfxCertStoreFlags = Win.PfxCertStoreFlags.None) + { + ImportPfx(pfxPath, pfxPassword, storeLocation, Enum.GetName(typeof(StoreName), storeName), + pfxCertStoreFlags); + } + + public static void ImportPfx(string pfxPath, string pfxPassword, string storeLocation, + StoreName storeName, Win.PfxCertStoreFlags pfxCertStoreFlags = Win.PfxCertStoreFlags.None) + { + ImportPfx(pfxPath, pfxPassword, storeLocation, Enum.GetName(typeof(StoreName), storeName), + pfxCertStoreFlags); + } + + public static void ImportPfx(string pfxPath, string pfxPassword, StoreLocation storeLocation, + string storeName, Win.PfxCertStoreFlags pfxCertStoreFlags = Win.PfxCertStoreFlags.None) + { + Win.StoreLocationFlags storeLocationFlags; + + switch (storeLocation) + { + case StoreLocation.CurrentUser: + storeLocationFlags = Win.StoreLocationFlags.CERT_SYSTEM_STORE_CURRENT_USER; + break; + case StoreLocation.LocalMachine: + storeLocationFlags = Win.StoreLocationFlags.CERT_SYSTEM_STORE_LOCAL_MACHINE; + break; + default: + throw new Exception("Unknown store location"); + } + + ImportPfx(pfxPath, pfxPassword, storeLocationFlags, storeName, pfxCertStoreFlags); + } + + public static void ImportPfx(string pfxPath, string pfxPassword, string storeLocation, + string storeName, Win.PfxCertStoreFlags pfxCertStoreFlags = Win.PfxCertStoreFlags.None) + { + Win.StoreLocationFlags storeLocationFlags; + + switch (storeLocation) + { + case "CurrentUser": + storeLocationFlags = Win.StoreLocationFlags.CERT_SYSTEM_STORE_CURRENT_USER; + break; + case "LocalMachine": + storeLocationFlags = Win.StoreLocationFlags.CERT_SYSTEM_STORE_LOCAL_MACHINE; + break; + default: + throw new Exception("Unknown store location"); + } + + ImportPfx(pfxPath, pfxPassword, storeLocationFlags, storeName, pfxCertStoreFlags); + } + + public static void ImportPfx(string pfxPath, string pfxPassword, Win.StoreLocationFlags storeLocationFlags, + string storeName, Win.PfxCertStoreFlags pfxCertStoreFlags = Win.PfxCertStoreFlags.None) + { + try + { + IntPtr hCryptProv = IntPtr.Zero; + IntPtr hCertStore = Win.CertOpenStore(Win.CertStoreProvider.CERT_STORE_PROV_SYSTEM, + 0, + hCryptProv, + storeLocationFlags, + storeName); + + if (hCertStore == IntPtr.Zero) return; + try + { + byte[] rawData = System.IO.File.ReadAllBytes(pfxPath); + + IntPtr pbData = Marshal.AllocHGlobal(rawData.Length); + try + { + Marshal.Copy(rawData, 0, pbData, rawData.Length); + + Win.CRYPT_DATA_BLOB ppfx = new Win.CRYPT_DATA_BLOB(); + ppfx.cbData = rawData.Length; + ppfx.pbData = pbData; + + IntPtr hMemStore = Win.PFXImportCertStore(ref ppfx, pfxPassword, pfxCertStoreFlags); + + if (hMemStore == IntPtr.Zero) return; + try + { + IntPtr pctx = IntPtr.Zero; + IntPtr pStoreContext = IntPtr.Zero; + + List certificateHashes = new List(); + + while (IntPtr.Zero != (pctx = Win.CertEnumCertificatesInStore(hMemStore, pctx))) + { + var certificateHash = GetCertificateHash(pctx); + certificateHashes.Add(certificateHash); + } + + string lastCertificateHash = certificateHashes.FindLast(x => true); + + while (IntPtr.Zero != (pctx = Win.CertEnumCertificatesInStore(hMemStore, pctx))) + { + var certificateHash = GetCertificateHash(pctx); + + if (certificateHash == lastCertificateHash) + { + //We always want to replace the actual certificate + Win.CertAddCertificateContextToStore(hCertStore, + pctx, + Win.CertStoreAddDisposition.CERT_STORE_ADD_REPLACE_EXISTING, + ref pStoreContext); + } + else + { + //If chain is not already added, then we add it + Win.CertAddCertificateContextToStore(hCertStore, + pctx, + Win.CertStoreAddDisposition.CERT_STORE_ADD_USE_EXISTING, + ref pStoreContext); + } + } + } + finally + { + Win.CertCloseStore(hMemStore, Win.CertStoreCloseDisposition.NONE); + } + } + finally + { + Marshal.FreeHGlobal(pbData); + } + } + finally + { + Win.CertCloseStore(hCertStore, Win.CertStoreCloseDisposition.NONE); + } + } + catch (Exception e) + { + Console.WriteLine(e.Message); + } + } + + private static string GetCertificateHash(IntPtr certificatePtr) + { + X509Certificate certificate = new X509Certificate(certificatePtr); + return certificate.GetCertHashString(); + } + } + + public static class AclHelper + { + public static TriageHandle TriageAcquireKeyHandle(X509Certificate2 certificate) + { + SafeNCryptKeyHandle ncryptKeyHandle = null; + Win.SafeCryptProviderHandle cspHandle = null; + Win.KeySpec keySpec; + bool ownHandle = true; + + if (!Win.CryptAcquireCertificatePrivateKey( + certificate.Handle, + Win.CryptAcquireKeyFlags.CRYPT_ACQUIRE_SILENT_FLAG, + IntPtr.Zero, + out cspHandle, + out keySpec, + out ownHandle)) + { + Win32Exception cspException = new Win32Exception(Marshal.GetLastWin32Error()); + + if (!Win.CryptAcquireCertificatePrivateKey( + certificate.Handle, + Win.CryptAcquireNCryptKeyFlags.CRYPT_ACQUIRE_SILENT_FLAG, + IntPtr.Zero, + out ncryptKeyHandle, + out keySpec, + out ownHandle)) + { + throw new AggregateException( + new Win32Exception(Marshal.GetLastWin32Error()), + cspException); + } + } + + if (!ownHandle) + { + throw new NotSupportedException("Must be able to take ownership of the certificate private key handle."); + } + + if (ncryptKeyHandle != null) + { + return new CngTriageHandle(ncryptKeyHandle); + } + else if (cspHandle != null) + { + if (keySpec != Win.KeySpec.AT_KEYEXCHANGE && keySpec != Win.KeySpec.AT_SIGNATURE) + { + throw new NotSupportedException("Only exchange or signature key pairs are supported."); + } + + return new CapiTriageHandle(cspHandle); + } + else + { + throw new NotSupportedException("The certificate private key cannot be accessed."); + } + } + + public static void AssertSuccess(this Win.ErrorCode code) + { + if (code != Win.ErrorCode.Success) + { + throw new Win32Exception((int)code); + } + } + + public abstract class TriageHandle : IDisposable + { + public TriageHandle() + { + } + + public abstract FileSecurity Acl + { + get; + set; + } + + public abstract bool IsValid + { + get; + } + + public abstract void Dispose(); + } + + public class CapiTriageHandle : TriageHandle + { + public CapiTriageHandle(Win.SafeCryptProviderHandle handle) : base() + { + this.Handle = handle; + } + + public override bool IsValid + { + get + { + return this.Handle != null && !this.Handle.IsInvalid && !this.Handle.IsClosed; + } + } + + public override FileSecurity Acl + { + get + { + uint securityDescriptorSize = 0; + if (!Win.CryptGetProvParam( + this.Handle, + Win.ProvParam.PP_KEYSET_SEC_DESCR, + Win.SafeSecurityDescriptorPtr.Null, + ref securityDescriptorSize, + Win.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION)) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + + Win.SafeSecurityDescriptorPtr securityDescriptorBuffer = new Win.SafeSecurityDescriptorPtr(securityDescriptorSize); + + if (!Win.CryptGetProvParam( + this.Handle, + Win.ProvParam.PP_KEYSET_SEC_DESCR, + securityDescriptorBuffer, + ref securityDescriptorSize, + Win.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION)) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + + using (securityDescriptorBuffer) + { + FileSecurity acl = new FileSecurity(); + acl.SetSecurityDescriptorBinaryForm(securityDescriptorBuffer.GetBinaryForm()); + return acl; + } + } + + set + { + if (!Win.CryptSetProvParam( + this.Handle, + Win.ProvParam.PP_KEYSET_SEC_DESCR, + value.GetSecurityDescriptorBinaryForm(), + Win.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION)) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + } + } + + protected Win.SafeCryptProviderHandle Handle + { + get; + set; + } + + public override void Dispose() + { + this.Handle.Dispose(); + } + } + + public class CngTriageHandle : TriageHandle + { + public CngTriageHandle(SafeNCryptHandle handle) : base() + { + this.Handle = handle; + } + + public override bool IsValid + { + get + { + return this.Handle != null && !this.Handle.IsInvalid && !this.Handle.IsClosed; + } + } + + public override FileSecurity Acl + { + get + { + uint securityDescriptorSize = 0; + Win.NCryptGetProperty( + this.Handle, + Win.NCRYPT_SECURITY_DESCR_PROPERTY, + Win.SafeSecurityDescriptorPtr.Null, + 0, + ref securityDescriptorSize, + Win.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION).AssertSuccess(); + + Win.SafeSecurityDescriptorPtr securityDescriptorBuffer = new Win.SafeSecurityDescriptorPtr(securityDescriptorSize); + + Win.NCryptGetProperty( + this.Handle, + Win.NCRYPT_SECURITY_DESCR_PROPERTY, + securityDescriptorBuffer, + securityDescriptorSize, + ref securityDescriptorSize, + Win.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION).AssertSuccess(); + + using (securityDescriptorBuffer) + { + FileSecurity acl = new FileSecurity(); + acl.SetSecurityDescriptorBinaryForm(securityDescriptorBuffer.GetBinaryForm()); + return acl; + } + } + + set + { + byte[] sd = value.GetSecurityDescriptorBinaryForm(); + Win.NCryptSetProperty( + this.Handle, + Win.NCRYPT_SECURITY_DESCR_PROPERTY, + sd, + (uint)sd.Length, + Win.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION).AssertSuccess(); + } + } + + protected SafeNCryptHandle Handle + { + get; + set; + } + + public override void Dispose() + { + this.Handle.Dispose(); + } + } + } } "@ -if (-not $global:ContosoCertificateManagementCompiled) +if (-not $global:CryptoCertificateManagementCompiled) { Write-Verbose "Loading certificate management p/invoke shim." Add-Type -TypeDefinition $source -Language CSharp -ReferencedAssemblies $references -ErrorAction Stop - $global:ContosoCertificateManagementCompiled = $true + $global:CryptoCertificateManagementCompiled = $true } else { @@ -993,26 +1247,4 @@ def library_script EOH set_library_script end - - def acl_script(hash) - return '' if new_resource.private_key_acl.nil? || new_resource.private_key_acl.empty? - set_acl_script = <<-EOH - $hash = #{hash} - $Location = '#{cert_location}' - $StoreName = '#{new_resource.store_name}' - - $CertificatePath = "cert:\\$($Location)\\$($StoreName)\\$($Thumbprint)" - $Certificate = Get-ChildItem $CertificatePath - if ($Certificate -eq $null) { - throw 'no certificate exists for: $Certificate' - } - - $cryptoTriage = [Contoso.CryptoTriage]::TriageAcquireKeyHandle($Certificate) - - EOH - new_resource.private_key_acl.each do |name| - set_acl_script << "$cryptoTriage.Acl = $cryptoTriage.Acl | Add-AccessRule -UserAccount '#{name}' -FileSystemRights ReadAndExecute -AccessControlType Allow\n" - end - set_acl_script - end end