通过Powershell分配证书私钥的权限(Win 2012 R2)

时间:2018-04-17 20:33:48

标签: windows powershell cryptography x509certificate

在连接到域的Windows Server 2012 R2计算机上,我运行以下语句:

$target_machine_fqdn = [System.Net.Dns]::GetHostByName($env:computerName)

$certificate_request = Get-Certificate `
    -Template 'AcmeComputer' `
    -DnsName $target_machine_fqdn `
    -CertStoreLocation 'Cert:\LocalMachine\My'

我正在从域CA中为主机申请证书。该语句返回时没有错误。为机器生成证书并将其放入其中" Cert:\ LocalMachine \ My"按要求。

问题:我无法弄清楚如何为证书的私钥授予服务帐户权限。

现在,大约有1,000名articles instructing人通过检索UniqueKeyContainerName并使用以下内容开头的代码来授予权限:

$certificate.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName

这不会在这里工作。虽然证书具有私钥,但私钥数据成员为空:

enter image description here

在我刚刚解决的解决方案工作的情况下,私钥位于文件系统上。但是,在这种情况下,私钥位于注册表中,如下所示:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates\MY\Keys

使用证书MMC-snapin时,我可以看到证书。我可以管理私钥的权限。所以,我知道它在那里。不幸的是,我需要自动执行权限分配,因此使用证书MMC-snapin不是一个选项。我需要通过Powershell做一些如何做到这一点。

2 个答案:

答案 0 :(得分:2)

我最近自己开始自动访问证书私钥。我也发现有很多地方告诉我要修改硬盘驱动器上关键数据的ACL,但这并不令人满意,因为当我使用PowerShell检查私钥的权限时,我添加的用户没有列出。如此多的网络搜索,一些文章,以及相当多的试验和错误让我想到了这一点。

我首先定义用户对象,以及我想授予他们的访问权限:

# Create NTAccount object to represent the account
$AccountName = 'Domain\UserName'
$User = New-Object System.Security.Principal.NTAccount($AccountName)
# Define AccessRule to be added to the private key, could use 'GenericRead' if all you need is read access
$AccessRule = New-Object System.Security.AccessControl.CryptoKeyAccessRule($User, 'FullControl', 'Allow')

然后我打开本地机器证书存储区作为读/写,找到我正在寻找的证书:

# Define the thumbprint of the certificate we are interested in
$Thumb = '63CFDDE9A748345CD77C106DAA09B805B33951BF'
# Open Certificate store as read/write
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My","LocalMachine")
$store.Open("ReadWrite")
# Look up the certificate's reference object in the store
$RWcert = $store.Certificates | where {$_.Thumbprint -eq $Thumb}

然后,我根据现有证书创建一个新的CSP(加密服务提供程序)参数集,将新的访问规则添加到参数集。

# Create new CSP parameter object based on existing certificate provider and key name
$csp = New-Object System.Security.Cryptography.CspParameters($RWcert.PrivateKey.CspKeyContainerInfo.ProviderType, $RWcert.PrivateKey.CspKeyContainerInfo.ProviderName, $RWcert.PrivateKey.CspKeyContainerInfo.KeyContainerName)

# Set flags and key security based on existing cert
$csp.Flags = "UseExistingKey","UseMachineKeyStore"
$csp.CryptoKeySecurity = $RWcert.PrivateKey.CspKeyContainerInfo.CryptoKeySecurity
$csp.KeyNumber = $RWcert.PrivateKey.CspKeyContainerInfo.KeyNumber

# Add access rule to CSP object
$csp.CryptoKeySecurity.AddAccessRule($AccessRule)

然后我们使用这些参数实例化一个新的CSP,它将根据我们定义的标志和我们提供的密钥信息将新的访问规则应用于现有证书。

# Create new CryptoServiceProvider object which updates Key with CSP information created/modified above
$rsa2 = New-Object System.Security.Cryptography.RSACryptoServiceProvider($csp)

然后我们关闭证书商店,我们都完成了。

# Close certificate store
$store.Close() 

编辑:环顾四周后,我意识到我有几个相同的证书。我认为这是由于非RSA密码用于加密私钥。我使用了this answer中的一些信息来解释如何使用第三方CNG加密提供程序。我不喜欢下载一个程序集来做那个答案中的事情,但是我使用了一些代码来获取密钥的路径(是的,驱动器上有一个密钥),并添加了一个ACL该文件适用于委派私钥的权限。所以这就是我做的......

首先,我们验证证书是否具有基于CNG的密钥:

[Security.Cryptography.X509Certificates.X509CertificateExtensionMethods]::HasCngKey($Certificate)

如果返回True,那么我们就可以继续前进。我做了,我猜也是你的。然后我们通过读取PrivateKey数据($Certificate中缺少)并获取UniqueName,然后在Crypto文件夹中搜索该文件,找到硬盘上的密钥。

$privateKey = [Security.Cryptography.X509Certificates.X509Certificate2ExtensionMethods]::GetCngPrivateKey($Certificate)
$keyContainerName = $privateKey.UniqueName
$keyMaterialFile = gci $env:ALLUSERSPROFILE\Microsoft\Crypto\*Keys\$keyContainerName

然后我抓住了文件的当前ACL,组成了一个新的AccessRule,为所需的用户提供了对该文件的访问权限,将规则添加到我刚抓取的ACL中,并将更新的ACL应用回文件。

$ACL = Get-Acl $keyMaterialFile
$AccountName = 'Domain\User'
$User = New-Object System.Security.Principal.NTAccount($AccountName)
$AccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($User,'FullControl','None','None','Allow')
$ACL.AddAccessRule($AccessRule)
Set-Acl -Path $keyMaterialFile -AclObject $ACL

之后我能够查看certlm.msc并验证用户是否拥有私钥的权限。

答案 1 :(得分:2)

TheMadTechnician像mf'n冠军一样回答了这个问题。如果他需要我帮助他埋葬尸体,他只需要打电话。我正在添加他的答案,其中包含无法构建/部署程序集的人员的详细信息

注意: clrsecurity项目已发布到CodePlex,该项目于2017年关闭。项目已移至github it can be downloaded。不再支持帖子中引用的clrsecurity程序集。

此外,还有写这篇文章的VadimsPodāns(Crypt32) Retrieve CNG key container name and unique name,它可以帮助读者使用Powershell中的非托管代码访问CNG私钥。

如果您像我一样并且无法使用clrsecurity程序集,那么.NET 4.5.6框架引入了我们可以利用的元素。请考虑以下事项:

"selector": ...

我们正在使用## Identify the certificate who's private key you want to grant ## permission to $certificate = $(ls 'cert:\LocalMachine\My\C51280CE3AD1FEA848308B764DDCFA7F43D4AB1A') ## Identify the user you'll be granting permission to $grantee_name = 'foo\lordAdam' $grantee = New-Object System.Security.Principal.NTAccount($grantee_name) ## Get the location and permission-of the cert's private key $privatekey_rsa = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($certificate) $privatekey_file_name = $privatekey_rsa.key.UniqueName $privatekey_path = "${env:ALLUSERSPROFILE}\Microsoft\Crypto\Keys\${privatekey_file_name}" $privatekey_file_permissions = Get-Acl -Path $privatekey_path ## Grant the user 'read' access to the key $access_rule = New-Object System.Security.AccessControl.FileSystemAccessRule($grantee, 'Read', 'None', 'None', 'Allow') $privatekey_file_permissions.AddAccessRule($access_rule) Set-Acl -Path $privatekey_path -AclObject $privatekey_file_permissions 静态类的GetRSAPrivateKey()方法来返回私钥。然后我们获取UniqueName(Key属性具有UniqueName属性)。我们使用它来推断密钥文件的位置。其余的是授予文件权限。

如果您想查看存储私钥的位置,请查看Key Storage and Retrieval