Contents of this directory is archived and no longer updated.

Posts on this page:

Update 05.12.2009: разработчики подтвердили баг в ICertAdmin2 интерфейсе. Проблема заключается в том, что после первого запроса указанных PropID они кешируются и при смене контекста конфигурации (Certification Authority), этот кеш не очищается.


Прежде чем продолжать исследование CryptoAPI COM интерфейсов, предлагаю посмотреть несколько полезных ссылок и поговорить о багах в интерфейсе ICertAdmin2.

При использовании интерфейса ICertAdmin2, ICertRequest2D (этот интерфейс мы ещё посмотрим в будущих постах) мы используем различные аргументы для получения свойст CA с использованием метода GetCAProerty(). И вот их где мы можем получить числовые значения этих аргументов:

А теперь поговорим о багах. Как выяснилось, ICertAdmin2::GetCAProperty(), который определён в библиотеке certadm.dll имеет баги с PropID, которые связаны с количественной информацией. Будь то количество сертификатов CA, количество агентов восстановления ключей (Key Recovery Agents) и т.д. И вот в чём это выражается:

  • DC1 — корневой CA с именем Contoso CA. Имеет 2 сертификата CA и одного назначенного агента восстановления;
  • DC2 — подчинённый CA с именем Contoso-DC2-CA. Имеет 1 сертификат CA и ни одного назначенного агента восстановления.

Мы будем получать данные как с локального CA, так и с удалённого CA запуская код с каждого сервера. Кратко об используемых PropID:

  • 0x0B — количество сертификатов CA;
  • 0x19 — общее количество агентов восстановления на CA;
  • 0x18 — общее количество используемых агентов восстановления;

И вот какие результаты мы видим, когда собираем эти свойства с сервера DC1:

[Administrator] # создаём COM объек:
[Administrator] $CertAdmin = New-Object -com CertificateAuthority.Admin.1
[Administrator] # получаем список сертификатов CA на локальном серве
[Administrator] # мы видим 2 сертификата, как положено
[Administrator] $certadmin.GetCAProperty("dc1\contoso ca",0xb,0,1,0)
2
[Administrator] # и он то же самое показывает и для удалённого сервера, хотя там всего 1 сертификат
[Administrator] $certadmin.GetCAProperty("dc2\contoso-dc2-ca",0xb,0,1,0)
2
[Administrator] # ладно, смотрим, сколько всего KRA у нас на локальном сервере. Их 1, как на самом деле
[Administrator] $certadmin.GetCAProperty("dc1\contoso ca",0x19,0,1,0)
1
[Administrator] # используемых сертификатов тоже 1.
[Administrator] $certadmin.GetCAProperty("dc1\contoso ca",0x18,0,1,0)
1
[Administrator] # как я уже говорил, на DC2 нет агентов восстановления, хотя метод показывает их по одному в обоих случаях
[Administrator] $certadmin.GetCAProperty("dc2\contoso-dc2-ca",0x19,0,1,0)
1
[Administrator] $certadmin.GetCAProperty("dc2\contoso-dc2-ca",0x18,0,1,0)
1
[Administrator]

собираем ту же информацию, запуская код с сервера DC2:

PS C:\> # создаём COM объект:
PS C:\> $CertAdmin = New-Object -com CertificateAuthority.Admin.1
PS C:\> # получаем список сертификатов CA с локального сервера.
PS C:\> # их мы видим ровно 1, как и должно быть
PS C:\> $certadmin.GetCAProperty("dc2\contoso-dc2-ca",0xb,0,1,0)
1
PS C:\> # в предыдущем примере мы видели, что на DC1 2 сертификата CA.
PS C:\> # проверим, так ли это?
PS C:\> $certadmin.GetCAProperty("dc1\contoso ca",0xb,0,1,0)
1
PS C:\> # wow! обломс, метод возвращает тоже 1. А сколько у нас агентов восстановления?
PS C:\> # и снова облом. На сервере DC1 нет агентов восстановления (хотя предыдущий пример говорит об обратном):
PS C:\> $certadmin.GetCAProperty("dc1\contoso ca",0x19,0,1,0)
0
PS C:\> $certadmin.GetCAProperty("dc1\contoso ca",0x18,0,1,0)
0

а теперь посмотрим на результаты с использованием того же метода и тех же PropID, но с использованием интерфейса ICertRequest, который определён в библиотеке certcli.dll. Те же тесты уже возвращают правильные результаты:

с DC1:

[Administrator] $request = New-Object -com CertificateAuthority.Request.1
[Administrator] $request.GetCAProperty("dc1\contoso ca",0xb,0,1,0)
2
[Administrator] $request.GetCAProperty("dc2\contoso-dc2-ca",0xb,0,1,0)
1
[Administrator] $request.GetCAProperty("dc1\contoso ca",0x19,0,1,0)
1
[Administrator] $request.GetCAProperty("dc1\contoso ca",0x18,0,1,0)
1
[Administrator] $request.GetCAProperty("dc2\contoso-dc2-ca",0x19,0,1,0)
0
[Administrator] $request.GetCAProperty("dc2\contoso-dc2-ca",0x18,0,1,0)
0
[Administrator]

