В предыдущем посте мы ознакомились с основными контейнерами с объектами 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 :-)
Comments: