查看智能卡上的所有证书

4
我正在尝试创建一个脚本,以从任何给定的智能卡(在SC读卡器上)中删除除最新证书之外的所有证书。我希望将其分发给终端用户,因此它应该是自给自足的。我的第一个问题是读取智能卡上的证书。我不想影响智能卡上没有的任何证书,因此我寻找了直接从卡片中读取的解决方案,我找到了这个宝石:如何枚举智能卡上的所有证书(PowerShell)。虽然它看起来有些过时,但它似乎可以做到我所需要的。总的来说,它确实有效,但当我到达以下行时,PowerShell ISE会崩溃:
$store = new-object System.Security.Cryptography.X509Certificates.X509Store($hwStore)

我可以创建一个通用的商店,通过从该行中排除($hwStore)来默认使用'My'商店,而不会出现问题,但是指定该商店会导致我的PowerShell ISE崩溃。

以下是该站点提供的函数,我遇到问题的那一行位于底部附近。

function Get-SCUserStore {
[string]$providerName ="Microsoft Base Smart Card Crypto Provider"
# import CrytoAPI from advapi32.dll
$signature = @"
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptGetProvParam(
   IntPtr hProv,
   uint dwParam,
   byte[] pbProvData,
   ref uint pdwProvDataLen, 
   uint dwFlags); 

[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptDestroyKey(
   IntPtr hKey);   

[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptAcquireContext(
   ref IntPtr hProv,
   string pszContainer,
   string pszProvider,
   uint dwProvType,
   long dwFlags);

[DllImport("advapi32.dll", CharSet=CharSet.Auto)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptGetUserKey(
   IntPtr hProv, 
   uint dwKeySpec,
   ref IntPtr phUserKey);

[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptGetKeyParam(
   IntPtr hKey,
   uint dwParam,
   byte[] pbData,
   ref uint pdwDataLen,
   uint dwFlags);

[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return : MarshalAs(UnmanagedType.Bool)]

public static extern bool CryptReleaseContext(
   IntPtr hProv,
   uint dwFlags);
"@

$CryptoAPI = Add-Type -member $signature -name advapiUtils -Namespace CryptoAPI -passthru

# set some constants for CryptoAPI
$AT_KEYEXCHANGE = 1
$AT_SIGNATURE = 2
$PROV_RSA_FULL = 1
$KP_CERTIFICATE = 26
$PP_ENUMCONTAINERS = 2
$PP_CONTAINER = 6
$PP_USER_CERTSTORE = 42
$CRYPT_FIRST = 1
$CRYPT_NEXT = 2
$CRYPT_VERIFYCONTEXT = 0xF0000000

[System.IntPtr]$hProvParent=0
$contextRet = $CryptoAPI::CryptAcquireContext([ref]$hprovParent,$null,$providerName,$PROV_RSA_FULL,$CRYPT_VERIFYCONTEXT)

[Uint32]$pdwProvDataLen = 0
[byte[]]$pbProvData = $null
$GetProvParamRet = $CryptoAPI::CryptGetProvParam($hprovParent,$PP_CONTAINER,$pbProvData,[ref]$pdwProvDataLen,0)

if($pdwProvDataLen -gt 0) 
  {
    $ProvData = new-Object byte[] $pdwProvDataLen
    $GetKeyParamRet = $CryptoAPI::CryptGetProvParam($hprovParent,$PP_CONTAINER,$ProvData,[ref]$pdwProvDataLen,0)
   }

$enc = new-object System.Text.UTF8Encoding($null)
$keyContainer = $enc.GetString($ProvData)

 write-host " The Default User Key Container:" $keyContainer

[Uint32]$pdwProvDataLen = 0
[byte[]]$pbProvData = $null
$GetProvParamRet = $CryptoAPI::CryptGetProvParam($hprovParent,$PP_USER_CERTSTORE,$pbProvData,[ref]$pdwProvDataLen,0)
if($pdwProvDataLen -gt 0) 
  {
    $ProvData = new-Object byte[] $pdwProvDataLen
    $GetKeyParamRet = $CryptoAPI::CryptGetProvParam($hprovParent,$PP_USER_CERTSTORE,$ProvData,[ref]$pdwProvDataLen,0)
    [uint32]$provdataInt = [System.BitConverter]::ToUInt32($provdata,0)
    [System.IntPtr]$hwStore = $provdataInt
   }

 $store = new-object System.Security.Cryptography.X509Certificates.X509Store($hwStore)

# release smart card
$ReleaseContextRet = $CryptoAPI::CryptReleaseContext($hprovParent,0)

return $store
}

我没有使用P/Invoke的经验(我想我说对了),所以我不确定如何排除由此导入的命令问题。

编辑:certutil -scinfo -silent列出的提供程序有:

Microsoft Base Smart Card Crypto Provider
Microsoft Smart Card Key Storage Provider

我已经在以下脚本中尝试了这两种方法,结果是一样的。第二种方法在脚本告诉我默认用户密钥容器是什么时,会给我返回 "�" 字符,所以我觉得它不正确。
我还尝试了 Vesper 建议的 x86 版 PowerShell。应用程序没有崩溃,并且确实返回了包含我的智能卡证书的有效存储。现在的问题是我无法将其发送给用户,因为期望他们能够导航到 x86 版 PowerShell,然后使用它运行脚本,就像期望我的狗给我做华夫饼一样... 我想这可能会发生,但更有可能出现问题,我最终还是要自己搞定。 编辑2:好吧,所以我想我会强制让脚本在 x86 模式下运行。我会发布一个含有更新代码的答案并接受它。如果 @Vesper 发布关于 64/32 位事情的答案(希望能提供更多信息),我会接受他的答案,这样他就可以得到功劳,因为他的评论让我找到了解决方案。

你的智能卡使用哪个加密提供者?另外,由于这个 gem 是旧版本的,请尝试运行 Powershell ISE x86 而不是 x64,因为在 32 位和 64 位版本之间,DLL 签名在参数宽度上有所不同。 - Vesper
4个回答

3

所以,主要问题实际上是您将一个x86 DLL链接到x64 Powershell进程中。您可以通过查询(Get-Process -Id $PID).StartInfo.EnvironmentVariables["PROCESSOR_ARCHITECTURE"]来检查您的Powershell进程是否为x64,就像这里一样,如果检测到x64 Powershell,则手动启动位于$env:windir\syswow64\WindowsPowerShell\v1.0\powershell.exe的Powershell (x86),并使用相同的脚本。要获取脚本的完整名称,请使用$MyInvocation.MyCommand.Definition。如果检测到Powershell为x86,则继续导入类型并运行枚举。以下是一个示例:

$Arch = (Get-Process -Id $PID).StartInfo.EnvironmentVariables["PROCESSOR_ARCHITECTURE"];
$Arch
if ($arch -eq "AMD64") {
    $here=$myinvocation.mycommand.definition
    "$here launched as $arch!"
    start-process C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe -NoNewWindow -ArgumentList $here -wait
    return
}
"now running under x86"

我采用了稍微不同的方法,但最终结果类似。非常感谢你提供的64/32位见解,这从未在我的脑海中出现过。 - TheMadTechnician

1
所以我的解决方案是检查PowerShell会话是否以32位或64位模式运行,如果它以64位模式运行(最有可能的情况),那么它将使用-RunAs32参数开关作为作业运行原始脚本。如果它已经在32位模式下运行,它将在当前会话中简单地调用脚本块。最终用于获取智能卡上证书(作为x509证书存储)的脚本如下:
$RunAs32Bit = {
[string]$providerName ="Microsoft Base Smart Card Crypto Provider"
# import CrytoAPI from advapi32.dll
$signature = @"
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptGetProvParam(
    IntPtr hProv,
    uint dwParam,
    byte[] pbProvData,
    ref uint pdwProvDataLen, 
    uint dwFlags); 

[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptDestroyKey(
    IntPtr hKey);   

[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptAcquireContext(
    ref IntPtr hProv,
    string pszContainer,
    string pszProvider,
    uint dwProvType,
    long dwFlags);

[DllImport("advapi32.dll", CharSet=CharSet.Auto)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptGetUserKey(
    IntPtr hProv, 
    uint dwKeySpec,
    ref IntPtr phUserKey);

[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptGetKeyParam(
    IntPtr hKey,
    uint dwParam,
    byte[] pbData,
    ref uint pdwDataLen,
    uint dwFlags);

[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return : MarshalAs(UnmanagedType.Bool)]

public static extern bool CryptReleaseContext(
    IntPtr hProv,
    uint dwFlags);
"@

$CryptoAPI = Add-Type -member $signature -name advapiUtils -Namespace CryptoAPI -passthru

# set some constants for CryptoAPI
$AT_KEYEXCHANGE = 1
$AT_SIGNATURE = 2
$PROV_RSA_FULL = 1
$KP_CERTIFICATE = 26
$PP_ENUMCONTAINERS = 2
$PP_CONTAINER = 6
$PP_USER_CERTSTORE = 42
$CRYPT_FIRST = 1
$CRYPT_NEXT = 2
$CRYPT_VERIFYCONTEXT = 0xF0000000

[System.IntPtr]$hProvParent=0
$contextRet = $CryptoAPI::CryptAcquireContext([ref]$hprovParent,$null,$providerName,$PROV_RSA_FULL,$CRYPT_VERIFYCONTEXT)

[Uint32]$pdwProvDataLen = 0
[byte[]]$pbProvData = $null
$GetProvParamRet = $CryptoAPI::CryptGetProvParam($hprovParent,$PP_CONTAINER,$pbProvData,[ref]$pdwProvDataLen,0)

if($pdwProvDataLen -gt 0) 
    {
    $ProvData = new-Object byte[] $pdwProvDataLen
    $GetKeyParamRet = $CryptoAPI::CryptGetProvParam($hprovParent,$PP_CONTAINER,$ProvData,[ref]$pdwProvDataLen,0)
    }

$enc = new-object System.Text.UTF8Encoding($null)
$keyContainer = $enc.GetString($ProvData)

    write-host " The Default User Key Container:" $keyContainer

[Uint32]$pdwProvDataLen = 0
[byte[]]$pbProvData = $null
$GetProvParamRet = $CryptoAPI::CryptGetProvParam($hprovParent,$PP_USER_CERTSTORE,$pbProvData,[ref]$pdwProvDataLen,0)
if($pdwProvDataLen -gt 0) 
    {
    $ProvData = new-Object byte[] $pdwProvDataLen
    $GetKeyParamRet = $CryptoAPI::CryptGetProvParam($hprovParent,$PP_USER_CERTSTORE,$ProvData,[ref]$pdwProvDataLen,0)
    [uint32]$provdataInt = [System.BitConverter]::ToUInt32($provdata,0)
    [System.IntPtr]$hwStore = $provdataInt
    }

    $store = new-object System.Security.Cryptography.X509Certificates.X509Store($hwStore)

# release smart card
$ReleaseContextRet = $CryptoAPI::CryptReleaseContext($hprovParent,0)

return $store
}

#Run the code in 32bit mode if PowerShell isn't already running in 32bit mode
If($env:PROCESSOR_ARCHITECTURE -ne "x86"){
    Write-Warning "Non-32bit architecture detected, collecting certificate information in separate 32bit process."
    $Job = Start-Job $RunAs32Bit -RunAs32
    $SCStore = $Job | Wait-Job | Receive-Job
}Else{
    $SCStore = $RunAs32Bit.Invoke()
}

1
我一直在试图解决同样的问题,并提供了以下代码。这与您所拥有的完全相同,只是增加了一些处理64位环境的内容。这应该可以满足您的需求,而无需将PowerShell重新启动为32位进程。
function Get-SCUserStore {
    [CmdletBinding()]
    param(
          [string]$providerName ="Microsoft Base Smart Card Crypto Provider"
        )
    # import CrytoAPI from advapi32.dll
    $signature = @"
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptGetProvParam(
    IntPtr hProv,
    uint dwParam,
    byte[] pbProvData,
    ref uint pdwProvDataLen, 
    uint dwFlags); 

[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptDestroyKey(
    IntPtr hKey);   

[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptAcquireContext(
    ref IntPtr hProv,
    string pszContainer,
    string pszProvider,
    uint dwProvType,
    long dwFlags);

[DllImport("advapi32.dll", CharSet=CharSet.Auto)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptGetUserKey(
    IntPtr hProv, 
    uint dwKeySpec,
    ref IntPtr phUserKey);

[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptGetKeyParam(
    IntPtr hKey,
    uint dwParam,
    byte[] pbData,
    ref uint pdwDataLen,
    uint dwFlags);

[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptReleaseContext(
    IntPtr hProv,
    uint dwFlags);
"@

    $CryptoAPI = Add-Type -member $signature -name advapiUtils -Namespace CryptoAPI -passthru

    # set some constants for CryptoAPI
    $AT_KEYEXCHANGE = 1
    $AT_SIGNATURE = 2
    $PROV_RSA_FULL = 1
    $KP_CERTIFICATE = 26
    $PP_ENUMCONTAINERS = 2
    $PP_CONTAINER = 6
    $PP_USER_CERTSTORE = 42
    $CRYPT_FIRST = 1
    $CRYPT_NEXT = 2
    $CRYPT_VERIFYCONTEXT = 0xF0000000


    [System.IntPtr]$hProvParent=0

    if([Environment]::Is64BitProcess) {
        [Uint64]$pdwProvDataLen = 0
    } else {
        [Uint32]$pdwProvDataLen = 0    
    }
    $contextRet = $CryptoAPI::CryptAcquireContext([ref]$hprovParent,$null,$providerName,$PROV_RSA_FULL,$CRYPT_VERIFYCONTEXT)

    [byte[]]$pbProvData = $null
    $GetProvParamRet = $CryptoAPI::CryptGetProvParam($hprovParent,$PP_CONTAINER,$pbProvData,[ref]$pdwProvDataLen,0)

    if($pdwProvDataLen -gt 0) 
    {
        $ProvData = new-Object byte[] $pdwProvDataLen
        $GetKeyParamRet = $CryptoAPI::CryptGetProvParam($hprovParent,$PP_CONTAINER,$ProvData,[ref]$pdwProvDataLen,0)
    }

    $enc = new-object System.Text.UTF8Encoding($null)
    $keyContainer = $enc.GetString($ProvData)

    Write-Verbose ("The Default User Key Container:{0}" -f $keyContainer)

    if([Environment]::Is64BitProcess) {
        [Uint64]$pdwProvDataLen = 0
    } else {
        [Uint32]$pdwProvDataLen = 0
    }

    [byte[]]$pbProvData = $null
    $GetProvParamRet = $CryptoAPI::CryptGetProvParam($hprovParent,$PP_USER_CERTSTORE,$pbProvData,[ref]$pdwProvDataLen,0)

    if($pdwProvDataLen -gt 0) 
    {
        $ProvData = new-Object byte[] $pdwProvDataLen
        $GetKeyParamRet = $CryptoAPI::CryptGetProvParam($hprovParent,$PP_USER_CERTSTORE,$ProvData,[ref]$pdwProvDataLen,0)

        if([Environment]::Is64BitProcess) {
            [UInt64]$provdataInt = [System.BitConverter]::ToUInt64($provdata,0)
            [System.IntPtr]$hwStore = [Long]$provdataInt
        } else {
            [UInt32]$provdataInt = [System.BitConverter]::ToUInt32($provdata,0)
            [System.IntPtr]$hwStore = $provdataInt
        }
    }

    $store = new-object System.Security.Cryptography.X509Certificates.X509Store($hwStore)

    # release smart card
    $ReleaseContextRet = $CryptoAPI::CryptReleaseContext($hprovParent,0)

    return $store
}

write-host ((get-WmiObject win32_PnPSignedDriver|where{$_.deviceID -like "*smartcard*"}).devicename) "reports the following certificates;" 

# returns System.Security.Cryptography.X509Certificates.X509Store object representing PP_USER_CERTSTORE on Smart Card
$SCcertStore = Get-SCuserSTore

# enumerate certificates
$SCcertStore.certificates

0
下面提供了一个完整的示例,将mstest覆盖文件转换为xml文件。 该示例包括传递参数和识别当前脚本位置的方法。
<#
.SYNOPSIS
    Script to convert code coverage report into xml format that can then be published by external tools.

.DESCRIPTION
    Covering code coverage staistics as part of quality improvement initiatives .

    https://dev59.com/s4rda4cB1Zd3GeqPRM6I
#>
Param(
    [String] $InputCoveragePath =@("..\GeneratedFiles\Docs\Reports"),
    [String] $OutputCoverageFileExtension =@(".coveragexml"),
    [String] $CoverageAnalysisAssembly =@("Microsoft.VisualStudio.Coverage.Analysis.dll"),
    [String[]] $ExecutablePaths =@(""),
    [String[]] $SymbolPaths =@("")
)
    $ScriptLocation = Split-Path $script:MyInvocation.MyCommand.Path -Parent
    Write-Host $ScriptLocation
<#
    if(!(Test-Path "$OutputCoverageFile")){
        Write-Host "Creating empty coveragle file $OutputCoverageFile"
        New-Item "$OutputCoverageFile" -ItemType "file" 
    }
#>

$RunAs32Bit = {
    Param(
        [String] $InputCoveragePath =@("..\GeneratedFiles\Docs\Reports"),
        [String] $OutputCoverageFileExtension =@(".coveragexml"),
        [String] $CoverageAnalysisAssembly =@("Microsoft.VisualStudio.Coverage.Analysis.dll"),
        [String[]] $ExecutablePaths =@(""),
        [String[]] $SymbolPaths =@(""),
        [String] $ScriptLocation =@(".")
    )
    Write-Host "[CoverageConverter][Begin]: Coverage conversion started..."

    Write-Host "[CoverageConverter][InputCoveragePath]: $InputCoveragePath"
    Write-Host "[CoverageConverter][OutputCoverageFileExtension]: $OutputCoverageFileExtension"
    Write-Host "[CoverageConverter][CoverageAnalysisAssembly]: $CoverageAnalysisAssembly"
    Write-Host "[CoverageConverter][ExecutablePaths]: $ExecutablePaths"
    Write-Host "[CoverageConverter][SymbolPaths]: $SymbolPaths"
    Write-Host "[CoverageConverter][ScriptLocation]: $ScriptLocation"
    
    Import-Module -Force -Name (Join-Path "$ScriptLocation" "Utilities.psm1")
    Add-Type -path "$CoverageAnalysisAssembly"

    $Result = 0
    if($InputCoveragePath -and (Test-Path "$InputCoveragePath") )
    {
        [string[]] $coverageFiles = $(Get-ChildItem -Path $InputCoveragePath -Recurse -Include *coverage)
        
        @($coverageFiles) | ForEach-Object {
            $coverageFile = $_
            $coverageFileOut = (Join-Path -Path $(Split-Path $_ -Parent) -ChildPath  ($(Get-Item $_).BaseName + "$OutputCoverageFileExtension"))

            Write-Host "If all OK the xml will be written to: $coverageFileOut"

            $info = [Microsoft.VisualStudio.Coverage.Analysis.CoverageInfo]::CreateFromFile($coverageFile, $ExecutablePaths, $SymbolPaths);
            if($info){
                $data = $info.BuildDataSet()
                $data.WriteXml($coverageFileOut)
            }
        }
    }
    else
    {
        Write-Host "Please specify a valid input coverage file."
        $Result = 1
    }

    Write-Host "[CoverageConverter][End]: Coverage conversion completed with result $Result"
    return $Result  
}

#Run the code in 32bit mode if PowerShell isn't already running in 32bit mode
If($env:PROCESSOR_ARCHITECTURE -ne "x86"){
    Write-Warning "Non-32bit architecture detected, processing original request in separate 32bit process."
    $Job = Start-Job $RunAs32Bit -RunAs32 -ArgumentList ($InputCoveragePath, $OutputCoverageFileExtension, $CoverageAnalysisAssembly, $ExecutablePaths, $SymbolPaths, $ScriptLocation)
    $Result = $Job | Wait-Job | Receive-Job
}Else{
    $Result = Invoke-Command -ScriptBlock $RunAs32Bit -ArgumentList ($InputCoveragePath, $OutputCoverageFileExtension, $CoverageAnalysisAssembly, $ExecutablePaths, $SymbolPaths, $ScriptLocation)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接