с DC2:

PS C:\> $request = New-Object -com CertificateAuthority.Request.1
PS C:\> $request.GetCAProperty("dc2\contoso-dc2-ca",0xb,0,1,0)
1
PS C:\> $request.GetCAProperty("dc1\contoso ca",0xb,0,1,0)
2
PS C:\> $request.GetCAProperty("dc1\contoso ca",0x19,0,1,0)
1
PS C:\> $request.GetCAProperty("dc1\contoso ca",0x18,0,1,0)
1
PS C:\> $request.GetCAProperty("dc2\contoso-dc2-ca",0x19,0,1,0)
0
PS C:\> $request.GetCAProperty("dc2\contoso-dc2-ca",0x18,0,1,0)
0
PS C:\>

Поэтому следует с осторожностью относиться к выводу ICertAdmin2::GetCAProperty(), а ещё лучше — использовать этот метод, который реализован в интерфейсе ICertRequest::GetCAProperty(). Чем это вызвано я пока не знаю, но задал я задал вопрос нужным людям, поэтому я дам знать в случае ответа на вопрос.

Сегодня первый и последний раз нарушаю негласное правило своего блога — не более одного поста в день. А дело в том, что я совсем забыл о том, что вчера этому блогу исполнялся ровно год. Мне кажется, что за этот год я донёс до своих читателей достаточно много актуальной и полезной информации и вам есть чему поучиться на этих материалах. Статистики как таковой я не веду, только руководствуюсь сведениями своего любимого Clustrmaps. И судя по его показаниям посещаемость блога выросла с 30-40 человек в день до 80-100, т.е. примерно в 2,5 раза, что не может не радовать :-), так что я двигаюсь явно в нужном направлении. Очень хочется надеяться, что это не первый день рождения моего бложика и он будет продолжать оставаться для меня кешем моего мозга. Вобщем,

С днём рождения, бложик!

:rock: :happy:

В первой части управления Certification Authority (CA) мы рассмотрели метод GetCAProperty, при помощи которого мы можем получать различные сведения про сервер CA. Сегодня мы посмотрим ещё несколько интересных моментов, которые будут связаны с CRL и опять будет немного треша с CryptoAPI, а именно:

  • Получение статуса публикации CRL;
  • Получение Base и Delta CRL из CA в виде файлов;
  • Отзыв сертификатов;
  • Извлечение сертификатов из CRL;
  • Публикация новых CRL.

Начало работы

Для начала мы должны будем создать ICertAdmin2 COM объект для управления CA, который будет использоваться нами для всех сегодняшних операций:

PS C:\> $CertAdmin = New-Object -ComObject "CertificateAuthority.Admin.1"

Получение Base и Delta CRL из CA в виде файлов

Далее мы должны узнать PropID, который бы отвечал за показ CRL вот по этой ссылке: 3.2.1.4.2.2 ICertRequestD2::GetCAProperty (Opnum 7). Нас будет интересовать 2 этих ID: CR_PROP_BASECRL (0x11) и CR_PROP_DELTACRL (0x12). Поскольку тип возвращаемых данных будет Binary, то PropType будет равен 3, а Flags выставим в 0 (Base64CRLHeader). Можно и другой указать, от этого только зависит метод записи в файл.

Примечание: напоминаю, что индекс PropType начинается с 1, а Flags с 0 и их возможные значения описаны здесь: GetCAProperty. А так же то, что в Flags первые 2 значения перепутаны местами на MSDN.

И вот как мы их можем получить:

