Contents of this directory is archived and no longer updated.

Posts on this page:

Примечание: данный материал публикуется как обязательный для знания IT-специалистами, которые занимаются или только собираются заниматься темой PKI (Public Key Infrastructure).

Большинство системных администраторов считают, что планирование списков отзыва сертификатов (Certificate Revocation ListCRL) и файлов сертификатов самих серверов CA — это элементарная вещь. Но практика показывает, что очень многие из них сильно заблуждаются. Поэтому предлагаю немного повременить с CryptoAPI и поговорить о немного более насущных вещах — рекомендации по планированию публикации списков CRL и сертификатов CA (Certification Authority), которые используются certificate chaining engine для построения и проверок цепочек сертификатов. О том, как работает этот движок можете почитать пост: Certificate Chaining Engine — как это работает.

Куда и как публиковать файлы CRL и CRT?

Как известно, каждый выданный в CA сертификат (кроме самоподписанных сертификатов. Корневой сертификат так же является самоподписанным сертификатом) содержит 2 расширения:

  1. В расширении «CRL Distribution Points (CDP)» хранятся ссылки на CRL издавшего конкретный сертификат CA;
  2. В расширении «Authority Information Access (AIA)» хранятся ссылки на сертификат CA, издавшего конкретный сертификат. А для сертификатов, выданных CA под управлением Windows Server 2008 и выше — могут содержаться ссылки на OCSP Responder (см. OCSP (часть 1) и OCSP (часть 2))

Эти ссылки берутся не из воздуха, а из настроек CA. Вот как они выглядят для дефолтной инсталляции Certificate Services:

Default CDP Default AIA

В принципе, эти настройки годятся для нормальной работы certificate chaining engine в небольших сетях с одним лесом и доменом без сайтов (или с сайтами, соединённых быстрыми каналами). Если сеть состоит из нескольких доменов (или лесов с настроенным Cross-forest enrollment) и сайтами, соединённых не очень быстрыми каналами, то эти настройки уже могут приводить к сбоям построения и проверок цепочек сертификатов. Я не буду рассказывать, что означают галочки, т.к. с ними вы можете ознакомиться в статьях CRL Publishing Properties и AIA Publishing Properties, а приступлю сразу к разбору путей.

Первый путь указывает путь файловой системы, куда физически публикуются файлы CRL и CRT. Следующая ссылка (LDAP://{LDAP path}) указывает, точку публикации CRL и CRT в Active Directory. Так же эти пути будут прописаны во всех выдаваемых сертификатах. Третья ссылка (HTTP://{URL}) указывает URL, по которому клиенты могут скачать файл через HTTP и этот URL будет включён в расширение CDP/AIA всех выдаваемых сертификатов. Последняя ссылка ничего не делает и добавлена в целях дополнительной точки публикации файлов CRL/CRT в сетевой папке. Почему эти настройки не оптимальны для больших сетей?

Вот как будут выглядеть расширения CDP и AIA в выдаваемых сертификатах при таких настройках:

  • CDP

[1]CRL Distribution Point
     Distribution Point Name:
          Full Name:   
  URL=ldap:///CN=Contoso%20CA,CN=DC1,CN=CDP,CN=Public%20Key%20Services,CN=Services,CN=Configuration,DC=contoso,DC=com?certificateRevocationList?base?objectClass=cRLDistributionPoint
               URL=http://dc1.contoso.com/CertEnroll/Contoso%20CA.crl

  • AIA

[1]Authority Info Access
     Access Method=Certification Authority Issuer (1.3.6.1.5.5.7.48.2)
     Alternative Name:   
  URL=ldap:///CN=Contoso%20CA,CN=AIA,CN=Public%20Key%20Services,CN=Services,CN=Configuration,DC=contoso,DC=com?cACertificate?base?objectClass=certificationAuthority
[2]Authority Info Access
     Access Method=Certification Authority Issuer (1.3.6.1.5.5.7.48.2)
     Alternative Name:
          URL=http://dc1.contoso.com/CertEnroll/DC1.contoso.com_Contoso%20CA.crt

Как вы видите, ссылки в расширениях расположены в том же порядке, что и в настройках CA.

Это важно знать, поскольку certificate chaining engine (сокращённо назовём его CCE) будет проверять ссылки в том порядке, в котором они приведены в расширениях сертификата. Т.е. сперва будет пробовать скачать файл из Active Directory в течении 10 секунд. Если за 10 секунд файл не скачается, CCE будет пытаться скачать указанный файл по следующей ссылке (HTTP). При этом времени на это будет отводиться в 2 раза меньше (т.е. 5 секунд), чем в предыдущей попытке. И так будет происходить с каждой последующей ссылкой, пока не будет добыт файл, закончатся ссылки или отвалится по таймауту. На обработку каждого расширения для CCE выделяется ровно 20 секунд.

Уже на данном этапе видно, что любой недоменный клиент (будь то смартфон, изолированная рабочая станция в интернете, etc.) при попытке скачать файл может терять до 10 секунд на обработку первой ссылки, которая всегда завершится неудачей. Следовательно, первой ссылкой в CDP/AIA должна быть ссылка, которая использует универсальный для всех протокол (это должен быть HTTP), не смотря на то, что в домене, где расположен CA доступ через LDAP будет чуточку быстрее.

Второй момент заключается в репликации объектов AD. После того как CRL/CRT были опубликованы в Active Directory, клиенты об этом узнают не сразу, т.к. здесь вступает фактор репликации AD. Поскольку все объекты PKI публикуются в AD в разделе forest naming context, то эти данные реплицируются не только в прелелах текущего домена, но и всего леса. Поэтому задержки в появлении новых файлов у клиентов могут быть очень значительными и достигать нескольких часов. Задержки могут составлять время до двух полных циклов полной репликации в лесу. А если у вас используется cross-forest enrollment, то там ситуация будет ещё хуже, поскольку это ещё будет зависить от периодичности репликации объектов PKI между лесами (AD не поддерживает репликацию между лесами и репликация объектов PKI выполняется вручную) и уже может достигать нескольких суток. По этой причине рекомендуется либо вовсе отказаться от публикации CRL/CRT в AD и включения этих ссылок в сертификаты, либо располагать следом за более доступными протоколами.

С HTTP тоже не всё так идеально, как это может показаться с первого взгляда. Совсем не обязательно, чтобы сервер CA выполнял и роль веб-сервера (хотя, только для внутреннего использования это допустимо с определёнными оговорками). Будет лучше даже если файлы CRL/CRT будут копироваться как на внутренний, так и на внешний веб-серверы. В идеале эти файлы должны копироваться как минимум на 1-2 внутренних и 1-2 внешних веб-сервера для обеспечения высокой доступности. В таких случаях уже используется 4-я ссылка в настройках CA, которая должна указываь на шару DFS, чтобы файлы автоматически распространялись по веб-серверам. И вот здесь мы снова сталкиваемся с латентностью репликации DFS между серверами. Если все схемы публикации CRL/CRT подвержены латентности репликации, то как с этим бороться, чтобы файлы были всегда в актуальном состоянии?

Примечание: хоть CCE поддерживает скачивание CRL и CRT с HTTPS ссылок, делать это категорически нельзя, иначе CCE свалится в бесконечный цикл проверки сертификатов.

Периодичность публикации и обновления файлов CRL и CRT

По умолчанию в Windows Server основные CRL (Base CRL) публикуются раз в неделю и инкрементные CRL (Delta CRL) публикуются раз в сутки. Файлы сертификатов CA обычно обновляются через промежутки равные времени жизни сертификата самого CA (или чаще, если сертификат CA обновляется внепланово). Если сертификаты CA нужно обновлять достаточно редко (раз в несколько лет) и к этому следует готовиться отдельно, то обновление CRL происходит автоматически без вмешательства администратора и здесь требуются особые корректировки о которых мы сейчас и поговорим.

Если посмотреть на CRL, то мы увидим следующее:

Certificate Revocation List Information  Certificate Revocation List Information

Сейчас нас будут интересовать только 3 поля:

  • Effective Date — указывает дату и время с которого данный CRL считается действительным и оно по умолчанию на 10 минут меньше, чем фактическое время, чтобы покрыть издержки рассинхронизации времени между сервером и клиентом;
  • Next Update — указывает дату и время, когда заканчивается срок действия конкретного CRL и он считается недействительным;
  • Next CRL Publish — указывает дату и время публикации следующего списка CRL.

Примечение: время в этих полях указывается в формате UTC без учёта временных зон.

Обычно время Next Update и Next CRL Publish одинаково. Но у меня, как вы видите на картинках, Next Update равен 8 дням (дефолтный срок действия CRL), а вот Next CRL Publish равен 7 дням после начала действия CRL. Т.е. CRL обновляется каждые 7 дней, но срок действия равен 8 дням (период публикации CRL + время overlap). Это сделано как раз для покрытия расходов времени (издержки репликации) распространения списков отозванных сертификатов от сервера CA до точек, откуда клиенты будут скачивать CRL. Как это делается?

Для этого в реестре на сервере CA по пути HKLM\System\CurrentControlSet\Services\CertSvc\Configuration\CA Name существует 4 ключа:

  • CRLOverlapUnits — указывает время до истечения срока действия текущего основного CRL, за которое будет публиковаться новый основной CRL.
  • CRLOverlapPeriod — указывает единицу измерения этого времени для основного CRL
  • CRLDeltaOverlapUnits — указывает время до истечения срока действия текущего инкрементального (если используется) CRL, за которое будет публиковаться новый инкрементальный CRL
  • CRLDeltaPeriodPeriod — указывает единицу измерения этого времени для инкрементального CRL.

Если вы используете LDAP ссылки в расширениях CDP/AIA сертификатов и/или у вас есть латентность репликации файлов между веб-серверами, то вы должны отрегулировать это время, которое должно быть не менее, чем максимальное время репликации каталога AD во всём лесу или DFS как для основных, так и для инкрементальных CRL (если они у вас используются). Данную операцию можно автоматизировать утилитой certutil:

certutil –setreg ca\CRLOverlapUnits 1
certutil –setreg ca\CRLOverlapPeriod "days"
certutil –setreg ca\CRLDeltaOverlapUnits 8
certutil –setreg ca\CRLDeltaOverlapPeriod "hours"
net stop certsvc & net start certsvc

Примечание: CRLOverlap не может быть больше периодичности публикации BaseCRL, а CRLDeltaOverlap не может быть больше 12 часов.

в результате новые основные CRL будут публиковаться за сутки до истечения времени действия предыдущего основного CRL и новые инкрементальные CRL будут публиковаться за 8 часов до истечения предыдущего. Этим самым вы сможете гарантировать, что клиенты всегда получат актуальные CRL'ы. Более детальные сведения о порядке подсчёта временных интервалов публикации CRL и срока их действия можно ознакомиться здесь: How EffectiveDate (thisupdate), NextUpdate and NextCRLPublish are calculated

Общая периодичность публикации самих файлов CRL зависит от количества отзываемых сертификатов за некоторый промежуток времени (обычно измеряется в неделях). Если сертификаты отзываются десятками в неделю, то есть смысл сократить периодичность публикации основных CRL до 2 раз в неделю и Delta CRL до 2-х раз в сутки. Если сертификаты отзываются редко (реже, чем раз в неделю), то периодичность публикации Base CRL можно увеличить до 2-4 недель, а от Delta CRL можно даже отказаться или публиковать раз в неделю. Но это касается только Issuing или Online CA. Для Offline CA рекомендации будут чуть другие. Поскольку Offline CA выдают сертификаты только другим CA и большую часть времени выключены, для них следует отключить публикацию Delta CRL (установив параметр CRLDeltaPeriodUnits в ноль), а основные CRL публиковать раз в 3-12 месяцев. Хоть это и Offline CA, но на него так же распространяются требования по корректировке времени публикации и срока действия CRL.

CDP и AIA в корневых сертификатах

Как уже отмечалось, расширения CDP и AIA содержат ссылки на CRL/CRT издавшего конкретный сертификат CA, то с корневыми сертификатами будет немного иначе. Если быть точнее, то в корневых сертификатах этих расширений быть не должно совсем. Почему? Windows Server 2003 по умолчанию добавлял эти расширения в самоподписанный сертификат, когда CA конфигурировался как корневой CA. В нём AIA содержал ссылки по которым можно было скачать этот же сертификат. Очень прикольно :-).

А CDP — не менее прикольно. Корневые сертификаты всегдя являются конечной точкой самой цепочки и доверия этой цепочки сертификатов. К корневым сертификатам доверие всегда устанавливается явное путём помещения сертификата в контейнер Trusted Root CAs (а ко всем остальным сертификатам устанавливается неявное доверие через цепочку сертификатов). Следовательно отменить доверие корневому сертификату CA можно только одним способом — удалить сам сертификат из контейнера Trusted Root CAs и никак иначе. Вторая проблема заключается в том, что все CRL подписываются закрытым ключом самого CA. А теперь предположим, что CA отозвал свой сертификат и поместил его в CRL. Клиент скачивает CRL и видит, что сертификат CA отозван. Можно предположить, что это всё и никакой проблемы здесь нет. Однако, получается, что CRL подписан отозванным сертификатом и мы не можем доверять этому CRL как и считать сертификат CA отозванным. Именно поэтому начиная с Windows Server 2008 при установке корневого CA, эти расширения по умолчанию уже не включены в корневой сертификат. А для Windows Server 2003 приходилось лепить костыли в файле CAPolicy.inf:

[AuthorityInformationAccess]
Empty = true
[CRLDistributionPoint]
Empty = true

Как показывает практика, очень много администраторов игнорируют такие вещи и делают всё простым Next-Next, за что они должны гореть в аду. Но не только простые Windows-админстраторы, а луноходы (фаны линукса) тоже должны там гореть. Как живой пример бардака в сертификатах приведу компанию StartCom, которая в сентябре 2009 получила право выдавать EV (Extended Validation) сертификаты и вот их корневой сертификат: http://www.startssl.com/sfsca.crt

Мало того, что у них в корневом сертификате есть расширение CDP, но и с ссылками на CRL у них в цепочке тоже бардак мутный. Есть подозрение, что это сделано для поддержки какой-то ветки линукса (для совместимости или просто как костыль), но вот такой он опенсорс. Так что не каждый публичный и коммерческий CA следует всем бест-практисам. А вам советую им следовать, тогда меньше вероятность, что вы потом будете гореть в аду.

Изменения в существующих инфраструктурах

Изменение путей в уже существующих инфраструктурах вопрос достаточно серьёзный, хоть и прост в реализации. Если вы решились на такой шаг, то следует руководствоваться следующими правилами:

  • пути публикации физических файлов можно располагать в произвольном порядке;
  • новые ссылки к файлам, по которым клиенты будут их скачивать должны располагаться первыми, т.е. с более высоким приоритетом (кроме случаев, когда вы добавляете ссылки только для обеспечения более высокой доступности файлов. Тогда новые ссылки можно просто добавить в хвост уже существующим);
  • если вы собираетесь уйти от существующих ссылок на CRL/CRT, то в для них следует отключить опцию публикации ссылок в сертификатах. Однако, до окончания действия сертификата CA вы будете обязаны их поддерживать в рабочем состоянии, т.к. они содержатся в уже выданных сертификатах. А новые ссылки появятся только в сертификатах, которые были выпущены после изменения CDP/AIA.
  • если у вас корневой сертификат уже содержит расширения CDP/AIA, то вы не можете их оттуда убрать до момента обновления корневого сертификата. При обновлении корневого сертификата на Windows Server 2003, вам потребуется создать CAPolicy.inf файл, прописать нужные настройки (например, как уже указано выше с пустыми CDP и AIA). Более подробно про файл CAPolicy.inf можно прочитать по ссылке: http://technet.microsoft.com/en-us/library/cc728279(WS.10).aspx

