Contents of this directory is archived and no longer updated.

Posts on this page:

В первой части управления 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.

Ещё небольшое дополнение к Certificate Management Pack, который я сейчас пишу. Данный код позволяет запускать и останавливать службу Certification Authority на сервере. Поскольку я для решения этой задачи использую WMI, то ремотинг будет обеспечен. Следует учесть, что все команды по управлению службой CA будут расположены за конвейером после команды Get-CertificationAuthority. Это было сделано в рамках удобства и стандартизации. Когда мы хотим что-то настроить в CA, то сначала должны указать его имя/объект. Поскольку у нас уже есть готовое решение для этого, то почему бы его и не использовать? Код на самом деле очень простой и в нём разберётся даже новичок:

#####################################################################
# Start-Stop CA.ps1
# Version 1.0
#
# Starts and stops Certification Authority service on specified CA
# Requires Get-CertificationAuthority cmdlet:
# http://www.sysadmins.lv/PermaLink,guid,947401b2-312a-4129-860b-4296dfe46cb2.aspx
#
# Usage:
# Get-CertificationAuthority "CAName" | Stop-CertificationAuthority
# stops CA service on CA named CAName
# Get-CertificationAuthority | Start-CertificationAuthority
# starts all Enterprise CAs in current forest
#
# Vadims Podans (c) 2009
# http://www.sysadmins.lv/
#####################################################################

function Start-CertificationAuthority ($CA) {
    process {
        $status = (gwmi Win32_Service -ComputerName $CA.Computer -Filter "Name = 'CertSvc'").StartService()
        if ($status.ReturnValue -eq 0) {
            Write-Host $CA.CAName Certificate Services successfully started on $CA.Computer -ForegroundColor Green
        } else {
            Write-Warning "Unable to start $($CA.CAName) certificate services"
        }
    }
}

function Stop-CertificationAuthority ($CA) {
    process {
        $status = (gwmi Win32_Service -ComputerName $CA.Computer -Filter "Name = 'CertSvc'").StopService()
        if ($status.ReturnValue -eq 0) {
            Write-Host $CA.CAName Certificate Services successfully stopped on $CA.Computer -ForegroundColor Green
        } else {
            Write-Warning "Unable to stop $($CA.CAName) certificate services"
        }
    }
}

Можно сказать, что выносить данные команды в отдельные функции излишне, ведь это можно и самому дописать в свой скрипт. Но можно и не сказать так, ведь представьте, насколько удобно, когда нужная функция у вас уже есть под рукой, и её не надо писать с нуля.

Update: хочу ещё раз отметить на одну из частых ошибок, которые допускают при написании скриптов в PowerShell. Вы можете просто использовать переменные в двойных кавычках и при использовании этой строки в неё будет подставляться содержимое переменной. Но этого не будет, если вы используете свойство объекта, который хранится в этой переменной, поскольку переменные экспандятся только 1 раз. Этого достаточно для простой переменной, но недостаточно для извлечения содержимого свойста объекта в этой переменной. Ведь сначала нужно извлечь содержимое переменной, а потом прочитать содержимое указанного свойства. Чтобы экспандить такие вещи, переменные в двойных кавычках следует заключать в подвыражение $().

А знаете ли вы как можно легко получить список всех Enterprise CA в текущем домене? А в текущем лесу? Оказывается это очень легко! ADSI — самый лучший способ, если вы хотите пошариться в своей базе AD. Список таких CA находится по пути:

CN=Enrollment Services, CN=Public Key Services, CN=Services, CN=Configuration, DC=Domain, DC=COM

Последние 2 значения уже будут отличаться в зависимости от имени домена. Чтобы получить список объектов по этому пути нужно просто создать соответствующий LDAP объект. Объект делается просто, сначала указывается тип объекта [ADSI], следом идёт префикс ссылки LDAP:// (почти как HTTP://) и после префикса уже этот путь (который называется Distinguished Name или просто DN):

[Administrator] $CA = [ADSI]"LDAP://CN=Enrollment Services, CN=Public Key Services, CN=Services, CN=Configuration, DC=co
ntoso,DC=COM"
[Administrator] $CA


distinguishedName : {CN=Enrollment Services,CN=Public Key Services,CN=Services,CN=Configuration,DC=contoso,DC=com}
Path              : LDAP://CN=Enrollment Services, CN=Public Key Services, CN=Services, CN=Configuration, DC=contoso,DC
                    =COM
[Administrator] $CA.distinguishedName
CN=Enrollment Services,CN=Public Key Services,CN=Services,CN=Configuration,DC=contoso,DC=com

Если посмотреть этот путь в ADSIEdit.msc, то мы увидим, что это контейнер. А раз это контейнер, то нам нужно в него заглянуть. Здесь, к сожалению, нельзя сделать dir $CA.distinguishedName, а так хочется. Чтобы посмотреть содержимое нужно использовать свойство Children (ворненк, дети отаке!):

