Page 1 of 1 in the PowerShellCertificatesActiveDirectory category

Продолжаем серию постов, которые посвящены базовому управлению объектами PKI в Active Directory. На данный момент мы рассмотрели сценарии публикации и просмотра сертификатов в Active Directory:

На данном этапе нам осталось последнее — удаление сертификатов из AD. Логика здесь очень простая: командой Get-ADPKIObject мы получаем коллекцию объектов, которые представляют собой сертификаты и через конвейер командой Remove-ADPKIObject указываем ID объектов, которые необходимо удалить. Если кто-то уже разбирал код предыдущих скриптов, то ему будет совсем нетрудно понять логику скрипта удаления объектов. Вот он, вместе с комментариями:

function Remove-ADPKIObject {
<#
.Synopsis
    Deletes certificates from Active Directory containers
.Description
    Deletes certificates from Active Directory containers by specifying particular ID or IDs
.Parameter ID
    Specifies certificate ID to delete that was set in Get-ADPKIObject command.
.EXAMPLE
    Get-ADPKIObject RootCA | Remove-ADPKIObject 2
    
    deletes certificate with ID = 2 in certificate viewer
.Outputs
    This command provide a resultant of operation.
#>
    param([int[]]$ID = $(throw "you must specify number of the object to delete"))
    # объявляем массив для хранения сертификатов из контейнера NTAuthCertificates
    begin {$sum = @()}
    process {
        # проверяем тип контейнера входящего объекта
        if ($_.Container -ne "NTAuthCertificates") {
            # если это не NTAuthCertificates, то проверяем, что ID текущего объекта
            # совпадает с ID, который нужно удалить
            if (@($ID) -contains $_.Id) {
                # если совпал, то собираем LDAP-запрос
                $ldap = [ADSI]"LDAP://CN=$($_.Container),$script:ConfigContext"
                # и удаляем текущий объект из AD
                $retn = $ldap.Delete("certificationAuthority", "CN=$($_.Subject)")
                if ($?) {
                    Write-Host "`'$($_.Subject)`' certificate was sucessfully deleted from `'$($_.Container)`' container"`
                    -ForegroundColor Green
                }
            }
        # если контейнер текущего объекта является NTAuthCertificates, то собираем их все в массив
        } else {$sum += $_}
    }
    end {
        # проверяем, что массив непустой (т.е. надо что-то удалять из NTAuthCertificates)
        if ($sum) {
            # если массив непустой, то выбираем те элементы, которые нужно сохранить
            # т.е. ID которых не содержится в аргументах скрипта
            $sum = @($sum |?{$ID -notcontains $_.Id})
            # делаем LDAP-запрос к этому контейнеру
            $ldap = [ADSI]"LDAP://CN=$($_.Container),$script:ConfigContext"
            # проверяем, что после фильтрации, хотя бы один сертификат нужно оставить
            if ($sum.count -ge 1) {
                # записываем первый сертификат. Это необходимо потому что ADSI не поддерживает запись
                # массива сертификатов в свойство cACertificate, а только один сертификат в виде byte[]
                $ldap.put("cACertificate", [byte[]]$sum[0].RawCertificate)
                # а вот простое добавление он поддерживает. Тогда ADSI сам пересоберёт объекты
                # в свойстве в нужный формат данных. На данном этапе я применил маленькую хитрость:
                # как видно, я первый сертификат записываю дважды - предыдущей строкой и в первой итерации
                # текущей строки. Но это не проблема, поскольку метод SetInfo() записывает только уникальные
                # объекты, а дублирующиеся просто отбросит.
                $sum | %{$ldap.cACertificate += ,[byte[]]$($_.RawCertificate)}
                $ldap.SetInfo()
                if ($?) {
                    Write-Host "`'$($_.Subject)`' certificate was sucessfully deleted from `'$($_.Container)`' container"`
                    -ForegroundColor Green
                }
            # а вот если после фильтрации объектов, у нас ничего не остаётся на запись, то это означает, что все
            # сертификаты из этого контейнера удаляются. Поэтому мы просто удаляем запись NTAuthCertificates.
            } else {
                ([ADSI]"LDAP://$script:ConfigContext").Delete("certificationAuthority", "CN=NTAuthCertificates")
                if ($?) {Write-Host "All certificates was sucessfully deleted from NTAuthCertificates entry ." -ForegroundColor Green
                    Write-Warning "This was last certificate in contaner. NTAuthCertificates entry is removed from Active Directory"
                }
            }
        }
    }
}

И теперь можно подвести краткие итоги. Мы смогли реализовать функционал certutil и других графических утилит (консоли MMC) в PowerShell значительно улучшив читабельность выходных объектов, адаптировали под работу из консоли (синтаксис стал значимо короче и более юзерфрендли) и шаг за шагом делаем из PowerShell единое консольное средство управления различными аспектами PKI.

Можно задать вопрос: а кто целевая аудитория всего этого? Целевая аудитория есть — администраторы PKI. Просто у вас не всегда будет возможность использовать графические консоли для решения этих задач (потому что их функционал далёк от идеального). Можно использовать certutil, который умеет много чего, но тоже имеет свои недостатки. Это и ужасный синтаксис, и вырвиглазный неуправляемый вывод результатов. Вобщем я надеюсь, что рано или поздно PowerShell сможет по-настоящему заменить certutil (который вообще-то ни в чём не виноват) и стать единой консолью всех Windows-администраторов. Вот не знаю на сколько это хорошо или плохо, потому что Microsoft всех насильно переводит на PowerShell (это очень показательно продемонстрировано в MS Exchange, где у вас по сути есть только PowerShell и всё). Обычно, насильно переводят на другой инструмент когда он является УГ и очень тяжело на него перевести людей посредством обычной рекламы. Но является ли PowerShell таким УГ? Я пока не готов ответить на этот вопрос. Моё мнение — PowerShell пока что особой революцией не стал. Даже не смотря на тонны рекламы, пеара и прочего, где восхваляют PowerShell, закидывают ногами CMD/WSH. Это обусловлено тем фактом, что не всегда PowerShell бывает удобней CMD/WSH, особенно в тривиальных задачах. Говорить, что синтаксис стал более простым и компактным тоже нельзя, потому что реально функционала из коробки хватает для решения процентов 10 задач. Всё остальное нужно скриптовать и программировать (да-да!) самому. Благо средств для этого в PowerShell хватает. Во что это обычно выливается? А в то, что в большинстве случаев результирующий объём кода будет не сильно меньше, чем в связке WSH + CMD. В любом случае преимущества PowerShell перед остальными очевидны, но они далеко не определяющие, ведь люди раньше решали свои задачи на WSH/CMD, пирожки продавались, бизнес шёл. С одной стороны Microsoft дал людям простор для творчества, т.е. делать в PowerShell всякие потрясающие штуки и всё такое. Но это не совсем то, что нужно было администраторам. Им нужна одна кнопка на весь экран с надписью «Сделать всё п**дато!». Пока что PowerShell и близко не готов стать такой кнопкой, а является «удочкой». Т.е. удочка у вас уже есть, а что касается конечной рыбы (результата), то дело осталось за малым — написать мега-скрипт. Мне вот интересно, что думают администраторы Exchange (поскольку пока что только они получили полноценную поддержку для своего продукта в PowerShell) — стало ли им жить легче с PowerShell или нет? Если да, то это может быть хорошим знаком, что однажды PowerShell станет такой кнопкой. И не будет более в системе certutil и всеми задачами будет рулить PowerShell (пока что это наиболее логичный сценарий развития событий). А вот если их жизнь не стала легче, то обещанной революции (которая по словам Microsoft уже наступила, как и вендекапец у луноходов) не будет, а будет просто какое-то логическое продолжение предыдущих инструментов для сценариев.

К чему я написал столько букв? К тому, что я ежедневно задаю себе один и тот же вопрос: а зачем я всё это делаю? А ответ найти очень непросто, потому что отмазы вида «проще, удобней, красивее» не годятся для серьёзного аргумента. На самом деле я не ищу ответ на него, а просто говорю себе «так надо» и делаю. Поэтому не надо меня использовать как пример «правильного пользователя PowerShell» и пытаться повторить что-то подобное астрономических масштабов на овер9000 строк — поверьте, оно не стоит того. Используйте его по мере сил. Если чего-то будет не хватать и его решение потребует значительных усилий — посмотрите на готовые утилиты, они наверняка будут уметь то, что вам надо.

Удачи!© One

Monday, December 14, 2009 8:47:12 PM (FLE Standard Time, UTC+02:00)   Comments [0]    

 

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

Friday, December 11, 2009 11:09:45 PM (FLE Standard Time, UTC+02:00)   Comments [0]    

 

Подоспела ещё одна задачка для PowerShell'а — управление объектами PKI в Active Directory. Active Directory содержит целый раздел посвящённый PKI и вот из чего он состоит:

  • CN=NTAuthCertificates, CN=Public Key Services, CN=Services, CN=Configuration, ForestRootDomain
  • CN={CA name},CN=AIA, CN=Public Key Services, CN=Services, CN=Configuration, ForestRootDomain
  • CN={CA name},CN=CDP, CN=Public Key Services, CN=Services, CN=Configuration, ForestRootDomain
  • CN={CA name},CN=Certification Authority, CN=Public Key Services, CN=Services, CN=Configuration, ForestRootDomain

Вот о них мы сегодня и поговорим. В AD есть ещё несколько контейнеров, которые связаны с PKI, но они сегодня интереса представлять не будут. Как мы уже знаем, в:

  • NTAuthCertificates публикуются все сертификаты CA, которые выдают сертификаты для аутентификации пользователей в домене. В том числе для логона смарт-картой, аутентификации в IIS или для аутентификации EAP-TLS в VPN. Все сертификаты в этой записи хранятся в виде массива;
  • AIA публикуются сертификаты промежуточных (Intermediate) CA, за счёт чего можно значительно сократить время построения цепочек сертификатов. При этом не обязательно должен содержать сертификаты CA, которые зарегистрированы в текущем лесу. Это могут быть сертификаты промежуточных CA сторонних компаний, сертификаты которых вы используете. По большому счёту сюда должны публиковаться все сертификаты;
  • CDP публикуются CRL списки CA для ускорения проверки сертификатов в цепочке. Так же, как и в случае с AIA не обязательно может содержать CRL'ы CA только текущего леса. Сюда могут публиковаться и CRL сторонних CA, сертификаты которых вы используете;
  • Certification Authority публикуются сертификаты корневых CA, которым должен доверять весь лес.

Вы спросите, а зачем это всё, если то же самое можно сделать через групповые политики? Ответ тут достаточно очевиден. Дело в том, что PKI не видит границ доменов и эти сертификаты распространяются по всему лесу вместе с репликацией. Поэтому для добавления нового доверенного корневого CA вам придётся создавать одинаковую политику в каждом домене леса. И вы некоторые объекты (например CRL) не можете распространять через GPO.

Какие у нас есть инструменты для управления данными контейнерами? Их у нас несколько:

  • ADSIEdit.msc — обеспечивает только просмотр содержимого контейнеров в AD (но сами записи там нечитабельны);
  • PKIView.msc — позволяет просматривать содержимое каждого контейнера и удалять оттуда сертификаты. C использованием данной консоли мы можем добавлять сертификаты только в NTAuthCertificates. Остальные — только чтение и удаление;
  • certutil — интерфейс командной строки, который позволяет добавлять, просматривать контейнеры и удалять сертифакты из них.

Кажется, что certutil'а хватит всем. Но у него есть одна большая проблема — ужасный синтаксис. Напрмер, если вы хотите посмотреть CRL'ы в AD, то придётся делать что типа такого:

