6

我使用以下 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 中也加载了私钥。权限设置相同。

有什么线索或经验吗?

4

5 回答 5

5

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

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();
}
于 2013-05-23T10:59:14.310 回答
2

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

于 2017-04-14T00:40:06.630 回答
0

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

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

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

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

于 2014-04-01T22:39:16.340 回答
0

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

$flags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet
于 2014-04-04T23:33:15.617 回答
0

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

将它与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
}
于 2021-06-02T08:03:00.020 回答