Новые технологии

С выходом Windows Server 2008 Enterprise Edition вы можете внедрить в своей сети Online Responder для снижения нагрузки на серверы публикации CRL (хоть пути к OCSP публикуются в расширении AIA, к файлам CRT это никакого отношения не имеет). Но даже внедрение OCSP не решает этих проблем, поскольку реализация OCSP в Windows Server основана на регулярном чтении CRL и, следовательно, зависит от латентности репликации AD и/или DFS, а так же этим сервисом могут пользоваться только клиенты начиная с Windows Vista. Хочу отметить один приятный момент. Если изменения ссылок на CRL/CRT скажутся только на новых сертификатах (уже выпущенные сертификаты ничего не будут знать о новых путях в CDP/AIA), то интегрировать OCSP внутри домена/леса с уже существующей инфраструктурой PKI достаточно легко. Все уже выданные сертификаты могут быть проверены на отзыв с использованием OCSP: Managing OCSP Settings with Group Policy.

Заключение

В данном посте я обозначил ключевые моменты в структуированном (как мне кажется) виде, которые следует знать при планировании публикации файлов CRL/CRT и ссылок на них. Как вы видите, внедрение новых технологий пока что не освобождает от знания и соблюдения рекомендаций публикации CRL/CRT в вашей инфраструктуре PKI. Я считаю этот материал достаточным для начального и среднего уровня знаний по теме revocation и chain building и для более детального изучения всего этого процесса уже следует обратиться сюда: Certificate Revocation Checking in Windows Vista and Windows Server 2008.

Слава сиськамПротоколу Сноверу!

>> PowerShell V2 RTM <<

Примечание: данный материал публикуется на правах ТЗ (ТЗТайное Знание) и обязателен для знания администраторам PKI.

В предыдущей части (Certificate chaining engine в PowerShell) мы рассмотрели реализацию certificate chaining engine в .NET в виде класса X509Chain. Но этот рассказ был бы неполным, если мы не поговорили про CAPICOM.Chain. Хоть Microsoft рекомендует использовать .NET, а не CAPICOM для решения данной задачи, я считаю необходимым осветить этот вопрос. Для начала начнём с реализации CAPICOM в PowerShell. По непонятным мне пока причинам мне не удалось завести его в PowerShell на системах Windows Vista и выше, поэтому примеры буду приводить на Windows Server 2003.

PS G:\> $chain = New-Object -ComObject "CAPICOM.Chain"
PS G:\> $chain

Certificates
------------



PS G:\> $chain | gm -MemberType methods


   TypeName: System.__ComObject#{ca65d842-2110-4073-aee3-d0aa5f56c421}

Name                MemberType Definition
----                ---------- ----------
ApplicationPolicies Method     IOIDs ApplicationPolicies ()
Build               Method     bool Build (ICertificate)
CertificatePolicies Method     IOIDs CertificatePolicies ()
ExtendedErrorInfo   Method     string ExtendedErrorInfo (int)


PS G:\>

Так же, как и в .NET у нас есть метод Build(), который запускает certificate chaining engine для указанного сертификата. Но это COM объект и X509Certificate2 объекты не принимает. Поэтому мы средствами этого же CAPICOM получим нужный объект сертификата:

PS G:\> $store = New-Object -ComObject "CAPICOM.Store"
PS G:\> $store.Open(2,"my",128)
PS G:\> $cert = @()
PS G:\> $store.Certificates | %{$cert += $_}
PS G:\> $cert = $cert[1]
PS G:\> $cert


Version       : 3
SerialNumber  : 167D6D32000000000011
SubjectName   : CN=Administrator, CN=Users, DC=sysadmins, DC=lv
IssuerName    : CN=sysadmins-LV-CA, DC=sysadmins, DC=lv
ValidFromDate : 2009.08.07. 16:09:56
ValidToDate   : 2010.08.07. 16:09:56
Thumbprint    : 1FA71B51BCE461BEE24B11AA9EF855ED00DFDFD4
Archived      : False
PrivateKey    :



PS G:\>

Вот такой объект сертификата мы получили. Вот его и пропустим через метод Build():

PS G:\> $chain.Build($cert)
False
PS G:\> $chain.Status()
32
PS G:\> $chain.Certificates


Version       : 3
SerialNumber  : 167D6D32000000000011
SubjectName   : CN=Administrator, CN=Users, DC=sysadmins, DC=lv
IssuerName    : CN=sysadmins-LV-CA, DC=sysadmins, DC=lv
ValidFromDate : 2009.08.07. 16:09:56
ValidToDate   : 2010.08.07. 16:09:56
Thumbprint    : 1FA71B51BCE461BEE24B11AA9EF855ED00DFDFD4
Archived      : False
PrivateKey    :

