Contents of this directory is archived and no longer updated.

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


Share this article:

Comments:

Comments are closed.