Inserting Certificate (with privatekey) in Root, LocalMachine certificate store fails in .NET 4

24,277

Solution 1

I had exactly the same problem and the solution turned out to be really simple. All I had to do is to pass

X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet

to X509Certificate2's ctor. Now you are using the DotNetUtilities to convert the bouncycastle certificate to the .net one, but the helper method creates the .net cert with the DefaultKeySet (instead of MachineKeySet + PersistKeySet ).

And arrange the private key like this:

var cspParams = new CspParameters
{
      KeyContainerName = Guid.NewGuid().ToString(),
      KeyNumber = (int)KeyNumber.Exchange,
      Flags = CspProviderFlags.UseMachineKeyStore
};

var rsaProvider = new RSACryptoServiceProvider(cspParams);

I hope this helps.

Solution 2

It seems to me you should import the key in a little other way. See http://support.microsoft.com/kb/950090 for an example.

Moreover I find not good to save private key in UseMachineKeyStore. In the most cases you need import certificate with the private key in My store of some user and import in Root only certificate without private key.

It you do need save private key on Machine key store, that you should at least protect the key for reading only for some selected users and not from Everyone. The key container is just a file in the file system (see files in the diriectory "%ALLUSERSPROFILE%\Microsoft\Crypto\Keys") which has security descriptors like other files in NTFS. To change security descriptors of the files you can use CspKeyContainerInfo.CryptoKeySecurity property and AddAccessRule, RemoveAccessRule and so on.

UPDATED: First of all sorry for the long answer.

I could divide your program code in two parts. In the first part you generate a self-signed certificate which can be used as a CA certificates and you save it as rootcert.pfx file. In the second part you import the certificate, but use RSACryptoServiceProvider filled with properties of previous created key instead of using rootcert.pfx.

I suggest to replace the second part of your code to more standard and simple code: import certificate with the private key from rootcert.pfx like it described in http://support.microsoft.com/kb/950090. It works very well.

I don't use myself the BouncyCastle, so I could not comment the first part of your code, but in general what you do in the code you could do also with respect of MakeCert.exe utility from the Windows SDK. You can do like following

MakeCert.exe -pe -ss MY -a sha1 -cy authority -len 2048 -m 120 -r -# 1
             -n "CN=Some Root CA, C=NL, OU=BleedingEdge, ST=Somewhere, L=Somelane"

Then you can export certificate with or without private key with respect of Certificate Snap-In (for mmc.exe). In the example above I don't restrict CA for some special EKU, so you can use it without any restriction, but if you do need the restrictions you can just add additional parameters to MakeCert.exe. You can also use MakeCert.exe to create other certificate which are signed with the CA certificate. So you are able to make small PKI with respect of MakeCert.exe only.

It seems to me that creating of the certificate is really a separate part of your code. Your main problem is in the second part.

If you want import CA certificate you should take in consideration some important things:

  • You should import it in Root or AuthRoot in localMachine on every (or many) computer of your organization, but you should import the certificate without the private key. You can do this with respect of following

CertMgr.exe -add -c CA.cer -s -r localMachine AuthRoot

  • You should import CA certificate with private key on the computer on one computer and only for the user who will issue other certificates (who will sign new certificates with the private key of CA). One use to import the certificate in the My certificate store of CurrentUser. So the code on the computer could looks like

following:

// import PFX
X509Certificate2 cert = new X509Certificate2 (@"c:\Oleg\rootcert.pfx", "password",
    X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
// save certificate and private key
X509Store storeMy = new X509Store (StoreName.My, StoreLocation.CurrentUser);
storeMy.Open (OpenFlags.ReadWrite);
storeMy.Add (cert);

// get certificate without private key
// one can import certificate from rootcert.cer instead
byte[] certBlobWithoutPrivateKey = cert.Export (X509ContentType.Cert);
// save pure certificate in Root of the local machine
X509Certificate2 certWithoutPrivateKey = new X509Certificate2 (certBlobWithoutPrivateKey);
X509Store storeRoot = new X509Store (StoreName.Root, StoreLocation.LocalMachine);
storeRoot.Open (OpenFlags.ReadWrite);
storeRoot.Add (certWithoutPrivateKey);

The code will work if you do will change StoreName.My and StoreLocation.CurrentUser to another values, but I don't recommend you to do this.

In general importing of certificates in .NET code look like a little strange and not shows what will be done under the hood. Windows knows only Key Containers where private keys (to be exactly the key pair) will be saved with respect of CSP and Certificate Stores where certificates will be saved (see http://msdn.microsoft.com/en-us/library/bb204781.aspx about location of the store). To be able to save information about the key container in the certificate store Microsoft introduced so named Certificate Extended Properties. If you use in .NET properties of X509Certificate2 like Thumbprint, FriendlyName, HasPrivateKey, Archived and so on you work with the Extended Properties of the certificate. So I recommend you to import CA certificate twice. One in Root or AuthRoot without setting CERT_KEY_PROV_INFO_PROP_ID Certificate Extended Properties and one more time in My store with the setting of information about the place of Key Container with the private key (CERT_KEY_PROV_INFO_PROP_ID). Moreover you can consider to remove private key directly after the usage, import it only if you really need to use it and not hold it permanently. All this is important to have better security.

Solution 3

I have encountered this problem and it seems that even the user with which you are running the FindPrivateKey tool does not have access to the key and therefore you would get the "Unable to obtain private key file name" message. You could run the tool as LocalSystem process.

More information here:

http://www.itsolutionbraindumps.com/2011/02/finding-private-key-for-your.html

Dinko

Share:
24,277
albertjan
Author by

albertjan

Software Engineer based in the UK.

Updated on July 09, 2022

Comments

  • albertjan
    albertjan almost 2 years

    I'm having problems inserting a new CA certificate with privatekey in the Root certificate store of the localmachine.

    This is what happens:

    //This doesn't help either.
    new StorePermission (PermissionState.Unrestricted) { Flags = StorePermissionFlags.AddToStore }.Assert();
    var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
    privkey.PersistKeyInCsp = true;
    //This shouldn't be necessary doesn't make a difference what so ever.
    RSACryptoServiceProvider.UseMachineKeyStore = true;
    cert.PrivateKey = privkey;
    store.Open (OpenFlags.MaxAllowed);
    store.Add (cert);
    store.Close ();
    

    The certificate gets inserted and it all looks dandy: (see!) note it says it has a private key

    Note: is says it has a privatekey.

    So you'd say one would be able to find it with FindPrivateKey

    C:\Users\Administrator\Desktop>FindPrivateKey.exe Root LocalMachine -t "54 11 b1 f4 31 99 19 d3 5a f0 5f 01 95 fc aa 6f 71 12 13 eb"
    FindPrivateKey failed for the following reason:
    Unable to obtain private key file name
    
    Use /? option for help 
    

    It's cute .... BUT IT'S WRONG!! (2 stupid dogs reference)

    And the Certificate export dialog gives me this very fine message: alt text

    This code is run while impersonating an administrator using this snippet: click here

    I'd just love to know WHY?

    (tested on Windows Server 2008 R2 & Windows 7)

    I'll be damned!

    It works when I compile it to v3.5!!!!

    What to do?

  • albertjan
    albertjan over 13 years
    All good advice but I really think you should have read my question a bit better I'm not importing a pfx file I'm generating and inserting a certificate in code. I do tell the private key to persist in the CSP the problem is it doesn't It look like it. If you don't tell the privatekey to persist it will only stay there as long as you application runs. This is not what happens it never arrives :) as soon as I insert the certficate it look like the key is inserted but it doesn't get stored.
  • Oleg
    Oleg over 13 years
    @the_ajp: Probably there are understanding problem what you want to do. Moreover you use words like "inserting certificate" or "inserting a certificate in code" which is not a standard terminology. There are "generate a key par and the corresponding certificate" and "import a certificate with or without private key". It is also unclear why you need generate certificate per code and not with respect of MakeCert.exe utility of Windows SDK. Do you want generate a lot of CA certificates? Nevertheless 1 Step in your program - generate a certificate and 2 Step import certificate with private key.
  • Oleg
    Oleg over 13 years
    Moreover mostly you need to have Root Certificate without private key on many computers and have the certificate with private key on one computer where you will do signing with the certificate. Better I'll update my answer to describe how I understand your possible problems.
  • albertjan
    albertjan almost 13 years
    I'll look into it. I have tried something like this but not or-ing them :). Thanks!
  • albertjan
    albertjan over 9 years
    Thanks you were absolutely right. Better late then never right :P
  • Raven
    Raven over 6 years
    I'm using bouncy castle to generate a cert where would i add the code you have provided? the code can be found in stackoverflow.com/questions/47417262/…