我正在尝试确定存储在远程Windows(2k3 / 2k8)计算机上的证书私钥的文件名,并且遇到了一些困难。我也不熟悉微软的CryptAPI,所以我正在寻找你能提供的任何帮助。
此练习的目的是查找在远程计算机上安装了符合特定条件的私钥的证书,并确保为其私钥文件分配了正确的权限。虽然我可以在文件夹级别分配权限,但我更愿意仅在必要时在私钥文件级别分配权限(出于显而易见的原因)。
以下是该方案,假设具有类似管理权限的服务帐户正在访问证书存储区:
我使用p / invoke使用C#中的以下调用来检索远程证书存储:
[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));
然后我使用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);
如果证书符合我的条件,我将从CertEnumCertificatesInStore调用返回的IntPtr创建一个X509Certificate2实例,如:
X509Certificate2 current = new X509Certificate2(certCtx);
一旦我拥有了我感兴趣的证书的X509Certificate2实例,我就致电CryptAcquireCertificatePrivateKey获取私钥提供者:
[DllImport(“crypt32”,CharSet = CharSet.Unicode,SetLastError = true)] 内部extern静态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);
要检索私钥文件名,我尝试从hProvider请求唯一容器名称为pData,如:
[DllImport(“advapi32”,CharSet = CharSet.Unicode,SetLastError = true)] 内部extern静态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));
到目前为止,上述所有步骤在本地都很有效(servername == local machine name);但是,为存储在远程计算机的本地计算机证书存储中的证书返回的唯一容器名称(私钥文件名)不会呈现为我在下面看到的实际私钥文件名:
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。
您可以使用该博客中描述的方法,在获得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);
}
答案 0 :(得分:5)
使用上面的CertGetCertificateContextProperty,我能够解决这个问题。因此,可以使用更新中提到的步骤确定远程计算机上证书私钥的文件名。