certutil –viewstore ldap:///CN=MyCA,CN=CRL,CN=CDP,CN=Public%20Key%
20Services,CN=Services,CN=Configuration,DC=contoso,DC=com?certificateRevocationList?base?objectClass=cRLDistributionPoint

этот синтаксис возможно чем-то универсален, но cовершенно неудобный для использования в командной строке, поэтому я поставил задачу решить этот вопрос с помощью PowerShell. И вот как я его решил:

Примечание: к сожалению я не в состоянии объяснять все особенности работы ADSI и PowerShell, поэтому для понимания работы скрипта нужно иметь представление и некоторый опыт скриптования с использованием ADSI и PowerShell.

#####################################################################
# dspublish.ps1
# Version 0.7
#
# Adds certificates in Active Directory containers
#
# Vadims Podans (c) 2009
# http://www.sysadmins.lv/
#####################################################################
#requires -Version 2.0

# любая ошибка будет фатальной, поэтому при её возникновении останавливаем работу
# скрипта, чтобы предотвратить фатальные изменения в Active Directory
trap {break}
# объявляем глобальные переменные, которые будут использоваться всеми функциями скрипта
$script:ConfigContext = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().GetDirectoryEntry().distinguishedName
$script:ConfigContext = "CN=Public Key Services,CN=Services,CN=Configuration," + $script:ConfigContext
$script:Cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2

