Как программно создать неэкспортируемый закрытый ключ с сертификатом X509 в хранилище ключей Windows (C#)

Я пытаюсь создать закрытый ключ и добавить сертификат (самоподписанный или подписанный из ЦС), где я должен иметь возможность экспортировать только сертификат и сделать закрытый ключ неэкспортируемым в С#. То есть, если кто-то попытается экспортировать сертификат из certmgr, опция экспорта будет отключена, как на этой картинке:

Мне нужна такая же неэкспортируемая опция программно на С# при ее создании. Закрытый ключ обычно становится неэкспортируемым, когда файл .pfx/.p12 устанавливается с помощью расширений Crypto Shell, когда снят флажок «Пометить этот ключ как экспортируемый».

Я могу успешно создавать пары ключей и добавлять записи сертификатов в хранилище ключей Windows. Но да, опция экспорта закрытого ключа всегда становится доступной, то есть я не могу запретить экспорт закрытого ключа. Я пробовал это -

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

И когда я пытаюсь экспортировать сертификат, я получаю вариант «Да, также экспортировать закрытый ключ», как показано на рисунке ниже.

Есть ли способ сделать закрытый ключ неэкспортируемым, как первое изображение, при его программном создании? Буду признателен за любые подсказки, ссылки или примеры кода. Спасибо.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
96
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Чтобы закрытый ключ нельзя было экспортировать, при импорте ключа необходимо дополнительно установить CspProviderFlags.UseNonExportableKey:

...
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));
...

Если это сделать, параметр Да, экспортировать закрытый ключ будет отключен в мастере.

Обратите внимание, что в выложенном коде этот флаг тоже установлен, но установлен слишком поздно, а именно после сохранения сертификата в хранилище.

Другие вопросы по теме