How to Grant permission to user on Certificate private key using powershell?
Asked Answered
A

9

38

Certificate is already installed on machine. Now I want to give read permission on PrivateKey of Certificate to application user.

Angeles answered 14/10, 2016 at 15:31 Comment(0)
A
47

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.

Angeles answered 14/10, 2016 at 15:31 Comment(4)
thanks a lot, great script. if anyone needs to give the full control permission, it is really - fullcontrolMegillah
I have an old Win2008 machine which has PowerShell V2. I had to use $certificateInstalled = Get-ChildItem cert:$certStoreLocation | Where {$_.thumbprint -eq $certThumbprint} in line 8 (the first statement)Charlinecharlock
This no longer works. $_.privatekey returns null now.Registrar
Thanks this really saved my week, I combine this with this answer #7334716 and was able to set the application pool to read the certificate.Discharge
E
37

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
}
Enharmonic answered 17/9, 2018 at 9:24 Comment(2)
Yes. The accepted answer use to work for me, but it's been a while since I ran it. Had the same problem, the $_.privatekey returned null. Thank you. This seems to have fixed it.Registrar
This doesn't seem to work right anymore either. The $rsaCert and $fileName variables don't seem to contain the correct information, pulling a UniqueName value that simply doesn't exist in a way that the $path variable can use.Burtis
P
8

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.

Pliocene answered 10/2, 2022 at 17:53 Comment(4)
Nicely done, keep in mind fewer and fewer certs are being stored in the RSA\MachineKeys folder, so you may be better off doing something like $keyFullPath = Get-ChildItem -Path $env:AllUsersProfile\Microsoft\Crypto -Recurse -Filter $containerName | Select -Expand FullNameNickerson
Ahah, good to know, thanks! I was curious which folders they are transitioning to and came across this document: learn.microsoft.com/en-us/windows/win32/seccng/… I'll update my answer once I have a chance to test your proposed modification!Pliocene
@Pliocene The suggestion of @Nickerson does return the exact same result as your current $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
If I remember correctly, PowerShell Core does not have the ability to modify ACLs and one of the symptoms of this is GetAccessControl not existing. Someone else may have had better luck/found a way, but I have not found a way to convince PowerShell Core to know how to modify ACLs and so only the built-in PowerShell 5.4 works for this. Sorry :(Pliocene
T
4

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()
        }
}
Tuber answered 23/6, 2020 at 14:52 Comment(0)
A
2

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

Angeles answered 14/10, 2016 at 16:6 Comment(0)
W
2

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]"*
Wendt answered 26/9, 2018 at 16:44 Comment(4)
Take the time to learn the Powershell version of this from Michael Armitage.Registrar
@DonaldAirey why? This is much simpler, if you have the tool and it supports your scenario.Ballenger
1. Because Powershell is a general purpose language. 2. Powershell will be around long after WinHttpCertCfg.exe is retired. 3. You solution involves setting the path to include the directory where WinHttpCertCfg.exe lives and, possibly, downloading it. I don't know where this tool is on my machine and I don't have time to go looking for it.Registrar
setting permission on certificate where $_.privatekey is null, this will throw error of access deniedTuber
S
1

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

}

Stretto answered 4/12, 2023 at 15:2 Comment(0)
E
0

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
Essayist answered 10/5 at 10:53 Comment(0)
P
0

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).

Pinsk answered 17/5 at 8:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.