Contents of this directory is archived and no longer updated.

Продолжая тему экспорта сертификатов поговорим о более сложных вещах, чем простой экспорт сертификата из хранилища в CER/DER файл. На сей раз расскажу про экспорт сертификатов из хранилища в PFX (Personal Information Exchange Syntax Standard) и PKCS#7 (Cryptographic Message Syntax Standard) формат. Будет немного треша, но в пределах разумного.

Для экспорта сертификата в PFX нам потребуется выполнить половину кода из предыдущей статьи: Простой экспорт сертификатов в PowerShell.

Сначала выберем объект сертификат из хранилища:

$cert = (dir cert:\currentuser)[4]

Как вы помните, если экспортировать сертификат в PFX нам нужно указывать пароль:

Certificate Export Wizard windows

Следовательно, конструктор метода Export(X509ContentType), который мы использовали нам уже не подойдёт, поскольку в этот конструктор нельзя вложить пароль. Мы можем воспользоваться одним из оставшихся конструкторов:

SecureString/String представляют собой аргумент для пароля. Я бы не советовал использовать простую строку для хранения пароля, а SecureString. Для этого мы сделаем такую строчку:

$password = Read-Host "Password" –AsSecureString

А теперь по аналогии с примером из предыдущего поста укажем тип сертификата как PFX и применим наш метод Export(X509ContentType, SecureString):