[Administrator] $ca.Children


distinguishedName : {CN=contoso-DC2-CA,CN=Enrollment Services,CN=Public Key Services,CN=Services,CN=Configuration,DC=co
                    ntoso,DC=com}
Path              : LDAP://CN=contoso-DC2-CA,CN=Enrollment Services,CN=Public Key Services, CN=Services, CN=Configurati
                    on, DC=contoso,DC=COM

distinguishedName : {CN=Contoso CA,CN=Enrollment Services,CN=Public Key Services,CN=Services,CN=Configuration,DC=contos
                    o,DC=com}
Path              : LDAP://CN=Contoso CA,CN=Enrollment Services,CN=Public Key Services, CN=Services, CN=Configuration,
                    DC=contoso,DC=COM



[Administrator]

Уже отсюда невооружённым глазом видны имена CA. Собственно, можно показать только имя самого CA и компьютера, на котором работает этот CA:

[Administrator] $ca.Children | ft Name, DNSHostName

Name                                                        DNSHostName
----                                                        -----------
{contoso-DC2-CA}                                            {dc2.contoso.com}
{Contoso CA}                                                {DC1.contoso.com}


[Administrator]

Здесь есть один важный нюанс. Если получить этот LDAP объект в PowerShell 1.0, то он будет содержать только Distinguished Name, а свойство Children будет отсутствовать в нём. Для этого нужно воспользоваться свойством PSBase, в котором уже будет Children. Командой Select можете выводить на экран и другие свойства, какие вы захотите:

PS C:\> $ca.children


MemberType          : Method
OverloadDefinitions :
TypeNameOfValue     : System.Management.Automation.PSMethod
Value               :
Name                : children
IsInstance          : True



PS C:\> $ca.psbase.children

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


PS C:\> $ca.psbase.children | select Name, DNSHostname

Name                                                        DNSHostname
----                                                        -----------
{contoso-DC2-CA}                                            {dc2.contoso.com}
{Contoso CA}                                                {DC1.contoso.com}


PS C:\>

Поэтому для обратной совместимости между версиями лучше использовать PSBase. В этом объекте будут содержаться не только CA вашего домена, а во всех доменах вашего леса, поскольку эта часть AD реплицируется как Forest naming context, т.е. между всеми контроллерами в лесу. Жизнь была бы неинтересной, если в каждом новом домене приходилось бы переписывать хвост (которая определяет домен, в котором следует искать) каждый раз. Для универсальности можно пойти на военную хитрость — раздобыть FQDN текущего домена, разобрать его и воткнуть в LDAP запрос. Получить имя текущего домена можно очень просто, с использованием статического метода GetCurrentDomain() класса System.DirectoryServices.ActiveDirectory.Domain. На самом деле у этого класса есть ещё куча других полезных методов, поэтому не лишним будет заглянуть по ссылке.

PS C:\> [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()


Forest                  : contoso.com
DomainControllers       : {DC1.contoso.com}
Children                : {}
DomainMode              : Windows2003Domain
Parent                  :
PdcRoleOwner            : DC1.contoso.com
RidRoleOwner            : DC1.contoso.com
InfrastructureRoleOwner : DC1.contoso.com
Name                    : contoso.com



PS C:\>

И свойство Name будет содержать имя нашего домена. Что дальше? А дальше, вполне очевидно, что нам надо заменить все точки на строку вида ", DC=". Вот так:

PS C:\> "contoso.com" -replace "\.", ", DC="
contoso, DC=com

А перед первым именем эту часть можно написать ручками. В итоге универсальная часть кода получится вот такая:

$domain = ([System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()).Name
$domain = "DC=" + $domain -replace '\.', ", DC="
$CA = [ADSI]"LDAP://CN=Enrollment Services, CN=Public Key Services, CN=Services, CN=Configuration, $domain"

Вот теперь у нас есть всё необходимое, чтобы написать простеньку функцию:

function Get-CertificationAuthority ([string]$CAName) {
    $domain = ([System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()).Name
    $domain = "DC=" + $domain -replace '\.', ", DC="
    $CA = [ADSI]"LDAP://CN=Enrollment Services, CN=Public Key Services, CN=Services, CN=Configuration, $domain"
    $CAs = $CA.psBase.Children | %{
        $current = "" | Select CAName, Computer
        $current.CAName = $_ | %{$_.Name}
        $current.Computer = $_ | %{$_.DNSHostName}
        $current
    }
    if ($CAName) {$CAs = @($CAs | ?{$_.CAName -eq $CAName})}
    if ($CAs.Count -eq 0) {throw "Sorry, here is no CA that match your search"}
    $CAs
}

Если выполнить эту функцию без аргументов, то она вернёт все CA в лесу. Но можно указать и какой-то один для каких-то других целей.