Version       : 3
SerialNumber  : 3DFFAF914D613282427BD591C1FB586D
SubjectName   : CN=sysadmins-LV-CA, DC=sysadmins, DC=lv
IssuerName    : CN=sysadmins-LV-CA, DC=sysadmins, DC=lv
ValidFromDate : 2009.08.06. 10:21:01
ValidToDate   : 2014.08.06. 10:30:54
Thumbprint    : E82ACC45841280DDEAB9F7847418FA26354457A7
Archived      : False
PrivateKey    :



PS G:\> $chain.ApplicationPolicies()

                                   Name FriendlyName                            Value
                                   ---- ------------                            -----
                                    123 Smart Card Logon                        1.3.6.1.4.1.311.20.2.2
                                    101 Client Authentication                   1.3.6.1.5.5.7.3.2


PS G:\>

Метод вернул False, т.е. наш сертификат не прошёл проверку. Свойство Certificates содержит все сертификаты в текущей цепочке. А метод ApplicationPolicies() показал Application Policies (в прошлом EKU). Ну и метод Status() вернул код ошибки. Все коды ошибок расписаны в описании самого метода. Т.е. ошибка 32 по табличке означает:

CAPICOM_TRUST_IS_UNTRUSTED_ROOT (&H00000020) — The certificate or certificate chain is based on an untrusted root.

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

Когда вышел Remote Desktop Connection 7.0 (который поставляется вместе с Windows 7), Александр Станкевич (MVP: Enterprise Security) заметил очень плохую особенность с ним, а именно — рандомно пропал доступ к терминальным серверам, которые используют SSL для терминальных подключений. В ходе множества проверок было констатировано:

  • цепочка завершалась на доверенном корневом сертификате (через просмотр сертификата);
  • CDP/AIA пути были валидные и CRL не были просрочены;
  • остальные параметры были сконфигурированы верно.

RDP клиент мотивировал отказ в подключении следюущим: «RDP клиент не смог проверить сертификат на отзыв». Причём подключение с предыдущих версий RDC, а так же подключение к TS Web Access через IE было успешным. Как выяснилось на днях, причина была в том, что в новом RDC был изменён алгоритм certificate chaining engine и новая версия RDC больше не доверяла корневым сертификатам в пользовательском хранилище. Переустановка корневого сертификата в компьютерное хранилище решала проблему RDC 7. Но, как я говорил, в Windows 7/2008 R2 было изменено поведение certificate chaining engine в реализации X509Chain. Продемонстрирую небольшую табличку, которая покажет доверие пользовательским корневым сертификатам в CAPICOM и .NET реализациях certificate chaining engine для различных операционных систем:

  X509Chain (.NET) CAPICOM.Chain (CryptoAPI)
Windows XP :yes: :no:
Windows Server 2003 :yes: :no:
Windows Vista :yes: :no:
Windows Server 2008 :yes: :no:
Windows 7 :no: :no:
Windows Server 2008 R2 :no: :no:

Таблица доверия пользовательскким корневым сертификатам различными движками построения цепочки сертификатов

Следует учитывать, что различные приложения могут использовать свой движок для построения цепочек сертификатов, как RDC 7.0 и PKIView.msc использует CAPICOM, а PowerShellX509Chain. Однако, у меня есть уверенность, что существует ещё одна реализация этого движка, которую использует Internet Explorer. Это только мои догадки, но они основаны на том, что Internet Explorer (кроме Windows 7 и выше) использует пользовательский контейнер для проверки SSL веб-сайтов, т.е. по симптомам похоже на X509Chain. Однако, этот класс появился только в .Net Framework 2.0, которого, к примеру в оригинальной инсталляции Windows XP/2003 не было. Либо, как вариант, есть возможность настраивать этот момент в каждой реализации движка построения цепочек, но я пока не нашёл как. Это пока всё, что у меня есть по этому поводу.

Я предлагаю снова поговорить о certificate chaining engine, о котором мы уже говорили в посте Certificate Chaining Engine — как это работает но в рамках его реализации в .NET и PowerShell. В Windows есть несколько реализаций этого chaining engine. Самые популярные:

CAPICOM — вещь несколько стрёмная и её лучше избегать. Тем более мне не удалось завести его в Vista/Windows 7. Реализация в .NET в виде X509Chain ничуть не хуже, но, в то же время, проще. Итак, предлагаю начать с создания этого объекта с помощью New-Object:

[↓] [vPodans] $chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain
[↓] [vPodans] $chain

                 ChainContext ChainPolicy                   ChainStatus                   ChainElements
                 ------------ -----------                   -----------                   -------------
                            0 System.Security.Cryptograp... {}                            {}


[↓] [vPodans] $chain | gm -membertype methods


   TypeName: System.Security.Cryptography.X509Certificates.X509Chain

Name        MemberType Definition
----        ---------- ----------
Build       Method     bool Build(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate)
Equals      Method     bool Equals(System.Object obj)
GetHashCode Method     int GetHashCode()
GetType     Method     type GetType()
Reset       Method     System.Void Reset()
ToString    Method     string ToString()


