简体   繁体   中英

How to create non-exportable private key with X509 certificate in windows key store programmatically (C#)

I am trying to create a private key and add a certificate(self-signed or signed from a CA) where I should be able to export the certificate only and make the private key non-exportable in C#. That is, if someone tries to export the certificate from certmgr , the export option will be disabled like this picture-

在此处输入图像描述

I want the same non-exportable option programmatically in C# while creating it. The private key usually becomes non-exportable when a.pfx/.p12 file is installed using Crypto Shell Extensions when Mark this Key is exportable is unchecked .

在此处输入图像描述

I can successfully create key pairs and add certificate entries in the windows key store. But Yes, export the private key option always becomes enabled that is I can't restrict the private key from export. I have tried this -

        public void init(){
            AsymmetricCipherKeyPair asymmetricCipherKeyPair = GetKeyPair();

            X509Name issuer = this.GenerateRelativeDistinguishedName("test org");
            X509Name subject = this.GenerateRelativeDistinguishedName("test user1");


            Org.BouncyCastle.X509.X509Certificate cert = GenerateCertificate(issuer, subject, asymmetricCipherKeyPair.Private, asymmetricCipherKeyPair.Public);
            importSelfSignedCert(asymmetricCipherKeyPair, cert);
        }
        
        private AsymmetricCipherKeyPair GetKeyPair()
        {
            return new Pkcs1xHandler().GenerateKeyPair(Constants.RsaKeyLength.Length2048Bits);
        }
         
        protected X509Name GenerateRelativeDistinguishedName(String commonName)
        {

            IDictionary attributes = new Hashtable();
            IList ordering;

            attributes.Add(X509Name.CN, commonName);

            ordering = new ArrayList(attributes.Keys);
            return new X509Name(ordering, attributes);
        }


        protected void importSelfSignedCert(AsymmetricCipherKeyPair asymmetricCipherKeyPair, Org.BouncyCastle.X509.X509Certificate cert)
        {
            try
            {
                int ID =1;
                AsymmetricCipherKeyPair ackp = asymmetricCipherKeyPair;
                var rsaPriv = Org.BouncyCastle.Security.DotNetUtilities.ToRSA(ackp.Private as RsaPrivateCrtKeyParameters);

                // Setup RSACryptoServiceProvider with "KeyContainerName" set to "KeyContainer"+ enrollmentID
                var csp = new CspParameters();
                csp.KeyContainerName = "TestPrivKey" + ID;
                csp.Flags |= CspProviderFlags.UseMachineKeyStore;

                var rsaPrivate = new RSACryptoServiceProvider(csp);

                // Import private key to windows keystrore, from already generated BouncyCastle rsa privatekey
                rsaPrivate.ImportParameters(rsaPriv.ExportParameters(true));
                //Console.Write("rsaprivate key:" + rsaPrivate.ToXmlString(true));

                System.Security.Cryptography.X509Certificates.X509Certificate2 certificate = new System.Security.Cryptography.X509Certificates.X509Certificate2();
                var flags = X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.UserKeySet;
                certificate.Import(cert.GetEncoded(), String.Empty, flags);
                certificate.PrivateKey = rsaPrivate;


                // opening up the windows cert store because thats where I want to save it.
                System.Security.Cryptography.X509Certificates.X509Store store = new System.Security.Cryptography.X509Certificates.X509Store(System.Security.Cryptography.X509Certificates.StoreName.My, System.Security.Cryptography.X509Certificates.StoreLocation.CurrentUser);
                store.Open(System.Security.Cryptography.X509Certificates.OpenFlags.MaxAllowed);
                store.Add(certificate);
                store.Close();

                rsaPrivate.PersistKeyInCsp = true; //persisting the key in container is important to retrieve the key later



                ///make non exporable
                csp.Flags = CspProviderFlags.UseNonExportableKey;
                var rsaPrivate2 = new RSACryptoServiceProvider(csp);
                rsaPrivate2.ExportParameters(false); //restrict to export
                rsaPrivate2.PersistKeyInCsp = true;
            }
            catch (Exception e)
            {
                System.Diagnostics.Debug.WriteLine("Error : " + e);
                Console.WriteLine(e);
                Log.Print(LogLevel.High, e.ToString());
            }
        }

And when I try to export the certificate, I get the option to Yes, export the private key too, like the below image -

在此处输入图像描述

Is there any way to make the private-key non-exportable like the first image while creating it programmatically? I would grateful for any hints, references or code samples. Thanks.

To make the private key non-exportable, the CspProviderFlags.UseNonExportableKey must additionally be set when importing the key:

...
var csp = new CspParameters();
csp.KeyContainerName = "TestPrivKey" + ID;
csp.Flags |= CspProviderFlags.UseMachineKeyStore | CspProviderFlags.UseNonExportableKey; // Fix
var rsaPrivate = new RSACryptoServiceProvider(csp);
rsaPrivate.ImportParameters(rsaPriv.ExportParameters(true));
...

If this is done, the Yes, export the private key option is disabled in the wizard.

Note that in the posted code that flag is also set, but it is set too late, namely after the certificate has been saved to the store.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM