Ray Lu
Ray Lu

Reputation: 26658

How to set read permission on the private key file of X.509 certificate from .NET

Here is the code to add a pfx to the Cert store.

X509Store store = new X509Store( StoreName.My, StoreLocation.LocalMachine );
store.Open( OpenFlags.ReadWrite );
X509Certificate2 cert = new X509Certificate2( "test.pfx", "password" );
store.Add( cert );
store.Close();

However, I couldn't find a way to set permission for NetworkService to access the private key.

Can anyone shed some light? Thanks in advance.

Upvotes: 33

Views: 50460

Answers (7)

LunicLynx
LunicLynx

Reputation: 1098

If the PrivateKey of Certificate is of type RSACng you can go this route:

For LocalMachine:

 var rsaPrivateKey = certificate.GetRSAPrivateKey();
 var privateKey = rsaPrivateKey as RSACng;
 var keyUniqueName = privateKey.Key.UniqueName;
 var folderPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
 var keyPath = Path.Combine(folderPath, "Microsoft", "Crypto", "RSA", "MachineKeys", keyUniqueName);
 var fileInfo = new FileInfo(keyPath);
 var accessControl = fileInfo.GetAccessControl();
 accessControl.AddAccessRule(new FileSystemAccessRule(new NTAccount("<account>"), FileSystemRights.FullControl, AccessControlType.Allow));
 fileInfo.SetAccessControl(accessControl);

If it is a User Certificate use Environment.SpecialFolder.ApplicationData and find the sid in the folder roaming\microsoft\crypto\rsa instead.

Upvotes: 3

eddiewould
eddiewould

Reputation: 1633

Based on @russ's answer,

This version copes with both Key Storage Provider & the Legacy Crypto Service Provider.

function Set-PrivateKeyPermissions {
    param(
        [Parameter(Mandatory=$true)]
        [string]$thumbprint,
        [Parameter(Mandatory=$true)]
        [string]$account
    )

    #Open Certificate store and locate certificate based on provided thumbprint
    $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My","LocalMachine")
    $store.Open("ReadWrite")
    $cert = $store.Certificates | where {$_.Thumbprint -eq $thumbprint}

    if ($cert.PrivateKey -Eq $null) {
        # Probably using Key Storage Provider rather than crypto service provider
        $rsaCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
        if ($rsaCert -Eq $null) {
            throw "Private key on certificate $($cert.Subject) not available"
        }

        $fileName = $rsaCert.key.UniqueName
        $path = "$env:ALLUSERSPROFILE\Microsoft\Crypto\Keys\$fileName"
        $permissions = Get-Acl -Path $path

        $access_rule = New-Object System.Security.AccessControl.FileSystemAccessRule($account, "FullControl", "Allow")
        $permissions.AddAccessRule($access_rule)
        Set-Acl -Path $path -AclObject $permissions
    } else {
        #Create new CSP object based on existing certificate provider and key name
        $csp = New-Object System.Security.Cryptography.CspParameters($cert.PrivateKey.CspKeyContainerInfo.ProviderType, $cert.PrivateKey.CspKeyContainerInfo.ProviderName, $cert.PrivateKey.CspKeyContainerInfo.KeyContainerName)

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

        # Create new access rule - could use parameters for permissions, but I only needed GenericRead
        $access = New-Object System.Security.AccessControl.CryptoKeyAccessRule($account,"GenericRead","Allow")
        # Add access rule to CSP object
        $csp.CryptoKeySecurity.AddAccessRule($access)

        #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()
}

Upvotes: 4

Russ
Russ

Reputation: 201

In case this helps anyone else out, I wrote Jim Flood's answer in Powershell

function Set-PrivateKeyPermissions {
param(
[Parameter(Mandatory=$true)][string]$thumbprint,
[Parameter(Mandatory=$false)][string]$account = "NT AUTHORITY\NETWORK SERVICE"
)
#Open Certificate store and locate certificate based on provided thumbprint
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My","LocalMachine")
$store.Open("ReadWrite")
$cert = $store.Certificates | where {$_.Thumbprint -eq $thumbprint}

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

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

# Create new access rule - could use parameters for permissions, but I only needed GenericRead
$access = New-Object System.Security.AccessControl.CryptoKeyAccessRule($account,"GenericRead","Allow")
# Add access rule to CSP object
$csp.CryptoKeySecurity.AddAccessRule($access)

#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()

}

Note that the account parameter can be in the form of "DOMAIN\USER" as well (not just built in names) - I tested this in my environment and it automatically converted it to the appropriate SID

Upvotes: 20

kennydust
kennydust

Reputation: 1165

This is the solution I found for windows server 2008 if anyone was interested: http://technet.microsoft.com/en-us/library/ee662329.aspx

Basically, I had to grant permissions to the service that needs to access the certificate using the MMC tool. Works like a charm.

Upvotes: 3

Jim Flood
Jim Flood

Reputation: 8467

This answer is late but I wanted to post it for anybody else that comes searching in here:

I found an MSDN blog article that gave a solution using CryptoKeySecurity here, and here is an example of a solution in C#:

var rsa = certificate.PrivateKey as RSACryptoServiceProvider;
if (rsa != null)
{
    // Modifying the CryptoKeySecurity of a new CspParameters and then instantiating
    // a new RSACryptoServiceProvider seems to be the trick to persist the access rule.
    // cf. http://blogs.msdn.com/b/cagatay/archive/2009/02/08/removing-acls-from-csp-key-containers.aspx
    var cspParams = new CspParameters(rsa.CspKeyContainerInfo.ProviderType, rsa.CspKeyContainerInfo.ProviderName, rsa.CspKeyContainerInfo.KeyContainerName)
    {
        Flags = CspProviderFlags.UseExistingKey | CspProviderFlags.UseMachineKeyStore,
        CryptoKeySecurity = rsa.CspKeyContainerInfo.CryptoKeySecurity
    };

    cspParams.CryptoKeySecurity.AddAccessRule(new CryptoKeyAccessRule(sid, CryptoKeyRights.GenericRead, AccessControlType.Allow));

    using (var rsa2 = new RSACryptoServiceProvider(cspParams))
    {
        // Only created to persist the rule change in the CryptoKeySecurity
    }
}

I'm using a SecurityIdentifier to identify the account but an NTAccount would work just as well.

Upvotes: 46

Eric Rosenberger
Eric Rosenberger

Reputation: 9117

To do it programmatically, you have to do three things:

  1. Get the path of the private key folder.

  2. Get the file name of the private key within that folder.

  3. Add the permission to that file.

See this post for some example code that does all three (specifically look at the "AddAccessToCertificate" method).

Upvotes: 18

Enrico Campidoglio
Enrico Campidoglio

Reputation: 59923

You can use the WinHttpCertCfg.exe tool that ships as part of the Windows Server 2003 Resource Kit Tools.

Example:

winhttpcertcfg -g -c LOCAL_MACHINE\My -s test -a NetworkService


Alternatively, you could use the Find Private Key tool that ships with the WCF SDK, to find the location on disk of the certificate's private key file. Then you can simply use ACL to set the right privileges on the file.

Example:

FindPrivateKey My LocalMachine -n "CN=test"

Upvotes: 11

Related Questions