Is it possible to determine the private key file name of a certificate on a remote Windows server?

I am trying to determine the name of a private key file for a certificate stored on a remote Windows machine (2k3 / 2k8), and I have some difficulties. I am also not familiar with Microsoft CryptAPI, so I'm looking for any help you can provide.

The goal of this exercise is to find certificates with private keys installed on a remote computer that meet certain criteria and provide the correct rights to their personal key files. Although I could assign permissions at the folder level, I would prefer only assign permissions at the file level of the private key, where necessary (for obvious reasons).

In this scenario, suppose that a service account with administrative-like permissions accesses a certificate store:

  • I am returning a remote certificate store using the following call from C # using p / invoke:

    [DllImport ("CRYPT32", EntryPoint = "CertOpenStore", CharSet = CharSet.Unicode, SetLastError = true)] public static extern IntPtr CertOpenStore (int storeProvider, int encodingType, int hcryptProv, int flags, string pvPara);

    IntPtr storeHandle = CertOpenStore (CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_LOCAL_MACHINE, string.Format (@ "\ {0} {1}", server_name, name));

  • Then I use CertEnumCertificatesInStore to restore the certificates I want to evaluate.

    [DllImport ("CRYPT32", EntryPoint = "CertEnumCertificatesInStore", CharSet = CharSet.Unicode, SetLastError = true)] public static extern IntPtr CertEnumCertificatesInStore (IntPtr storeProvider, IntPtr prevCertContext); IntPtr certCtx = IntPtr.Zero; certCtx = CertEnumCertificatesInStore (storeHandle, certCtx);

  • If the certificate meets my criteria, I create an X509Certificate2 instance from IntPtr returned from the CertEnumCertificatesInStore call, for example:

    X509Certificate2 current = new X509Certificate2 (certCtx);

  • Once I have X509Certificate2 instances for the certificates that interest me, I call CryptAcquireCertificatePrivateKey to get the private key provider:

    [DllImport ("crypt32", CharSet = CharSet.Unicode, SetLastError = true)] internal extern static bool CryptAcquireCertificatePrivateKey (IntPtr pCert, uint dwFlags, IntPtr pvReserved, ref IntPtr phCryptProv, ref int pcwrl;

    // cert is X509Certificate2

    CryptAcquireCertificatePrivateKey (cert.Handle, 0, IntPtr.Zero, ref hProvider, ref_keyNumber, ref freeProvider);

  • To restore the private key file name, I am trying to request a unique container name from hProvider as pData, for example:

    [DllImport ("advapi32", CharSet = CharSet.Unicode, SetLastError = true)] internal extern static bool CryptGetProvParam (IntPtr hCryptProv, CryptGetProvParamType dwParam, IntPtr pvData, ref int pcbData, uint;

    IntPtr pData = IntPtr.Zero; CryptGetProvParam (hProvider, PP_UNIQUE_CONTAINER, pData, ref cbBytes, 0));

So far, all of the above steps have worked fine locally (server_name == local machine name); however, the unique name of the container (the name of the private key file) that is returned for the certificate stored in the certificate store of the local computer of the remote computer does not appear as the actual name of the private key file that I see under:

w2k3: \ Documents and Settings \ All Users \ Application Data \ Microsoft \ Crypto \ RSA \ MachineKeys

ws08: \ ProgramData \ Microsoft \ Crypto \ RSA \ MachineKeys

For example, if I run the above steps directly on a remote machine, I get the private key file name AAAAAAA-111111, but if I run them remotely, I get the private key BBBBBBBBB-2222222. Also, if I install the remote certificate locally and follow the steps on my local computer, I get the same BBBBBBBBB-2222222 private key name.

Most likely, I feel that in step 4 I can skip the disclaimer by calling CryptAcquireCertificatePrivateKey. Perhaps this call is based on the local machine identifier to generate the name of a unique container that will be used to store the private key block.

Update

After some further research, I found a blog that describes in detail how file names are created for containers with a private key here .

Instead of using CryptAcquireCertificatePrivateKey, you can use the methods described in this blog to get the name of the private key container on any computer, as soon as you have the name of the container received by CertGetCertificateContextProperty. This code shows how to get the name of the private key container so that you can generate the name of the private key file. * disclaimer - I'm sure this can be changed and may not even be complete, but I am sending it in case it helps someone else in the future *

Structures and P / Challenge:

[StructLayout(LayoutKind.Sequential)] public struct CryptKeyProviderInfo { [MarshalAs(UnmanagedType.LPWStr)] public String pwszContainerName; [MarshalAs(UnmanagedType.LPWStr)] public String pwszProvName; public uint dwProvType; public uint dwFlags; public uint cProvParam; public IntPtr rgProvParam; public uint dwKeySpec; } public const uint CERT_KEY_PROV_INFO_PROP_ID = 0x00000002; [DllImport("crypt32.dll", SetLastError = true)] internal extern static bool CertGetCertificateContextProperty(IntPtr pCertContext, uint dwPropId, IntPtr pvData, ref uint pcbData); IntPtr providerInfo = IntPtr.Zero; string containerName = string.Empty; try { //Win32 call w/IntPtr.Zero will get the size of our Cert_Key_Prov_Info_Prop_ID struct uint pcbProviderInfo = 0; if (!Win32.CertGetCertificateContextProperty(certificate.Handle, Win32.CERT_KEY_PROV_INFO_PROP_ID, IntPtr.Zero, ref pcbProviderInfo)) { //if we can't get the certificate context, return string.empty return string.Empty; } //Allocate heap for Cert_Key_Prov_Info_Prop_ID struct providerInfo = Marshal.AllocHGlobal((int)pcbProviderInfo); //Request actual Cert_Key_Prov_Info_Prop_ID struct with populated data using our allocated heap if (Win32.CertGetCertificateContextProperty(certificate.Handle, Win32.CERT_KEY_PROV_INFO_PROP_ID, providerInfo, ref pcbProviderInfo)) { //Cast returned pointer into managed structure so we can refer to it by it structure layout Win32.CryptKeyProviderInfo keyInfo = (Win32.CryptKeyProviderInfo)Marshal.PtrToStructure(providerInfo, typeof(Win32.CryptKeyProviderInfo)); //Get the container name containerName = keyInfo.pwszContainerName; } //Do clean-up immediately if possible if (providerInfo != IntPtr.Zero) { Marshal.FreeHGlobal(providerInfo); providerInfo = IntPtr.Zero; } } finally { //Do clean-up on finalizer if an exception cause early terminiation of try - after alloc, before cleanup if (providerInfo != IntPtr.Zero) Marshal.FreeHGlobal(providerInfo); } 
+6
source share
1 answer

Using CertGetCertificateContextProperty above, I was able to resolve this issue. Thus, you can determine the name of the certificate private key file on the remote computer using the steps specified in the update.

+5
source

Source: https://habr.com/ru/post/910214/


All Articles