Contents of this directory is archived and no longer updated.

Posts on this page:

Примечание: данный материал публикуется на правах ТЗ (ТЗТайное Знание) и обязателен для знания администраторам 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: :no:
Windows Server 2003 :yes: :no:
Windows Vista :yes: :no:
Windows Server 2008 :yes: :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 не было. Либо, как вариант, есть возможность настраивать этот момент в каждой реализации движка построения цепочек, но я пока не нашёл как. Это пока всё, что у меня есть по этому поводу.

Я предлагаю снова поговорить о 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

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

Примечание: данный материал публикуется как обязательный для знания IT-специалистами, которые занимаются или только собираются заниматься темой PKI (Public Key Infrastructure).

Введение

На самом деле эта тема тесно переплетается с фундаментальными основами PKI, как доверие сертификатов, защита от подделки цифровых подписей сертификатов, отзыв сертификатов и т.д. Иными словами, сама модель PKI лежит на Certificate Chaining Engine (механизм построения цепочек сертификатов). Миллионы людей в мире видят результат работы этого механизма ежедневно, но не знают, как он работает. Им это, в принципе, и не надо, а вот IT-специалистам по PKI это знать просто необходимо.

Одной из основ PKI является модель доверия центрам сертификации (Certification Authority или просто CA). Прежде чем мы начнём доверять сертификату мы должны явно доверять корневому сертификату CA и так же явно или косвенно (по правилу одностороннего транзитивного доверия) доверять всем промежуточным CA в цепочке. Корневые сертификаты устанавливаются в систему вручную путём добавляения сертификата CA в секцию Trusted Root CAs. Если корневой сертификат CA есть в этом списке, то мы доверяем всем сертификатам, которые выдал этот CA и любые подчинённые CA (Intermediate CA).

В качестве наиболее доступного и понятного примера можно рассмотреть SSL веб-сайт. Если мы зайдём на https://www.dreamspark.com, то мы увидим что с SSL сертификатом этого сайта всё в порядке. Если же мы зайдём на https://cert.startcom.org, то мы наоборот увидим грозное предупреждение, что с SSL сертификатом этого сайта что-то не так. Это как раз и есть результат работы Chaining Engine. А вот что он сделал на самом деле — вот об этом мы сейчас и поговорим.

Постройка цепочки сертификатов

Давайте сначала посмотрим на путь сертификатов (цепочки сертификатов) обоих веб-сайтов:

 Valid certification path   Invalid certification path with untrusted root

В первом случае цепочка сертификатов заканчивается на сертификате VeriSign, которому мы доверяем явно. Вы можете убедиться в этом нажав кнопку View Certificate, чтобы посмотреть его внутренности и найти этот же сертификат в оснастке certmgr.msc –> Trusted Root CAs. Однако, вы видите, что не корневой CA выдал сертификат сайту, а подчинённый (Intermediate CA). Это доказывает то, что если мы доверяем корню, то и явно или косвенно доверяем всем сертификатам этого CA или любым его прямым или косвенным промежуточным CA. Если говорить простым языком, то в сертификатах используется одностороннее транзитивное доверие сертификатам CA. Чуть позже будет рассказано как строится эта цепочка. Во втором случае мы видим, что корневой сертификат CA не находится у нас в Trusted Root CAs и, следовательно, мы не доверяем ни одному сертификату, который издал этот CA или любой другой его подчинённый CA. И, так же как и в первом случае, сертификат самому веб-сайту выдал подчинённый CA. На самом деле этот момент является одной из наиболее частых ошибок системных администраторов, которые организовывают доступ к ресурсам с использованием цифровых сертификатов.

Но нас теперь интересует другой вопрос — а откуда же взялись все эти сертификаты в цепочке? Chaining engine как правило использует только 3 метода построения цепочки:

  • по полю AIA (Authority Information Access) каждого сертификата
  • с использованием файла цепочки сертификатов в формате pkcs7
  • поиск подходящих сертификатов в локальном хранилище (с использованием exact, key, name matches, о которых будет рассказно ниже)

Примечание: в сертификате любое поле содержащее слово Authority будет означать какие-то сведения о сертификате вышестоящего CA. Например, Authority Key Identifier — комбинация серийного номера, поля Subject или хеша открытого ключа вышестоящего CA. А любое поле содержащее слово Subject — означает то же самое, но применительно к конкретно этому сертификату.

Давайте посмотрим поле AIA первого сертификата:

[1]Authority Info Access
     Access Method=Certification Authority Issuer (1.3.6.1.5.5.7.48.2)
     Alternative Name:
          URL=
http://www.microsoft.com/pki/mscorp/Microsoft%20Secure%20Server%20Authority(5).crt

В этом поле содержится ссылка на сертификат того CA, который непосредственно издал и подписал этот сертификат. Если нажать по этой ссылке, то вы скачаете сертификат центра сертификации Microsoft Secure Server Authority. В этом сертификате тоже будет поле AIA, по ссылке из которого скачается сертификат вышестоящего CA — Microsoft Internet Authority. И так проверка проводится далее до тех пор, пока цепочка не оборвётся или не закончится каким-либо корневым CA (корневой CA характеризуется самоподписанным сертификатом, т.е. сертификат выдан самому себе). Если вы посмотрите сертификат Microsoft Internet Authority, то вы в нём не обнаружите поля AIA. И вот здесь мы сталкиваемся со вторым методом добычи сертификатов в цепочке – файл сертификатов формата pkcs7.

Бывают случаи, когда цепочка сертификатов не может быть построена и она обрывается на пути. Типичный пример такого обрыва (когда цепочка не может быть построена до корня с использованием любого вышеуказанного метода) можно посмотреть на сайте БиЛайна: https://trust.beeline.ru

 Invalid certificate chain. Root certificate s unreachable

если посмотреть поле AIA этого сертификата, то мы увидим там 2 пути до CA, который издал этот сертификат. Но оба эти пути нерабочие совсем (кстати говоря, пути в CDP там тоже нерабочие. Причём, во всех сертификатах! Вот такой он БиЛайн :-) ). Но цепочка сертификатов в этом случае всё равно построилась за счёт сертификатов в pkcs7 формате. Хотя корневой сертификат в этом pkcs7 просто отсутствует, поэтому мы даже не можем собрать корневой сертификат. Неработающие CDP/AIA — ещё одна из наиболее частых ошибок реализации инфраструктуры PKI, в результате которой по сути PKI является неработоспособной.

Оффтоп: если на этой странице нажать Продолжить, то на новой странице будет ссылка на корневой сертификат, а так же на CRL. CRL БиЛайна доставил очень сильно. Вы посмотрите срок годности этого CRL :-)

Проверка соответствия сертификатов внутри построенной цепочки

Однако, это только половина дела. Мы просто построили цепочку сертификатов, но не более того. Поскольку сертификаты могут быть скачаны в pkcs7 формате, а по ссылкам AIA просто подменить сертификаты, chaining engine делает полнуюю проверку доверия каждого сертификата в цепочке, чтобы исключить возможность подмены сертификатов, а так же достраивает цепочку (если этого не удалось сделать с помощью AIA и pkcs7 используя локальное хранилище сертификатов). Соответствие сертификатов внутри цепочки может происходить по нескольким правилам (в порядке приоритета):

  • Exact Match
  • Key Match
  • Name MAtch

Для этого chaining engine использует следующие поля сертификатов:

  • Authority Key Identfier (AKI) — может содержать комбинацию поля Subject и Serial Number сертификата вышестоящего CA или хеш открытого ключа вышестоящего CA (но бывает, что это поле совсем отсутствует).
  • Issuer – данное поле используется только если AKI отсутствует в сертификате и это поле должно быть эквивалентно полю Subject вышестоящего CA. По этому полю определяется имя CA, котороый издал этот сертификат.
  • Subject в сертификате вышестоящего CA. Данное поле должно быть эквивалентно полю Issuer в издаваемых сертификатах.
  • Serial Number сертификата вышестоящего CA.
  • Subject Key Identifier (SKI) — может содержать комбинацию поля Subject и Serial Number текущего сертификата или хеш открытого ключа текущего сертификата.

Exact match

Данный метод определения правильности построения цепочки является наиболее точным и такая цепочка почти никогда не будет заканчиваться более чем одним корневым сертификатом, поскольку требует совпадения 2-х полей вышестоящего сертификата – Subject и серийного номера с полем AKI проверяемого сертификата. Давайте посмотрим пример такой проверки в сертификате сайта https://cert.startcom.org. Поле AKI содержит следующую информацию:

KeyID=a1 e1 9e 45 25 79 4d 06 d9 02 17 92 82 d5 30 89 72 25 14 a0
Certificate Issuer:
     Directory Address:
          CN=StartCom Certification Authority
          OU=Secure Digital Certificate Signing
          O=StartCom Ltd.
          C=IL
