В предыдущем посте мы ознакомились с основными контейнерами с объектами PKI в Active Directory и смогли изучить функциональный аналог ключа dspublish в утилите certutil. Если публикация сертификатов в AD задача простая даже для Certutil, то просмотр содержимого может быть весьма нетривиальным. Например, если вы хотите посмотреть содержимое записи NTAuthCertificates, то придётся выполнить вот такую команду:
certutil –viewstore "CN=NTAuthCertificates,CN=Public Key Services,CN=Services,CN=Configuration, ForestRootDomainDN"
такие вещи совершенно неприспособлены к командной строке, поскольку надо набирать много текста и ошибиться весьма просто. Одно хорошо, команда выводит графическое окошко, где мы можем посмотреть содержимое. Но тут есть несколько неудобных моментов: мы не можем посмотреть несколько контейнеров сразу, для каждого контейнера надо выполнять отдельную команду. В этом окошке мы можем только посмотреть на содержимое контейнера и всё. Ни добавить, ни удалить сертификат мы не можем. Для добавления сертификатов мы можем воспользоваться тем же certutil или моим скриптом, который был опубликован в предыдущем посте. Графика — хорошо и замечательно, но мы можем хотеть автоматизировать какие-то задачи или просто посмотреть информацию в консоли. Вы можете подумать, что это не нужно, но преимущество между консольным выводом и графическим диалоговым окном очевидное: из первого можно копировать информацию в буфер обмена. Есть ещё вариант — для просмотра и удаления сертификатов из AD, пользоваться консолью pkiview.msc. Но мы сразу же теряем главную нить — единое средство управления. Т.е. даже похожие операции мы должны выполнять в разных инструментах! Но с появлением PowerShell мы получили единый (хоть и консольный) инструмент, которым можно автоматизировать абсолютно всё! Даже сам PowerShell
Вот, собственно код, который в разы упрощает процесс просмотра содержимого контейнеров:
function Get-ADPKIObject {
<#
.Synopsis
Displays certificates info from Active Directory containers
.Description
Displays info about certificates that are stored in AD PKI-related containers.
.Parameter Container
Optional parameter. Specifies particular container to view. May contain one or
more value from following possible values:
RootCA - retrieves certificates from Certification Authorities container
SubCA - retrieves certificates from AIA container
NTAuthCA - retrieves certificates from NTAuthCertificate directory entry.
if no parameter is set, command will return all certificates from all applicable
containers.
.EXAMPLE
Get-ADPKIObject RootCA
Retrieves certificates from Certification Authorities container
.EXAMPLE
Get-ADPKIObject RootCA, AIA
Retrieves certificates from Certification Authorities and AIA containers
.Outputs
Output AD PKI object collection
#>
[CmdletBinding()]
param([string[]][ValidateSet("RootCA", "SubCA", "NTAuthCA", "")]$Container)
# объявляем массив, который будет хранить выходные объекты
$script:sum = @()
# это весьма крутая штука будет. Каждый объект будет содержать свойство Id или просто
# порядковый номер объекта. При дальнейших операциях с этими объектами вам достаточно
# будет указать его Id вместо длинных и неудобных LDAP/Thumpbrint значений, как это делается
# в certutil и подобных ему утилитах. Нумерацию начнём с единицы
$script:n = 1
# итоговая функция, которая будет разбирать бинарные массивы и готовить выходные объекты
function _formatter_ ($certs, $type, $name) {
# поскольку у нас все объекты в AD находятся в бинарном формате, мы импортируем каждый из них
# в X509Certificate2 объект.
$certs | %{
$script:Cert.Import($_)
# здесь мы создаём образец выходного объекта и обвязываем этот объект необходимыми свойстами и данными
$current = "" | select @{n='Id';e={$script:n}}, Subject, @{n='Type';e={$type}}, @{n='Container';e={$name}},
@{n='Thumbprint';e={$script:Cert.Thumbprint}}, @{n='SerialNumber';e={$script:Cert.SerialNumber}},
@{n='ValidFrom';e={$script:Cert.NotBefore}}, @{n='ValidTo';e={$script:Cert.NotAfter}},
@{n='RawCertificate';e={$script:Cert.RawData}}
# чтобы не писать полный DN поля Subject, мы будем показывать только первую его часть
# (которая отображается в самом сертификате)
[void]($script:Cert.Subject -match 'CN=([^,]+)')
$current.Subject = $matches[1]
# добавляем объект в массив выходных объектов
$script:sum += $current
# очищаем X509Certificate2
$script:Cert.Reset()
# увеличиваем счётчик и обрабатываем следующий элемент
$script:n++
}
}
# ещё одна суб-функция, которая выдёргивает сертификаты из AD в бинарном виде и отправляет
# их в _formatter_, который уже сформирует итоговые объекты.
function _switcher_ ($name) {
# подключаемся к нужному контейнеру
$ldap = [ADSI]("LDAP://CN=$name,$script:ConfigContext")
# как мы знаем, NTAuthCertificates не является контейнером, поэтому для него код будет немного
# отличаться. А отличие будет состоять в том, что мы не будем залезать в контейнер, а сразу читать
# свойства объекта NTAuthCA
if ($name -eq "NTAuthCertificates") {
# убеждаемся, что длина первого элемента свойства cACertificate больше единицы, т.е. содержит ненулевое
# значение. Так же проверяем свойство crossCertificatePair, которое содержит Cross-certificates
# и если оно не нулевое, то отправляем и его на формирование вывода
if ($ldap.cACertificate[0].count -gt 1) {
$certs = @($ldap.cACertificate)
_formatter_ $certs "CA Certificate" $name
}
if ($ldap.crossCertificatePair[0].count -gt 1) {
$certs = @($ldap.cACertificate)
_formatter_ $certs "Cross CA Certificate" $name
}
# и переходим к следующему контейнеру
return
}
# если контейнер указан как Certification Authority и/или AIA, то заглядываем
# внутрь контейнера
$ldap.psbase.children | %{
# и заглядываем в каждую запись на исследование свойств cACetificate и crossCertificatePair
$certs = @($_.cACertificate)
$ccerts = @($_.crossCertificatePair)
# проверяем, что свойство имеет ненулевое значение. Если так, то отправляем
# содержимое этих свойств на формирование вывода
if ($certs[0].count -gt 1) {_formatter_ $certs "CA Certificate" $name}
if ($ccerts[0].count -gt 1) {_formatter_ $ccerts "Cross CA Certificate" $name}
}
}
switch ($Container) {
# конструкцией switch проверяем содержимое аргумента функции, чтобы определить какие именно
# контейнеры надо обследовать.
"RootCA" {_switcher_ "Certification Authorities"}
"SubCA" {_switcher_ "AIA"}
"NTAuthCA" {_switcher_ "NTAuthCertificates"}
# если контейнер в аргументе не указан, то проверяем все контейнеры и записи
"" {
_switcher_ "Certification Authorities"
_switcher_ "AIA"
_switcher_ "NTAUthCertificates"
}
}
# когда вывод будет полностью сформирован, выбрасываем все объекты в консоль
$script:sum
}
Примечание: данная функция является частью файла dspublish.ps1, т.к. использует глобально объявленные переменные.
и вывод у него вот такой красивый:
Id : 3
Subject : Contoso CA
Type : CA Certificate
Container : AIA
Thumbprint : BA8FECE99165E68CE27C9F0AF5F0664FDA39F7A2
SerialNumber : 5DD87E4CFFE3B3BC43F608EB57C767F7
ValidFrom : 2009.02.15. 16:31:15
ValidTo : 2014.02.15. 16:40:11
RawCertificate : {48, 130, 4, 98...}
Id : 4
Subject : sysadmins-LV-CA
Type : Cross CA Certificate
Container : AIA
Thumbprint : 1A28B582E21803D2BFE0DAEEF4593DE372C8EC3C
SerialNumber : 170AD11B0000000000A7
ValidFrom : 2009.11.24. 19:43:10
ValidTo : 2011.03.30. 17:06:53
RawCertificate : {48, 130, 7, 94...}
я считаю его достаточно информативным. Но если хотите посмотреть любой сертификат из этого списка, то можно и сделать просмотр. К сожалению я в программировании не шарю и как работать напрямую с библиотекой просмотрщика сертификатов, поэтому я реализовал просмотр обходным путём:
filter View-ADPKIObject {
<#
.Synopsis
Displays certificates in certificate viewer
.Description
Displays certificates in certificate viewer by selecting necessary certificates ID.
Must be placed after Get-ADPKIObject command only.
.Parameter ID
Specifies certificate ID that was set in Get-ADPKIObject command.
.EXAMPLE
Get-ADPKIObject RootCA | ViewADPKIObject 1, 3
displays certificate with ID = 2 in certificate viewer
.Outputs
Script doesn't generate any output except errors
#>
# судя по конструкции int[] мы можем указать несколько чисел, тогда все выбранные
# сертификаты будут отображены. Номер указывать обязательно.
param([int[]]$ID = $(throw "you must specify number of the object to display"))
# и проверяем входные объекты с конвейера на предмет их ID. Если ID совпадает с
# одним из ID в аргументах, обрабатываем его. Если ID не совпадает, ничего не делаем.
if (@($ID) -contains $_.Id) {
# генерируем в пользовательской папке Temp временный файл с рандомным расширением
$TempFile = [System.IO.Path]::GetTempFileName() + ".cer"
# записываем бинарный массив сертификата в файл в виде DER кодировки
[System.IO.File]::WriteAllBytes($TempFile, $_.RawCertificate)
# запускаем файл (просмоторщик сертификатов)
& $TempFile
# в санитарных целях ждём пол секунды
Start-Sleep 0.5
# и удаляем этот файл, чтобы не копился мусор
del $TempFile -Force
}
}
Как вы видите, ничего сверх-космического или магического в этом коде нет, самое трудное здесь — придумать логику работы. А остальное — накидать несколько строк кода и у нас PowerShell в лёгкую может соперничать с certutil за право называться единой утилитой управления PKI 
Чтобы подкрутить рейтинг PowerShell в этой конкуренции, в следующий раз я покажу как мы можем легко и просто удалять сертификаты из контейнеров AD с использованием PowerShell 