如何使用PowerShell向用户授予证书私钥权限?

36
证书已安装在机器上。现在我想授予应用程序用户对证书的私钥的读取权限。
6个回答

45

这里是答案。

创建了一个名为AddUserToCertificate.ps1的PowerShell脚本文件。

以下是脚本文件的内容。

param(
    [string]$userName,
    [string]$permission,
    [string]$certStoreLocation,
    [string]$certThumbprint
);
# check if certificate is already installed
$certificateInstalled = Get-ChildItem cert:$certStoreLocation | Where thumbprint -eq $certThumbprint

# download & install only if certificate is not already installed on machine
if ($certificateInstalled -eq $null)
{
    $message="Certificate with thumbprint:"+$certThumbprint+" does not exist at "+$certStoreLocation
    Write-Host $message -ForegroundColor Red
    exit 1;
}else
{
    try
    {
        $rule = new-object security.accesscontrol.filesystemaccessrule $userName, $permission, allow
        $root = "c:\programdata\microsoft\crypto\rsa\machinekeys"
        $l = ls Cert:$certStoreLocation
        $l = $l |? {$_.thumbprint -like $certThumbprint}
        $l |%{
            $keyname = $_.privatekey.cspkeycontainerinfo.uniquekeycontainername
            $p = [io.path]::combine($root, $keyname)
            if ([io.file]::exists($p))
            {
                $acl = get-acl -path $p
                $acl.addaccessrule($rule)
                echo $p
                set-acl $p $acl
            }
        }
    }
    catch 
    {
        Write-Host "Caught an exception:" -ForegroundColor Red
        Write-Host "$($_.Exception)" -ForegroundColor Red
        exit 1;
    }    
}

exit $LASTEXITCODE

现在将其作为部署的一部分运行。以下是在 PowerShell 控制台窗口中运行上述脚本的示例。

现在将其作为部署的一部分运行。在 PowerShell 控制台窗口中运行上述脚本的示例。

C:\>.\AddUserToCertificate.ps1 -userName testuser1 -permission read -certStoreLocation \LocalMachine\My -certThumbprint 1fb7603985a8a11d3e85abee194697e9784a253

此示例将读取权限授予在\LocalMachine\My中安装了指纹为1fb7603985a8a11d3e85abee194697e9784a253的证书的用户testuser1

如果您正在使用ApplicationPoolIdentity,则用户名将为'IIS AppPool\AppPoolNameHere'

注意:由于IIS和AppPool之间有一个空格,因此您需要使用' '


2
非常感谢,脚本很棒。如果有人需要给予完全控制权限,那么就是 - _fullcontrol_。 - iBobb
我有一台旧的Win2008机器,它有PowerShell V2。我不得不在第8行(第一条语句)使用$certificateInstalled = Get-ChildItem cert:$certStoreLocation | Where {$_.thumbprint -eq $certThumbprint} - Tony
3
这不再起作用了。 $_.privatekey 现在返回 null。 - Quark Soup
谢谢,这真的救了我的一周。我结合了这个答案https://dev59.com/yms05IYBdhLWcg3wR_63,并成功将应用程序池设置为读取证书。 - Juan

36

对我来说,被接受的答案并不起作用,因为$_.privatekey返回null。我成功获取了私钥,并按以下方式为我的应用程序池分配了“读取”权限:

param (
[string]$certStorePath  = "Cert:\LocalMachine\My",
[string]$AppPoolName,
[string]$certThumbprint
)

Import-Module WebAdministration
    
$certificate = Get-ChildItem $certStorePath | Where thumbprint -eq $certThumbprint

if ($certificate -eq $null)
{
    $message="Certificate with thumbprint:"+$certThumbprint+" does not exist at "+$certStorePath
    Write-Host $message -ForegroundColor Red
    exit 1;
}else
{
    $rsaCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($certificate)
    $fileName = $rsaCert.key.UniqueName
    $path = "$env:ALLUSERSPROFILE\Microsoft\Crypto\Keys\$fileName"
    $permissions = Get-Acl -Path $path

    $access_rule = New-Object System.Security.AccessControl.FileSystemAccessRule("IIS AppPool\$AppPoolName", 'Read', 'None', 'None', 'Allow')
    $permissions.AddAccessRule($access_rule)
    Set-Acl -Path $path -AclObject $permissions
}

是的。接受的答案以前对我有用,但是已经有一段时间没有运行它了。我遇到了同样的问题,$_.privatekey返回null。谢谢。这似乎已经解决了它。 - Quark Soup
这似乎也不再正常工作了。$rsaCert和$fileName变量似乎不包含正确的信息,以一种$path变量可以使用的方式提取一个根本不存在的UniqueName值。 - nightsurfer

6
几周前我注意到某些事情发生了变化(我怀疑是Windows更新),破坏了某些证书使用Michael Armitage脚本中引用的CspKeyContainerInfo.UniqueKeyContainerName属性的能力。调查发现Windows决定开始使用CNG而不是Crypto服务提供程序来保护密钥。以下脚本解决了我的问题,并应正确支持CNG与CSP使用情况:
$serviceUser = "DOMAIN\Service User"
$certificate = Get-ChildItem Cert:\LocalMachine\My | Where-Object Thumbprint -eq "certificatethumbprint"

$privateKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($certificate)
$containerName = ""
if ($privateKey.GetType().Name -ieq "RSACng")
{
    $containerName = $privateKey.Key.UniqueName
}
else
{
    $containerName = $privateKey.CspKeyContainerInfo.UniqueKeyContainerName
}

$keyFullPath = $env:ProgramData + "\Microsoft\Crypto\RSA\MachineKeys\" + $containerName;
if (-Not (Test-Path -Path $keyFullPath -PathType Leaf))
{
    throw "Unable to get the private key container to set permissions."
}

# Get the current ACL of the private key
$acl = (Get-Item $keyFullPath).GetAccessControl()

# Add the new ACE to the ACL of the private key
$accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($serviceUser, "Read", "Allow")
$acl.AddAccessRule($accessRule);

# Write back the new ACL
Set-Acl -Path $keyFullPath -AclObject $acl;

当然,您肯定希望根据自己的具体需求进行适应和改进。


2
做得很好,记住越来越少的证书被存储在RSA\MachineKeys文件夹中,所以你最好做一些像$keyFullPath = Get-ChildItem -Path $env:AllUsersProfile\Microsoft\Crypto -Recurse -Filter $containerName | Select -Expand FullName这样的事情。 - TheMadTechnician
啊哈,好的,谢谢!我很好奇他们正在转换哪些文件夹,然后发现了这个文档:https://learn.microsoft.com/en-us/windows/win32/seccng/key-storage-and-retrieval一旦我有机会测试您提出的修改,我会更新我的答案! - anxkha
@anxkha,@TheMadTechnician的建议确实会返回与您当前的$keyFullPath = $env:ProgramData + "\Microsoft\Crypto\RSA\MachineKeys\" + $containerName;行完全相同的结果。但是,在我的Win2k19服务器机器上使用PS 7.2.4时,没有可用的.GetAccessControl()方法:InvalidOperation: Method invocation failed because [System.IO.FileInfo] does not contain a method named 'GetAccessControl'. - 有什么帮助吗? - Yoda
1
如果我没记错的话,PowerShell Core 没有修改 ACL 的能力,其中一个症状是 GetAccessControl 不存在。也许其他人会有更好的运气/找到方法,但我没有找到说服 PowerShell Core 知道如何修改 ACL 的方法,因此只有内置的 PowerShell 5.4 可以用于这个问题。抱歉:( - anxkha

4

在Michael Armitage脚本的基础上,这将适用于PrivateKey值存在的情况,也适用于它为空的情况。

function setCertificatePermission {
    param($accountName, $certificate)
    if([string]::IsNullOrEmpty($certificate.PrivateKey))
    {
        $rsaCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($certificate)
        $fileName = $rsaCert.key.UniqueName
        $path = "$env:ALLUSERSPROFILE\Microsoft\Crypto\Keys\$fileName"
        $permissions = Get-Acl -Path $path
        $access_rule = New-Object System.Security.AccessControl.FileSystemAccessRule($accountName, 'FullControl', 'None', 'None', 'Allow')
        $permissions.AddAccessRule($access_rule)
        Set-Acl -Path $path -AclObject $permissions
    } else{
            $user = New-Object System.Security.Principal.NTAccount($accountName)
            $accessRule = New-Object System.Security.AccessControl.CryptoKeyAccessRule($user, 'FullControl', 'Allow')
            $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My","LocalMachine")
            $store.Open("ReadWrite")
            $rwCert = $store.Certificates | where {$_.Thumbprint -eq $certificate.Thumbprint}
            $csp = New-Object System.Security.Cryptography.CspParameters($rwCert.PrivateKey.CspKeyContainerInfo.ProviderType, $rwCert.PrivateKey.CspKeyContainerInfo.ProviderName, $rwCert.PrivateKey.CspKeyContainerInfo.KeyContainerName)
            $csp.Flags = "UseExistingKey","UseMachineKeyStore"
            $csp.CryptoKeySecurity = $rwCert.PrivateKey.CspKeyContainerInfo.CryptoKeySecurity
            $csp.KeyNumber = $rwCert.PrivateKey.CspKeyContainerInfo.KeyNumber
            $csp.CryptoKeySecurity.AddAccessRule($AccessRule)
            $rsa2 = New-Object System.Security.Cryptography.RSACryptoServiceProvider($csp)
            $store.close()
        }
}

2

抽出时间学习Michael Armitage的PowerShell版本。 - Quark Soup
1
@DonaldAirey 为什么?如果你有工具并且它支持你的场景,这会简单得多。 - Ohad Schneider
2
  1. 因为Powershell是一种通用语言。
  2. Powershell将在WinHttpCertCfg.exe退役后长期存在。
  3. 您的解决方案涉及设置路径以包括WinHttpCertCfg.exe所在的目录,并可能下载它。我不知道这个工具在我的机器上在哪里,也没有时间去寻找它。
- Quark Soup
1
$_.privatekey 为空的情况下设置证书权限,这将抛出 访问被拒绝 的错误。 - Ankit Patel

2

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