Contents of this directory is archived and no longer updated.

Posts on this page:

В продолжении темы исследования COM интерфейсов CryptoAPI для управления службами Certificate Services предлагаю теперь разобрать вопросы KRA — Key Recovery Agents. Сначала напомню уже изученные темы по CryptoAPI:

И на сегодня наш план выглядит достаточно интересно:

  • Получение списка доступных KRA в лесу и просмотр их сертификатов
  • Получение списка KRA назначенных на сервере CA;
  • Назначение новых KRA для CA.

Получение списка доступных KRA в лесу

KRA используется для архивирования закрытых ключей сертификатов в БД CA. В случае если пользователь уничтожил свой закрытый ключ он может его восстановить. Для этого нужно обратиться к администратору CA для извлечения BLOB (Binary Large OBject) в файл. После чего агент восстановления (KRA) использует свой закрытый ключ для расшифровки закрытого ключа из BLOB'а. Учитывая, что я и не только уже рассматривали этот вопрос, я не буду пересказывать принцип работы Key Archival:

Все сертификаты KRA публикуются в forest naming configuration context партиции AD и реплицируется между всеми контроллерами в лесу. Публикация просисходит в точке:

CN=KRA, CN=Public Key Services, CN=Services, CN=Configuration, DC=ForestRootDomain

PS C:\> $ldap = [ADSI]"LDAP://CN=KRA, CN=Public Key Services, CN=Services, CN=Configuration, DC=contoso,dc=com"
PS C:\> $ldap

distinguishedName
-----------------
{CN=KRA,CN=Public Key Services,CN=Services,CN=Configuration,DC=contoso,DC=com}


PS C:\> $kra = $ldap.psbase.children | %{$_}
PS C:\> $kra

distinguishedName
-----------------
{CN=contoso-DC2-CA,CN=KRA,CN=Public Key Services,CN=Services,CN=Configuration,DC=contoso,DC=com}
{CN=Contoso CA,CN=KRA,CN=Public Key Services,CN=Services,CN=Configuration,DC=contoso,DC=com}

Мы получили список CA в лесу и посмотрев каждый из них можем посмотреть сколько KRA сертификатов выпустил каждый CA:

PS C:\> $kra[1] | fl *


