【问题标题】:PowerShell Import Pfx, and Private Key "Lost"PowerShell 导入 Pfx 和私钥“丢失”
【发布时间】:2013-04-17 01:24:18
【问题描述】:

我使用以下 PowerShell 函数将 PFX 导入到我的 Windows 2008 R2 服务器的证书存储中

function Import-PfxCertificate ([String]$certPath,[String]$certificateStoreLocation = "CurrentUser",[String]$certificateStoreName = "My",$pfxPassword = $null)
{
    $pfx = new-object System.Security.Cryptography.X509Certificates.X509Certificate2    

    $pfx.Import($certPath, $pfxPassword, "Exportable,PersistKeySet")    

    $store = new-object System.Security.Cryptography.X509Certificates.X509Store($certificateStoreName,$certificateStoreLocation)    
    $store.open("MaxAllowed")    
    $store.add($pfx)    
    $store.close()
    return $pfx
}

函数的调用者看起来像$importedPfxCert = Import-PfxCertificate $pfxFile "LocalMachine" "My" $password 我将它安装到本地机器的我的商店。然后我授予对我的 IIS 应用程序池的读取权限。

我有一个 WCF 服务需要使用它

<behaviors>
  <serviceBehaviors>
    <behavior>
      <serviceCredentials>
        <serviceCertificate findValue="MyCertName" x509FindType="FindBySubjectName" />
        <userNameAuthentication userNamePasswordValidationMode="Custom"
          customUserNamePasswordValidatorType="MyValidator" />
      </serviceCredentials>
    </behavior>
  </serviceBehaviors>
</behaviors>

当我使用客户端调用服务时,我收到了来自 WCF It is likely that certificate 'CN=MyCertName' may not have a private key that is capable of key exchange or the process may not have access rights for the private key. 的异常

如果我将其从 MMC 中删除,并从证书 MMC 手动导入相同的 PFX 文件到相同的存储并授予相同的权限,我的客户可以毫无问题地调用该服务。

所以这让我想到,出于某种原因,如果我使用 PowerShell,私钥会以某种方式被搞砸。

有趣的是,无论哪种方式,我都去 MMC 并双击我安装的证书,我可以看到You have a private key that corresponds to the certificate.,所以看起来即使在 PowerShell 中也加载了私钥。权限设置相同。

有什么线索或经验吗?