PS C:\> $BaseCRL =  $certadmin.GetCAProperty("dc2\contoso-dc2-ca",0x11,0,3,0)
PS C:\> $BaseCRL
-----BEGIN X509 CRL-----
MIIEDTCCAvUCAQEwDQYJKoZIhvcNAQEFBQAwRzETMBEGCgmSJomT8ixkARkWA2Nv
bTEXMBUGCgmSJomT8ixkARkWB2NvbnRvc28xFzAVBgNVBAMTDmNvbnRvc28tREMy
LUNBFw0wOTExMDkwNjI0NDlaFw0wOTExMTcwNjQ0NDlaMIIBzDAbAgpG7E9iAAAA
AACSFw0wOTExMDgxODM5MDBaMCkCCm716aoAAAAAAI8XDTA5MTAyMjE2MjI1OVow
DDAKBgNVHRUEAwoBBDAbAgpxOBcQAAAAAACLFw0wOTA5MjgwNjMzMDBaMBsCCl6/
ZbYAAAAAAIkXDTA5MDkyNDE3MTcwMFowGwIKXnVZiQAAAAAAiBcNMDkwOTI0MTUw
MjAwWjApAgoggMu3AAAAAAAVFw0wOTA1MTcyMDE0MDBaMAwwCgYDVR0VBAMKAQUw
KQIKYRZ2nAAAAAAADRcNMDkwNTAxMTk1MzAwWjAMMAoGA1UdFQQDCgEFMCkCCmJ0
33IAAAAAAAoXDTA5MDQxOTEzMDUwMFowDDAKBgNVHRUEAwoBBDApAgphINAVAAAA
AAAHFw0wOTAzMzExMTEzMDBaMAwwCgYDVR0VBAMKAQUwKQIKYRT3zAAAAAAABhcN
MDkwMzMxMTAzMTAwWjAMMAoGA1UdFQQDCgEFMCkCCmERwOwAAAAAAAUXDTA5MDMz
MTEwMTgwMFowDDAKBgNVHRUEAwoBBDApAgphDxXgAAAAAAAEFw0wOTAzMzExMDE0
MDBaMAwwCgYDVR0VBAMKAQSggakwgaYwHwYDVR0jBBgwFoAUZiAsZ5wOPk1F8vmX
BZdmE1QTJjYwEAYJKwYBBAGCNxUBBAMCAQAwCwYDVR0UBAQCAgDyMBwGCSsGAQQB
gjcVBAQPFw0wOTExMTYwNjM0NDlaMEYGA1UdLgQ/MD0wO6A5oDeGNWh0dHA6Ly9k
YzIuY29udG9zby5jb20vQ2VydEVucm9sbC9jb250b3NvLURDMi1DQSsuY3JsMA0G
CSqGSIb3DQEBBQUAA4IBAQCCNVxtZbAbB5C1bI5Z6PF+Eph2MaRhl9pv0FjYhH/j
Xlvffau1m5bW72No7FC2k8FFU2oHFSZvAVNKgjm6+ECb4iEpzREcHpwsot1TG0kn
Pv+DRGjQ2ndes8OM11v3oYUJxlNOMqY6q03lj/BEJb+ocNerWRay8G0sycy1ABCD
/UPvP9qq54WvwQnJyV5bZJcJG3WYtN7zkZZzf5MR3v+c4OODLiLw5pXgku49USFu
kTuEh5DYcF5zbo6h5IqAW+OP6iMFepxkUEZUb/5VOYK8VcpXDwMzIRzlf+bITHyX
6PVugwm6TXkn9eVrAfJUd70S9LV7XVTz32IykC3tNXFY
-----END X509 CRL-----


PS C:\> $DeltaCRL =  $certadmin.GetCAProperty("dc2\contoso-dc2-ca",0x12,0,3,0)
PS C:\> $BaseCRL > base.crl
PS C:\> $DeltaCRL > delta.crl
PS C:\> & .\base.crl
PS C:\> & .\delta.crl
PS C:\>

последние 2 команды просто запускают эти CRL файлы. К сожалению не существует ни одного родного класса, который бы представлял собой объект CRL, поэтому как-то разбирать его на таком уровне пока не представляется возможным. Хотя существуют сторонние библиотеки (например, в Mono есть класс X509CRL).

Получение статуса публикации CRL

Но мы можем кое что узнать о статусе наших CRL. Для этого существует PropID = CR_PROP_CRLSTATE (0x14). Тип возвращаемых данных будет Long, значит PropType будет 1, а Flags будет игнорироваться, поэтому поставим его в 0:

PS C:\> $certadmin.GetCAProperty("dc2\contoso-dc2-ca",0x14,0,1,0)
3

Расшифровку этого значения можно найти здесь:  3.2.1.4.2.2.20 PropID = 0x00000014 (CR_PROP_CRLSTATE) "CA CRL State". Число 3 означает, что с CRL у нас всё хорошо (CA_DISP_VALID (0x03)). В принципе, на данном этапе нам больше ничего и не нужно. Но мы можем посмотреть более детальные сведения по каждому типу CRL. Для получения более детальной ошибки мы должны использовать следующие PropID:

  • CR_PROP_BASECRLPUBLISHSTATUS (0x1E)
  • CR_PROP_DELTACRLPUBLISHSTATUS (0x1F)

Для этих PropID тип данных так же указан Long, поэтому PropType выставим в 1, а Flags в 0:

PS C:\> $certadmin.GetCAProperty("dc2\contoso-dc2-ca",0x1E,0,1,0)
5
PS C:\> $certadmin.GetCAProperty("dc2\contoso-dc2-ca",0x1F,0,1,0)
6
PS C:\>