objectClass            : {top, msPKI-PrivateKeyRecoveryAgent}
cn                     : {Contoso CA}
userCertificate        : {48 130 6 104 48 130 5 80 160 3 2 1 2 2 10 21 29 22 96 0 0 0 0 0 4 48 13 6 9 42 134 72 134 247
                          13 1 1 5 5 0 48 67 49 19 48 17 6 10 9 146 38 137 147 242 44 100 1 25 22 3 99 111 109 49 23 48
                          21 6 10 9 146 38 137 147 242 44 100 1 25 22 7 99 111 110 116 111 115 111 49 19 48 17 6 3 85 4
                          3 19 10 67 111 110 116 111 115 111 32 67 65 48 30 23 13 48 57 48 50 49 53 49 53 48 53 50 53 9
                         0 23 13 49 49 48 50 49 53 49 53 48 53 50 53 90 48 86 49 19 48 17 6 10 9 146 38 137 147 242 44
                         100 1 25 22 3 99 111 109 49 23 48 21 6 10 9 146 38 137 147 242 44 100 1 25 22 7 99 111 110 116
<...>

Свойство UserCertificate в виде массива может содержать сертификаты KRA в виде байтового массива. Например, указанный CA выпустил 2 сертификата KRA:

PS C:\> $kra[1].usercertificate.count
2

И обратившись к индексам этого свойства можно получить конкретные сертификаты: $kra[1].usercertificate[0]. Если CA не выпускал сертификаты для KRA, то свойство UserCertificate будет содержать число ноль:

PS C:\> $kra[0] | fl *


objectClass            : {top, msPKI-PrivateKeyRecoveryAgent}
cn                     : {contoso-DC2-CA}
userCertificate        : {0}
<...>

Поэтому для получения всех сертификатов KRA нужно пройтись по всем членам CN=KRA и выбрать из них ненулевые значения свойства UserCertificate. Если сертификатов больше, чем 1, то они будут храниться в этом свойстве в виде массива. Чтобы посмотреть сам сертификат, вы можете просто сохранить этот массив байтов в файл:

PS C:\> [System.IO.File]::WriteAllBytes("C:\kra.cer",$kra[1].usercertificate[1])
PS C:\> & .\kra.cer

Получение списка KRA назначенных на сервере CA

А теперь вернёмся к интерфейсу ICertAdmin2. С его помощью мы можем посмотреть количество агентов восстановления, которые назначены на CA и посмотреть их сертификаты. Однако, этот интерфейс имеет какой-то баг с PropID, которые связаны с KRA, о которых я писал в предыдущей статье, поэтому для получения этих данных мы будем использовать интерфейс ICertRequest3.

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

Мы видим, что на сервере DC1 назначен 1 агент восстановления, а на DC2 ни одного. Сегодня мы добавим агентов восстановления на DC2.

Примечание: чем отличаются KRACERTUSEDCOUNT и KRACERTCOUNT? KRACERTCOUNT показывает сколько всего KRA назначено на конкретном сервере CA. А KRACERTUSEDCOUNT показывает сколько из них будет использовать CA при архивации каждого закрытого ключа сертификата и это число может быть меньше, чем общее количество сертификатов KRA. В таком случае CA рандомно выбирает KRACERTUSEDCOUNT сертификатов и шифрует ими закрытый ключ. Например, KRACERTCOUNT = 5, а KRACERTUSEDCOUNT = 3, то CA при архивации ключа выберет рандомно 3 сертификата KRA из списка и зашифрует ими ключ. И только эти 3 агента восстановления могут восстановить ключ, а остальные 2 уже не смогут.

Получение списка KRA назначенных на сервере CA

Чтобы посмотреть сами сертификаты KRA, мы должны воспользоваться PropID = CR_PROP_KRACERT (0x1A).

И вот что мы увидим:

PS C:\> $CertAdmin.GetCAProperty("dc1\contoso ca",0x1A,0,3,0)
-----BEGIN CERTIFICATE-----
MIIGaDCCBVCgAwIBAgIKFR0WYAAAAAAABDANBgkqhkiG9w0BAQUFADBDMRMwEQYK
CZImiZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHY29udG9zbzETMBEGA1UE
AxMKQ29udG9zbyBDQTAeFw0wOTAyMTUxNTA1MjVaFw0xMTAyMTUxNTA1MjVaMFYx
EzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdjb250b3NvMQ4w
DAYDVQQDEwVVc2VyczEWMBQGA1UEAxMNQWRtaW5pc3RyYXRvcjCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAJG7T5yMninhrkXFQU8WGUr5BYYUeOz10Rkn
<...>

В нулевом индексе мы видим первый сертификат. Последующие будут храниться в других индексах:

$CertAdmin.GetCAProperty("dc1\contoso ca",0x1A,$Index,3,0)

Этот сертификат можно спокойно записать в файл и через GUI просмотреть его свойства. Но вообще это не родной формат сертификата KRA для данного интерфейса, поскольку добавлять сертификаты KRA на сервере CA нужно в ASN1 DER-encoded string формате. Т.е. Flags должен быть не 0, а 2:

$CertAdmin.GetCAProperty("dc1\contoso ca",0x1A,$Index,3,2)

и увидим примерно такое:

PS C:\> $kra = $CertAdmin.GetCAProperty("dc1\contoso ca",0x1a,0,3,2)
PS C:\> $kra
?????A???`  ?????c?♣??????????????????T???????????????????????????????????????T????????????????????????????????????????
??ca♣??????☺???????????????????????????????????????????????????????????????????????????????????????????????????????????
??????????????????????☺????????Є?????????????????☻???????☻??????????????ЎЖ?????????????CA??????Ё????????????h??A???????
??????????????????????????????????????????????╣????????????????????????????????????????????????????????????????????????
?????????????????????C?cЁ??????C???????????????????????????????????????????????????????????????????????????????????????
???????????????????????????????????????Х???CA?????Ё???????CA?????Б????CA??????????????????????a???☺????????????????????
????????????????????????????????????c?????????????????????????????????????H╝??????????????????{?????????????

выглядит весьма уныло, но вот в таком формате и надо записывать сертификаты агентов восстановления в CA.

Назначение новых KRA для CA

Когда мы рассмотрели основные моменты связанные с агентами восстановления в контексте CryptoAPI, мы можем приступить к финальной части — назначению новых агентов восстановления. В принципе, здесь нет ничего сложного, поскольку для добавления KRA нужно сделать 5 вещи:

  1. Получить массив байтов, которые бы представляли сертификат агента восстановления откуда угодно. Хоть из AD, хоть из файла сертификата;
  2. Используя специальный конвертер, разобрать этот массив на пары байтов, сконвертировать эти пары в little-endian hex строку, получить десятичное представление этой hex-строки и получить итоговую символьную строку;
  3. Записать эту строку используя метод SetCAProperty();
  4. Задать новое количество используемых агентов восстановления;
  5. Перезапустить службу CA.

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

$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import(".\desktop\kra.cer")
$bytes = $cert.RawData

и переменная $bytes будет содержать такой же байтовый массив.

А вот с конвертером чуть сложнее. Я не нашёл ни одного готового конвертера, который бы выполнял все шаги из пункта 2, поэтому я написал свой конвертер с блек-джеком и шлюхами:

function ConvertTo-DERstring ([byte[]]$bytes) {
    # создаём объект StringBuilder, который нам будет собирать конечную символьную строку
    $SB = New-Object System.Text.StringBuilder
    # преобразовываем каждый байт в его hex значение
    $bytes1 = $bytes | %{"{0:X2}" -f $_}
    # циклом перебираем каждую пару байт
    for ($n = 0; $n -lt $bytes1.count; $n = $n + 2) {
        # переставляем элементы пары местами, чтобы получить little-endian hex-строку
        # после чего получаем десятичное представление этой строки и получаем Unicode
        # символ, который соответствует этому десятичному значению.
        [void]$SB.Append([char](Invoke-Expression 0x$(($bytes1[$n+1]) + ($bytes1[$n]))))
    }
    $SB.ToString()
}

А дальше уже по накатанной дороге:

[Administrator] # создаём COM объект ICertAdmin2
[Administrator] $CertAdmin = new-object -com certificateauthority.admin.1
[Administrator] # выбираем все Enterprise CA
[Administrator] $ldap = [ADSI]"LDAP://CN=KRA, CN=Public Key Services, CN=Services, CN=Configuration, DC=contoso,dc=com"
[Administrator] $kra = $ldap.psbase.children | %{$_}
[Administrator] # сразу конвертируем все сертификаты в DER-encoded string
[Administrator] $kra1 = ConvertTo-DERstring $kra[1].usercertificate[0]
[Administrator] $kra2 = ConvertTo-DERstring $kra[1].usercertificate[1]
[Administrator] # можем убедиться на месте, что строка имеет ожидаемый вид
[Administrator] $kra2
?????A???E  ?????c?♣??????????????????T???????????????????????????????????????T????????????????????????????????????????
??ca♣??????☺????????????????????????????K?????????????????????????????????????????????т????????????????????????????????
??????????????????????☺????????Є?????????????????☻???????☻??????????????ЎЖ?????????????CA??????Ё????????????h??A???????
??????????????????????????????????????????????╣????????????????????????????????????????????????????????????????????????
?????????????????????C?cЁ??????C???????????????????????????????????????????????????????????????????????????????????????
???????????????????????????????????????Х???CA?????Ё???????CA?????Б????CA??????????????????????a???☺????????????????????
????????????????????????????????????????????????????????????????????????????????????????????????????????????
[Administrator] # и записываем первую строку сертификат агента восстанвовления в нулевой индекс
[Administrator] $CertAdmin.SetCAProperty("dc2\contoso-dc2-ca",0x1a,0,3,$kra1)
[Administrator] # все последующие строки сертификатов записываются в последующие индексы массива
[Administrator] $CertAdmin.SetCAProperty("dc2\contoso-dc2-ca",0x1a,1,3,$kra2)
[Administrator] # используем PropID = KRACERTUSEDCOUNT (0x18), чтобы сказать сколько у нас будет использоваться агентов
[Administrator] # я буду их использовать все
[Administrator] $CertAdmin.SetCAProperty("dc2\contoso-dc2-ca",0x18,0,1,2)
[Administrator] # перезапускаем службу CertSvc
[Administrator] $wmi = gwmi win32_service -comp dc2 -filter "name='certsvc'"
[Administrator] [void]$wmi.StopService()
[Administrator] [void]$wmi.StartService()
[Administrator] # и проверяем нашу работу:
[Administrator] $CertReq = new-object -com certificateauthority.request.1
[Administrator] $CertReq.GetCAProperty("dc2\contoso-dc2-ca",0x18,0,1,0)
2
[Administrator] $CertReq.GetCAProperty("dc2\contoso-dc2-ca",0x19,0,1,0)
2
[Administrator]

всё.

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(). Чем это вызвано я пока не знаю, но задал я задал вопрос нужным людям, поэтому я дам знать в случае ответа на вопрос.

В первой части управления 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 без обновления их содержимого и дат.

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

Продолжая тему криптографии, предлагаю несколько ближайших постов посвятить вопросам использования 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.