是否可以在远程 Windows 服务器上确定证书私钥的文件名?

Posted

技术标签:

【中文标题】是否可以在远程 Windows 服务器上确定证书私钥的文件名?【英文标题】:Is it possible to determine the file name of a certificate's private key on a remote Windows server? 【发布时间】:2012-03-25 12:35:22 【问题描述】:

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

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

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

    我使用来自 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(@"\01", serverName, name));

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

    [DllImport("CRYPT32", EntryPoint = "CertEnumCertificatesInStore", CharSet = CharSet.Unicode, SetLastError = true)] 公共静态外部 IntPtr CertEnumCertificatesInStore(IntPtr storeProvider, IntPtr prevCertContext); IntPtr certCtx = IntPtr.Zero; certCtx = CertEnumCertificatesInStore(storeHandle, certCtx);

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

    X509Certificate2 当前 = 新 X509Certificate2(certCtx);

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

    [DllImport("crypt32", CharSet = CharSet.Unicode, SetLastError = true)] 内部外部静态 bool CryptAcquireCertificatePrivateKey(IntPtr pCert, uint dwFlags, IntPtr pvReserved, ref IntPtr phCryptProv, ref int pdwKeySpec, ref bool pfCallerFreeProv);

    //cert 是一个 X509Certificate2

    CryptAcquireCertificatePrivateKey(cert.Handle, 0, IntPtr. 零, 参考 hProvider, 参考 _keyNumber, 参考免费提供者);

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

    [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)] 内部外部静态 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 的唯一容器的名称。

更新

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

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

结构和 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);

【问题讨论】:

您可能希望从答案部分创建一个答案并接受它,以便将问题标记为已解决。 谢谢@ivan_pozdeev,我在下面添加了我的更新作为答案。 【参考方案1】:

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

【讨论】:

以上是关于是否可以在远程 Windows 服务器上确定证书私钥的文件名?的主要内容,如果未能解决你的问题,请参考以下文章

在 Windows 上使用私钥导出 SSL 证书

何时在SSL证书申请过程中生成私钥?

Windows Server 2008中怎么安装证书服务

windows系统-PKI证书服务器

在 Windows 上不使用 OpenSSL 从 pfx 文件或证书存储中提取私钥

保护你的网站数字证书私钥