$type = [System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx
$bytes = $cert.Export($type, $password)

У нас снова получится массив байтов, т.к. этот метод возвращает только его. Но что с этим бинарным мусором делать? Я сначала по наивности предположил, что его так же запаковать в Base64 и записать в файл. Да, у меня всё вышло хорошо, данные были записаны в файл и Certificate Import Wizard принял его за валидный PFX файл. Но у меня всё обломалось на стадии ввода пароля, т.к. этот визард не хотел его брать. Ввиду отсутствия практики в этих делах, пришлось попросить помощи на одном из бложиков технета, откуда и пришёл ответ, что этот Certificate Import Wizard не декодирует файл обратно из Base64, поэтому для записи в файл нужно писать сразу байты:

[System.IO.File]::WriteAllBytes('certificate.pfx', $bytes)

В качестве первого аргумента указывается путь к файлу и вторым аргументом наш массив. Лично я без подсказки долго бы ещё ломал голову, что сделать с этим массивом, чтобы получить нужный эффект.

Но это всё относительно просто. Когда я задался вопросом “а как экспортировать в PKCS#7?”, то пришлось убить несколько часов на выяснение этого процесса. PKCS#7 удобен тем, что в себе может содержать кучу сертификатов и главным образом используется для хранения цепочки сертификатов (Certificate Chain). Например, особенно наблюдательные пользователи при посещении многих HTTPS сайтов могут заметить, что поле AIA есть только у самих SSL сертификатов, а у промежуточных CA его уже нету. Но этих сертификатов нету и в хранилище сертификатов (Certificate Store). Напрашивается вопрос: а как же система построила эту цепочку сертификатов при отсутствующем AIA в сертификатах? Оказывается, всё очень просто, в RFC2246 есть хороший момент:

certificate_list
       This is a sequence (chain) of X.509v3 certificates. The sender's
       certificate must come first in the list. Each following
       certificate must directly certify the one preceding it. Because
       certificate validation requires that root keys be distributed
       independently, the self-signed certificate which specifies the
       root certificate authority may optionally be omitted from the
       chain, under the assumption that the remote end must already
       possess it in order to validate it in any case.

Это означает, что веб-сервер может вместе с SSL сертификатом отправлять клиенту и цепочку сертификатов, которая как раз и есть в формате PKCS#7. Если что, это было просто введение к назначению данного типа сертификатов.

Предупреждаю сразу, что методы x509Certificate2.Export Method нам не сгодятся, т.к. в описании метода есть ремарка:

The contentType parameter accepts only the following values of the X509ContentType enumeration: Cert, SerializedCert, and Pkcs12. Passing any other value causes a CryptographicException to be thrown.

Хоть класс System.Security.Cryptography.X509Certificates.X509ContentType и имеет в себе атрибут PKCS7, но метод Export класса x509Certificate2 его просто не принимает. Это был провал! Но делать нечего, пришлось искать. Наткнулся я вот на один замечательный пост: PKCS7 (p7b) bag of certificates and powershell. Но там пример как раз наоборот – как извлечь сертификаты из p7b файла и вывести их в виде x509Certificate2 объектов:

[reflection.assembly]::LoadWithPartialName("System.Security")
$data = [System.IO.File]::ReadAllBytes("certificates.p7b")
$cms = new-object system.security.cryptography.pkcs.signedcms
$cms.Decode($data)
$cms.Certificates | foreach {New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $_} | echo

Автор поста вообще удивительный человек. Просто взял и выложил код без каких-либо комментариев, предполагая, что все знают что делает этот код в деталях. Попробую за него сделать это я. Данный код читает бинарный мусор из файла (обратите внимание, что используется обратная операция экспорта PFX бинарного массива в файл). Далее создаётся объект CMS (Cryptographic Message Syntax). И что мы с него имеем:

[↓] [vPodans] [void][reflection.assembly]::LoadWithPartialName("System.Security")
[↓] [vPodans] $cms = new-object system.security.cryptography.pkcs.signedcms
[↓] [vPodans] $cms


Version      : 0
ContentInfo  : System.Security.Cryptography.Pkcs.ContentInfo
Detached     : False
Certificates : {}
SignerInfos  : {}



[↓] [vPodans] $cms | gm -MemberType methods


   TypeName: System.Security.Cryptography.Pkcs.SignedCms

Name             MemberType Definition
----             ---------- ----------
CheckHash        Method     System.Void CheckHash()
CheckSignature   Method     System.Void CheckSignature(bool verifySignatureOnly), System.Void CheckSignature(System....
ComputeSignature Method     System.Void ComputeSignature(), System.Void ComputeSignature(System.Security.Cryptograph...
Decode           Method     System.Void Decode(byte[] encodedMessage)
Encode           Method     byte[] Encode()
Equals           Method     bool Equals(System.Object obj)
GetHashCode      Method     int GetHashCode()
GetType          Method     type GetType()
RemoveSignature  Method     System.Void RemoveSignature(int index), System.Void RemoveSignature(System.Security.Cryp...
ToString         Method     string ToString()


[↓] [vPodans]

Мы имеем объект с 5 свойствами и различными методами. В коде используется метод Decode() и как видно в описании метода, в качестве аргумента принимается массив байтов, полученный из p7b файла. Этот метод преобразует этот массив обратно в массив объектов x509Certificate2, что видно из последней строки кода. Более-менее тут что-то понятно. Следовательно, для экспорта нам нужно проделать операцию наоборот, создать объект CMS, в свойство Certificates запихать x509Certificate2 объекты сертификатов и применить метод Encode(). Чтобы использовать этот метод у нас в свойстве Certificates должны храниться x509Certificate2 объекты наших сертификатов, которые хотим экспортировать. Но тут меня ждал облом, т.к. свойство Certificates является Read-only и записать туда ничего нельзя. Однако, PowerTab мне показал, что там есть что-то интересное:

[↓] [vPodans] $cms.Certificates.
                                ╔═ $cms.Certificates. ═════════════╗
                                ║ $cms.Certificates.PSBase         ║
                                ║ $cms.Certificates.Add(           ║
                                ║ $cms.Certificates.AddRange(      ║
                                ║ $cms.Certificates.Clear(         ║
                                ║ $cms.Certificates.Contains(      ║
                                ║ $cms.Certificates.CopyTo(        ║
                                ║ $cms.Certificates.Equals(        ║
                                ║ $cms.Certificates.Export(        ║
                                ║ $cms.Certificates.Find(          ║
                                ║ $cms.Certificates.GetEnumerator( ║
                                ║ $cms.Certificates.GetHashCode(   ║
                                ║ $cms.Certificates.GetType(       ║
                                ║ $cms.Certificates.Import(        ║
                                ║ $cms.Certificates.IndexOf(       ║
                                ║ $cms.Certificates.Insert(        ║
                                ║ $cms.Certificates.Remove(        ║
                                ║ $cms.Certificates.RemoveAt(      ║
                                ║ $cms.Certificates.RemoveRange(   ║
                                ║ $cms.Certificates.ToString(      ║
                                ║ $cms.Certificates.Item(          ║
                                ║ $cms.Certificates.Capacity       ║
                                ║ $cms.Certificates.Count          ║
                                ╚═[1] 1-22 (22/22)]════════════════╝

Я испробовал несколько по смыслу похожих методов, как Add, AddRange и Import, но всё неудачно. Сертификаты никак не хотели туда помещаться. После некоторого времени возни я бросил это занятие, как бесполезное, т.к. там ловить нечего:

[↓] [vPodans] $cms.Certificates | gm
Get-Member : No object has been specified to the get-member cmdlet.
At line:1 char:23
+ $cms.Certificates | gm <<<<
    + CategoryInfo          : CloseError: (:) [Get-Member], InvalidOperationException
    + FullyQualifiedErrorId : NoObjectInGetMember,Microsoft.PowerShell.Commands.GetMemberCommand

[↓] [vPodans]

Вобщем, всякие вариации с этим CMS объектов никаких успехов не приносили до тех пор, пока я не посмотрел тип этого свойства Certificates:

[↓] [vPodans] $cms.Certificates.GetType().FullName
System.Security.Cryptography.X509Certificates.X509Certificate2Collection

Я отправился на MSDN читать макулатуру по этому классу (точнее по его членам): X509Certificate2Collection Members. Если вы ещё не забыли, то мы ищем способ разобрать наш x509Certificate2 объект на массив байтов, который бы соответствовал формату PKCS#7. Здесь из годных методов я нашёл метод Add(X509Certificate2). У меня уже есть этот объект, поэтому мы его можем добавить в объект X509Certificate2Collection. Данный класс по сути представляет массив объектов x509Certificate2. Давайте сделаем:

[↓] [vPodans] $cert = (dir cert:\currentuser\my)[4]
[↓] [vPodans] $cert1 = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection
[↓] [vPodans] $cert1.Add($cert)
0
[↓] [vPodans] $cert1

Thumbprint                                Subject
----------                                -------
0F5157A8342C66493E7B1354530DD7A9F980BC69  CN=vPodans


[↓] [vPodans]

ну хорошо, преобразовали мы x509Certificate2 в X509Certificate2Collection, а что дальше? Нам по прежнему нужен этот дурацкий массив байтов. После этого я посмотрел на метод X509Certificate2Collection Export Method (X509ContentType) нового класса и прочитал ремарки:

This method supports content types that do not require a password.

Вот тут я и сказал “слава сиськамПротоколу!”. Данный метод позволяет экспортировать объекты сертификатов в любой тип, который не требует пароля (т.е. всё, кроме PFX/PKCS#12). А дальше уже по отработанной схеме:

[↓] [vPodans] $type = [System.Security.Cryptography.X509Certificates.X509ContentType]::Pkcs7
[↓] [vPodans] $bytes = $cert1.Export($type)
[↓] [vPodans] [System.IO.File]::WriteAllBytes('certificate.p7b', $bytes)
[↓] [vPodans]

Вот таким долгим и тяжёлым (для меня, во всяком случае) я смог его победить и научиться экспортировать сертификаты в PKCS#7.

Что нас ждёт дальше? А дальше, возможно, поговорим про SerializedCert и SerializedStore (а может и не будем говорить) и уже импорт сертификатов из файлов в Certificate Store. А на сегодня, пожалуй, хватит.


Share this article:

Comments:

Comments are closed.