【问题讨论】:

    标签: powershell-2.0 x509certificate


    【解决方案1】:

    有同样的问题。下一个脚本工作:

    function InstallCert ($certPath, [System.Security.Cryptography.X509Certificates.StoreName] $storeName)
    {
        [Reflection.Assembly]::Load("System.Security, Version=2.0.0.0, Culture=Neutral, PublicKeyToken=b03f5f7f11d50a3a")
    
        $flags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet
    
        $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($certPath, "", $flags)
    
        $store = New-Object System.Security.Cryptography.X509Certificates.X509Store($storeName, [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine)
    
        $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite);
    
        $store.Add($cert);
    
        $store.Close();
    }
    

    【讨论】:

    • 对于任何试图解析实际修复的东西的人来说,MachineKeySet 标志可确保私钥存储在机器级别而不是当前用户级别。
    • 我能够使用此功能将 cert+key p12 文件导入“我的”商店,但无法让它接受“远程桌面”作为商店名称。我最终从函数签名中删除了类型定义 System.Security.Cryptography.X509Certificates.StoreName 并且它起作用了。我知道“远程桌面”是有效的,因为dir 'Cert:\localmachine\Remote Desktop'
    • 也就是说,把第一行改成function InstallCert ($certPath, $storeName)
    【解决方案2】:

    我更新了 Sergey 对以下内容的回答。请注意,using namespace ... 语法仅对 PS 5.0 及更高版本有效。如果您在早期版本中需要此功能,则必须根据需要添加完整的命名空间 System.Security.Cryptography.X509Certificates

    using namespace System.Security
    
    [CmdletBinding()]
    param (
        [parameter(mandatory=$true)] [string] $CertificateFile,
        [parameter(mandatory=$true)] [securestring] $PrivateKeyPassword,
        [parameter(mandatory=$true)] [string] $AllowedUsername
    )
    
    # Setup certificate
    $Flags = [Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet `
        -bor [Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet `
        -bor [Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable
    $Certificate = New-Object Cryptography.X509Certificates.X509Certificate2($CertificateFile, $PrivateKeyPassword, $Flags)
    
    # Install certificate into machine store
    $Store = New-Object Cryptography.X509Certificates.X509Store(
        [Cryptography.X509Certificates.StoreName]::My, 
        [Cryptography.X509Certificates.StoreLocation]::LocalMachine)
    $Store.Open([Cryptography.X509Certificates.OpenFlags]::ReadWrite)
    $Store.Add($Certificate)
    $Store.Close()
    
    # Allow read permission of private key by user
    $PKFile = Get-ChildItem "$env:ProgramData\Microsoft\Crypto\RSA\MachineKeys\$($Certificate.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName)"
    $PKAcl = $PKFile.GetAccessControl("Access")
    $ReadAccessRule = New-Object AccessControl.FileSystemAccessRule(
        $AllowedUsername,
        [AccessControl.FileSystemRights]::Read,
        [AccessControl.AccessControlType]::Allow
    )
    $PKAcl.AddAccessRule($ReadAccessRule)
    Set-Acl $PKFile.FullName $PKAcl
    

    将此脚本保存到InstallCertificate.ps1,然后以管理员身份运行:

    PS C:\Users\me> .\InstallCertificate.ps1
    
    cmdlet InstallCertificate.ps1 at command pipeline position 1
    Supply values for the following parameters:
    CertificateFile: c:\my\path\mycert.pfx
    PrivateKeyPassword: *********************
    AllowedUsername: me
    PS C:\Users\me> ls Cert:\LocalMachine\My
    <Observe that your cert is now listed here.  Get the thumbprint>
    PS C:\Users\me> (ls Cert:\LocalMachine\My | ? { $_.Thumbprint -eq $Thumbprint }).PrivateKey
    

    重启后,最后一行应该显示私钥仍然安装,即使是非管理员。

    已编辑以添加 ACL 步骤,如 https://stackoverflow.com/a/37402173/7864889 中所述。

    【讨论】:

      【解决方案3】:

      通过 MMC 导入证书时,我在我们的一个开发服务器上遇到了类似的问题。我的问题是管理员组对 MachineKeys 文件夹没有任何权限。

      C:\Users\All Users\Microsoft\Crypto\RSA\MachineKeys

      我向管理员添加了对 MachineKeys 文件夹的完全控制权,它能够在导入证书时成功创建私钥。

      确保运行 Powershell 的用户有权写入 MachineKeys 文件夹。

      【讨论】:

        【解决方案4】:

        下面由 Sergey Azarkevich 引用的代码对我有用:

        $flags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet
        

        【讨论】:

          【解决方案5】:

          由于内置的​​ Import-PfxCertificate 没有正确导入 CAPI 证书,我进入了这个 SO 线程。不幸的是,powerdude's answer cmdlet 对我不起作用,因为它在设置权限时抛出了The property 'CspKeyContainerInfo' cannot be found on this object. Verify that the property exists.

          在将它与这个美妙的gist from milesgratz 结合后,它起作用了。

          这是最终修改后的版本,看起来像 Import-PfxCertificate 替换。

          # Import-CapiPfxCertificate.ps1
          # for CNG certificates use built in Import-PfxCertificate
          
          using namespace System.Security
          
          [CmdletBinding()]
          param (
              [parameter(mandatory=$true)] [string] $FilePath,
              [parameter(mandatory=$true)] [securestring] $Password,
              [parameter(mandatory=$true)] [string] $CertStoreLocation,
              [parameter(mandatory=$false)] [string] $AllowedUsername
          )
          
          if (-not ($CertStoreLocation -match '^Cert:\\([A-Z]+)\\([A-Z]+)$')) {
              Write-Host "Incorrect CertStoreLocation. See usage in the Import-PfxCertificate documentation" -ForegroundColor Red
              exit 1;
          }
          
          $StoreName = $Matches.2
          $StoreLocation = $Matches.1
          
          # Setup certificate
          $Flags = [Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet `
              -bor [Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet `
              -bor [Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable
          $Certificate = New-Object Cryptography.X509Certificates.X509Certificate2($FilePath, $Password, $Flags)
          
          # Install certificate into the specified store
          $Store = New-Object Cryptography.X509Certificates.X509Store(
              $StoreName, 
              $StoreLocation)
          $Store.Open([Cryptography.X509Certificates.OpenFlags]::ReadWrite)
          $Store.Add($Certificate)
          $Store.Close()
          
          if (-not ([string]::IsNullOrEmpty($AllowedUsername))) {
              # Allow read permission of private key by user
              $PKUniqueName = ([System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($Certificate)).key.UniqueName
              $PKFile = Get-Item "$env:ProgramData\Microsoft\Crypto\RSA\MachineKeys\$PKUniqueName"
              $PKAcl = Get-Acl $PKFile
              $PKAcl.AddAccessRule((New-Object AccessControl.FileSystemAccessRule($AllowedUsername, "Read", "Allow")))
              Set-Acl $PKFile.FullName $PKAcl
          }
          

          【讨论】:

            猜你喜欢
            • 2013-04-22
            • 2018-10-12
            • 1970-01-01
            • 1970-01-01
            • 2018-05-20
            • 2019-01-02
            • 2015-01-20
            • 2020-06-25
            • 1970-01-01
            相关资源
            最近更新 更多