【问题标题】:View All Certificates On Smart Card查看智能卡上的所有证书
【发布时间】:2015-09-02 01:14:12
【问题描述】:

我正在尝试创建一个脚本,以从任何给定的智能卡中删除除最新证书之外的所有证书(当时在 SC 阅读器中)。这是我打算能够分发给最终用户的东西,所以它应该是自给自足的。我的第一个问题是阅读卡上的证书。我不想影响任何不在智能卡上的证书,所以我寻找直接从卡中读取的解决方案,我发现了这个 gem:

How to enumerate all certificates on a smart card (PowerShell)

它很旧,但它看起来应该可以满足我的需要。它似乎确实可以正常工作,但是当我到达线路时 PowerShell ISE 崩溃:

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

我可以通过从该行中排除 ($hwStore) 来创建一个默认为“我的”存储的通用存储,但指定该存储可靠地使我的 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。该应用程序不会崩溃,它会返回一个有效的存储,上面有我的智能卡证书。现在的问题是我无法将其发送给用户,因为期望他们能够导航到 PowerShell 的 x86 版本,然后使用它运行脚本就像期望我的狗给我做华夫饼......我想它可能发生,但很可能会出现问题,而且我最终还是不得不自己做。

Edit2: 好的,所以我想我会强制这部分脚本在 x86 模式下运行。我将用我更新的代码发布答案并接受它。如果@Vesper 发布关于 64/32 位问题的答案(希望有更多信息),我会接受他的答案,以便他得到信任,因为他的评论是导致我找到解决方案的原因。

【问题讨论】:

  • 您的智能卡使用什么加密提供商?此外,由于这个 gem 很旧,请尝试运行 Powershell ISE x86 而不是 x64,因为 32 位和 64 位版本之间的 DLL 签名在参数宽度方面是不同的。

标签: powershell x509certificate


【解决方案1】:

所以,主要问题实际上是您将 x86 DLL 链接到 x64 Powershell 进程。您可以检查您的 Powershell 进程是否为 x64 like here(通过查询 (Get-Process -Id $PID).StartInfo.EnvironmentVariables["PROCESSOR_ARCHITECTURE"]),如果检测到 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 位的洞察力,我从未想过它。
【解决方案2】:

所以我的解决方案是检查 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()
}

【讨论】:

    【解决方案3】:

    我一直在尝试解决同样的问题,并提出了以下代码。这正是您所拥有的,并添加了一些处理 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
    

    【讨论】:

      【解决方案4】:

      下面提供了一个将 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://stackoverflow.com/questions/30215324/vstest-code-coverage-report-in-jenkins
      #>
      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)
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-02-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-11
        相关资源
        最近更新 更多