[↓] [vPodans]

Здесь видно, что метод, который будет строить и проверять цепочку будет называться Build() и в качестве аргумента этот метод принимает только объекты x509Certificate2, а возвращать объект типа Boolean — True/False. Объекты такого типа можно найти в провайдере Certififcates (просто выполнить dir cert:\store\container) или импортировать из файла — Импорт сертификатов в PowerShell. Но сначала мы посмотрим, что здесь можнол настроить. А настроить можно ChainPolicy, представляющий собой объект X509ChainPolicy. Эта политика позволяет задавать порядок проверки отзыва сертификатов, соответствие сертификата каким-то certificate policy, наличие определённых application policy (в прошлом известен нам как EKUExtended Key Usage) и некритичность каких-то ошибок. Мы всё разбирать не будем, а только основное:

[↓] [vPodans] $chain.ChainPolicy


ApplicationPolicy   : {}
CertificatePolicy   : {}
RevocationMode      : Online
RevocationFlag      : ExcludeRoot
VerificationFlags   : NoFlag
VerificationTime    : 17.10.2009 20:35:17
UrlRetrievalTimeout : 00:00:00
ExtraStore          : {}



[↓] [vPodans]

Первое, что может быть интересным — Revocation Mode, который задаёт режим проверки отзыва и может иметь 3 вполне понятных значения, которые перечислены в X509RevocationMode:

[↓] [vPodans] [System.Enum]::GetNames([system.security.cryptography.x509certificates.x509revocationmode])
NoCheck
Online
Offline

Тут очень просто:

  • NoCheck — проверяет цепочку сертификатов без проверки на отзыв любых сертификатов в цепочке;
  • Online — проверяет сертификаты в цепочке скачивая новые списки CRL из свойства CDP сертификата, игнорируя локальный кеш CRL (используется по умолчанию, как это видно в просмотре свойств ChainPolicy)
  • Offline — проверяет сертификаты на отзыв с использованием кешированного CRL (если есть).

Если проверяем сертификаты на отзыв, то мы можем выбрать что именно будем проверять и этот выбор перечислен в X509RevocationFlag:

[↓] [vPodans] [System.Enum]::GetNames([system.security.cryptography.x509certificates.x509revocationflag])
EndCertificateOnly
EntireChain
ExcludeRoot

и здесь тоже всё достаточно понятно:

  • EndCertificateOnly — проверяет на отзыв только сам проверяемый сертификат. Остальные сертификаты в цепочке не проверяются;
  • EntireChain — проверяет на отзыв абсолютно все сертификаты в цепочке включая корневой сертификат. Правила хорошего тона диктуют нам не включать в корневой сертификат CA поля CDP и AIA, поскольку это лишено смысла и в большинстве случаев с этим флагом вы будете получать невалидную цепочку;
  • ExcludeRoot — проверяет на отзыв все сертификаты в цепочке, кроме корневого сертификата, что есть религионзно правильно и этот флаг установлен по умолчанию.

Следующий момент определяет флаги проверки, т.е. по каким именно критериям будет проверяться цепочка сертификатов:

[↓] [vPodans] [System.Enum]::GetNames([system.security.cryptography.x509certificates.x509verificationflags])
NoFlag
IgnoreNotTimeValid
IgnoreCtlNotTimeValid
IgnoreNotTimeNested
IgnoreInvalidBasicConstraints
AllowUnknownCertificateAuthority
IgnoreWrongUsage
IgnoreInvalidName
IgnoreInvalidPolicy
IgnoreEndRevocationUnknown
IgnoreCtlSignerRevocationUnknown
IgnoreCertificateAuthorityRevocationUnknown
IgnoreRootRevocationUnknown
AllFlags

Эти флаги так же достаточно понятны и подробно разжёваны в перечислении X509VerificationFlags.

Примечание: эти флаги показывают что будет исключено из проверки, а не наоборот. По умолчанию проверяется всё (установлен NoFlag).

Как можно задавать эти флаги и режимы, если есть такая потребность? Можно пойти двумя способами — правильным и неправильным, хотя оба рабочие:

[↓] [vPodans] # правильный метод:
[↓] [vPodans] $revflag = [System.Security.Cryptography.X509Certificates.X509RevocationFlag]::EndCertificateOnly
[↓] [vPodans] $chain.ChainPolicy.RevocationFlag = $revflag
[↓] [vPodans] $chain.ChainPolicy


ApplicationPolicy   : {}
CertificatePolicy   : {}
RevocationMode      : Online
RevocationFlag      : EndCertificateOnly
VerificationFlags   : NoFlag
VerificationTime    : 17.10.2009 20:35:17
UrlRetrievalTimeout : 00:00:00
ExtraStore          : {}



[↓] [vPodans] # неправильный метод, но мне он нравится
[↓] [vPodans] $chain.ChainPolicy.RevocationFlag = "excluderoot"
[↓] [vPodans] $chain.ChainPolicy