Certificate SerialNumber=17

Поле KeyID не используется. Здесь мы видим сведения о поле Subject и серийный номер вышестоящего CA. Если посмотреть сертификат этого CA, то его серийный номер и поле Subject будут идентичными тем, которые записаны в поле AKI издаваемых ими сертификатов. Используя эти данные chaining engine может найти нужный сертификат в локальном хранилище (если такой сертификат там есть), для восполнения недостающих звеньев цепочки. Если же будет обнаружено хоть одно несоответствие между этими полями, то цепочка мгновенно обрывается и любому сертификату в этой цепочке (начиная от сертификата с несоответствующими полями и вниз до конечного сертификата выданного потребителю) будет отказано в доверии.

Key Match

Это наиболее распространённый тип проверки цепочки, т.к. зачастую поле AKI у сертификатов содержит только KeyID, который является лишь хешом открытого ключа вышестоящего CA. Например сертификат с https://www.dreamspark.com:

KeyID=14 55 c4 39 e0 3d 2e d1 55 2e 48 96 b0 d8 7e 14 22 06 93 bc

Этот хеш должен быть идентичным хешу, который записан в поле Subject Key Identifier вышестоящего CA. В этом вы можете убедиться просмотрев сертификат CA, который выдал сертификат для dreamspark.com. В случае несоответствия этого поля, любому сертификату вниз по цепочке будет отказано.

Заметка: при этом, возможна достаточно интересная ситуация, когда цепочка может заканчиваться несколькими корнями. Это может происходить если корневой сертификат CA был обновлён с использованием текущей пары открытого и закрытого ключа. Поскольку KeyID — всего лишь хеш открытого ключа, то при смене корневого сертификата CA, хеш не изменится и, следовательно, chaining engine может рандомно строить цепочку до старого или до нового корневого сертификата CA.

Name match

Но поле AKI не обязательно должно присутствовать в сертификате. Его может не быть совсем и тогда остаётся только один способ проверки целостности цепочки — сравнение поля Issuer текущего сертификата и поля Subject вышестоящего сертификата. Это самый крайний и наименее надёжный (хотя это достаточно относительно) метод проверки цепочки. Так же, как и предыдущий пример, данный метод может заканчиваться несколькими корневыми сертификатами, если сертификат CA был обновлён с использованием текущей пары открытого и закрытого ключей. Но этого не произойдёт, если при обновлении сертификата CA будут использоваться новая пара ключей. Хоть хеши и серийные номера издающих CA не проверяются, то подделка сертификатов в вашем случае закончится провалом. А об этом читайте заключительный раздел.

Не забываем про подписи

На самом деле может показаться, что обмануть chaining engine достаточно легко — надо лишь подсунуть нужные поддельные сертификаты. Но в действительности это не так. Помимо всего прочего следует помнить, что каждый сертификат подписан цифровой подписью издающего CA. Если вы ещё не знаете, что такое цифровая подпись, то пора бы и узнать. Когда CA составляет сертификат, то он высчитывает конечный хеш (с использованием алгоритма указанного в поле Signature algorithm) этого сертификата и шифрует его своим закрытым ключом и пристыковывает подпись к сертификату. При проверке сертификата chaining engine удаляет цифровую подпись из сертификата и подсчитывает сам хеш файла сертификата. Далее берёт открытый ключ у издающего CA и сравнивает полученный хеш с тем, что содержится в подписи. Следовательно, если вам удастся обмануть все 3 метода проверки цепочки путём подмены сертификатов, то вы сломаетесь на проверке цифровой подписи, т.к. здесь уже задействуется закрытый ключ легального CA. Вот как это может выглядеть:

Altered certificate certification path Altered certificate General tab

На картинке слева вы видите, что chaining engine построил цепочку до доверенного корня, поскольку мой сертификат содержал нужные поля для работы метода Name match. Но сертификат был выдан не тем CA, который виден в цепочке, а поддельным CA. Хоть мой поддельный CA внешне не отличим от легитимного CA, но у него нету самого важного — правильного закрытого ключа, чтобы подписать сертификат. Ведь при проверке подписи используется открытый ключ легитимного CA и, следовательно, подпись не может быть проверена.

Написал очень много страшных букв даже без надежды, что кто-то с первого раза поймёт о чём тут шёл разговор. Но поверьте, это достаточно полезное знание, чтобы уметь отыскивать и разрешать проблемы с построением цепочек сертификатов.