Pergunta

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.

Foi útil?

Solução 2

Cmdlet code for getting private key filename.

[Cmdlet("Get", "PrivateKeyName")]
public class GetKeyNameCmdlet : Cmdlet
{
    [Parameter(Position = 0, Mandatory = false)]
    public X509Certificate2 Cert;

    protected override void ProcessRecord()
    {
        WriteObject(GetUniqueKeyName(Cert));
    }

    private static string GetUniqueKeyName(X509Certificate2 cert)
    {
        if (cert == null)
            throw new ArgumentNullException("cert");

        var cngPrivateKey = cert.GetCngPrivateKey();

        if (cngPrivateKey != null)
            return cngPrivateKey.UniqueName;

        var rsaPrivateKey = cert.PrivateKey as RSACryptoServiceProvider;
        if (rsaPrivateKey != null)
            return rsaPrivateKey.CspKeyContainerInfo.UniqueKeyContainerName;

         throw new Exception("cert");
    }
}

using cmdlet. CngCrypt.dll - dll with cmdlet code.

  Import-Module .\CngCrypt.dll
  $local:certificateRootPath = join-path $env:ALLUSERSPROFILE      '\Microsoft\Crypto\RSA\MachineKeys\'
  $WorkingCert = Get-ChildItem CERT:\LocalMachine\My |where {$_.Subject -match 'Test'}| sort 
  Get-PrivateKeyName ($WorkingCert) 

Outras dicas

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.

If you have certificate already installed on machine/server and just looking for how to give permission to specific user using powershell.

Here is the answer How to Grant permission to user on Certificate private key using powershell?

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top