ApplicationPolicy   : {}
CertificatePolicy   : {}
RevocationMode      : Online
RevocationFlag      : ExcludeRoot
VerificationFlags   : NoFlag
VerificationTime    : 17.10.2009 20:35:17
UrlRetrievalTimeout : 00:00:00
ExtraStore          : {}



[↓] [vPodans]

В принципе, все эти enumeration можно просто указывать в виде строки, а PowerShell уже сам подобъёт его под нужный тип.

Примечание: флаги проверки на отзыв можно указывать только по одному, а флаги исключений можно перечислять через запятую. Это видно по названию класса: если класс указан в единственном числе (X509RevocationFlag), то и значение можно указать только одно, а если во множественном числе (X509VerificationFlags), то их можно указать несколько через запятую.

В принципе, можно делать проверку:

[↓] [vPodans] $valid = (dir cert:\currentuser\my)[1]
[↓] [vPodans] $invalid = (dir cert:\currentuser\my)[0]
[↓] [vPodans] $chain.Build($valid)
True
[↓] [vPodans] $chain.ChainElements | select -expand certificate

Thumbprint                                Subject
----------                                -------
986D375362652FE9E39BA4D042A6B8BA75745998  CN=Administrator, CN=Users, DC=sysadmins, DC=lv
E82ACC45841280DDEAB9F7847418FA26354457A7  CN=sysadmins-LV-CA, DC=sysadmins, DC=lv


[↓] [vPodans]

Я из локального хранилища взял заведомо хороший сертификат и плохой (разумеется, это сертификат с сайта БиЛайн :-)). Метод Build() вернул True, что означает успешную проверку всей цепочки моего сертификата до доверенного корня. А вот что случилось с сертификатом билайна:

[↓] [vPodans] $chain.reset()
[↓] [vPodans] $chain.Build($invalid)
False
[↓] [vPodans] $chain.ChainElements | select -expand certificate

Thumbprint                                Subject
----------                                -------
EB74DA32E865C78FCB853DDA5FE45962098E1B3B  CN=trust.beeline.ru, OU=DIT, O=Vimpelcom, L=Moscow, S=Moscow, C=RU


[↓] [vPodans] $chain.ChainStatus

                                                     Status StatusInformation
                                                     ------ -----------------
                                               PartialChain Sertificesanas kedi nevareja veidot pie uzticamas saknes...
                                    RevocationStatusUnknown Atsauksanas funkcija nevareja parbaudit sertifikata atsa...
                                          OfflineRevocation Atsauksanas funkcija nevareja parbaudit atsauksanu, jo a...


[↓] [vPodans] $chain.ChainStatus | %{$_.statusinformation.trim()}
Sertificesanas kedi nevareja veidot pie uzticamas saknes iestades.
Atsauksanas funkcija nevareja parbaudit sertifikata atsauksanu.
Atsauksanas funkcija nevareja parbaudit atsauksanu, jo atsauksanas serveris bija bezsaiste.
[↓] [vPodans]

Поскольку при каждой проверке сертификаты в ChainElements накапливаются, то после каждой проверки следует очищать объект методом Reset(). Как и следовало ожидать, сертификат билайна вернул False, показывая, что цепочка у этого сертификата имеет проблемы. В случае неуспешной проверки, будет заполняться свойство ChainStatus. Данное свойство содержит все ошибки, которые были костатированы при проверке цепочки. Для меня пока непонятным стал факт, что ошибки он написал на латышском языке, хотя я его об этом не просил. Откуда он это взял — непонятно, но он сказал, что не смог построить цепочку до доверенного корня и функция проверки отзыва провалилась по всем статьям, поскольку пути в CDP сертификата нерабочие.

Примечание: обязательно следует учитывать тот факт, что при построении цепочки сертификатов и проверке доверия класс X509Chain в Windows 7 и Windows Server 2008 R2 работает в контексте LocalSystem и всегда игнорирует пользовательские контейнеры Trusted Root Certification Authorities. Поэтому если корень цепочки не заканчивается на одном из сертификатов Trusted Root CAs хранилища LocalMachine, то цепочка будет считаться недоверенной. Для предыдущих ОС цепочка может заканчиваться на пользовательском хранилище Current User.

В принципе это всё, что вам следует знать про реализацию certificate chaining engine в .NET и его использование в PowerShell. В качестве бонуса прилагаю скрипт для проверки цепочки сертификатов:

#####################################################################
# Test-Certificate.ps1
# Version 1.0
#
# Passes certificate through certificate chaining engine
#
# Vadims Podans (c) 2009
# http://www.sysadmins.lv/
#####################################################################
#requires –Version 2.0

