8

我正在尝试确定存储在远程 Windows (2k3/2k8) 机器上的证书私钥的文件名,但遇到了一些困难。我对微软的 CryptAPI 也不是很熟悉,所以我正在寻找你能提供的任何帮助。

本练习的目的是查找安装在远程计算机上且满足特定条件的带有私钥的证书,并确保为其私钥文件分配正确的权限。虽然我可以在文件夹级别分配权限,但我更愿意只在必要的私钥文件级别分配权限(出于显而易见的原因)。

这是场景,假设具有类似管理权限的服务帐户正在访问证书存储:

  1. 我使用来自 C# 的以下调用使用 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}", serverName, name));

  2. 然后我使用 CertEnumCertificatesInStore 来检索我想要评估的证书。

    [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);

  3. 如果证书符合我的条件,我会从 CertEnumCertificatesInStore 调用返回的 IntPtr 创建一个 X509Certificate2 实例,例如:

    X509Certificate2 当前 = 新 X509Certificate2(certCtx);

  4. 一旦我有了我感兴趣的证书的 X509Certificate2 实例,我就会调用CryptAcquireCertificatePrivateKey来获取私钥提供程序:

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

    //cert 是一个 X509Certificate2

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

  5. 为了检索私钥文件名,我尝试从 hProvider 请求唯一容器名称作为 pData,例如:

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

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

到目前为止,上述所有步骤都在本地运行良好(服务器名 == 本地机器名);但是,为存储在远程计算机的本地计算机证书存储中的证书返回的唯一容器名称(私钥文件名)不会呈现为我在下面看到的实际私钥文件名:

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

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

例如,如果我直接在远程机器上运行上述步骤,我会得到一个私钥文件名 AAAAAAA-111111,但如果我远程运行它们,我会得到一个私钥 BBBBBBBB-2222222。此外,如果我在本地安装远程证书并在本地计算机上运行这些步骤,我会得到相同的私钥名称 BBBBBBBB-2222222。

很可能我觉得我可能在第 4 步中遗漏了一个警告,即调用 CryptAcquireCertificatePrivateKey。可能是此调用依赖于本地机器的身份来生成将用于存储私钥 blob 的唯一容器的名称。

更新

经过一些进一步的研究,我发现了一个博客,其中详细说明了如何在此处创建私钥容器的文件名。

一旦您拥有由 CertGetCertificateContextProperty 获得的容器名称,您可以使用该博客中描述的方法在任何机器上获取私钥容器名称,而不是使用 CryptAcquireCertificatePrivateKey。此处的代码显示了如何获取私钥容器名称,以便您可以生成私钥文件名。* 免责声明 - 我很确定这可能会发生变化,甚至可能不完整,但我会发布它以防将来对其他人有所帮助*

结构和 P/Invoke:

[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's 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);
}
4

1 回答 1

6

使用上面的 CertGetCertificateContextProperty,我能够解决这个问题。因此,可以使用更新中提到的步骤确定远程计算机上证书私钥的文件名。

于 2012-03-12T22:45:11.817 回答