Previous Page Page 3 of 4 in the PowerShellCertificates category Next Page

Подоспела ещё одна задачка для 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 [4]    

 

Примечание: данный материал публикуется на правах ТЗ (ТЗТайное Знание) и обязателен для знания администраторам PKI.

В предыдущей части (Certificate chaining engine в PowerShell) мы рассмотрели реализацию certificate chaining engine в .NET в виде класса X509Chain. Но этот рассказ был бы неполным, если мы не поговорили про CAPICOM.Chain. Хоть Microsoft рекомендует использовать .NET, а не CAPICOM для решения данной задачи, я считаю необходимым осветить этот вопрос. Для начала начнём с реализации CAPICOM в PowerShell. По непонятным мне пока причинам мне не удалось завести его в PowerShell на системах Windows Vista и выше, поэтому примеры буду приводить на Windows Server 2003.

PS G:\> $chain = New-Object -ComObject "CAPICOM.Chain" PS G:\> $chain Certificates ------------ PS G:\> $chain | gm -MemberType methods TypeName: System.__ComObject#{ca65d842-2110-4073-aee3-d0aa5f56c421} Name MemberType Definition ---- ---------- ---------- ApplicationPolicies Method IOIDs ApplicationPolicies () Build Method bool Build (ICertificate) CertificatePolicies Method IOIDs CertificatePolicies () ExtendedErrorInfo Method string ExtendedErrorInfo (int) PS G:\>

Так же, как и в .NET у нас есть метод Build(), который запускает certificate chaining engine для указанного сертификата. Но это COM объект и X509Certificate2 объекты не принимает. Поэтому мы средствами этого же CAPICOM получим нужный объект сертификата:

PS G:\> $store = New-Object -ComObject "CAPICOM.Store" PS G:\> $store.Open(2,"my",128) PS G:\> $cert = @() PS G:\> $store.Certificates | %{$cert += $_} PS G:\> $cert = $cert[1] PS G:\> $cert Version : 3 SerialNumber : 167D6D32000000000011 SubjectName : CN=Administrator, CN=Users, DC=sysadmins, DC=lv IssuerName : CN=sysadmins-LV-CA, DC=sysadmins, DC=lv ValidFromDate : 2009.08.07. 16:09:56 ValidToDate : 2010.08.07. 16:09:56 Thumbprint : 1FA71B51BCE461BEE24B11AA9EF855ED00DFDFD4 Archived : False PrivateKey : PS G:\>

Вот такой объект сертификата мы получили. Вот его и пропустим через метод Build():

PS G:\> $chain.Build($cert) False PS G:\> $chain.Status() 32 PS G:\> $chain.Certificates Version : 3 SerialNumber : 167D6D32000000000011 SubjectName : CN=Administrator, CN=Users, DC=sysadmins, DC=lv IssuerName : CN=sysadmins-LV-CA, DC=sysadmins, DC=lv ValidFromDate : 2009.08.07. 16:09:56 ValidToDate : 2010.08.07. 16:09:56 Thumbprint : 1FA71B51BCE461BEE24B11AA9EF855ED00DFDFD4 Archived : False PrivateKey : Version : 3 SerialNumber : 3DFFAF914D613282427BD591C1FB586D SubjectName : CN=sysadmins-LV-CA, DC=sysadmins, DC=lv IssuerName : CN=sysadmins-LV-CA, DC=sysadmins, DC=lv ValidFromDate : 2009.08.06. 10:21:01 ValidToDate : 2014.08.06. 10:30:54 Thumbprint : E82ACC45841280DDEAB9F7847418FA26354457A7 Archived : False PrivateKey : PS G:\> $chain.ApplicationPolicies() Name FriendlyName Value ---- ------------ ----- 123 Smart Card Logon 1.3.6.1.4.1.311.20.2.2 101 Client Authentication 1.3.6.1.5.5.7.3.2 PS G:\>

Метод вернул False, т.е. наш сертификат не прошёл проверку. Свойство Certificates содержит все сертификаты в текущей цепочке. А метод ApplicationPolicies() показал Application Policies (в прошлом EKU). Ну и метод Status() вернул код ошибки. Все коды ошибок расписаны в описании самого метода. Т.е. ошибка 32 по табличке означает:

CAPICOM_TRUST_IS_UNTRUSTED_ROOT (&H00000020) — The certificate or certificate chain is based on an untrusted root.

Особо углубляться дальше я смысла не вижу, поскольку используя эти примеры можно поупражняться в других задачах с использованием CAPICOM. А вместо этого, мы разберём кое-какие неочевидные, но очень важные вещи.

Когда вышел Remote Desktop Connection 7.0 (который поставляется вместе с Windows 7), Александр Станкевич (MVP: Enterprise Security) заметил очень плохую особенность с ним, а именно — рандомно пропал доступ к терминальным серверам, которые используют SSL для терминальных подключений. В ходе множества проверок было констатировано:

  • цепочка завершалась на доверенном корневом сертификате (через просмотр сертификата);
  • CDP/AIA пути были валидные и CRL не были просрочены;
  • остальные параметры были сконфигурированы верно.

RDP клиент мотивировал отказ в подключении следюущим: «RDP клиент не смог проверить сертификат на отзыв». Причём подключение с предыдущих версий RDC, а так же подключение к TS Web Access через IE было успешным. Как выяснилось на днях, причина была в том, что в новом RDC был изменён алгоритм certificate chaining engine и новая версия RDC больше не доверяла корневым сертификатам в пользовательском хранилище. Переустановка корневого сертификата в компьютерное хранилище решала проблему RDC 7. Но, как я говорил, в Windows 7/2008 R2 было изменено поведение certificate chaining engine в реализации X509Chain. Продемонстрирую небольшую табличку, которая покажет доверие пользовательским корневым сертификатам в CAPICOM и .NET реализациях certificate chaining engine для различных операционных систем:

  X509Chain (.NET) CAPICOM.Chain (CryptoAPI)
Windows XP Yes, of course! No!
Windows Server 2003 Yes, of course! No!
Windows Vista Yes, of course! No!
Windows Server 2008 Yes, of course! No!
Windows 7 No! No!
Windows Server 2008 R2 No! No!

Таблица доверия пользовательскким корневым сертификатам различными движками построения цепочки сертификатов

Следует учитывать, что различные приложения могут использовать свой движок для построения цепочек сертификатов, как RDC 7.0 и PKIView.msc использует CAPICOM, а PowerShellX509Chain. Однако, у меня есть уверенность, что существует ещё одна реализация этого движка, которую использует Internet Explorer. Это только мои догадки, но они основаны на том, что Internet Explorer (кроме Windows 7 и выше) использует пользовательский контейнер для проверки SSL веб-сайтов, т.е. по симптомам похоже на X509Chain. Однако, этот класс появился только в .Net Framework 2.0, которого, к примеру в оригинальной инсталляции Windows XP/2003 не было. Либо, как вариант, есть возможность настраивать этот момент в каждой реализации движка построения цепочек, но я пока не нашёл как. Это пока всё, что у меня есть по этому поводу.

Monday, October 19, 2009 9:52:44 PM (FLE Daylight Time, UTC+03:00)   Comments [5]    

 

Я предлагаю снова поговорить о certificate chaining engine, о котором мы уже говорили в посте Certificate Chaining Engine — как это работает но в рамках его реализации в .NET и PowerShell. В Windows есть несколько реализаций этого chaining engine. Самые популярные:

CAPICOM — вещь несколько стрёмная и её лучше избегать. Тем более мне не удалось завести его в Vista/Windows 7. Реализация в .NET в виде X509Chain ничуть не хуже, но, в то же время, проще. Итак, предлагаю начать с создания этого объекта с помощью New-Object:

[↓] [vPodans] $chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain [↓] [vPodans] $chain ChainContext ChainPolicy ChainStatus ChainElements ------------ ----------- ----------- ------------- 0 System.Security.Cryptograp... {} {} [↓] [vPodans] $chain | gm -membertype methods TypeName: System.Security.Cryptography.X509Certificates.X509Chain Name MemberType Definition ---- ---------- ---------- Build Method bool Build(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) Equals Method bool Equals(System.Object obj) GetHashCode Method int GetHashCode() GetType Method type GetType() Reset Method System.Void Reset() ToString Method string ToString() [↓] [vPodans]