function Publish-ADPKIObject {
<#
.Synopsis
    Publishes certificates to Active Directory containers
.Description
    Publishes certificates to Public Key Services containers in Active Directory.
.Parameter File
    Specifies the path to certificate file or X509Certificate2 object.
    Certificates may be passed through pipeline.
.Parameter Container
    Specifies the AD PKI container to publish file. For certificates only
    following containers MUST be used:
    
    RootCA - indicates that certificate will be published to Certification Authorities container
    SubCA - indicates that certificate will be published to AIA container
    NTAuthCA - indicates that certificate will be added to NTAuthCertificate directory
    entry. If not exist, entry will be created.
    
    you MAY specify several containers at once. Certificate will be added to all
    specified containers. Entry names are based on certificate subject.
    
.Parameter Force
    forces object rewrite if object already exist in Active Directory
.EXAMPLE
    dir *.cer | Publish-ADPKIObject RootCA, SubCA
    
    will publish all .CER certificates to Certification Authorities and AIA containers
.EXAMPLE
    Publish-ADPKIObject certificate.cer RootCA -Force
    
    will publish 'Certificte.cer' certificate to Certification Authorities container. If
    object already exist, it will rewrited
.Outputs
    This command provide a resultant of operation.
#>
[CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [object[]]$file,
        [Parameter(Mandatory = $true, Position = 1)]
        [string[]]$Container,
        [switch]$Force
    )
    begin {
        # функция, которая будет осуществлять запись сертификатов в AD
        function _ldaproutine_ ($ldap, $CN, $script:Cert, $name, $Force) {
            $path = [ADSI]"LDAP://$ldap"
            # убеждаемся, что такой же объект не существует в AD
            if ($($path.psbase.children | ?{$_.cn -eq $CN})) {
                # если существует, проверяем ключ Force, который позволяет перезаписывать
                # объекты
                if ($force) {
                    $ldap = [ADSI]"LDAP://CN=$CN,$ldap"
                    # если ключ Force указан, то мы просто перезаписываем свойство
                    # cACertificate новым сертификатом
                    $ldap.put("cACertificate", $script:Cert.RawData)
                    # предыдущей строкой мы просто перезаписали объект, который теперь
                    # надо записать в AD методом SetInfo()
                    $retn = $ldap.SetInfo()
                    if ($?) {Write-Host "`'$CN`' certificate is sucessfully rewrited to `'$name`' container" -ForegroundColor Green}
                # если объект уже существует и ключ Force не указан, то просто выводим сообщение, что такой объект уже существует
                } else {Write-Warning "Object already exist in `'$name`' container. Use -Force switch to rewrite"}
            } else {
                # если объект ещё не существует в указанном контейнере, то создаём в нём новый объект
                # с типом certificationAuthority
                $CA = $path.Create("certificationAuthority","CN=$CN")
                # записываем сертификат в свойство cACertificate в бинарном виде из объекта X509Certificate2
                $CA.Put("cACertificate", $script:Cert.RawData)
                # записываем нулями обязательные поля, которые требуются для создания объекта. Использовать как есть.
                $CA.Put("authorityRevocationList", 0)
                $CA.Put("certificateRevocationList",0)
                # когда объект сформирован, записываем его в AD
                $retn = $CA.SetInfo()
                if ($?) {Write-Host "`'$CN`' certificate is sucessfully added to `'$name`' container" -ForegroundColor Green}
            }
        }
    }
    # рабочая секция, которая будет разбирать входные сертификаты и подготавливать необходимые данные
    # которые необходимы для записи 
    process {
        # выбираем текущий объект сертификата
        $script:Certificate = gi $file -ErrorAction Stop
        # проверяем, что объект не является готовым объектом X509Certificate2
        if ($script:Certificate -isnot [System.Security.Cryptography.X509Certificates.X509Certificate2]) {
            # если не является, то это будет файл. Проверяем расширение файла.
            # допускаются только расширения CER и CRT
            if (".cer", ".crt" -contains $script:Certificate.Extension) {
                # если это CER или CRT файл, то конвертируем его в объект X509Certificate2
                $script:Cert.Import($script:Certificate.FullName)
                # в переменную $CN записываем первую часть поля Subject сертификата
                [void]($script:Cert.Subject -match 'CN=([^,]+)')
                $CN = $matches[1]
            }
        # если у нас на конвейер или через аргументы поступил готовый X509Certificate2, то только выбираем
        # поле Subject в отдельную переменную.
        } else {
            $script:Cert = $script:Certificate
            [void]($script:Cert.Subject -match 'CN=([^,]+)')
            $CN = $matches[1]
        }
        # проверяем аргументы, которые указывают на контейнеры, куда надо записывать наши сертификаты.
        # я не делал проверку этих контейнеров в секции param() через использование ValidateSet
        # поскольку данная функция в будущем будет использоваться и для публикации CRL. А для
        # них нужно вручную прописывать имя записи в контейнере CDP и оно может быть произвольным.
        # отфильтровываем все неправильные контейнеры, которые были заданы при вызове функции
        $Container = $Container | ?{"RootCA","SubCA","NTAuthCA" -contains $_}
        # если после фильтрации ни одного валидного контейнера не осталось, то очень сильно ругаемся.
        if (!$Container) {throw "For certificate containers only following values are applicable: RootCA, SubCA, NTAuthCA"}
        # проверяем, что входной сертификат содержит свойство CertificateAuthority равным True.
        # это свойство соответствует расширению Basic Constraints в сертификате, которое может иметь
        # значения: Subject Type=CA - это сертификат CA и Subject Type=End entity, если это конечный
        # сертификат. А в X509Certificate2 конечный сертификат будет иметь значение False в свойстве
        # CertificateAuthority. Но работу скрипта не прерываем, а просто пропускаем текущий сертификат.
        if (!$script:Cert.Extensions | ?{$_.CertificateAuthority -eq $true}) {
            Write-Warning "Input certificate `'$CN`' is not recognized as CA certificate. Skipping"
            return
        }
        # а теперь подготавливаем необходимые данные для записи в зависимости от названия контейнера
        switch ($Container) {
            "RootCA" {
                # указываем название данного контейнера в AD
                $name = "Certification Authorities"
                # получаем LDAP объект этого контейнера
                $ldap = "CN=$name,$script:ConfigContext"
                # и отправляем всё это в функцию записи
                _ldaproutine_ $ldap $CN $script:Cert $name $Force
                $script:Cert.Reset()
            }
            "SubCA" {
                # здесь то же самое, что и для RootCA
                $name = "AIA"
                $ldap = "CN=$name,$script:ConfigContext"
                _ldaproutine_ $ldap $CN $script:Cert $name $Force
                $script:Cert.Reset()
            }
            "NTAuthCA" {
                $name = "NTAuthCertificates"
                $ldap = [ADSI]"LDAP://CN=$name,$script:ConfigContext"
                # поскольку NTAuthCertificates не является контейнером, а отдельной
                # записью, которая содержит массив сертификатов, то правила записи
                # здесь немного иные. Сначала проверяем, что эта запись уже существует в AD.
                if (!$ldap.cn) {
                    # если нет, то создаём её
                    $CA = ([ADSI]"LDAP://$script:ConfigContext").Create("certificationAuthority","CN=$name")
                    # заполняем обязательные свойства объекта и первый сертификат.
                    $CA.Put("authorityRevocationList", 0)
                    $CA.Put("certificateRevocationList",0)
                    $CA.put("cACertificate", $script:Cert.RawData)
                    # когда объект уже готов, то просто записываем его в AD
                    $retn = $CA.SetInfo()
                    if ($?) {Write-Host "`'$CN`' certificate is sucessfully added to `'$name`' container" -ForegroundColor Green}
                    return
                }
                # а вот если эта запись уже есть, то ничего создавать не надо, а просто добавляем
                # новый сертификат вдобавок к существующим. При этом обратите внимание, что объект добавляется
                # как массив (используется запятуя сразу за оператором). Это связано с особенностью работы PowerShell с
                # массивами. Поскольку бинарный сертификат сам по себе является массивом, то при простом добавлении
                # к существующему бинарному массиву, просто сделает ресайз текущего массива. Чтобы новый массив
                # записать как отдельный элемент нового массива - надо при помощи запятой явно это указать
                $ldap.cACertificate += ,$cert.RawData
                # и записываем объект обратно в AD.
                $retn = $ldap.SetInfo()
                if ($?) {Write-Host "`'$CN`' certificate is sucessfully added to `'$name`' container" -ForegroundColor Green}
                $script:Cert.Reset()
            }
        }
    }
}

