PowerShell导入Pfx和私钥“丢失”

时间:2013-04-17 01:24:18

标签: powershell-v2.0 x509certificate

我使用以下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我将它安装到本地机器的My store。然后我将读取权限授予了我的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中删除它,并手动将同一个PFX文件从证书MMC导入同一个商店并授予相同权限,我的客户端可以毫无问题地调用该服务。

所以它引导我思考,出于某种原因,如果我使用PowerShell,私钥会以某种方式被搞砸。

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

任何线索或经验?

5 个答案:

答案 0 :(得分:4)

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

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();
}

答案 1 :(得分:1)

我更新了谢尔盖对以下内容的回答。请注意,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

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

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

答案 2 :(得分:0)

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

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

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

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

答案 3 :(得分:0)

谢尔盖·阿扎克维奇(Sergey Azarkevich)在下面引用的以下代码是我的诀窍:

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

答案 4 :(得分:0)

因为内置的 Import-PfxCertificate 没有正确导入 CAPI 证书,所以我登陆了这个 SO 线程。不幸的是,powerdude 的回答 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
}