是否可以确定证书的文件名';s在远程Windows服务器上的私钥
本文关键字:Windows 私钥 服务器 证书 文件名 是否 | 更新日期: 2023-09-27 18:27:10
我正在尝试确定存储在远程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,一串格式(@"''{0}{1}",serverName,name);
-
然后,我使用CertEnumCertificatesIStore检索要评估的证书。
[DllImport("CRYPT32",EntryPoint="CertEnumCertificatesInStore",CharSet=CharSet.Unicode,SetLastError=true)]public static extern IntPtr CertEnumCertificatesIStore(IntPtr storeProvider,IntPtr prevCertContext);IntPtr certCtx=IntPtr.Zero;certCtx=CertEnumCertificatesInStore(storeHandle,certCpx);
-
如果证书符合我的条件,我将从CertEnumCertificatesIStore调用返回的IntPtr创建一个X509Certificate2实例,如:
X509Certificate2 current=新的X509Certification2(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.Zero,参考hProvider,ref _keyNumber,ref freeProvider);
-
为了检索私钥文件名,我尝试从hProvider请求唯一容器名作为pData,如:
[DllImport("advapi32",CharSet=CharSet.Unicode,SetLastError=true)]内部外部静态布尔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==本地机器名);然而,为存储在远程计算机的本地计算机证书存储中的证书返回的唯一容器名称(私钥文件名)并没有呈现为我在下面看到的实际私钥文件名:
w2k3:''Documents and Settings''All Users''Application Data''Microsoft''Crypto''RSA''MachineKeys
ws08:''ProgramData''Microsoft''Crypto''RSA''MachineKeys
例如,如果我直接在远程机器上运行上述步骤,我会得到一个私钥文件名AAAAAAA-1111111,但如果我远程运行它们,我会获得一个私钥BBBBBBBB-2222222。此外,如果我在本地安装远程证书并在本地机器上运行这些步骤,我会得到相同的私钥名称BBBBBBB-2222222。
最有可能的是,我觉得在步骤4中可能遗漏了一个警告,即调用CryptAcquireCertificatePrivateKey。此调用可能依赖于本地计算机的标识来生成将用于存储私钥blob的唯一容器的名称。
已更新
经过进一步的研究,我发现了一个博客,详细介绍了私钥容器的文件名是如何在这里创建的。
您可以使用该博客中描述的方法,而不是使用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);
}
使用上面的CertGetCertificateContext属性,我能够解决这个问题。因此,可以使用更新中提到的步骤来确定远程计算机上证书私钥的文件名。