function Test-Certificate {
[CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        $Certificate,
        [System.Security.SecureString]$Password,
        [ValidateSet("NoCheck", "Online", "Offline")]
        [string]$CRLMode = "Online",
        [ValidateSet("EndCertificateOnly", "EntireChain", "ExcludeRoot")]
        [string]$CRLFlag = "ExcludeRoot",
        [ValidateSet("AllFlags", "AllowUnknownCertificateAuthority", "NoFlag", "IgnoreNotTimeValid",
            "IgnoreCtlNotTimeValid", "IgnoreNotTimeNested", "IgnoreInvalidBasicConstraints",
            "IgnoreWrongUsage", "IgnoreInvalidName", "IgnoreInvalidPolicy", "IgnoreEndRevocationUnknown",
            "IgnoreCtlSignerRevocationUnknown", "IgnoreCertificateAuthorityRevocationUnknown",
            "IgnoreRootRevocationUnknown")]
        [string[]]$VerificationFlags = "NoFlag"
    )
    
    begin {
        # создаём объекты X509Certificate2 и X509chain ещё до поступлениея первого объекта сертификата
        $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
        $chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain
        # в X509Chain записываем параметры проверки сертификатов. Как критерии
        # отзыва, так и исключения из проверки
        $chain.ChainPolicy.RevocationFlag = $CRLFlag
        $chain.ChainPolicy.RevocationMode = $CRLMode
        $chain.ChainPolicy.VerificationFlags = $VerificationFlags
        # мини-функция для предоставления понятного вывода на экран о статусе проверки
        # и ошибках в цепочке, если они есть.
        function _getstatus_ ($status, $chain, $cert) {
            if ($status) {
                Write-Host Current certificate $cert.SerialNumber chain and revocation status is valid -ForegroundColor Green
            } else {
                Write-Warning "Current certificate $($cert.SerialNumber) chain is invalid due of the following errors:"
                $chain.ChainStatus | %{Write-Host $_.StatusInformation.trim() -ForegroundColor Red}
            }
        }
    }
    
    process {
        # если аргумент сертификата уже является объектом X509Certificate2, то
        # сразу строим цепочку и получаем статус цепочки вызывая мини-функцию
        if ($_ -is [System.Security.Cryptography.X509Certificates.X509Certificate2]) {
            $status = $chain.Build($_)
            _getstatus_ $status $chain $_
        } else {
        # если аргумент не является объектом X509Certificate2, то это будет путь к файлу
        # если указанный путь не существует, то ругаемся и выходим
            if (!(Test-Path $Certificate)) {Write-Warning "Specified path is invalid"; return}
            else {
            # если путь существует, то проверяем, что это путь файловой системы.
            # если это не так, то ругаемся и выходим
                if ((Resolve-Path $Certificate).Provider.Name -ne "FileSystem") {
                    Write-Warning "Spicifed path is not recognized as filesystem path. Try again"; return
                } else {
                # если предварительные проверки прошли успешно, то выбираем объект файла
                    $Certificate = gi $(Resolve-Path $Certificate)
                # и на основании расширения файла через Switch преобразуем файл в
                # X509Certificate2 объект или объекты
                    switch -regex ($Certificate.Extension) {
                    "\.CER|\.DER|\.CRT" {$cert.Import($Certificate.FullName)}
                    "\.PFX" {
                        if (!$Password) {$Password = Read-Host "Enter password for PFX file $certificate" -AsSecureString}
                        # тут пришлось указать контекст хранилища, куда предполагается сохранить сертификат.
                        # на самом деле мы никуда не будем сохранять объект сертификата, это было сделано
                        # по причине отсутствия подходящего конструктора метода Import(), чтобы
                        # можно было указать только путь к файлу и пароль от PFX
                            $cert.Import($Certificate.FullName, $password, "UserKeySet")
                        }
                    "\.P7B|\.SST" {
                        $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection
                        $cert.Import([system.IO.File]::ReadAllBytes($file.FullName))
                        }
                    default {Write-Warning "Looks like your specified file is not a certificate file"; return}
                    }
                    # полученный объект или объекты сертификатов пропускаем через метод Build() и вызываем
                    # мини-функцию расшифровки статуса проверки
                    $cert | %{
                        $status = $chain.Build($_)
                        _getstatus_ $status $chain $_
                    }
                    # после каждой итерации очищаем объекты сертификата и цепочки
                    $cert.Reset()
                    $chain.Reset()
                }
            }
        }
    }
}

Данный скрипт может принимать аргументы в виде уже готовых объектов X509Certificate2 или с указанием пути к файлу сертификата, например:

dir cert:\currentuser\my | Test-Certificate
Test-Certificate .\mycert.cer

Ну и при желании можно поуказывать там разные параметры и флаги проверки.

Знаете ли вы как подключать IntelliSense к новым командлетам, которые появляются после подключения оснастки? Например, в консоли PowerShell вы подключаете оснастку Windows.ServerBackup и сразу получаете доступ к новым командлетам. В PowerGUI, к сожалению, после подключения оснастки через Add-PSSnapin или Import-Module новые командлеты начинают работать при отладке, но без подсветки и прочих красивостей блондинка-стайл. Мне потребовалось несколько часов, чтобы узнать, в чём тут дело. Из них минут 10 заняла переписка с командой разработчиков PowerGUI (угу, я иногда злоупотребляю своим положением).

Итак, если вы хотите получать IntelliSense для новых командлетов, которые появляются после подключения оснастки или импорта модуля (Import-Module), то нужно сделать: File –> PowerShell Libraries:

Powershell Snapins and Modules in PowerGUI

В окне натыкать галочек и/или кнопкой Add Module добавить модуль/модули и тогда появится IntelliSense и прочие побочные принадлежности в самом редакторе.

Have fun with a script writing with PowerGUI!