Мы получили число 5 для BaseCRL и 6 для DeltaCRL. Расшифровку этих значений можно посмотреть вот здесь: 3.2.1.4.2.2.30 PropID = 0x0000001E (CR_PROP_BASECRLPUBLISHSTATUS) "Base CRL Publishing Status". Поскольку вывод — не конкретное число, а результат двоичного оператора И (AND), то мы это число должны разбить на составляющие. И вот как это делается:

$Return = $certadmin.GetCAProperty("ServerName\CA Name",0x1E,0,1,0)
$options = 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048 | %{$Return -band $_} | ?{$_}
# поскольку значения масок являются результаты возведения двойки в степень
# начиная от 0 до 11, то эту строчку можно переписать более готично.
# Напоминаю, что возведение любого числа в нулевую степень
# всегда вернёт 1. Кажется, это из алгебры 5-6 класса.
$options = 0..11| %{[Math]::Pow(2,$_)} | %{$Return -band $_} | ?{$_}
switch ($options) {
    1 {"The CRL is a base CRL"}
    2 {"The CRL is a delta CRL"}
    4 {"The last CRL publication that was completed and published to the locations specified in the CA"}
    8 {"The CRL is a shadow delta CRL"}
    16 {"The CA MAY publish the CRL to a local store that is not externally accessible.
        This error is returned when publishing to this intermediate store failed"}
    32 {"An error occurred during publication of the CRL.
        The error indicates that the file schema cannot be recognized.The schema must be file: or ldap:"}
    64 {"Publication of the CRL was manually initiated by an administrator"}
    128 {"A CRL signature error was detected. The CSP was not correct"}
    256 {"An error occurred during publication of the CRL to an LDAP URL"}
    512 {"An error occurred during publication of the CRL to a file URL"}
    1024 {"An error occurred during publication of the CRL to an FTP URL. FTP URLs are not supported"}
    2048 {"The CA cannot publish CRLs to HTTP URLs. This error is returned if a CA administrator
        configured the CA to publish CRLs to HTTP URLs"}
}

Следовательно число 5 (1+4) говорит нам о том, что это был Base CRL и он был опубликован успешно. А 6 (2+4) — Delta CRL был опубликован успешно.

Отзыв сертификатов

Интерфейс IcertAdmin2 позволяет нам отзывать сертификаты на сервере CA. Для этого мы воспользуемся методом RevokeCertificate(). Метод достаточно простой и примерная команда для отзыва сертификата будет выглядеть так:

$CertAdmin.RevokeCertificate("ServerName\CA Name", 0123456789,4,(Get-Date).AddDays(1))

0123456789 будет обозначать серийный номер сертификата, 4 — причина отзыва будет Superseded и эффективная дата отзыва (когда сертификат будет помещён в Revoked Certificates) будет 24 часа после выполнения команды. Да, именно этим методом можно отзывать корневые сертификаты :-).

Извлечение сертификатов из CRL

Примечание: хоть Microsoft и поддерживает извлечение сертификатов из CRL (отмена статуса Revoked), этой возможностью не следует пользоваться в силу определённых причин, как невозможность определеления в какое время сертификат был помещён и извлечён из CRL.

Если сертификат был отозван со статусом Certificate Hold (6), то его можно в любое время вернуть обратно. Для всех остальных типов отзыва такая возможность не поддерживается. Для отмены статуса Revoked нужно использовать этот же метод, только в качестве причины отзыва указать значение MAX_DWORD, которое является 0xffffffff или просто –1:

$CertAdmin.RevokeCertificate("ServerName\CA Name", 0123456789,-1,0)

Публикация новых CRL

Если вы хотите опубликовать новые CRL вручную (т.е. в промежутке между плановой публикацией CRL), то мы должны воспользоваться методом PublishCRLs(). Его синтаксис достаточно простой:

$CertAdmin.PublishCRLs("ServerName\CA Name",0,0)

Эта команда переопубликует как основной, так и инкрементальный CRL (при публикации Base CRL инкрементальный CRL публикуется всегда, потому что Effective Date у него не может быть меньше, чем у Base CRL). Что касается последнего аргумента, то он бывает ещё полезен, если по каким-то причинам потерялся какой-то файл CRL. Тогда вы можете выставить CRLFlags в 4 (0x4) и тогда CA переопубликует текущие CRL без обновления их содержимого и дат.

На сегодня это всё.

Disclaimer: данный материал публикуется исключительно в учебных целях и его НЕ следует повторять! Любые действия описанные в данной статье вы можете делать ТОЛЬКО на свой страх и риск.

Мы с вами уже достаточно много обсудили про certificate chaining engine и расширения CDP в сертификатах и связанные с ними вопросы:

Но я решил немного расширить сознание по этому вопросу и рассмотреть ситуацию с отозванным корневым (который является и самоподписанным) сертификатом. Для экспериментов использовались CA под управлением Windows Server 2003 и Windows Server 2008 R2 (в обоих случаях поведение было идентичное). Штатно (из консоли certsrv.msc) у нас нет возможности отозвать корневой сертификат CA, но мы можем технически это сделать с использованием CryptoAPI COM интерфейсов. Сразу после отзыва корневого сертификата в свойствах CA мы увидим вот такую картинку:

Revoked Root CA certificate

и вот такое сообщение в журнале событий:

Log Name:      Application
Source:        Microsoft-Windows-CertificationAuthority
Date:          2009.11.08. 17:35:44
Event ID:      51
Task Category: None
Level:         Error
Keywords:      Classic
User:          SYSTEM
Computer:      RootCA
Description:
A certificate in the chain for CA certificate 0 for Adatum CA has been revoked.  The certificate is revoked. 0x80092010 (-2146885616).

Всё, приехали, что дальше? А дальше мы рассмотрим актуальность наличия CDP в корневых сертификатах, при помощи которых мы можем посмотреть CRL рассматриваемого CA (это корневой CA, поэтому в его CDP могут быть ссылки только на его собственный CRL), как это делают некоторые публичные коммерческие CA (например, StartCom). Когда корневой сертификат отозван, мы продолжаем его не наблюдать в консоли certsrv.msc, но в БД это хорошо видно:

Request Status Code: 0x0 (WIN32: 0) -- The operation completed successfully.
Request Disposition: 0xf (15) -- CA Cert
Request Disposition Message: "Revoked by ADATUM\Administrator"
Request Submission Date: 2009.11.07. 22:41
Request Resolution Date: 2009.11.07. 22:41
Revocation Date: 2009.11.08. 17:35:43
Effective Revocation Date: 2009.11.08. 17:35:43
Revocation Reason: 0x0 -- Reason: Unspecified

Но мы же можем попробовать опубликовать новый CRL? Ответ — нет, не можете. Поскольку CRL подписывается закрытым ключом CA, то перед подписью CRL производится проверка валидности этого ключа для подписи CRL. Т.к. на данном этапе CA уже знает, что сертификат отозван, то ключ блокируется для подписи новых CRL, т.к. они будут считаться поддельными. Это будет первый аргумент в пользу отсутствия CDP в корневых сертификатах. Если CRL подписан отозванным ключом, то этот CRL должен игнорироваться по очевидным причинам.

Однако, тут есть одна интересная вещь — пока служба CertSvc не перезапущена, мы можем энролить новые сертификаты. Т.е. в этом промежутке клиенты могут нагенерить запросы и получить в ответ сертификаты. Это действительно проблема, потому что сертификаты так же подписываются закрытым ключом CA. И, по всей видимости, CA лишь с некоторой периодичностью проверяет валидность закрытого ключа для данной операции. Хотя, в случае с CRL эта проверка производится каждый раз перед подписью. После остановки службы CertSvc наш CA отключается навсегда:

Log Name:      Application
Source:        Microsoft-Windows-CertificationAuthority
Date:          2009.11.08. 17:42:21
Event ID:      100
Task Category: None
Level:         Error
Keywords:      Classic
User:          SYSTEM
Computer:      RootCA
Description:
Active Directory Certificate Services did not start: Could not load or verify the current CA certificate.  Adatum CA The certificate is revoked. 0x80092010 (-2146885616).

А дальше становится ещё интересней. Методика работы CCE описана в RFC3280. Для администраторов PKI описываемый там материал нужно знать обязательно. Параграф 6 описывает процедуру построения цепочки сертификатов:

§ 6.1.

When the trust anchor is provided in the form of a self-signed
certificate, this self-signed certificate is not included as part of
the prospective certification path

Это означает, что самоподписанный сертификат (корневой таковым и является) исключается из проспективной цепочки сертификатов. А проверка CRL допускается только для этой проспективной цепочки.

§ 6.1.1.

The trusted anchor information is trusted because it was delivered
to the path processing procedure by some trustworthy out-of-band
procedure.

§ 9.

The certification path validation algorithm depends on the certain
knowledge of the public keys (and other information) about one or
more trusted CAs. The decision to trust a CA is an important
decision as it ultimately determines the trust afforded a
certificate.

Иными словами RFC предусматривает безоговорочное доверие конкретному корневому сертификату, когда доверие осуществляется каким-то механизмом (в Windows это наличие сертификата в Trusted Root CAs). И в § 6.1.3 говорится, что из цепочки (проспективной) удаляются все самоподписанные сертификаты, следовательно произвести проверку CRL можно только для сертификатов, которые в цепочке находятся на уровень ниже, чем самоподписанный сертификат. Это означает только одну вещь: если CCE является RFC3280-compliant, то он должен игнорировать расширение CDP в самоподписанном сертификате. Учитывая, что Windows CA технически не позволяет поместить свой сертификат в свой CRL, то скорее всего реализация CCE в CryptoAPI будет дейтсвовать адекватно и во всех случаях игнорировать это расширение. Поэтому даже при гипотетической возможности помещения отозванного корневого сертификата в его собственный CRL мы проигнорируем такой CRL и не узнаем, что корневой сертификат ВНЕЗАПНО был отозван.

Это была реклама VeriSign.

Продолжая тему криптографии, предлагаю несколько ближайших постов посвятить вопросам использования CryptoAPI в PowerShell для управления Cerficiation Authority (сокращённо CA). Я считаю эту тему достаточно интересной, хоть у нас есть космическая утилита certutil.exe. Но тот, кто видел хелп к нему, тот знает какой это ужас, автоматизировать что-то с использованием certutil. Microsoft подарил нам не менее космический и православный инструмент автоматизации — PowerShell. К сожалению, вынужден признать, что в ряде задач PowerShell сильно уступает аналогам в cmd и с этим ничего нельзя поделать. Но по мере своих сил буду пытаться доказывать обратное. Одно из преимуществ PowerShell является поддержка .NET, который очень удобен в использовании. Но .NET не всемогущ и не содержит в себе ни единого класса, при помощи которого можно было бы управлять службой certificate services (точнее самим CA). CryptoAPI неплохо документированы на уровне unmanaged code (а это подразумевает использование низкоуровневых языков программирования, как C++). Но, опять, слава сиськамСноверуПротоколу у нас есть одна лазейка — COM интерфейсы к CryptoAPI. Изучив немного эти интерфейсы, обнаружил, что они местами очень интересные и досточно легко управляемы. Но это не всегда так, иногда они бывают достаточно сложными для понимания с первого раза. В процессе исследования, были обнаружены вообще нелогичные вещи, с которыми нам придётся считаться.

Эти COM интерфейсы позволили мне реализовать едва ли не половину функционала моего PowerPack'а для PowerGUI. Оценить моё детище можно по этой ссылке: Enterprise PKI management. Я в него вложил достаточно много знаний для того, чтобы упростить жизнь администраторам инфраструктур PKI. Пока прогресс неважный, т.к. я пока только повторяю функционал консолей certmgr.msc, certsrv.msc, ocsp.msc и certutil.exe. Но я надеюсь реализовать там действительно что-то нужное и очень полезное.

Наша отправная точка на MSDN будет находиться здесь: Cryptography Interfaces. Для начала мы поработаем с интерфейсом ICertAdmin2, который зарегистрирован в системе как COM класс CertificateAuthority.Admin.1. Вот как он создаётся:

PS C:\> $CertAdmin = New-Object -ComObject "CertificateAuthority.Admin.1"
PS C:\> $CertAdmin | gm


   TypeName: System.__ComObject#{f7c3ac41-b8ce-4fb4-aa58-3d1dc0e36b39}

Name                     MemberType Definition
----                     ---------- ----------
DeleteRow                Method     int DeleteRow (string, int, Date, int, int)
DenyRequest              Method     void DenyRequest (string, int)
GetArchivedKey           Method     string GetArchivedKey (string, int, int)
GetCAProperty            Method     Variant GetCAProperty (string, int, int, int, int)
GetCAPropertyDisplayName Method     string GetCAPropertyDisplayName (string, int)
GetCAPropertyFlags       Method     int GetCAPropertyFlags (string, int)
GetConfigEntry           Method     Variant GetConfigEntry (string, string, string)
GetCRL                   Method     string GetCRL (string, int)
GetMyRoles               Method     int GetMyRoles (string)
GetRevocationReason      Method     int GetRevocationReason ()
ImportCertificate        Method     int ImportCertificate (string, string, int)
ImportKey                Method     void ImportKey (string, int, string, int, string)
IsValidCertificate       Method     int IsValidCertificate (string, string)
PublishCRL               Method     void PublishCRL (string, Date)
PublishCRLs              Method     void PublishCRLs (string, Date, int)
ResubmitRequest          Method     int ResubmitRequest (string, int)
RevokeCertificate        Method     void RevokeCertificate (string, string, int, Date)
SetCAProperty            Method     void SetCAProperty (string, int, int, int, Variant)
SetCertificateExtension  Method     void SetCertificateExtension (string, int, string, int, int, Variant)
SetConfigEntry           Method     void SetConfigEntry (string, string, string, Variant)
SetRequestAttributes     Method     void SetRequestAttributes (string, int, string)


PS C:\>

Как вы видите, этот интерфейс позволяет использовать достаточно много интересных методов. Давайте начнём с простого (а может быть и не очень простого) — GetCAProperty. По ссылке вообще ничего непонятно, что и как там использовать. Единственное, что я понял — это то, что данный метод принимает 5 параметров в определённом порядке и всё. Поэтому давайте будем считать, что этой странички не существует в природе (кроме случаев, когда будем преследовать порядок расположения аргументов). Пошарившись на MSDN, нашёл более годную ссылку в спецификациях Windows Communications Protocols3.2.1.4.2.2 ICertRequestD2::GetCAProperty (Opnum 7). И вот как должны выглядеть параметры:

  • strConfig — имя компьютера и сервера CA в формате ComputerName\CAName.
  • PropId — ID требуемого свойства. Должно указываться в числовом формате (т.к. тип данных указан Long). Это число можно взять из колонки Numerical Value таблички 3.2.1.4.2.2 ICertRequestD2::GetCAProperty (Opnum 7). Например, для получения DeltaCRL надо указать число 18 (0x12).
  • PropIndex — если свойство индексированное (т.е. содержит несколько искомых объектов), то PropIndex указывает на индекс массива, в котором хранится определённый объект искомого свойства. Например, CA может содержать несколько своих сертификатов. И с использованием PropIndex мы сможем выбирать различные сертификаты, выбирая их из массива. Начало индексирования начинается с нуля. Если свойство неиндексируемое, то это значение будет игнорироваться.
  • PropType — указываете тип данных, в котором хотите получить конкретное свойство. Во второй таблице уже указаны рекомендованные значения типов данных для каждого набора свойств. Тип данных так же нужно указывать в числовом формате. Однако, здесь индекс начинается не с нуля, а с единицы. Т.е. тип данных String будет иметь значение 4.
  • Flags — помимо типа возвращаемых данных нам нужно указать формат этих данных. Во второй таблице так же приведены рекомендованные (хотя слово MUST и ещё капсом как бы намекает на обязательность. Хотя, это не совсем так. Если вы в первый раз будете этим заниматься, то используйте значения из второй таблички). А вот здесь индекс начинается с нуля.

Примечание: по всей видимости в табличке с флагами закралась ошибка, т.к. использование первого флага даёт Base64, но с заголовками Begin/End Certificate. А второй — наоборот, без заголовка.

Вам кажется, что это всё ужасно и сложно? Поверьте, это не так. Давайте прочитаем парочку свойств. Например, прочитаем ссылки на CRL и CRT, которые публикуются в CDP и AIA издаваемых сертификатах. Исходя из таблички мы знаем, что CRL'ы вызываются PropID = 0x29 (или 41 в десятичной нотации), а CRT — PropID = 0x2A (или 42). Поскольку это индексируемое свойство, то первая ссылка будет содержаться в индексе 0 (первый индекс массива), а последующие ссылки в индексах 1 и далее и PropIndex мы укажем 0. PropType у нас будет String и числовое значение — 4 (не забываем, что PropType считаются начиная с единицы). Flags может быть любой, т.к. он будет игнорироваться при PropType = 4:

PS C:\> $CertAdmin.GetCAProperty("ca\sysadmins-lv-ca", 41, 0, 4, 0)
http://ca.sysadmins.lv/sysadmins-LV-CA.crl

PS C:\> $CertAdmin.GetCAProperty("ca\sysadmins-lv-ca", 42, 0, 4, 0)
http://ca.sysadmins.lv/sysadmins-LV-CA.crt

PS C:\>

Как вы видите, на самом деле здесь нет ничего страшного. Нужно лишь научиться оперировать табличкой. Давайте для разнообразия посмотрим на сертификат CA. Для этого мы должны найти PropID, который отвечал за сертификат. Это будет PropID = 0xC. Поскольку этот параметр так же индексируемый, то он будет содержать все сертификаты CA, если их там несколько. Самый первый сертификат будет храниться в индексе 0, следующий сертификат в индексе 1 и т.д. Исходя из таблички, PropType должен быть 3. Посмотрим сертификат в Base64 кодировке, т.е. Flags должен быть 0 или 1:

PS C:\> $cert = $CertAdmin.GetCAProperty("ca\sysadmins-lv-ca", 0xc, 0, 3, 0)
PS C:\> $cert
-----BEGIN CERTIFICATE-----
MIIFbTCCA1WgAwIBAgIQPf+vkU1hMoJCe9WRwftYbTANBgkqhkiG9w0BAQUFADBJ
MRIwEAYKCZImiZPyLGQBGRYCbHYxGTAXBgoJkiaJk/IsZAEZFglzeXNhZG1pbnMx
GDAWBgNVBAMTD3N5c2FkbWlucy1MVi1DQTAeFw0wOTA4MDYwNzIxMDFaFw0xNDA4
MDYwNzMwNTRaMEkxEjAQBgoJkiaJk/IsZAEZFgJsdjEZMBcGCgmSJomT8ixkARkW
CXN5c2FkbWluczEYMBYGA1UEAxMPc3lzYWRtaW5zLUxWLUNBMIICIjANBgkqhkiG
9w0BAQEFAAOCAg8AMIICCgKCAgEAwL5wHTMEsMg6yDzqWPd9e/GT856o9OWJrv4Y
9iXtTa5mkdEZsbckVmAJODG28lcI7ScuD0rfceB6/gVn4VMK2SITsOv2XzC3ApRE
zExEIuRmspFlpaDmOvBTYafkzokN2RJ+UQq8M9RSnSBHrOcvNJQYgglbjQOHsg4f
qyMoPoZ+LYjoGZcCtqmR+FMOnalUGohnanf/fQJ/gcoAET6H45rqAB/P90sWOsZo
pwCgGmvJe4GBXbZDg4ADZIbrYm2baxwKJlhsDAhzZ8Fo2irnSBFJavu6+LwQHv8P
fL/ZGHBlf8s+uTeHfluG95bIe8VAGRwEwCyV5t+emBLq4ny5Q21MjWGDD3YjkKjA
lqC7c2mpCC6YVxBArVVm1G7/d8mpBuO5ZzyqIVkEhxZuYPfK6k7EEKx3BuCC97UT
ZcSVJBCHmxbxWm/U30VIpu0HDlbCiHX0OSDRJhv32ppazDonGSp/GiBocXNKTLA+
PyXQqRvObovTFPMutzLQDczeee5zvfJ7Gs7xCskcsLp8uBuhGJMnW87hRglm4p0/
tdIViIbMrwbVxjtHPnp5MARvQBT26ZzE3k0zr9Do3Gi6AY8Jt6ssD6r5CXbBA5SF
iGgp1czWb2xToqJtzuEjG1ssBj5MTMzINa4bMhQs52nXdUI2aG7n7mwoAVR3tHiW
sK1lvdsCAwEAAaNRME8wCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYD
VR0OBBYEFHrmiNrp+Fw+BwTeEaI4Iql1oxOrMBAGCSsGAQQBgjcVAQQDAgEAMA0G
CSqGSIb3DQEBBQUAA4ICAQCuLfQSOcRoQ8EcuVMgIVjepuonY1MG7PhPvJ1fyanw
0MeKonNZ4a7gOop7R/8DSGBfnQMQJClVcFTxe8Osa88OkeJmb9LQix9wMfR1GAaw
NwaX6ve0x4Ixr7m3KQEBOc+fAAGNLcxnlIDUD9EiSlFqqH+HkjxREU5RmE0TQFVS
NfNtwFSX0loMuL8mo10m8CD/ETRdq6jXO6vMWll8wBirSD9/Zostn5+t1g6TU4fi
bpR/56ueiRh0px2A1lhDTTGjV7UTqPvavUqJejZJCHf269xJb1r8mc+AJn/O4g4q
WvVsTILLMw+Oit/7ZagFbjZa7jSBRFfEyU4lKnV9oxIO47FTjWgGi7R+Rg8VDWvQ
a5JMhMadUWrz5VQRY9iBr1wlmvZH5100otrikZLfVxQ+HZVccgLvZ4BJpdk+Lzyk
bqZSAwbOJlFF0po+4wWbakhzS/jZLcoO8NzE4SgW3NTcuWYsS2G8ubV139IykSCy
RA9UcyoNNQM8CXr6I2UmhQwBqMrGZIi/rybPRQvlVSssuusfbxIiWMjdo/mQyUH2
A1diiIt1bpkVdJ9yRSDtuldoYe2w0QtId6WWAg2H8bTiF+/cu1kZ//rcK5UQyuku
1ggS22YqWCUrSQ4/heQdP1Ni619sMHFcMYWZTP5XySLiN+BBrs6/4pnJOVNsy742
KQ==
-----END CERTIFICATE-----

PS C:\> $cert > file.cer
PS C:\> & c:\file.cer
PS C:\>

Последней строчкой я вызвал команду, которая покажет мне сертификат как я бы запустил сам файл (я это и сделал этой командой на самом деле). Если там сертификат не один, то следующий сертификат можно было бы получить вот такой командой:

$CertAdmin.GetCAProperty("ServerName\CA Name", 0xC, 1, 3, 0)

Я просто увеличил PropIndex на единицу. Чтобы узнать сколько же там всего сертификатов, достаточно посмотреть PropID = 0x0B, PropType = 1 (т.к. команда нам вернёт число, то тип должен быть числовой — Long):

PS C:\> $CertAdmin.GetCAProperty("ca\sysadmins-lv-ca", 0xb, 0, 1, 0)
1
PS C:\>

Вот в данном случае у меня там всего 1 сертификат. На другом сервере у меня 2 сертификата:

[Administrator] $CertAdmin.GetCAProperty("dc1\contoso ca", 0xb, 0, 1, 0)
2
[Administrator]

Если вам это интересно, то советую поупражняться в получении различных свойств в различном формате с использованием ICertAdmin2 интерфейса и таблички: 3.2.1.4.2.2 ICertRequestD2::GetCAProperty (Opnum 7).

Это была первая часть использования CryptoAPI в PowerShell. В следующих частях мы рассмотрим другие интересные методы интерфейса ICertAdmin2.