Posts on this page:
В предыдущем посте мы ознакомились с основными контейнерами с объектами PKI в Active Directory и смогли изучить функциональный аналог ключа dspublish в утилите certutil. Если публикация сертификатов в AD задача простая даже для Certutil, то просмотр содержимого может быть весьма нетривиальным. Например, если вы хотите посмотреть содержимое записи NTAuthCertificates, то придётся выполнить вот такую команду:
certutil –viewstore "CN=NTAuthCertificates,CN=Public Key Services,CN=Services,CN=Configuration, ForestRootDomainDN"
такие вещи совершенно неприспособлены к командной строке, поскольку надо набирать много текста и ошибиться весьма просто. Одно хорошо, команда выводит графическое окошко, где мы можем посмотреть содержимое. Но тут есть несколько неудобных моментов: мы не можем посмотреть несколько контейнеров сразу, для каждого контейнера надо выполнять отдельную команду. В этом окошке мы можем только посмотреть на содержимое контейнера и всё. Ни добавить, ни удалить сертификат мы не можем. Для добавления сертификатов мы можем воспользоваться тем же certutil или моим скриптом, который был опубликован в предыдущем посте. Графика — хорошо и замечательно, но мы можем хотеть автоматизировать какие-то задачи или просто посмотреть информацию в консоли. Вы можете подумать, что это не нужно, но преимущество между консольным выводом и графическим диалоговым окном очевидное: из первого можно копировать информацию в буфер обмена. Есть ещё вариант — для просмотра и удаления сертификатов из AD, пользоваться консолью pkiview.msc. Но мы сразу же теряем главную нить — единое средство управления. Т.е. даже похожие операции мы должны выполнять в разных инструментах! Но с появлением PowerShell мы получили единый (хоть и консольный) инструмент, которым можно автоматизировать абсолютно всё! Даже сам PowerShell :-) Вот, собственно код, который в разы упрощает процесс просмотра содержимого контейнеров:
function Get-ADPKIObject { <# .Synopsis Displays certificates info from Active Directory containers .Description Displays info about certificates that are stored in AD PKI-related containers. .Parameter Container Optional parameter. Specifies particular container to view. May contain one or more value from following possible values: RootCA - retrieves certificates from Certification Authorities container SubCA - retrieves certificates from AIA container NTAuthCA - retrieves certificates from NTAuthCertificate directory entry. if no parameter is set, command will return all certificates from all applicable containers. .EXAMPLE Get-ADPKIObject RootCA Retrieves certificates from Certification Authorities container .EXAMPLE Get-ADPKIObject RootCA, AIA Retrieves certificates from Certification Authorities and AIA containers .Outputs Output AD PKI object collection #> [CmdletBinding()] param([string[]][ValidateSet("RootCA", "SubCA", "NTAuthCA", "")]$Container) # объявляем массив, который будет хранить выходные объекты $script:sum = @() # это весьма крутая штука будет. Каждый объект будет содержать свойство Id или просто # порядковый номер объекта. При дальнейших операциях с этими объектами вам достаточно # будет указать его Id вместо длинных и неудобных LDAP/Thumpbrint значений, как это делается # в certutil и подобных ему утилитах. Нумерацию начнём с единицы $script:n = 1 # итоговая функция, которая будет разбирать бинарные массивы и готовить выходные объекты function _formatter_ ($certs, $type, $name) { # поскольку у нас все объекты в AD находятся в бинарном формате, мы импортируем каждый из них # в X509Certificate2 объект. $certs | %{ $script:Cert.Import($_) # здесь мы создаём образец выходного объекта и обвязываем этот объект необходимыми свойстами и данными $current = "" | select @{n='Id';e={$script:n}}, Subject, @{n='Type';e={$type}}, @{n='Container';e={$name}}, @{n='Thumbprint';e={$script:Cert.Thumbprint}}, @{n='SerialNumber';e={$script:Cert.SerialNumber}}, @{n='ValidFrom';e={$script:Cert.NotBefore}}, @{n='ValidTo';e={$script:Cert.NotAfter}}, @{n='RawCertificate';e={$script:Cert.RawData}} # чтобы не писать полный DN поля Subject, мы будем показывать только первую его часть # (которая отображается в самом сертификате) [void]($script:Cert.Subject -match 'CN=([^,]+)') $current.Subject = $matches[1] # добавляем объект в массив выходных объектов $script:sum += $current # очищаем X509Certificate2 $script:Cert.Reset() # увеличиваем счётчик и обрабатываем следующий элемент $script:n++ } } # ещё одна суб-функция, которая выдёргивает сертификаты из AD в бинарном виде и отправляет # их в _formatter_, который уже сформирует итоговые объекты. function _switcher_ ($name) { # подключаемся к нужному контейнеру $ldap = [ADSI]("LDAP://CN=$name,$script:ConfigContext") # как мы знаем, NTAuthCertificates не является контейнером, поэтому для него код будет немного # отличаться. А отличие будет состоять в том, что мы не будем залезать в контейнер, а сразу читать # свойства объекта NTAuthCA if ($name -eq "NTAuthCertificates") { # убеждаемся, что длина первого элемента свойства cACertificate больше единицы, т.е. содержит ненулевое # значение. Так же проверяем свойство crossCertificatePair, которое содержит Cross-certificates # и если оно не нулевое, то отправляем и его на формирование вывода if ($ldap.cACertificate[0].count -gt 1) { $certs = @($ldap.cACertificate) _formatter_ $certs "CA Certificate" $name } if ($ldap.crossCertificatePair[0].count -gt 1) { $certs = @($ldap.cACertificate) _formatter_ $certs "Cross CA Certificate" $name } # и переходим к следующему контейнеру return } # если контейнер указан как Certification Authority и/или AIA, то заглядываем # внутрь контейнера $ldap.psbase.children | %{ # и заглядываем в каждую запись на исследование свойств cACetificate и crossCertificatePair $certs = @($_.cACertificate) $ccerts = @($_.crossCertificatePair) # проверяем, что свойство имеет ненулевое значение. Если так, то отправляем # содержимое этих свойств на формирование вывода if ($certs[0].count -gt 1) {_formatter_ $certs "CA Certificate" $name} if ($ccerts[0].count -gt 1) {_formatter_ $ccerts "Cross CA Certificate" $name} } } switch ($Container) { # конструкцией switch проверяем содержимое аргумента функции, чтобы определить какие именно # контейнеры надо обследовать. "RootCA" {_switcher_ "Certification Authorities"} "SubCA" {_switcher_ "AIA"} "NTAuthCA" {_switcher_ "NTAuthCertificates"} # если контейнер в аргументе не указан, то проверяем все контейнеры и записи "" { _switcher_ "Certification Authorities" _switcher_ "AIA" _switcher_ "NTAUthCertificates" } } # когда вывод будет полностью сформирован, выбрасываем все объекты в консоль $script:sum }
Примечание: данная функция является частью файла dspublish.ps1, т.к. использует глобально объявленные переменные.
и вывод у него вот такой красивый:
Id : 3 Subject : Contoso CA Type : CA Certificate Container : AIA Thumbprint : BA8FECE99165E68CE27C9F0AF5F0664FDA39F7A2 SerialNumber : 5DD87E4CFFE3B3BC43F608EB57C767F7 ValidFrom : 2009.02.15. 16:31:15 ValidTo : 2014.02.15. 16:40:11 RawCertificate : {48, 130, 4, 98...} Id : 4 Subject : sysadmins-LV-CA Type : Cross CA Certificate Container : AIA Thumbprint : 1A28B582E21803D2BFE0DAEEF4593DE372C8EC3C SerialNumber : 170AD11B0000000000A7 ValidFrom : 2009.11.24. 19:43:10 ValidTo : 2011.03.30. 17:06:53 RawCertificate : {48, 130, 7, 94...}
я считаю его достаточно информативным. Но если хотите посмотреть любой сертификат из этого списка, то можно и сделать просмотр. К сожалению я в программировании не шарю и как работать напрямую с библиотекой просмотрщика сертификатов, поэтому я реализовал просмотр обходным путём:
filter View-ADPKIObject { <# .Synopsis Displays certificates in certificate viewer .Description Displays certificates in certificate viewer by selecting necessary certificates ID. Must be placed after Get-ADPKIObject command only. .Parameter ID Specifies certificate ID that was set in Get-ADPKIObject command. .EXAMPLE Get-ADPKIObject RootCA | ViewADPKIObject 1, 3 displays certificate with ID = 2 in certificate viewer .Outputs Script doesn't generate any output except errors #> # судя по конструкции int[] мы можем указать несколько чисел, тогда все выбранные # сертификаты будут отображены. Номер указывать обязательно. param([int[]]$ID = $(throw "you must specify number of the object to display")) # и проверяем входные объекты с конвейера на предмет их ID. Если ID совпадает с # одним из ID в аргументах, обрабатываем его. Если ID не совпадает, ничего не делаем. if (@($ID) -contains $_.Id) { # генерируем в пользовательской папке Temp временный файл с рандомным расширением $TempFile = [System.IO.Path]::GetTempFileName() + ".cer" # записываем бинарный массив сертификата в файл в виде DER кодировки [System.IO.File]::WriteAllBytes($TempFile, $_.RawCertificate) # запускаем файл (просмоторщик сертификатов) & $TempFile # в санитарных целях ждём пол секунды Start-Sleep 0.5 # и удаляем этот файл, чтобы не копился мусор del $TempFile -Force } }
Как вы видите, ничего сверх-космического или магического в этом коде нет, самое трудное здесь — придумать логику работы. А остальное — накидать несколько строк кода и у нас PowerShell в лёгкую может соперничать с certutil за право называться единой утилитой управления PKI :-)
Чтобы подкрутить рейтинг PowerShell в этой конкуренции, в следующий раз я покажу как мы можем легко и просто удалять сертификаты из контейнеров AD с использованием PowerShell :-)
Подоспела ещё одна задачка для PowerShell'а — управление объектами PKI в Active Directory. Active Directory содержит целый раздел посвящённый PKI и вот из чего он состоит:
Вот о них мы сегодня и поговорим. В AD есть ещё несколько контейнеров, которые связаны с PKI, но они сегодня интереса представлять не будут. Как мы уже знаем, в:
Примечание: данный материал публикуется на правах ТЗ (ТЗ — Тайное Знание) и обязателен для знания администраторам 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 для терминальных подключений. В ходе множества проверок было констатировано:
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, а PowerShell — X509Chain. Однако, у меня есть уверенность, что существует ещё одна реализация этого движка, которую использует 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 (в прошлом известен нам как EKU — Extended 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
Тут очень просто:
Если проверяем сертификаты на отзыв, то мы можем выбрать что именно будем проверять и этот выбор перечислен в X509RevocationFlag:
[↓] [vPodans] [System.Enum]::GetNames([system.security.cryptography.x509certificates.x509revocationflag])
EndCertificateOnly
EntireChain
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
Ну и при желании можно поуказывать там разные параметры и флаги проверки.
Представлю на обозрение пробный скрипт по управлению сертификатами в хранилище Certificate Store — CertMgmtPack.ps1, который (как я надеюсь) может найти применение на серверах Windows Server 2008 R2 Server Core и реализован на основе наших недавних исследований:
Версия пока что 0.47 (т.к. предстоит добавить ещё разного функционала и допилить существующий). Пока что мне удалось реализовать функционал импорта сертификатов из файлов в store и экспорт из store в файлы сертификатов.
Примечание: скрипт работает только в PowerShell V2. В каждую функцию вложена справка, которую вы можете прочитать набрав Get-Help Import/Export-Certiifcate.
После подключения скрипта (используя dot-sourcing) у вас будут доступны 2 функции:
И пару слов о том, как их использовать.
содержит следующие параметры:
dir *.cer | Import-CertificateПараметр обязательный.
Read-Host "Enter password for PFX file" –AsSecureString
И пару примеров, как использовать функцию:
$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.p7b –Storage 'Computer' –Container 'TrustedPublisher'
Импортирует все сертификаты из файла certs.p7b в контейнер Trusted Publishers компьютерного хранилища.
Я не смог провести полноценное тестирование функции экспорта, поэтому гарантировать 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 может содержать недопустимые символы для имён файлов, мне скорее всего придётся искать другой метод именования экспортируемых сертификатов и чтобы они были распознаваемые. Если вы обнаружите какие-то ошибки или будут пожелания, то пишите мне в коментарии или на почту. По мере дописывания я буду выкладывать новые версии скрипта.