А примеры использования этого скрипта приведены во встроенном хелпе и сводятся к одной из схем:

Publish-ADPKIObject <certificate> <container> –Force
<certificate> | Publish-ADPKIObject –Force

Причём вы можете указывать несколько контейнеров сразу, например: Publish-ADPKIObject file.cer NTAuthCA, RootCA, SubCA. Я думаю, что этот скрипт получился не такой уж и сложный и его разобрать с помощью моих комментариев не так и сложно. Его можно спокойно расширить под другие контейнеры, например, CDP или KRA. Единственное, что здесь пока не реализовано — санитизация имён объектов. На сколько я знаю, certutil этого тоже не поддерживает. Но сделать её надо. Правда, я пока не нашёл ни одного стандартного механизма, который бы санитизировал имена :'(

Wednesday, December 09, 2009 9:35:17 PM (FLE Standard Time, UTC+02:00)   Comments [0]    

 

Page 1 of 1 in the PowerShellCertificatesActiveDirectory category
 · 
All content © 2008 - 2010, Vadims Podāns
"Spaces" Theme provided by: Vadims Podāns
About


E-mail - Send mail to the author(s)
Live Messenger -
My former blog -
For english language visitors

Translate via Google Translator

Библиотека
Календарик
<March 2010>
SunMonTueWedThuFriSat
28123456
78910111213
14151617181920
21222324252627
28293031123
45678910

Карта расположения посетителей
Favorites

Домашняя страничка Теры Патрик

Disclaimer
Вся информация на сайте предоставляется на условиях «как есть», без предоставления каких-либо гарантий и прав.

При использовании материалов c данного сайта ссылка на оригинальный источник обязательна.