Posts on this page:
Не каждый знает, что центр сертификации Windows поддерживает не одну, а несколько таблиц:
Мы уже знаем, как обращаться к таблице запросов. По схожему принципу можно обращаться и к другим таблицам. Для переключения между ними используется метод SetTable интерфейса ICertView2. По умолчанию всегда используется таблица CVRC_TABLE_REQCERT. Вот какие кодовые номера у таблиц:
Метод SetTable нужно вызывать сразу после вызова метода OpenConnection. Таблица CRL'ов хранит историю всех CRL'ов (вместе с самими CRL'ами), которые были сгенерированы сервером CA, а так же прочую полезную информацию.
Вот как смотрится схема этой таблицы:
$CA = "dc1\Contoso CA" $CaView = New-Object -ComObject CertificateAuthority.View # открываем подключение к CA $CaView.OpenConnection($CA) # указываем необходимую таблицу $CaView.SetTable(0x5000) # говорим, что мы хотим посмотреть схему $Columns = $CaView.EnumCertViewColumn(0) # начинаем итерацию по столбцам БД [void]$Columns.Next() do { # создаём временный объект, чтобы получить красивый вывод и наполняем его данными $Column = "" | Select Name, DisplayName, Type, MaxLength $Column.Name = $Columns.GetName() $Column.DisplayName = $Columns.GetDisplayName() $Column.Type = switch ($Columns.GetType()) { 1 {"Long"} 2 {"DateTime"} 3 {"Binary"} 4 {"String"} } [string]$Column.MaxLength = $Columns.GetMaxLength() if ($Columns.IsIndexed() -eq 1) {$Column.MaxLength += ", Indexed"} $Column } until ($Columns.Next() -eq -1) # закрываем подключение к БД $Columns.Reset()
Её вид:
Name DisplayName Type MaxLength ---- ----------- ---- --------- CRLRowId CRL Row ID Long 4, Indexed CRLNumber CRL Number Long 4, Indexed CRLMinBase CRL Minimum Base Number Long 4 CRLNameId CRL Name ID Long 4 CRLCount CRL Count Long 4 CRLThisUpdate CRL This Update DateTime 8 CRLNextUpdate CRL Next Update DateTime 8, Indexed CRLThisPublish CRL This Publish DateTime 8 CRLNextPublish CRL Next Publish DateTime 8, Indexed CRLEffective CRL Effective DateTime 8 CRLPropagationComplete CRL Propagation Complete DateTime 8, Indexed CRLLastPublished CRL Last Published DateTime 8, Indexed CRLPublishAttempts CRL Publish Attempts Long 4, Indexed CRLPublishFlags CRL Publish Flags Long 4 CRLPublishStatusCode CRL Publish Status Code Long 4, Indexed CRLPublishError CRL Publish Error Information String 8192 CRLRawCRL CRL Raw CRL Binary 536870912
Вот пример вывода:
$CA = "dc1\Contoso CA" $CaView = New-Object -ComObject CertificateAuthority.View $CaView.OpenConnection($CA) $CaView.SetTable(0x5000) $ColumnCount = $CaView.GetColumnCount(0) $CaView.SetResultColumnCount($ColumnCount) 0..($ColumnCount - 1) | %{$CAView.SetResultColumn($_)} $Row = $CaView.OpenView() [void]$Row.Next() while ($Row.Next() -ne -1) { $cert = New-Object psobject $Column = $Row.EnumCertViewColumn() while ($Column.Next() -ne -1) { $current = $Column.GetName() $Cert | Add-Member -MemberType NoteProperty $($Column.GetDisplayName()) -Value $($Column.GetValue(1)) -Force } $Cert $Column.Reset() } $Row.Reset()
CRL Row ID : 409 CRL Number : 347 CRL Minimum Base Number : 340 CRL Name ID : 1 CRL Count : 0 CRL This Update : 2010.02.25. 17:55:51 CRL Next Update : 2010.02.26. 19:15:51 CRL This Publish : 2010.02.25. 18:05:51 CRL Next Publish : 2010.02.26. 18:05:51 CRL Effective : 2010.02.18. 17:55:51 CRL Propagation Complete : 2010.02.25. 19:05:51 CRL Last Published : 2010.02.25. 18:05:51 CRL Publish Attempts : 1 CRL Publish Flags : 6 CRL Publish Status Code : 0 CRL Publish Error Information : - CRL Raw CRL : MIICzDCCAbQCAQEwDQYJKoZIhvcNAQEFBQAwQzETMBEGCgmSJomT8ixkARkWA2Nv bTEXMBUGCgmSJomT8ixkARkWB2NvbnRvc28xEzARBgNVBAMTCkNvbnRvc28gQ0EX DTEwMDIyNTE3NTU1MVoXDTEwMDIyNjE5MTU1MVqgggE7MIIBNzAfBgNVHSMEGDAW gBQSyac4taTAA9cqtmWlsPEzGBKsPzAQBgkrBgEEAYI3FQEEAwIBATALBgNVHRQE BAICAVswHAYJKwYBBAGCNxUEBA8XDTEwMDIyNjE4MDU1MVowDgYDVR0bAQH/BAQC AgFUMIHGBgkrBgEEAYI3FQ4EgbgwgbUwgbKgga+ggayGgalsZGFwOi8vL0NOPUNv bnRvc28lMjBDQSxDTj1EQzEsQ049Q0RQLENOPVB1YmxpYyUyMEtleSUyMFNlcnZp Y2VzLENOPVNlcnZpY2VzLENOPUNvbmZpZ3VyYXRpb24sREM9Y29udG9zbyxEQz1j b20/ZGVsdGFSZXZvY2F0aW9uTGlzdD9iYXNlP29iamVjdENsYXNzPWNSTERpc3Ry aWJ1dGlvblBvaW50MA0GCSqGSIb3DQEBBQUAA4IBAQB3DuZRToyK/OkrnCQQUl1P qgv/V9nPhAR6LF/b25Tq7fhKZniAk/cPgj2L3IMsLx2lmrI7GEEDt4UDcuc3EPtF f7gixrN3K+eSE/Er0NxBdJFUhHK9e/CVXqJFV2pGSa77mvcI75l2h5mlUGDZzGOl <...> <...>
Что касается остальных двух таблиц, то они, как я уже говорил, работают по тому же принципу и вы так же можете накладывать фильтры вывода методом SetRestriction. Они содержат ту же информацию, что вы видите в оснастке CertSrv.msc, когда выделяете запрос, нажимаете Action –> All Tasks –> View Attributes/Extensions.
На этом я завершаю цикл статей по работе с базой данных центров сертификации Windows с помощью Windows PowerShell.
В предыдущих статьях (раз и два) мы говорили о том, как получить схему БД центра сертификации и как получать определённые свойства каждой записи в БД. Но очень часто нам нужно будет ограничивать вывод БД по каким-то критериям. Например, мы можем хотеть получать сведения о запросах на сертификат, которые выданы на определённое имя (common name). А можем и хотеть получать сведения о ещё не одобренных запросах (хранящихся в папке Pending Requests). А может хотим отфильтровать вывод по дате, когда заканчивается срок действия сертификата. Вобщем, хотелок может быть очень много. Самый простой вариант фильтрации — получить все строки из БД, а потом через Where-Object отфильтровать нужные. Практически во всех случаях это будет плохим решением, потому что оно медленное, создаёт нагрузку на БД и очень ресурсоёмким.
Решение этой проблемы мы можем возложить на COM интерфейсы, которые будут самостоятельно извлекать только те данные, которые нам нужны (подпадают под определённый фильтр). Для этого у ICertView2 есть метод SetRestriction:
CCertView.SetRestriction( _ ByVal ColumnIndex, _ ByVal SeekOperator, _ ByVal SortOrder, _ ByVal pvarValue _ )
В свойстве ColumnIndex мы указываем номер колонки или название таблицы. Если мы захотим посмотреть запросы сертификатов, находящиеся в папке Pending Requests мы можем указать таблицу CV_COLUMN_QUEUE_DEFAULT, а если только отклонённые/ошибочные запросы, то таблицу CV_COLUMN_LOG_FAILED_DEFAULT. Вот значения для этих параметров:
Как видите, при использовании отрицательных значений мы задаём фильтр на уровне таблиц. Если хотим фильтровать на уровне столбцов (т.е. значение определённого столбца соответствует чему-то), значение этого аргумента должно быть натуральным и это значение должно быть равно номеру столбца. Как мы уже знаем, номер столбца можно получить при помощи метода GetColumnIndex.
Дальнейшие аргументы имеют смысл только если ColumnIndex является натуральным числом. SeekOperator задаёт уровень сравнения и они достаточно понятно расписаны в таблице. Единственное, что тут хочу отметить — это числовые значения операторов сравнения:
Далее идёт аргумент, задающий порядок сортировки:
По большому счёту этот аргумент использовать не нужно, особенно учитывая, что иногда его использовать нельзя. И последний аргумент указывает маску, которой должен соответствовать фильтр.
Возьмём пример, мы хотим получить все строки БД, у которых столбец Request Common Name равен "contoso-dc2-ca" (сертификаты выданные подчинённому CA). Для этого в ColumnIndex укажем 60 (номер столбца CommonName), в SeekOperator укажем 1, а в pvarValue укажем значение, которому должно оно соответствовать: contoso-dc2-ca.
Давайте попробуем создать такой фильтр. Сначала обязательная часть по инициализации интерфейса и определения отображаемых столбцов выходных объектов.
$CaView = New-Object -ComObject CertificateAuthority.View $CaView.OpenConnection("dc1\contoso ca") $properties = "RequestID","RequesterName","CommonName","NotBefore","NotAfter","SerialNumber" $CaView.SetResultColumnCount($properties.Count) $properties | %{$CAView.SetResultColumn($CAView.GetColumnIndex($False, $_))}
Примечание: столбец, по которому устанавливается фильтр не обязательно должен присустствовать в выходе. Вы можете вообще показывать только столбец RequestID, а фильтры устанавливать по другим столбцам.
И сам фильтр:
# получаем номер столбца, по которому будет производиться фильтрация $RColumn = $CAView.GetColumnIndex($False, "CommonName") # устанавливаем сам фильтр $CaView.SetRestriction($RColumn,1,0,"contoso-dc2-ca")
По умолчанию фильтр устанавливается на таблице CV_COLUMN_LOG_DEFAULT, которая содержит запросы сертификатов, содержащихся в папках Revoked Certificates, Issued Certificates и Failed Requests. Мы не можем в пределах одного фильтра указать конкретную таблицу. Но вы можете использовать несколько фильтров одновременно и каждый из них будет обрабатываться по очереди с применением логического оператора И. Т.е. в результате мы получим только те строки БД, которые подпадают под каждый фильтр. Если строка не соответствует какому-то фильтру, она будет отфильтрована и не будет отображена на выходе. Поэтому я вам покажу ещё один вариант полезного фильтра, который фильтрует строки БД по папкам (Revoked Certificates, Issued Certificates, Pending Requests, FailedRequests). В БД есть такой столбец под названием Disposition. Этот столбец имеет тип Long и вот какие полезные значения может принимать:
Поэтому если мы хотим получить выданные и неотозванные сертификаты на имя contoso-dc2-ca, мы создадим ещё один фильтр по столбцу Disposition (и значением равным 20):
$RColumn = $CAView.GetColumnIndex($False, "Disposition") $CaView.SetRestriction($RColumn,1,0,20)
Если мы хотим добавить фильтр, что сертификат должен быть действующий, т.е. столбец NotAfter должен быть больше, чем текущие дата и время:
$RColumn = $CAView.GetColumnIndex($False, "NotAfter") $CaView.SetRestriction($RColumn,0x10,0,[datetime]::Now)
Когда с фильтрами покончено, можно начинать шахматы:
$Row = $CaView.OpenView() while ($Row.Next() -ne -1) { $cert = New-Object psobject $Column = $Row.EnumCertViewColumn() while ($Column.Next() -ne -1) { $current = $Column.GetName() $Cert | Add-Member -MemberType NoteProperty $($Column.GetDisplayName()) -Value $($Column.GetValue(1)) -Force } $Cert $Column.Reset() } $Row.Reset()
и что мы получили на выходе:
Issued Request ID : 21 Requester Name : CONTOSO\Administrator Issued Common Name : contoso-DC2-CA Certificate Effective Date : 2009.03.30. 13:56:53 Certificate Expiration Date : 2011.03.30. 14:06:53 Serial Number : 6127fbc7000000000015 Issued Request ID : 40 Requester Name : CONTOSO\Administrator Issued Common Name : contoso-DC2-CA Certificate Effective Date : 2010.03.06. 10:56:53 Certificate Expiration Date : 2015.03.05. 10:56:53 Serial Number : 158f4a3b000100000028 Issued Request ID : 42 Requester Name : CONTOSO\Administrator Issued Common Name : contoso-DC2-CA Certificate Effective Date : 2010.03.06. 11:10:31 Certificate Expiration Date : 2015.03.05. 11:10:31 Serial Number : 159bc07a00010000002a
т.е. мы получили только те строки БД, которые подпали под каждый указанный фильтр. Если запустить этот же код 1 апреля, мы уже не получим строку под номером 21, потому что NotAfter (Certificate Expiration Date) не будет больше, чем текущее время. На сегодня всё.
В предыдущей части мы рассмотрели возможность извлечения схемы БД центров сертификации. По схожему принципу можно и делать выборку из неё посредством тех же COM интерейсов. Как я уже говорил, основным интерфейсом для доступа к БД будет ICertView2:
$CaView = New-Object -ComObject CertificateAuthority.View
Подключаемся к конкретному серверу CA при помощи метода OpenConnection:
$CaView.OpenConnection("dc1\contoso ca")
Для начала мы рассмотрим простой пример, выберем все строки из БД, но с заранее определёнными столбцами. Это нужно затем, чтобы не отображать ненужную информацию, которая будет мешать и поедать память. Каждая строка в БД максимально может занимать до полумегабайта места в памяти. Выглядит нестрашно, но когда у вас тысяча запросов в БД это уже будет примерно 500 мегабайт. Но тысяча запросов — это очень даже немного. Поэтому давайте для начала посмотрим все запросы вот в каком виде: номер запроса, имя реквестора (пользователя, от лица которого был подан запрос в CA), имя, на которое выдан сертификат, серийный номер сертификата и срок действия сертификата. Схемы БД для всех существующих Windows CA можно получить при помощи скрипта в предыдущей части или найти и в оффлайне: [MS-CSRA] Product Behavior, note 5.
Необходимые столбцы добавляются при помощи методов SetResultColumnCount, GetColumnIndex и SetResultColumn:
$CaView.SetResultColumnCount(6) $Column0 = $CaView.GetColumnIndex($false,"RequestID") $CaView.SetResultColumn($Column0) $Column1 = $CaView.GetColumnIndex($false,"RequesterName") $CaView.SetResultColumn($Column1) <...>
Здесь я показал последовательность действий. Сначала задаём количество столбцов, которые будут возвращены. Далее извлекаем номер каждого столбца и добавляем его в результирующую таблицу. Но эту процедуру можно упростить до трёх строчек, вне зависимости от количества требуемых столбцов:
$properties = "RequestID","RequesterName","CommonName","NotBefore","NotAfter","SerialNumber" $CaView.SetResultColumnCount($properties.Count) $properties | %{$CAView.SetResultColumn($CAView.GetColumnIndex($False, $_))}
Если попытаетесь добавить столбцов больше, чем указано в методе SetResultColumnCount, получите ошибку. Поэтому сначала подсчитывайте количество требуемых столбцов и только после этого их добавлять. В принципе, теперь всё, можно начинать опрашивать БД. Для этого открываем её методом OpenView:
$Row = $CaView.OpenView()
Этот метод вернёт указатель на объект интерфейса IEnumCERTVIEWROW и начинаются шахматы (перевод шагового искателя в исходную позицию). Дальше уже нужно запускать цикл Do…Until или While…Do. Я предпочитаю второй вариант по той простой причине, что если если БД пустая (при указании дополнительных фильтров и ничего под него не подпало) первый же вызов метода Next вернёт –1. Поэтому нужно либо сразу отслеживать значение индекса или использовать такой тип цикла, где значение индекса проверяется перед запуском первой итерации цикла, как While…Do. И вот как это выглядит:
Внимание: если в БД CA находится большое количество запросов, вы получите огромную кучу объектов на выходе, потому что будут возвращены все запросы из секций Issued Certificates, Revoked Certificates и Failed Requests.
# начинаем шахматы и шагать шаговым искателем по строчкам while ($Row.Next() -ne -1) { # создаём пустой объект, который будет являться нашим выходным объектом $cert = New-Object psobject # переставляем шаговый искатель с вертикального в горизонтальное направление $Column = $Row.EnumCertViewColumn() # шагаем последовательно по столбцам, которые мы определили вначале скрипта while ($Column.Next() -ne -1) { # извлекаем название текущего столбца $current = $Column.GetName() # добавляем свойство к нашему выходному объекту с именем равным имени столбца # и добавляем соответствующее значение столбца текущей строчки $Cert | Add-Member -MemberType NoteProperty $($Column.GetDisplayName()) -Value $($Column.GetValue(1)) -Force } # выкидываем объект на выход $Cert # сбрасываем положение горизонтального шагового искателя $Column.Reset() } # сбрасываем положение вертикального шагового искателя $Row.Reset()
И вот как выглядит примерный вывод:
Issued Request ID : 1 Requester Name : CONTOSO\DC1$ Issued Common Name : Contoso CA Certificate Effective Date : 2009.02.15. 14:31:15 Certificate Expiration Date : 2014.02.15. 14:40:11 Serial Number : 5dd87e4cffe3b3bc43f608eb57c767f7 Issued Request ID : 2 Requester Name : CONTOSO\DC1$ Issued Common Name : DC1.contoso.com Certificate Effective Date : 2009.02.15. 14:51:45 Certificate Expiration Date : 2010.02.15. 14:51:45 Serial Number : 15109536000000000002 Issued Request ID : 3 Requester Name : CONTOSO\administrator Issued Common Name : Users Administrator Certificate Effective Date : 2009.02.15. 15:04:02 Certificate Expiration Date : 2011.02.15. 15:04:02 Serial Number : 151bd2c9000000000003 <...>
круто, да? Но здесь мы получили сведения о каждом запросе, а мы хотим получать что-то более конкретное, как быть? В следующий раз я как раз и покажу, как можно создавать более конкретные запросы, чтобы получать только результаты, подпадающие под определённый критерий.
Иногда бывает очень полезным найти какой-нибудь запрос в БД CA. Можно, конечно же, сделать это через certutil, но это не всегда удобно, потому что если под конкретный запрос подпадает несколько строк из БД, нужно уже вручную как-то парсить вывод, чтобы найти то, что нужно. Поэтому я решил рассказать, как наваять подобный костыль (только религиозно-православный) родными средствами PowerShell и необходимыми COM интерфейсами. Для работы с БД сервера CA нам понадобится лишь один интерфейс: ICertView2.
Каждая БД состоит из столбцов и рядов. То же самое относится и к БД CA (которая, кстати говоря, является стандартной встроенной Jet БД, что позволяет с ней работать стандартными тулзами типа eseutil). Ряды представляют собой набор свойств каждого запроса и для каждого запроса выделяется ровно один ряд. Столбцы представляют собой сами свойства. Каждому столбцу соответствует одно свойство. CA использует БД с заранее созданной (и неизменяемой) схемой, т.е. набором поддерживаемых столбцов. Давайте попробуем вывести схему БД. Для начала создадим объект ICertView2:
$CaView = New-Object -ComObject CertificateAuthority.View
Далее нужно подключиться к какому-то CA серверу при помощи метода OpenConnection. В качестве аргумента следует указать конфигурационную строку CA сервера вида CAComputerName\CAName.
$CaView.OpenConnection("dc1\Contoso CA")
Если ошибок не высыпало, значит, подключение произошло удачно. Теперь мы хотим посмотреть схему и это можно сделать при помощи метода EnumCertViewColumn. В качестве аргумента указываем CVRC_COLUMN_SCHEMA (которое имеет значение 0).
$CaView.EnumCertViewColumn(0)
В результате мы молучим другой объект интерфейса IEnumCERTVIEWCOLUMN. А теперь методом Next будем шагать по каждому столбцу и собирать сведения о нём, как: имя столбца, локализованное имя, тип данных и максимальный размер данных для этого столбца при помощи набора методов, описанных на страничке ICertView2. Вот как это выглядит:
PS C:\> $CaView = New-Object -ComObject CertificateAuthority.View PS C:\> $CA = "dc1\Contoso CA" PS C:\> $CaView.OpenConnection($CA) PS C:\> $Columns = $CaView.EnumCertViewColumn(0) PS C:\> $Columns.Next() 0 PS C:\> $Columns.GetName() Request.RequestID PS C:\> $Columns.GetDisplayName() Request ID PS C:\> $Columns.GetType() 1 PS C:\> $Columns.GetMaxLength() 4 PS C:\> $Columns.Next() 1 PS C:\> $Columns.GetName() Request.RawRequest PS C:\> $Columns.GetDisplayName() Binary Request PS C:\> $Columns.GetType() 3 PS C:\> $Columns.GetMaxLength() 65536 PS C:\>
После метода Next мы видим текущий индекс столбца. Что касается вывода метода GetType(), то вместо типа данных мы видим только циферки. Это нормальное поведение для CryptoAPI интерфейсов. Каждому числу соответствует свой тип данных (их поддерживается всего 4) и вот как они расшифровываются:
Если всё это затолкать в цикл DO…UNTIL, можно всё это дело автоматизировать. По правде говоря некоторые столбцы могут содержать массив данных и это можно проверить при помощи метода IsIndexed. Давайте теперь напишем окончательный скрипт, который нам по одному клику выдаст всю схему БД:
function Get-CADataBaseSchema { [CmdletBinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [string]$CAConfig ) # создаём объект ICertView try {$CaView = New-Object -ComObject CertificateAuthority.View} catch {Write-Error "Unable to instantiate ICertView object"; return} # открываем подключение к CA $CaView.OpenConnection($CAConfig) # говорим, что мы хотим посмотреть схему $Columns = $CaView.EnumCertViewColumn(0) # начинаем итерацию по столбцам БД [void]$Columns.Next() do { # создаём временный объект, чтобы получить красивый вывод и наполняем его данными $Column = "" | Select Name, DisplayName, Type, MaxLength $Column.Name = $Columns.GetName() $Column.DisplayName = $Columns.GetDisplayName() $Column.Type = switch ($Columns.GetType()) { 1 {"Long"} 2 {"DateTime"} 3 {"Binary"} 4 {"String"} } [string]$Column.MaxLength = $Columns.GetMaxLength() if ($Columns.IsIndexed() -eq 1) {$Column.MaxLength += ", Indexed"} $Column } until ($Columns.Next() -eq -1) # закрываем подключение к БД $Columns.Reset() }
А вывод весьма простенький:
PS C:\> Get-CADataBaseSchema "dc1\Contoso CA" Name DisplayName Type MaxLength ---- ----------- ---- --------- Request.RequestID Request ID Long 4, Indexed Request.RawRequest Binary Request Binary 65536 Request.RawArchivedKey Archived Key Binary 65536 Request.KeyRecoveryHashes Key Recovery Agent Hashes String 8192 Request.RawOldCertificate Old Certificate Binary 16384 Request.RequestAttributes Request Attributes String 32768 Request.RequestType Request Type Long 4 Request.RequestFlags Request Flags Long 4 Request.StatusCode Request Status Code Long 4 Request.Disposition Request Disposition Long 4, Indexed Request.DispositionMessage Request Disposition Message String 8192 Request.SubmittedWhen Request Submission Date DateTime 8, Indexed Request.ResolvedWhen Request Resolution Date DateTime 8, Indexed Request.RevokedWhen Revocation Date DateTime 8 Request.RevokedEffectiveWhen Effective Revocation Date DateTime 8, Indexed Request.RevokedReason Revocation Reason Long 4 Request.RequesterName Requester Name String 2048, Indexed Request.CallerName Caller Name String 2048, Indexed Request.SignerPolicies Signer Policies String 8192 Request.SignerApplicationP... Signer Application Policies String 8192 Request.Officer Officer Long 4 <...>
В следующей части (или частях, пока ещё не придумал) поговорим о том, как блуждать по самой БД.
Disclaimer: автор блога не несёт ответственности за содержание опубликованных внешних ссылок и гарантирует, что они были рабочие только на момент публикации статьи. Со временем они могут перестать работать по независящим от автора блога причинам. Прошу учитывать это.
Решил я тут провести небольшое исследование на предмет работы интернет-браузеров с цифровыми сертификатами — как серверными, так и клиентскими и ещё по мелочам. Откровенно говоря, домашним пользователям подобные вещи не нужны совершенно, а вот корпоративному пользователю они могут потребоваться. Результаты исследования стали для меня небольшой неожиданностью. Но будем двигаться попорядку. Для начала список испытуемых (наиболее актуальные версии на момент постинга):
Все браузеры проверялись на Windows 7 Enterprise x64. Что проверялось? А проверялось следующее:
Под этим пунктом я понимаю просто поддержку SSL 3.0 и стандартных SSL сертификатов. Ничего специфичного. Вобщем-то это стандарт де-факто и все браузеры с этим пунктом полностью солидарны :-)
С выходом Windows Vista, Microsoft представил свою реализацию ассиметричных алгоритмов шифрования и генерации ключей под названием CNG (Cryptography Next Generation), в том числе с поддержкой ECC (Elliptic Curve Cryptography) и прочих стандартов. С момента выхода Windows Vista уже прошло ровно 3 года (конец 2007 года) и уже вышла следующая версия настольных Windows — Windows 7. Эти 2 системы всё шире и шире заполняют места в корпоративных сетях. Уже никого не удивишь сетью с системами под управлением только Windows Vista и выше. А, следовательно, уже приобретают актуальность SSL сертификаты с поддержкой CNG. Но все ли браузеры поддерживают их? Это тоже можно назвать баяном, но один браузер этот пункт провалил. Для меня было неожиданностью, что Opera ни в какую не хочет подключаться к сайтам с такими сертификатами.
Тоже стандарт де-факто 1999 года (RFC 2560). Ну за 11 лет браузеры как-то осилили 23-страничный RFC :-). Всё-таки, лучше потратить 2кб на запрос-ответ (а напомню, запрос весит примерно 70-100 байт, а ответ порядка 1,5кб), чем тратить мегабайты трафика (особенно на нешустрых мобильных каналах) на скачиваение CRL'ов. Вот один из таких пруфов:
http://SVRSecure-G2-crl.verisign.com/SVRSecureG2.crlДовольно важный момент, который многие пытаются игнорировать. Потому что это не всем по зубам настроить правильно и практической пользы от этого они (они — кто не смог настроить доступность проверки отзыва) не видят. А это очень неправильно, ведь может быть так, что сертификат давно отозван (например, украли ключи от сертификата), а мы об этом ничего не знаем, т.к. списки отзыва недоступны. Как правило браузеры игнорируют эту ошибку, когда CRL'ы и OCSP недоступны, но опционально можно включить предупреждение, что сервер отзыва недоступен. В IE это сделано очень готично — выводит жёлтую полоску в адрес-баре: IE7 & SSL. Про другие браузеры не знаю, но похожих опций в настройках не нашёл.
Весьма актуальный пункт для корпоративного сектора. Поскольку внутренние порталы (особенно на SharePoint) используют учётные записи Active Directory для разграничения прав доступа на элементы и страницы корпоративного веб-сайта с использованием Windows (или Integrated) Authentication. Без него пользователю пришлось бы каждый раз вводить свои логин и пароль для входа на сайт, что весьма уныло и очень раздражает. А если есть вот такая сквозная аутентификация, система сама пересылает учётные данные (как правило, билет кербероса, а не логин и пароль в чистом виде, как некоторые могли бы подумать) на сервер. Безусловно, раздавать свои учётные данные (хоть и шифрованные) всем подряд — не самая лучшая затея в этой жизни. Поэтому существует некоторый дополнительный механизм, который определяет, кому можно их пересылать или нет. IE использует зоны интернета для разделения сайтов. По умолчанию Internet Explorer'у разрешено пересылать учётные данные только тем сайтам, которые находятся в зоне Local Intranet. Как правило корпоративные порталы добавляются в эту зону администраторами через GPO. На одиночных станциях это настраивается в апплете Internet Options панели управления.
Бытует мнение, что только IE может использовать сквозную аутентификацию на вебе. Но это всё неправда, потому что Google Chrome тоже использует зоны интернета для определения, кому можно пересылать учётные данные.
Далеко не обязательно (хотя и рекомендуется) использовать стандартное (можно даже сказать системное) разделение сайтов по зонам для определения разрешения на пересылку учётных данных для аутентификации и браузеры могут использовать свою реализацию сквозной аутентификации на вебе. И Mozilla FireFox в данном случае является примером такой реализации. Вот ссылка на настройку Windows Authentication в FireFox: Firefox and Integrated Windows Authentication. Opera и Safari никаким образом не поддерживают сквозную аутентификацию и не имеют собственных реализаций.
Бывают в этой жизни такие ситуации, когда использование логина и пароля для доступа к веб-сайту очень небезопасно (особенно если на сайте хранится очень конфиденциальная информация). Для усиления аутентификации можно использовать пользовательские сертификаты. Пользователь просто предъявляет его и тем самым аутентифицируется на веб-сервере. Опять же, по умолчанию для поиска пользовательских сертификатов используется системное пользовательское хранилище сертификатов (certmgr.msc). Но это тоже не обязательно (хотя и рекомендуется) и можно сделать свою реализацию.
В принципе, эту функцию поддерживают (тем или иным образом) все браузеры. Однако, в Opera она не работает. Никак. Кнопки нужные есть, но они выдают ошибки и всё. Поэтому для Opera здесь ставим красный крестик.
Большинство приложений, использующих сертификаты, используют встроенное в Windows хранилище сертификатов для определения доверия и использования пользовательских сертификатов. Но в интернет-браузерах это не строгое условие, хотя очень полезное, т.к. ничего дополнительно настраивать не надо.
Internet Explorer (удивительно, да?), Google Chrome и Apple Safai используют стандартное хранилище сертификатов. Mozilla FireFox и Opera не поддерживают его никоим образом.
Примечание: однако, это вовсе не значит, что Chrome и Safari будут показывать зелёные полоски на вашем Extended Validation SSL сертификате. Т.е. если система доверяет вашему корневому сертификату (он установлен в контейнере Trusted Root CAs), Chrome и Safari будут ему доверять тоже. Но зелёнку (если она настроена для Internet Explorer) показывать не будут.
Т.к. использование стандартного хранилища сертификатов не обязательно для браузеров, Mozilla FireFox и Opera используют свои собственные хранилища для проверки доверия SSL сертификатам, а так же и для использования клиентских сертификатов для аутентификации на вебе. Поэтому если у вас используются внутренние сертификаты (выданые вашим собственным CA), скорее всего они не будут работать в этих браузерах и вам придётся их отдельно настраивать, чтобы эти браузеры доверяли вашим сертификатам. Что касается пользовательских сертификатов, то здесь придётся экспортировать сертификат из хранилища в PFX и импортировать в браузер. Это неудобно и вообще плохо.
Что касается Opera, этот браузер вообще как-то по-своему относится к сертификатам. Например, даже не показывает зелёнки (Extended Validation SSL) на тех сайтах, где остальные браузеры её показывают. Например, Opera до сих пор не показывает зелёнку на https://startssl.org.
Бывают в этой жизни и более тяжёлые случаи, когда пользователи не имеют логина и пароля для входа в систему и вообще для аутентификации в Active Directory. У них есть сертификат, но он не просто лежит в хранилище, а находится на защищённом криптоустройстве, называемом смарт-картой. Это уже из разряда многофакторной аутентификации, о которой я здесь рассказывать не буду, но этот вид аутентификации значительно надёжней комбинации логина и пароля, т.к. тут нужно знать не только PIN код от смарт-карты (а его можно и узнать при большом желании), но и иметь при себе саму смарт-карту. Для работы со смарт-картой в систему устанавливается её драйвер и криптопровайдер (Cryptographic Service Provider, CSP). Этот CSP так же встраивает себя в LSA (Local Security Authority), что позволяет приложениям легко обнаруживать смарт-карты для аутентификации, даже если приложение не обучено специально работать со смарт-картами. Для этого, разумеется, необходимо использовать системные API.
Вот эти самые системные API используют следующие браузеры: Internet Explorer (ну куда же без него :-)), Chrome и Safari. Как только вы установили драйвер для смарт-карты, эти браузеры уже готовы с ней работать. Просто, удобно и со вкусом.
Mozilla FireFox и Opera не используют системные API для получения сведений о смарт-картах и в этих браузерах эту поддержку нужно включать вручную. Вот пример, как это делается в Mozilla FireFox на примере Aladdin eToken: eToken Settings for Mozilla-Firefox 3.5 (по ссылке документ PDF). Opera, к сожалению, смарт-карты не поддерживает ни в каком виде.
А теперь соберём это всё в аккуратненькую табличку:
Internet Explorer | Mozilla FireFox | Opera | Google Chrome | Apple Safari | |
Legacy cryptography support | :yes: | :yes: | :yes: | :yes: | :yes: |
CNG Support | :yes: | :yes: | :no: | :yes: | :yes: |
OCSP Support | :yes: | :yes: | :yes: | :yes: | :yes: |
Offline revocation detection | :no: | :no: | :no: | :no: | :no: |
Native Windows/Integrated Authentication | :yes: | :no: | :no: | :yes: | :no: |
Custom Windows/Integrated Authentication implementation | Not required | :yes: | :no: | Not required | :no: |
Client Certificate Authentication | :yes: | :yes: | :no: | :yes: | :yes: |
Windows Certificate Store support | :yes: | :no: | :no: | :yes: | :yes: |
Custom Certificate Store support | Not required | :yes: | :yes: | Not required | Not required |
Native smart card support | :yes: | :no: | :no: | :yes: | :yes: |
Custom smart card support | Not required | :yes: | :no: | Not required | Not required |
Какие выводы следуют из этого?
Вот, собственно говоря, и всё :-)