Certificate is already installed on machine. Now I want to give read permission on PrivateKey of Certificate to application user.
Here is the Answer.
Created a powershell script file AddUserToCertificate.ps1
Here is the content for script file.
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
Now run it as part of deployment. Example to running above script in powershell console window.
C:\>.\AddUserToCertificate.ps1 -userName testuser1 -permission read -certStoreLocation \LocalMachine\My -certThumbprint 1fb7603985a8a11d3e85abee194697e9784a253
this example give read permission to user testuser1 on certificate that in installed in \LocalMachine\My and has thumb print 1fb7603985a8a11d3e85abee194697e9784a253
If you are using ApplicationPoolIdentity then you username will be 'IIS AppPool\AppPoolNameHere'
Note: You will need to use ' ' as there is a space between IIS and AppPool.
$certificateInstalled = Get-ChildItem cert:$certStoreLocation | Where {$_.thumbprint -eq $certThumbprint}
in line 8 (the first statement) –
Charlinecharlock The accepted answer did not work for me as the $_.privatekey
returned null. I managed to get access to the private key and assign 'Read' permissions for my Application Pool as follows:
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
}
It came to my attention a few weeks ago that something changed (I suspect a Windows update) and broke the ability for some certificates to use the CspKeyContainerInfo.UniqueKeyContainerName
property referenced in Michael Armitage's script. Some sleuthing uncovered that Windows decided to start using CNG instead of Crypto Service Provider to protect the key. The following script fixed my issue and should correctly support CNG vs CSP use case scenarios:
$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;
You would, of course, want to adapt/enhance this to meet your specific needs.
$keyFullPath = Get-ChildItem -Path $env:AllUsersProfile\Microsoft\Crypto -Recurse -Filter $containerName | Select -Expand FullName
–
Nickerson $keyFullPath = $env:ProgramData + "\Microsoft\Crypto\RSA\MachineKeys\" + $containerName;
line above. However, on my Win2k19 Server machine using PS 7.2.4, there's no .GetAccessControl()
method available: InvalidOperation: Method invocation failed because [System.IO.FileInfo] does not contain a method named 'GetAccessControl'.
- any help? –
Bloodthirsty Adding on Michael Armitage script, This will work for both the cases where PrivateKey value is present and when it is blank
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()
}
}
As an alternate to above script. You can use PowerShell module. I have not tried it myself but module looks good. http://get-carbon.org/index.html
Here is command to set permissions http://get-carbon.org/Grant-Permission.html
You can use WinHttpCertCfg.exe, a Certificate Configuration Tool Link: https://learn.microsoft.com/en-us/windows/desktop/winhttp/winhttpcertcfg-exe--a-certificate-configuration-tool
Some code example:
Set privatekeyAcces to [email protected]
*.\WinHttpCertCfg.exe -g -c LOCAL_MACHINE\MY -s *.d365.mydomain.com -a "[email protected]"*
$_.privatekey
is null, this will throw error of access denied –
Tuber Came by this post recently and wanted to throw in a simplified code that I use.
It's taken from the script above with some simplification (in my mind).
I use it to set permissions on my Let's Encrypt Certificates when a new one is generated every 45 days or so. Hence the same name in subject and selecting the newest one.
It takes a part of or the full subject of a certificate and then the username to add and optionally FullControl. Default to only giving read on the key if nothing is specified. It also supports -WhatIf before running.
Ps. Used ChatGPT for initial code, and tested by FleshCPU 🧠 😉
function Set-CertificatePrivateKeyPermission {
[CmdletBinding(SupportsShouldProcess=$true)]
param(
[string]$certSubjectPart,
[string]$adUsername,
[ValidateSet('Read','FullControl')]
[string]$permissionType = 'Read'
)
try {
# Find the newest certificate that matches the subject part
$newestCert = Get-ChildItem -Path Cert:\LocalMachine\My |
Where-Object { $_.Subject -like "*$certSubjectPart*" } |
Sort-Object NotBefore -Descending |
Select-Object -First 1
if ($newestCert -eq $null) {
Write-Error "No certificate found with subject part '$certSubjectPart'"
return
}
# Define the private key path
$root = "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys"
$keyname = $newestCert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
$path = [IO.Path]::Combine($root, $keyname)
if ([IO.File]::Exists($path)) {
if ($PSCmdlet.ShouldProcess($path, "Add $permissionType permission for $adUsername")) {
# Creating the access rule for the user
$rule = new-object Security.AccessControl.FileSystemAccessRule $adUsername, $permissionType, 'Allow'
$acl = Get-Acl -Path $path
$acl.AddAccessRule($rule)
Set-Acl -Path $path -AclObject $acl
Write-Host "Permissions would be updated for certificate with thumbprint: $($newestCert.Thumbprint)"
}
} else {
Write-Error "Private key file not found."
}
} catch {
Write-Host "Caught an exception:" -ForegroundColor Red
Write-Host "$($_.Exception)" -ForegroundColor Red
exit 1;
}
}
Couldn't get the answers to work,
I managed with this script:
$url = "ReplaceWithCertUrl"
$accountName = "ReplaceNameHere"
$certificate = Get-ChildItem Cert:\LocalMachine\My | Where-Object Subject -eq "CN=$url" | Sort-Object NotBefore -Descending | Select-Object -First 1
$rsaCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($certificate)
$fileName = $rsaCert.key.UniqueName
$path = "$env:ALLUSERSPROFILE\Microsoft\Crypto\Keys\$fileName"
$permissions = Get-Acl -Path $path
$rule = new-object security.accesscontrol.filesystemaccessrule $accountName, "read", allow
$permissions.AddAccessRule($rule)
Set-Acl -Path $path -AclObject $permissions
You should not be using File System access to set the permissions on the private key. Instead, set the permissions directly on the private key itself using a CryptoKeyAccessRule:
# Fill in correct username
$AccessRule = [Security.AccessControl.CryptoKeyAccessRule]::New('<DOMAIN\USERNAME>', 'ReadData', 'Allow')
# Fill in as needed, or use one of the above examples to find the cert on subject, domain, ...
$Certificate = Get-ChildItem Cert:\LocalMachine\My\<THUMBPRINT>
$Certificate.PrivateKey.CSPContainerInfo.CryptoKeySecurity.AddAccessRule($AccessRule)
And don't forget to run as admin (otherwise you may not have access to the private key yourself).
© 2022 - 2024 — McMap. All rights reserved.