Здесь видно, что метод, который будет строить и проверять цепочку будет называться Build() и в качестве аргумента этот метод принимает только объекты x509Certificate2, а возвращать объект типа Boolean — True/False. Объекты такого типа можно найти в провайдере Certififcates (просто выполнить dir cert:\store\container) или импортировать из файла — Импорт сертификатов в PowerShell. Но сначала мы посмотрим, что здесь можнол настроить. А настроить можно ChainPolicy, представляющий собой объект X509ChainPolicy. Эта политика позволяет задавать порядок проверки отзыва сертификатов, соответствие сертификата каким-то certificate policy, наличие определённых application policy (в прошлом известен нам как EKUExtended Key Usage) и некритичность каких-то ошибок. Мы всё разбирать не будем, а только основное:

[↓] [vPodans] $chain.ChainPolicy ApplicationPolicy : {} CertificatePolicy : {} RevocationMode : Online RevocationFlag : ExcludeRoot VerificationFlags : NoFlag VerificationTime : 17.10.2009 20:35:17 UrlRetrievalTimeout : 00:00:00 ExtraStore : {} [↓] [vPodans]

Первое, что может быть интересным — Revocation Mode, который задаёт режим проверки отзыва и может иметь 3 вполне понятных значения, которые перечислены в X509RevocationMode:

[↓] [vPodans] [System.Enum]::GetNames([system.security.cryptography.x509certificates.x509revocationmode]) NoCheck Online Offline

Тут очень просто:

  • NoCheck — проверяет цепочку сертификатов без проверки на отзыв любых сертификатов в цепочке;
  • Online — проверяет сертификаты в цепочке скачивая новые списки CRL из свойства CDP сертификата, игнорируя локальный кеш CRL (используется по умолчанию, как это видно в просмотре свойств ChainPolicy)
  • Offline — проверяет сертификаты на отзыв с использованием кешированного CRL (если есть).

Если проверяем сертификаты на отзыв, то мы можем выбрать что именно будем проверять и этот выбор перечислен в X509RevocationFlag:

[↓] [vPodans] [System.Enum]::GetNames([system.security.cryptography.x509certificates.x509revocationflag]) EndCertificateOnly EntireChain ExcludeRoot

и здесь тоже всё достаточно понятно:

  • EndCertificateOnly — проверяет на отзыв только сам проверяемый сертификат. Остальные сертификаты в цепочке не проверяются;
  • EntireChain — проверяет на отзыв абсолютно все сертификаты в цепочке включая корневой сертификат. Правила хорошего тона диктуют нам не включать в корневой сертификат CA поля CDP и AIA, поскольку это лишено смысла и в большинстве случаев с этим флагом вы будете получать невалидную цепочку;
  • ExcludeRoot — проверяет на отзыв все сертификаты в цепочке, кроме корневого сертификата, что есть религионзно правильно и этот флаг установлен по умолчанию.

Следующий момент определяет флаги проверки, т.е. по каким именно критериям будет проверяться цепочка сертификатов:

[↓] [vPodans] [System.Enum]::GetNames([system.security.cryptography.x509certificates.x509verificationflags]) NoFlag IgnoreNotTimeValid IgnoreCtlNotTimeValid IgnoreNotTimeNested IgnoreInvalidBasicConstraints AllowUnknownCertificateAuthority IgnoreWrongUsage IgnoreInvalidName IgnoreInvalidPolicy IgnoreEndRevocationUnknown IgnoreCtlSignerRevocationUnknown IgnoreCertificateAuthorityRevocationUnknown IgnoreRootRevocationUnknown AllFlags

Эти флаги так же достаточно понятны и подробно разжёваны в перечислении X509VerificationFlags.

Примечание: эти флаги показывают что будет исключено из проверки, а не наоборот. По умолчанию проверяется всё (установлен NoFlag).

Как можно задавать эти флаги и режимы, если есть такая потребность? Можно пойти двумя способами — правильным и неправильным, хотя оба рабочие:

[↓] [vPodans] # правильный метод: [↓] [vPodans] $revflag = [System.Security.Cryptography.X509Certificates.X509RevocationFlag]::EndCertificateOnly [↓] [vPodans] $chain.ChainPolicy.RevocationFlag = $revflag [↓] [vPodans] $chain.ChainPolicy ApplicationPolicy : {} CertificatePolicy : {} RevocationMode : Online RevocationFlag : EndCertificateOnly VerificationFlags : NoFlag VerificationTime : 17.10.2009 20:35:17 UrlRetrievalTimeout : 00:00:00 ExtraStore : {} [↓] [vPodans] # неправильный метод, но мне он нравится [↓] [vPodans] $chain.ChainPolicy.RevocationFlag = "excluderoot" [↓] [vPodans] $chain.ChainPolicy ApplicationPolicy : {} CertificatePolicy : {} RevocationMode : Online RevocationFlag : ExcludeRoot VerificationFlags : NoFlag VerificationTime : 17.10.2009 20:35:17 UrlRetrievalTimeout : 00:00:00 ExtraStore : {} [↓] [vPodans]

В принципе, все эти enumeration можно просто указывать в виде строки, а PowerShell уже сам подобъёт его под нужный тип.

Примечание: флаги проверки на отзыв можно указывать только по одному, а флаги исключений можно перечислять через запятую. Это видно по названию класса: если класс указан в единственном числе (X509RevocationFlag), то и значение можно указать только одно, а если во множественном числе (X509VerificationFlags), то их можно указать несколько через запятую.

В принципе, можно делать проверку:

[↓] [vPodans] $valid = (dir cert:\currentuser\my)[1] [↓] [vPodans] $invalid = (dir cert:\currentuser\my)[0] [↓] [vPodans] $chain.Build($valid) True [↓] [vPodans] $chain.ChainElements | select -expand certificate Thumbprint Subject ---------- ------- 986D375362652FE9E39BA4D042A6B8BA75745998 CN=Administrator, CN=Users, DC=sysadmins, DC=lv E82ACC45841280DDEAB9F7847418FA26354457A7 CN=sysadmins-LV-CA, DC=sysadmins, DC=lv [↓] [vPodans]

Я из локального хранилища взял заведомо хороший сертификат и плохой (разумеется, это сертификат с сайта БиЛайн :)). Метод Build() вернул True, что означает успешную проверку всей цепочки моего сертификата до доверенного корня. А вот что случилось с сертификатом билайна:

[↓] [vPodans] $chain.reset() [↓] [vPodans] $chain.Build($invalid) False [↓] [vPodans] $chain.ChainElements | select -expand certificate Thumbprint Subject ---------- ------- EB74DA32E865C78FCB853DDA5FE45962098E1B3B CN=trust.beeline.ru, OU=DIT, O=Vimpelcom, L=Moscow, S=Moscow, C=RU [↓] [vPodans] $chain.ChainStatus Status StatusInformation ------ ----------------- PartialChain Sertificesanas kedi nevareja veidot pie uzticamas saknes... RevocationStatusUnknown Atsauksanas funkcija nevareja parbaudit sertifikata atsa... OfflineRevocation Atsauksanas funkcija nevareja parbaudit atsauksanu, jo a... [↓] [vPodans] $chain.ChainStatus | %{$_.statusinformation.trim()} Sertificesanas kedi nevareja veidot pie uzticamas saknes iestades. Atsauksanas funkcija nevareja parbaudit sertifikata atsauksanu. Atsauksanas funkcija nevareja parbaudit atsauksanu, jo atsauksanas serveris bija bezsaiste. [↓] [vPodans]

Поскольку при каждой проверке сертификаты в ChainElements накапливаются, то после каждой проверки следует очищать объект методом Reset(). Как и следовало ожидать, сертификат билайна вернул False, показывая, что цепочка у этого сертификата имеет проблемы. В случае неуспешной проверки, будет заполняться свойство ChainStatus. Данное свойство содержит все ошибки, которые были костатированы при проверке цепочки. Для меня пока непонятным стал факт, что ошибки он написал на латышском языке, хотя я его об этом не просил. Откуда он это взял — непонятно, но он сказал, что не смог построить цепочку до доверенного корня и функция проверки отзыва провалилась по всем статьям, поскольку пути в CDP сертификата нерабочие.

Примечание: обязательно следует учитывать тот факт, что при построении цепочки сертификатов и проверке доверия класс X509Chain в Windows 7 и Windows Server 2008 R2 работает в контексте LocalSystem и всегда игнорирует пользовательские контейнеры Trusted Root Certification Authorities. Поэтому если корень цепочки не заканчивается на одном из сертификатов Trusted Root CAs хранилища LocalMachine, то цепочка будет считаться недоверенной. Для предыдущих ОС цепочка может заканчиваться на пользовательском хранилище Current User.

В принципе это всё, что вам следует знать про реализацию certificate chaining engine в .NET и его использование в PowerShell. В качестве бонуса прилагаю скрипт для проверки цепочки сертификатов:

#####################################################################
# Test-Certificate.ps1
# Version 1.0
#
# Passes certificate through certificate chaining engine
#
# Vadims Podans (c) 2009
# http://www.sysadmins.lv/
#####################################################################
#requires –Version 2.0

function Test-Certificate {
[CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        $Certificate,
        [System.Security.SecureString]$Password,
        [ValidateSet("NoCheck", "Online", "Offline")]
        [string]$CRLMode = "Online",
        [ValidateSet("EndCertificateOnly", "EntireChain", "ExcludeRoot")]
        [string]$CRLFlag = "ExcludeRoot",
        [ValidateSet("AllFlags", "AllowUnknownCertificateAuthority", "NoFlag", "IgnoreNotTimeValid",
            "IgnoreCtlNotTimeValid", "IgnoreNotTimeNested", "IgnoreInvalidBasicConstraints",
            "IgnoreWrongUsage", "IgnoreInvalidName", "IgnoreInvalidPolicy", "IgnoreEndRevocationUnknown",
            "IgnoreCtlSignerRevocationUnknown", "IgnoreCertificateAuthorityRevocationUnknown",
            "IgnoreRootRevocationUnknown")]
        [string[]]$VerificationFlags = "NoFlag"
    )
    
    begin {
        # создаём объекты X509Certificate2 и X509chain ещё до поступлениея первого объекта сертификата
        $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
        $chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain
        # в X509Chain записываем параметры проверки сертификатов. Как критерии
        # отзыва, так и исключения из проверки
        $chain.ChainPolicy.RevocationFlag = $CRLFlag
        $chain.ChainPolicy.RevocationMode = $CRLMode
        $chain.ChainPolicy.VerificationFlags = $VerificationFlags
        # мини-функция для предоставления понятного вывода на экран о статусе проверки
        # и ошибках в цепочке, если они есть.
        function _getstatus_ ($status, $chain, $cert) {
            if ($status) {
                Write-Host Current certificate $cert.SerialNumber chain and revocation status is valid -ForegroundColor Green
            } else {
                Write-Warning "Current certificate $($cert.SerialNumber) chain is invalid due of the following errors:"
                $chain.ChainStatus | %{Write-Host $_.StatusInformation.trim() -ForegroundColor Red}
            }
        }
    }
    
    process {
        # если аргумент сертификата уже является объектом X509Certificate2, то
        # сразу строим цепочку и получаем статус цепочки вызывая мини-функцию
        if ($_ -is [System.Security.Cryptography.X509Certificates.X509Certificate2]) {
            $status = $chain.Build($_)
            _getstatus_ $status $chain $_
        } else {
        # если аргумент не является объектом X509Certificate2, то это будет путь к файлу
        # если указанный путь не существует, то ругаемся и выходим
            if (!(Test-Path $Certificate)) {Write-Warning "Specified path is invalid"; return}
            else {
            # если путь существует, то проверяем, что это путь файловой системы.
            # если это не так, то ругаемся и выходим
                if ((Resolve-Path $Certificate).Provider.Name -ne "FileSystem") {
                    Write-Warning "Spicifed path is not recognized as filesystem path. Try again"; return
                } else {
                # если предварительные проверки прошли успешно, то выбираем объект файла
                    $Certificate = gi $(Resolve-Path $Certificate)
                # и на основании расширения файла через Switch преобразуем файл в
                # X509Certificate2 объект или объекты
                    switch -regex ($Certificate.Extension) {
                    "\.CER|\.DER|\.CRT" {$cert.Import($Certificate.FullName)}
                    "\.PFX" {
                        if (!$Password) {$Password = Read-Host "Enter password for PFX file $certificate" -AsSecureString}
                        # тут пришлось указать контекст хранилища, куда предполагается сохранить сертификат.
                        # на самом деле мы никуда не будем сохранять объект сертификата, это было сделано
                        # по причине отсутствия подходящего конструктора метода Import(), чтобы
                        # можно было указать только путь к файлу и пароль от PFX
                            $cert.Import($Certificate.FullName, $password, "UserKeySet")
                        }
                    "\.P7B|\.SST" {
                        $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection
                        $cert.Import([system.IO.File]::ReadAllBytes($file.FullName))
                        }
                    default {Write-Warning "Looks like your specified file is not a certificate file"; return}
                    }
                    # полученный объект или объекты сертификатов пропускаем через метод Build() и вызываем
                    # мини-функцию расшифровки статуса проверки
                    $cert | %{
                        $status = $chain.Build($_)
                        _getstatus_ $status $chain $_
                    }
                    # после каждой итерации очищаем объекты сертификата и цепочки
                    $cert.Reset()
                    $chain.Reset()
                }
            }
        }
    }
}

Данный скрипт может принимать аргументы в виде уже готовых объектов X509Certificate2 или с указанием пути к файлу сертификата, например:

dir cert:\currentuser\my | Test-Certificate
Test-Certificate .\mycert.cer

Ну и при желании можно поуказывать там разные параметры и флаги проверки.

Saturday, October 17, 2009 11:51:59 PM (FLE Daylight Time, UTC+03:00)   Comments [18]    

 

Представлю на обозрение пробный скрипт по управлению сертификатами в хранилище Certificate Store — CertMgmtPack.ps1, который (как я надеюсь) может найти применение на серверах Windows Server 2008 R2 Server Core и реализован на основе наших недавних исследований:

Версия пока что 0.47 (т.к. предстоит добавить ещё разного функционала и допилить существующий). Пока что мне удалось реализовать функционал импорта сертификатов из файлов в store и экспорт из store в файлы сертификатов.

Примечание: скрипт работает только в PowerShell V2. В каждую функцию вложена справка, которую вы можете прочитать набрав Get-Help Import/Export-Certiifcate.

После подключения скрипта (используя dot-sourcing) у вас будут доступны 2 функции:

  • Export-Certificate
  • Import-Certificate

И пару слов о том, как их использовать.

Import-Certificate

содержит следующие параметры:

  • Path <String> — путь к файлу сертификата, который может иметь следующий набор расширений: CER, DER, PFX, P7B, SST. Распознавание типов сертификатов осуществляется по расширению файла. Данная функция может принимать параметр пути из конвейера следующим путём:
    dir *.cer | Import-Certificate
    Параметр обязательный.
  • Password <SecureString> — пароль для PFX файла. Пароль не требуется для остальных типов файлов. Однако, хочу предупредить, что пароли не принимаются в открытом виде, а должны передаватьсякак SecureString. Это можно реализовать различными способами, например, вот так:
    Read-Host "Enter password for PFX file" –AsSecureString
  • Storage <String> — указывает контекст импорта сертификата, т.е. это будет хранилище текущего пользователя (по умолчанию) или в хранилище компьютера (требуются права локального администратора). Может принимать значения User или Computer. Если параметр не указан, то берётся значение по умолчанию — User.
  • Container <String> — указывает контейнер размещения сертификата. Это может быть один из: AuthRoot, CA, Disallowed, My (по умолчанию), REQUEST, Root, SmartCardRoot, Trust, TrustedPeople, TrustedPublisher, UserDS. Расшифровка контейнеров (сопоставление названиям в оснастке CertMgr.msc) приведена внутри скрипта в хелпе функции. Если параметр не указан, то применяется параметр по умолчанию — My.
  • Exportable <Switch> — применяется только для импорта PFX файлов. Данный ключ помечает импортируемый сертификат как экспортируемый. Это означает, что вы после процедуры импорта сможете снова экспортировать сертификат с закрытым ключом. Эсли ключ Exportable не указан, то закрытый ключ сертификата не помечается как экспортируемый и при экспорте у вас не будет возможности экспортировать закрытый ключ данного сертификата.
  • StrongProtection <Switch> — применяется только для импорта PFX файлов. Данный ключ включает режим усиленной защиты закрытого ключа PFX файла. Это означает, что для каждой попытки использования закрытого ключа от этого сертификата потребуется ручной ввод пароля. Данный ключ нельзя использовать, если Storage указан как Computer, поскольку компьютерные сертификаты не поддерживают такой режим. Это связано с тем, что компьютерные сертификаты используются в контексте учётной записи LocalSystem и lsass.exe не предоставляет пользователю UI для ввода пароля. В любом случае, если вы попробуете это сделать, скрипт выдаст соответствующую ошибку и импорт произведён не будет.

И пару примеров, как использовать функцию:

$pass = Read-Host "Enter password for PFX" –AsSecureString
Import-Certificate mycert.pfx -Password $pass -Exportable

импортирует файл mycert.pfx в контейнер Personal хранилища текущего пользователя и пометит ключ как экспортируемый.

Import-Certificate mycert.pfx -Password $pass -StrongProtection

Импортирует файл mycert.pfx в контейнер Personal хранилища текущего пользователя, пометит ключ как неэкспортируемый и включит режим усиленной защиты закрытого ключа, что потребует ввод пароля пользователя каждый раз при его использовании.

Import-Certificate certs.p7bStorage 'Computer'Container 'TrustedPublisher'

Импортирует все сертификаты из файла certs.p7b в контейнер Trusted Publishers компьютерного хранилища.

Экспорт сертификатов

  • Path <String> — указывает путь к папке (не к файлу!), в которую будут экспортированы все указанные сертификаты. Парамер обязателен.
  • Type <String> — указывает тип экспортируемых сертификатов. Может принимать одно из значений: CERT, PFX, PKCS12, PKCS7, SST (Serialized Store). Параметр так же обязателен.
  • Password <SecureString> — задаёт пароль для экспортируемых PFX файлов (в качестве типа экспортируемых сертификатов указано PFX или PKCS12 и является для них обязательным). Параметр не принимает значение в виде простой строки, а только SecureString.
  • Storage <String> — указывает контекст поиска экспортируемых сертификатов, т.е. поиск будет производиться в компьютерном хранилище или хранилище текущего пользователя (по умолчанию). Может принимать значения CurrentUser или LocalMachine. Если параметр не указан, то берётся значение по умолчанию — User.
  • Container <String> — указывает контейнер размещения сертификата (или сертификатов). Это может быть один из: AuthRoot, CA, Disallowed, My (по умолчанию), REQUEST, Root, SmartCardRoot, Trust, TrustedPeople, TrustedPublisher, UserDS. Расшифровка контейнеров (сопоставление названиям в оснастке CertMgr.msc) приведена внутри скрипта в хелпе функции. Если параметр не указан, то поиск производится в контейнере по умолчанию — My.
  • Thumbprint <String> — задаёт критерий поиска сертификатов по отпечатку ключа (соответствующее поле Thumbprint сертификата). Можно указывать как полный отпечаток, так и любую его часть.
  • Subject <String> — задаёт критерий поиска сертификатов по полю Subject. Можно указывать как полный Subject, так и любую его часть.
  • Issuer <String> — задаёт критерий поиска по конкретному издателю сертификата. Можно указывать как полный CN издателя, так и любую его часть.
  • SerialNumber <String> — задаёт критерий поиска по серийному номеру сертификата. Можно указывать как точный серийный номер, так и любую его часть.
  • NotAfter <String> — задаёт критерий поиска по дате истечения сертификата. Дата задаётся в формате dd.MM.yyyy (день.месяц.год). В результате использования этого параметра будут отобраны сертификаты, которые истекут не позднее указанной даты.
  • NotBefore <String> — задаёт критерий поиска по дате начала действия сертификате. Как и в NotAfter дата указывается в формате dd.MM.yyyy и в результате использования этого параметра будут отобраны сертификаты, действие которых начинается не раньше указанной даты.
  • DeleteKey <Swtich> — используется только при экспорте сертификатов вместе с закрытыми ключами в PFX файлы. Данный ключ при успешном экспорте удаляет закрытый ключ данного сертификата из хранилища.

Я не смог провести полноценное тестирование функции экспорта, поэтому гарантировать 100% работу пока не могу. Но вы, уважаемые читатели, можете мне в этом помочь.

И несколько примеров использования:

Export-Certificate c:\certs -Type 'PKCS7'

Экспортирует все сертификаты из Current User\Personal в один PKCS7 файл с именем ExportedCertificates.p7b (это имя зашито на уровне скрипта).

Export-Certificate c:\certs -Type 'cert' -Storage 'LocalMachine' -Container 'Root' -SerialNumber '663'

Экспортирует все сертификаты из контейнера доверенных центров сертификации (Trusted Root CAs) компьютерного хранилища, у которых в серийном номере встречается последовательность 663 в CER файлы. При этом каждый сертификат будет экспортирован в индивидуальный файл с именем, которое строится по схеме: SubjectCN_Thumprint.CER, например: Thawte Premium Server CA_4F65566336DB6598581D584A596C87934D5F2AB4.cer

Export-Certificate c:\certs -Type 'PKCS12' -Password $pass -DeleteKey -Subject 'UserName'

Экспортирует все сертификаты из Current User\Personal в PFX файлы у которых в любом месте поля Subject есть UserName. При успешном экспорте закрытый ключ сертификата будет удалён из хранилища и будет содержаться только в PFX файле.

В связи с тем, что CN поля Subject может содержать недопустимые символы для имён файлов, мне скорее всего придётся искать другой метод именования экспортируемых сертификатов и чтобы они были распознаваемые. Если вы обнаружите какие-то ошибки или будут пожелания, то пишите мне в коментарии или на почту. По мере дописывания я буду выкладывать новые версии скрипта.

Monday, September 14, 2009 10:02:32 PM (FLE Daylight Time, UTC+03:00)   Comments [2]    

 

Короткая заметка. Когда вы экспортируете сертификат вместе с закрытым ключом, то можете заметить такую опцию:

Delete the Private key if the export is successful

К сожалению, метод Export() у объектов X509Certificate2 не позволяет штатно проделывать данную операцию. Это можно сделать через CAPICOM.PrivateKey.Delete(), но данная возможность отсутствует в PowerShell, в то время, как вы можете это сделать в VBS. Это связано с весьма паршивой поддержкой COM со стороны PowerShell, поэтому для реализации функционала этой галочки вам придётся проделать следующие шаги:

  1. Получить объект X509Certificate2 самого сертификата;
  2. Экспортировать его в PFX;
  3. Открыть хранилище в режиме ReadWrite;
  4. Удалить данный сертификат из хранилища;
  5. Экспортировать объект сертификата уже не в PFX, а в Cert;
  6. Импортировать этот экспортированный объект Cert (хотя, на самом деле там будет массив байтов, но это не столь существенно).

На языке PowerShell это выглядеть будет примерно так:

# получаем объект сертификата с закрытым ключом
$cert = (dir cert:\currentuser\my)[0]
# записываем пароль для PFX файла
$pass = Read-Host "Password" -AsSecureString
# экспортируем его в PFX формат
$bytes = $cert.Export("pfx", $pass)
# и записывем его в файл
[System.IO.File]::WriteAllBytes('mycert.pfx', $bytes)
# снова экспортируем данный объект, но уже в Cert формат
$tempcert = $cert.Export("Cert")
# создаём объект нашего хранилища
$store = New-Object system.security.cryptography.X509Certificates.X509Store "my", "CurrentUser"
# открываем его на чтение и на запись
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
# удаляем текущий сертификат с закрытым ключом из хранилища
$store.Remove($cert)
# записываем обратно Cert объект в хранилище, теперь уже без закрытого ключа
$store.Add($tempcert)
$store.Close()

Это только образец кода и его не стоит использовать прямо в таком виде, т.к. вы должны точно убедиться, что у вас PFX файл создался и он валидный. В противном случае вы останетесь совсем без закрытого ключа. И ещё раз напоминаю, при работе с хранилищем – не забывайте его закрывать после работы.

Thursday, September 10, 2009 10:07:54 PM (FLE Daylight Time, UTC+03:00)   Comments [2]    

 

Previous Page Page 3 of 4 in the PowerShellCertificates category Next Page
 · 

All content © 2008 - 2012, Vadims Podāns
"Spaces" Theme provided by: Vadims Podāns
About


E-mail - Send mail to the author(s)
Live Messenger -
For english language visitors
Библиотека
Календарик
<February 2012>
SunMonTueWedThuFriSat
2930311234
567891011
12131415161718
19202122232425
26272829123
45678910

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





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

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