Вставка сертификата (с закрытым ключом) в корне, хранилище сертификатов LocalMachine не выполняется в .NET 4

У меня возникли проблемы с вставкой нового сертификата CA с закрытым ключом в хранилище корневого сертификата localmachine.

Вот что происходит:

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

Сертификат вставляется, и все это выглядит денди: (см.!) note it says it has a private key

Примечание: говорится, что у него есть закрытый ключ.

Итак, вы сказали бы, что он сможет найти его с помощью 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 

Это мило... НО ЭТО НЕПРАВИЛЬНО!! (Ссылка 2 глупых собак)

И диалог экспорта сертификатов дает мне это очень хорошее сообщение: alt text

Этот код запускается при выдаче имени администратору с помощью этого фрагмента: нажмите здесь

Я просто хотел бы знать, ПОЧЕМУ?

(протестирован на Windows Server 2008 R2 и Windows 7)

Я буду проклят!

Работает, когда я компилирую его в v3.5!!!!

Что делать?

Ответ 1

У меня была точно такая же проблема, и решение оказалось очень простым. Все, что мне нужно было сделать, это передать

X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet

до X509Certificate2 ctor. Теперь вы используете DotNetUtilities для преобразования сертификата bouncycastle в .net, но вспомогательный метод создает сертификат .net с DefaultKeySet (вместо MachineKeySet + PersistKeySet ).

И расположите секретный ключ следующим образом:

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

var rsaProvider = new RSACryptoServiceProvider(cspParams);

Надеюсь, это поможет.

Ответ 2

Мне кажется, вы должны импортировать ключ немного другим способом. См. http://support.microsoft.com/kb/950090 для примера.

Кроме того, мне не очень удобно сохранять закрытый ключ в UseMachineKeyStore. В большинстве случаев вам нужно импортировать сертификат с закрытым ключом в "Мой магазин" некоторого пользователя и импортировать только в сертификат Root без закрытого ключа.

Вам нужно сохранить закрытый ключ в хранилище ключей от машины, чтобы вы, по крайней мере, защищали ключ для чтения только для некоторых выбранных пользователей, а не для всех. Контейнер ключей - это всего лишь файл в файловой системе (см. Файлы в diriectory "% ALLUSERSPROFILE%\Microsoft\Crypto\Keys" ), который имеет дескрипторы безопасности, такие как другие файлы в NTFS. Чтобы изменить дескрипторы дескрипторов файлов, вы можете использовать свойство CspKeyContainerInfo.CryptoKeySecurity и AddAccessRule, RemoveAccessRule и т.д.

ОБНОВЛЕНО. Прежде всего, извините за длинный ответ.

Я мог бы разделить ваш код программы в двух частях. В первой части вы создаете самозаверяющий сертификат, который может использоваться как сертификаты ЦС, и вы сохраняете его как файл rootcert.pfx. Во второй части вы импортируете сертификат, но используйте RSACryptoServiceProvider, заполненный свойствами предыдущего созданного ключа, вместо использования rootcert.pfx.

Я предлагаю заменить вторую часть кода на более стандартный и простой код: импортировать сертификат с закрытым ключом из rootcert.pfx, как описано в http://support.microsoft.com/kb/950090. Он работает очень хорошо.

Я не использую сам BouncyCastle, поэтому я не мог прокомментировать первую часть вашего кода, но в целом, что вы делаете в коде, который вы могли бы сделать и в отношении MakeCert.exe из Windows SDK. Вы можете выполнить следующие действия

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"

Затем вы можете экспортировать сертификат с закрытым ключом или без него в отношении оснастки сертификата (для mmc.exe). В приведенном выше примере я не ограничиваю CA для некоторых специальных EKU, поэтому вы можете использовать его без каких-либо ограничений, но если вам нужны ограничения, вы можете просто добавить дополнительные параметры MakeCert.exe. Вы также можете использовать MakeCert.exe для создания другого сертификата, подписанного с сертификатом CA. Таким образом, вы можете сделать небольшую PKI только для MakeCert.exe.

Мне кажется, что создание сертификата действительно является отдельной частью вашего кода. Основная проблема заключается во второй части.

Если вы хотите импортировать сертификат CA, вы должны принять во внимание некоторые важные вещи:

  • Вы должны импортировать его в Root или AuthRoot в localMachine на каждом (или многих) компьютерах вашей организации, но вы должны импортировать сертификат без закрытого ключа. Вы можете сделать это в отношении следующих

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

  • Вам следует импортировать сертификат CA с закрытым ключом на компьютер с одним компьютером и только для пользователя, который выдаст другие сертификаты (кто будет подписывать новые сертификаты с закрытым ключом ЦС). Для импорта сертификата в хранилище сертификатов My используется CurrentUser. Таким образом, код на компьютере может выглядеть как

следующее:

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

Код будет работать, если вы измените StoreName.My и StoreLocation.CurrentUser на другие значения, но я не рекомендую вам это делать.

