如何在PowerShell中用非托管函数获取签名文件的完整证书链?
获取文件的完整证书链(使用CertGetCertificateChain非托管函数)
问题背景
我有一个文件,其证书路径包含4个证书,但用常规PowerShell代码只能得到3个证书的最短链,文件资源管理器里能看到完整的4个证书链;还有其他文件有5个证书,但PowerShell里只能看到3个,因为返回的是最短链。常规代码如下:
$FilePath = '.\NordPassSetup_x86.exe' # 从文件路径获取证书 $Cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $FilePath # 构建证书链 $Chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain [void]$Chain.Build($Cert) $Chain.ChainElements.count foreach ($Element in $Chain.ChainElements) { $Element.Certificate | ft -AutoSize }
解决方案:调用CertGetCertificateChain非托管函数
要获取完整的证书链,需要直接调用Windows的CertGetCertificateChain非托管API,以下是PowerShell实现步骤:
1. 定义API结构体和函数
通过Add-Type导入非托管代码,定义API所需的结构体和函数签名:
Add-Type @" using System; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; public class CertChainHelper { [StructLayout(LayoutKind.Sequential)] public struct CERT_CHAIN_PARA { public uint cbSize; public IntPtr RequestedUsage; } [DllImport("crypt32.dll", SetLastError = true)] public static extern bool CertGetCertificateChain( IntPtr hChainEngine, IntPtr pCertContext, IntPtr pTime, IntPtr hAdditionalStore, ref CERT_CHAIN_PARA pChainPara, uint dwFlags, IntPtr pvReserved, out IntPtr ppChainContext ); [DllImport("crypt32.dll", SetLastError = true)] public static extern void CertFreeCertificateChain(IntPtr pChainContext); [StructLayout(LayoutKind.Sequential)] public struct CERT_CHAIN_CONTEXT { public uint cbSize; public IntPtr pCertChain; } [StructLayout(LayoutKind.Sequential)] public struct CERT_SIMPLE_CHAIN { public uint cbSize; public IntPtr pCertContext; public uint TrustStatus; public IntPtr pTrustListInfo; public IntPtr pRevocationInfo; public IntPtr pIssuanceUsage; public IntPtr pApplicationUsage; public uint cElement; public IntPtr rgpElement; } [StructLayout(LayoutKind.Sequential)] public struct CERT_CHAIN_ELEMENT { public uint cbSize; public IntPtr pCertContext; } } "@
2. 编写完整链获取函数
实现PowerShell函数,加载文件证书并调用API解析完整链:
function Get-FullCertificateChain { param( [Parameter(Mandatory=$true)] [string]$FilePath ) # 加载文件证书 $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $FilePath $certContext = $cert.Handle # 初始化链参数 $chainPara = New-Object CertChainHelper+CERT_CHAIN_PARA $chainPara.cbSize = [System.Runtime.InteropServices.Marshal]::SizeOf($chainPara) # 调用CertGetCertificateChain $chainContextPtr = [IntPtr]::Zero $success = [CertChainHelper]::CertGetCertificateChain( [IntPtr]::Zero, # 使用默认链引擎 $certContext, [IntPtr]::Zero, # 使用当前时间 [IntPtr]::Zero, # 无额外存储 [ref]$chainPara, 0x00000001, # 排除根证书的吊销检查,可按需调整 [IntPtr]::Zero, [ref]$chainContextPtr ) if (-not $success) { throw "调用CertGetCertificateChain失败,错误码: $([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())" } try { # 解析链上下文 $chainContext = [System.Runtime.InteropServices.Marshal]::PtrToStructure( $chainContextPtr, [type][CertChainHelper+CERT_CHAIN_CONTEXT] ) # 获取第一个简单链(通常仅存在一个) $simpleChainPtr = [System.Runtime.InteropServices.Marshal]::ReadIntPtr($chainContext.pCertChain) $simpleChain = [System.Runtime.InteropServices.Marshal]::PtrToStructure( $simpleChainPtr, [type][CertChainHelper+CERT_SIMPLE_CHAIN] ) # 遍历所有链元素 $chainElements = @() for ($i=0; $i -lt $simpleChain.cElement; $i++) { $elementPtr = [System.Runtime.InteropServices.Marshal]::ReadIntPtr($simpleChain.rgpElement, $i * [IntPtr]::Size) $chainElement = [System.Runtime.InteropServices.Marshal]::PtrToStructure( $elementPtr, [type][CertChainHelper+CERT_CHAIN_ELEMENT] ) # 将非托管证书上下文转为X509Certificate2对象 $elementCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($chainElement.pCertContext) $chainElements += $elementCert } return $chainElements } finally { # 释放资源 if ($chainContextPtr -ne [IntPtr]::Zero) { [CertChainHelper]::CertFreeCertificateChain($chainContextPtr) } } }
3. 使用函数获取完整链
调用上述函数即可获取文件的完整证书链:
$fullChain = Get-FullCertificateChain -FilePath '.\NordPassSetup_x86.exe' Write-Host "完整证书链长度: $($fullChain.Count)" $fullChain | Format-Table -AutoSize Subject, Issuer, Thumbprint
说明
CertGetCertificateChain会构建包含所有中间证书的完整链,不会像X509Chain.Build()那样返回最短信任链(可能跳过系统信任存储中已存在的根/中间证书)。- 代码中的
dwFlags参数可根据需求调整,例如禁用吊销检查可使用0x00000010(CERT_CHAIN_REVOCATION_CHECK_NONE)。
内容的提问来源于stack exchange,提问作者SpyNet
相关产品推荐
相关产品推荐

