Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to grant permission to private key from powershell

Tags:

powershell

cng

I'm trying to find a way to grant permissions for private key from powershell script. Certificate is stored in CNG. All ideas are welcome.

like image 558
T_T Avatar asked Feb 16 '23 04:02

T_T


1 Answers

The answer above is technically correct however it did not help me when I was looking for the same thing because it fails to mention that you need to use assemblies loaded from the CLRSecurity project on codeplex https://clrsecurity.codeplex.com/.

Here is an extract of how I achieved the same thing including loading the CLR Security assembly that you need to use Security.Cryptography.dll. There are a couple of function declarations that are needed first. I have these included in modules however you can use them as you wish.

Function Load-Assembly()
{
    [CmdletBinding(PositionalBinding=$false)]   
    param(
        [Parameter(Mandatory)][string][ValidateScript({Test-Path $_})] $DirectoryPath,
        [Parameter(Mandatory)][string][ValidateNotNullOrEmpty()] $Name
    )

    $assemblyFileNameFullPath = Join-Path -Path $DirectoryPath -ChildPath $Name

    If (Test-Path -Path  $assemblyFileNameFullPath -PathType Leaf)
    {
        Write-Verbose "Loading .NET assembly from path ""$assemblyFileNameFullPath"""

        #Load the assembly using the bytes as this gets around security restrictions that stop certain assemblies from loading from external sources
        $assemblyBytes = [System.IO.File]::ReadAllBytes($assemblyFileNameFullPath)
        $assemblyLoaded = [System.Reflection.Assembly]::Load($assemblyBytes);

        if ($assemblyLoaded -ne $null)
        {
            return $assemblyLoaded
        }
        else
        {
            Throw "Cannot load .NET assembly ""$Name"" from directory ""$DirectoryPath"""
        }
    }
    else
    {
        Write-Error "Cannot find required .NET assembly at path ""$assemblyFileNameFullPath"""
    }
}

Function Get-PrivateKeyContainerPath()
{
    [CmdletBinding(PositionalBinding=$false)]
    Param(
        [Parameter(Mandatory=$True)][string][ValidateNotNullOrEmpty()] $Name,
        [Parameter(Mandatory=$True)][boolean] $IsCNG
    )

    If ($IsCNG)
    {
        $searchDirectories = @("Microsoft\Crypto\Keys","Microsoft\Crypto\SystemKeys")
    }
    else
    {
        $searchDirectories = @("Microsoft\Crypto\RSA\MachineKeys","Microsoft\Crypto\RSA\S-1-5-18","Microsoft\Crypto\RSA\S-1-5-19","Crypto\DSS\S-1-5-20")
    }

    foreach ($searchDirectory in $searchDirectories)
    {
        $machineKeyDirectory = Join-Path -Path $([Environment]::GetFolderPath("CommonApplicationData")) -ChildPath $searchDirectory
        $privateKeyFile = Get-ChildItem -Path $machineKeyDirectory -Filter $Name -Recurse
        if ($privateKeyFile -ne $null)
        {
           return $privateKeyFile.FullName
        }
    }

    Throw "Cannot find private key file path for key container ""$Name"""
}


#Extracted code of how to obtain the private key file path (taken from a function)
#Requires an x509Certificate2 object in variable $Certificate and string variable $CertificateStore that contains the name of the certificate store

#Need to use the Security.Cryptography assembly
    $assembly = Load-Assembly -DirectoryPath $PSScriptRoot -Name Security.Cryptography.dll

    #Uses the extension methods in Security.Cryptography assembly from (https://clrsecurity.codeplex.com/)
    If ([Security.Cryptography.X509Certificates.X509CertificateExtensionMethods]::HasCngKey($Certificate))
    {
        Write-Verbose "Private key CSP is CNG"
        $privateKey = [Security.Cryptography.X509Certificates.X509Certificate2ExtensionMethods]::GetCngPrivateKey($Certificate)
        $keyContainerName = $privateKey.UniqueName
        $privateKeyPath = Get-PrivateKeyContainerPath -Name $keyContainerName -IsCNG $true
    }
    elseif ($Certificate.PrivateKey -ne $null)
    {
        Write-Verbose "Private key CSP is legacy"
        $privateKey = $Certificate.PrivateKey        
        $keyContainerName = $Certificate.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName      
        $privateKeyPath = Get-PrivateKeyContainerPath -Name $keyContainerName -IsCNG $false
    }
    else
    {
        Throw "Certificate ""$($Certificate.GetNameInfo("SimpleName",$false))"" in store ""$CertificateStore"" does not have a private key, or that key is inaccessible, therefore permission cannot be granted"
    }

Sorry if this seems like a repeat from above, as I said it does use the same technique but hopefully others may find this more useful since it explains how to use the methods in the CLR Security project including how to load the assembly.

like image 146
CarlR Avatar answered Feb 23 '23 04:02

CarlR