В общем, импорт сертификатов в .NET-код выглядит немного странным и не показывает, что будет сделано под капотом. Windows знает только Key Containers, где секретные ключи (чтобы быть именно парой ключей) будут сохранены в отношении CSP и Хранилища сертификатов, где будут сохранены сертификаты (см. http://msdn.microsoft.com/en-us/library/bb204781.aspx о местонахождении магазина). Чтобы иметь возможность сохранять информацию о контейнере ключей в хранилище сертификатов, Microsoft представила так называемый расширенные свойства сертификата. Если вы используете в .NET-свойствах X509Certificate2, например Thumbprint, FriendlyName, HasPrivateKey, Archived и т.д., Вы работаете с расширенными свойствами сертификата. Поэтому я рекомендую вам дважды импортировать сертификат CA. Один в Root или AuthRoot без настройки CERT_KEY_PROV_INFO_PROP_ID Сертификат расширенных свойств и еще раз в My сохранить с помощьюнастройка информации о месте Key Container с закрытым ключом (CERT_KEY_PROV_INFO_PROP_ID). Кроме того, вы можете рассмотреть возможность удалить закрытый ключ непосредственно после использования, импортировать его, только если вам действительно нужно его использовать, а не удерживать его навсегда. Все это важно для обеспечения лучшей безопасности.

Ответ 3

Я столкнулся с этой проблемой, и кажется, что даже пользователь, с которым вы используете инструмент FindPrivateKey, не имеет доступа к ключу, и поэтому вы получите сообщение "Невозможно получить личное имя файла ключа". Вы можете запустить инструмент как процесс LocalSystem.

Дополнительная информация здесь:

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

Динко

Ответ 4

новый X509Certificate2 (localPFXPath, inputPass, X509KeyStorageFlags.MachineKeySet и X509KeyStorageFlags.PersistKeySet); с и вместо | работал на меня.

Ответ 5

Обычно у сертификатов в корне не будет закрытого ключа для управления. Вы должны импортировать в мою папку, если вы связываете ключ в веб-запросе. У меня есть исключение TLS/SSl, где у меня есть цепочка клиентских сертификатов. Если вы храните всю цепочку сертификатов в My store, я избавился от этого исключения. Если проблема связана с учетными записями пользователей. Утилита для хранения сертификатов использует текущую учетную запись пользователя, и фактическое приложение запускается на системной учетной записи.

Ответ 6

Основная проблема заключается в том, что API сертификатов .NET - это всего лишь обертка вокруг диспетчера сертификатов С++ advapi32 api, поэтому вы не можете указать все параметры, которые передаются этому api, который фактически отвечает за прикрепление сертификата к Windows cert хранит и сохраняет ключи. Суть в том, что опция "UseMachineStore" должна быть передана в CspProviderFlags, которая, в свою очередь, будет передана в CAPI.CRYPT_MACHINE_KEYSET. Это маленький парень, который определяет, сохраняется ли ключ для реального или нет. Кажется, есть несколько разных причин, почему этот параметр не устанавливается, даже если вы установили X509KeyStorageFlags.PersistKeySet и MachineKeySet и Exportable. Все эти параметры доступны только до тех пор, пока глупый ключ остается в папке C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys \. Если CRYPT_MACHINE_KEYSET не устанавливается во время импорта, то advapi32 удаляет ключ, как только дескриптор сертификата будет удален GC.

Решение. Добавьте сертификат в доверенный корень. Прежде чем импортировать сертификат в хранилище личных машин. При чтении журналов из CAPI2 я фактически вижу два вызова "Объектов X509" каждый раз, когда сертификат "Импортирован". У каждого всегда есть <Flags value="20" CRYPT_MACHINE_KEYSET="true"/> (что мы хотим), но другое не делает UNLESS "Проверка цепочки" не возвращает ошибок. Таким образом, похоже, что advapi32 проверяет "достоверность" сертификата и возвращает исключение, которое проглатывается X509Certificate2 (Мне нравится, сколько пустых блоков уловов у них есть в этом коде), или advapi32 только в одностороннем порядке решает не сохранять ключи для ненадежных сертификатов. (Кстати, я подозреваю, что это изменение поведения в период с 2008 по 20012 год, но я этого не доказал.) Чтобы обойти это, я добавил код проверки на мой код, чтобы добавить сертификат, который, если Эмитент равен Субъекту ( это самозаверяющий сертификат), затем добавьте сертификат в Корневую область, прежде чем добавлять его в My.

    if (certificate.Issuer.Equals(certificate.Subject))
    {
        using (X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine)) { 
            store.Open(OpenFlags.ReadWrite);
            store.Add(certificate);
            store.Close();
        }
    }

    using (X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine)){ 
        store.Open(OpenFlags.ReadWrite);
        store.Add(certificate);
        store.Close();
    }

Примечание. Я обнаружил, что это необязательно, если вы используете сертификат, в котором не есть уже существующий идентификатор ключевого слова. Так или иначе, когда вы запускаете api, чтобы на самом деле генерировать SKI вместо того, чтобы передавать его, он вызывает условие, чтобы передать волшебный флаг CRYPT_MACHINE_KEYSET в advapi32.