Каково обоснование для всех различных X509KeyStorageFlags?
Сегодня коллега обнаружил еще одну ошибку, связанную с этим! В прошлом я обнаружил, что эти флаги действительно расстраивают, потому что если вы ошибаетесь при создании экземпляров объектов X509Certificate2, или при их экспорте, или сохранении их в магазине X509, вы можете столкнуться с ситуациями со всевозможными странными ошибками, такими как:
- неожиданно не может сказать NETSH.exe или ASP.net использовать определенный сертификат SSL [по его отпечатку], даже если у вас есть этот сертификат в вашем машинном магазине
- неожиданно вы можете экспортировать данные сертификата, но они экспортируются без использования закрытого ключа с помощью.Export()
- неожиданно ваши модульные тесты начинают давать сбой в новой версии Windows, по-видимому, потому что вы не использовали правильные флаги
Да, они документированы и все (и некоторые из документации, кажется, имеют смысл), но почему это должно быть так сложно?
1 ответ
Главным образом, это должно быть сегодня так сложно, потому что вчера это было так сложно, и никто не придумал ничего проще.
Я не могу придумать линейное повествование здесь, поэтому, пожалуйста, терпите переплетение, которое требуется.
Что такое файл PFX/PKCS#12?
Хотя я не могу полностью сказать, каково происхождение PFX, есть подсказки в именах функций Windows для их чтения и записи: PFXImportCertStore и PFXExportCertStore. Они содержат много отдельных объектов (сертификаты, закрытые ключи и другие вещи), которые могут использовать идентификаторы свойств для взаимосвязи. Похоже, они разработаны как механизм экспорта / импорта для всего хранилища сертификатов, как и все CurrentUser\My. Но поскольку одним из видов хранилищ является "хранилище памяти" (произвольная коллекция), импорт / экспорт.NET имеет смысл, но возникает некоторая сложность (до.NET).
Закрытые ключи Windows
Windows поддерживает множество различных мест для закрытых ключей, но для устаревшего крипто-API они сводятся к схеме адресации из четырех частей:
- Наименование криптографического провайдера
- Наименование ключа контейнера
- Идентификатор, если это машинно-относительный ключ или пользовательский ключ
- Идентификатор, если это ключ "подписи" или "обмен".
Это было упрощено до схемы с 3 частями для CNG:
- Наименование хранилища
- Наименование ключа
- Идентификатор того, является ли это ключом, относящимся к компьютеру, или ключом, относящимся к пользователю.
Зачем нужен идентификатор машины или нет?
CAPI и CNG поддерживают непосредственное взаимодействие с именованными ключами. Таким образом, вы создаете ключ с именем "EmailDecryption". Другой пользователь в системе создает ключ с тем же именем. Должно ли это работать? Ну, наверное. Итак, хазз, это так! Отдельные ключи, потому что они хранятся в контекстах, привязанных к пользователю, который их создал.
Но теперь вам нужен ключ, которым могут пользоваться несколько пользователей. Это не то, что вы обычно хотите, так что это не по умолчанию. Это вариант. CRYPT_MACHINE_KEYSET
флаг родился.
Я скажу здесь, что я слышал, что прямое использование именованных ключей теперь не рекомендуется; команда CAPI/CNG очень предпочитает ключи с именами GUID и то, что вы взаимодействуете с ними через хранилища сертификатов. Но это часть эволюции.
Что делает импорт PFX?
PFXImportCertStore копирует все сертификаты из PFX в предоставленное хранилище. Он также импортирует ( CryptImportKey или BCryptImportKey, в зависимости от того, что ему нужно). Затем для каждого импортированного ключа он находит (через значения свойств в PFX) соответствующий сертификат и устанавливает свойство в представлении хранилища сертификатов для "это мой 4-компонентный идентификатор" (ключи CNG просто устанавливают 4-й ключ). часть к 0); что действительно все, что сертификат знает о своем секретном ключе.
(PFX - очень сложный формат файла, это описание верно при условии, что ни одна из "странных частей" не используется)
Ключевые жизни
Закрытые ключи Windows живут вечно или до тех пор, пока кто-то их не удалит.
Поэтому, когда PFX импортирует их, они живут вечно. Это имеет смысл, если вы импортировали в CurrentUser\My. Это имеет меньше смысла, если вы делали что-то временное.
.NET переворачивает отношения / делает это слишком легким
Дизайн Windows (в основном) заключается в том, что вы взаимодействуете с хранилищами сертификатов, а из сертификатов вы получаете сертификаты. .NET появился позже, и (предположительно, основываясь на том, что на самом деле делали приложения), сделал сертификаты объектом верхнего уровня и хранит что-то вторичное.
Поскольку сертификаты Windows (которые действительно являются "элементами хранилища сертификатов") "знают", каков их закрытый ключ, сертификаты.NET "знают", каков их закрытый ключ.
Да, но диспетчер сертификатов MMC говорит, что он может экспортировать сертификат со своим закрытым ключом (в PFX), почему конструктор сертификата не может принимать эти байты в дополнение к формату "просто сертификат"? Хорошо, теперь это возможно.
Согласование продолжительности жизни
Вы открываете несколько байтов как X509Certificate/X509Certificate2. Это PFX без пароля (любым из возможных способов). Вы видите, что он не тот, и позволяете сертификату отправляться на сборщик мусора. Этот закрытый ключ живет вечно, поэтому ваш жесткий диск медленно заполняется, а доступ к хранилищу ключей становится все медленнее и медленнее. Тогда вы злитесь и переформатируете свой компьютер.
Это кажется плохим, так что.NET делает, когда (поле) сертификата получает сборщик мусора (фактически, завершен), он говорит CAPI (или CNG) удалить ключ. Теперь все работает как положено, верно? Ну, пока программа не прекратит работу ненормально.
О, вы добавили его в постоянный магазин? Но я собираюсь удалить закрытый ключ после того, как новый объект хранилища сертификатов "знает", как найти закрытый ключ. Это кажется плохим.
Войти X509KeyStorageFlags.PersistKeySet
PersistKeySet говорит: "не делайте этого, удаляя вещи". Это когда вы собираетесь добавить сертификат в X509Store.
Если вы хотите выполнить то же поведение без указания флага, вызовите Environment.FailFast или отключите компьютер после выполнения импорта.
О том, что машина или пользователь бит
В.NET вы можете легко получить сумку сертификатов в коллекции и позвонить Export
в теме. Что делать, если у некоторых есть машинные ключи, а у других - пользовательские ключи? PFXExportCertStore на помощь. Когда машинный ключ экспортируется, он записывает идентификатор, который говорит, что это был машинный ключ, поэтому импорт возвращает его в то же место.
Ну обычно. Возможно, вы экспортировали ключ компьютера с одной машины, и вы хотите просто проверить его как неадминистративный на другой машине. Хорошо, вы можете указать CRYPT_USER_KEYSET
ака X509KeyStorageFlags.UserKeySet
,
О, вы создали это как пользователь на одной машине, но хотите, чтобы это было как ключ машины на другой? Хорошо. CRYPT_MACHINE_KEYSET
/ X509KeyStorageFlags.MachineKeySet
,
Мне действительно нужны "временные" файлы?
Если вы просто проверяете файлы PFX или иным образом хотите работать с ними на временной основе, зачем вообще писать ключ на диск? Хорошо, говорит Windows Vista, мы можем просто загрузить закрытый ключ непосредственно в объект криптографического ключа, и мы сообщим вам указатель.
PKCS12_NO_PERSIST_KEY
/ X509KeyStorageFlags.EphemeralKeySet
Я хотел бы думать, что если бы в Windows была эта функция в NT4, то это было бы по умолчанию для.NET. Сейчас это не может быть значением по умолчанию, потому что слишком многое зависит от того, как "нормальный" импорт работает, чтобы определить, можно ли использовать закрытый ключ.
Как насчет последних двух?
Режим PFXImportCertStore по умолчанию состоит в том, что закрытые ключи не должны реэкспортироваться. Чтобы сказать, что это неправильно, вы можете указать CRYPT_EXPORTABLE
/ X509KeyStorageFlags.Exportable
,
CAPI и CNG поддерживают механизм, в котором программным ключам может потребоваться согласие или пароль перед использованием закрытого ключа (например, запрос PIN-кода для смарт-карты), но вы должны заявить, что при первом создании (или импорте) ключа, Таким образом, PFXImportCertStore позволяет вам указать CRYPT_USER_PROTECTED
(и.NET выставляет его как X509KeyStorageFlags.UserProtected
).
Последние два действительно имеют смысл только для PFX "с одним закрытым ключом", потому что они применяются ко всем ключам. Они также не охватывают весь спектр опций, которые могли иметь исходные ключи... и CNG, и CAPI поддерживают "архивируемые" ключи, что означает "экспортируемый один раз". Пользовательские ACL на машинных ключах также не получают никакой поддержки в PFX.
Резюме
Для сертификата (или коллекции сертификатов) все просто. Как только закрытые ключи задействованы, все становится беспорядочным, и абстракция над сертификатом Windows (хранилищами) становится немного тоньше, и вам необходимо знать модель персистентности и модель хранения.