Contents of this directory is archived and no longer updated.

Ссылки на другие материалы из этой серии:

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

Обращение к криптопровайдеру

Итак, снова посмотрим на описание функции CertCreateSelfSignCertificate и посмотрим, что нам надо в качестве аргументов функции. Во-первых, это hCryptProvOrNCryptKey. Хоть и написано, что этот аргумент опциональный, мы его будем использовать. Чтобы получить хэндл криптопровайдера (CSP) мы воспользуемся универсальной функцией CryptAcquireContext. На выходе этой функции (в аргументе phProv) мы получим наш хэндл. Чтобы запустить функцию нужно указать имя контейнера (контейнер криптопровайдера, а не хранилища сертификатов. В качестве имени можно использовать что угодно, лишь бы это имя было уникальным для конкретного CSP. В качестве уникального имени я предпочитаю использовать рандомный GUID. Далее, нам нужно указать имя криптопровайдера. Мы не будем использовать что-то космическое и обойдёмся вполне стандартным Microsoft Base Cryptographic Provider v1.0. Тип провайдера у нас будет RSA_FULL — 0x1 (RSA Full (Signature and Key Exchange) ). В dwFlags мы будем использовать CRYPT_NEWKEYSET — 0x8 (мы всё же будем создавать ключи). Итак, определяем аргументы и выполняем функцию:

$pszContainer = [Guid]::NewGuid().ToString()
[IntPtr]$phProv = [IntPtr]::Zero
$Provider = "Microsoft Base Cryptographic Provider v1.0"
$Result = [Quest.PowerGUI]::CryptAcquireContext([ref]$phProv,$pszContainer,$Provider,0x1,0x8)
[↓] [vPodans] $pszContainer = [Guid]::NewGuid().ToString()
[↓] [vPodans] [IntPtr]$phProv = [IntPtr]::Zero
[↓] [vPodans] $Provider = "Microsoft Base Cryptographic Provider v1.0"
[↓] [vPodans] $Result = [Quest.PowerGUI]::CryptAcquireContext([ref]$phProv,$pszContainer,$Provider,0x1,0x8)
[↓] [vPodans] $Result
True
[↓] [vPodans] $phProv
484709040
[↓] [vPodans]

Мы создали нужные аргументы и запустили функцию. Я проверил, что статус вызова True, т.е. с высокой долей вероятности всё прошло успешно. И заглянул в переменную $phProv, чтобы убедиться, что там есть наш хэндл. Если там 0 или отрицательное число, можете быть уверены, что это не хэндл и что-то сделано неправильно. Всё, с этой функцией закончили, у нас есть хэндл, все счастливы и довольны.

Получение имени сертификата (Subject и Issuer)

Второй аргумент для CertCreateSelfSignCertificate является pSubjectIssuerBlob. Это должно быть имя в виде байтового массива и записанного в нотации ASN.1. На MSDN нам предлагают функцию CertStrToName. Но мы не будем её использовать, потому что у нас есть замечательный класс X500DistinguishedName, который это всё сделает:

[↓] [vPodans] $Subject = [System.Security.Cryptography.X509Certificates.X500DistinguishedName]"CN=Hello World"
[↓] [vPodans] $Subject

Name                                    Oid                                     RawData
----                                    ---                                     -------
CN=Hello World                          System.Security.Cryptography.Oid        {48, 22, 49, 20...}


[↓] [vPodans]

Мы видим кусочек данных в свойстве RawData. Это как раз и есть тот самый массив байтов в нотации ASN.1. Осталось только перекинуть этот массив в неуправляемую память. По предыдущей статье мы уже знаем как при помощи маршалинга это сделать:

# выделяем область памяти для хранения нашего массива
$ptrSubject = [Runtime.InteropServices.Marshal]::AllocHGlobal($Subject.RawData.Count)
# копируем наш массив байтов в неуправляемую память
[Runtime.InteropServices.Marshal]::Copy($Subject.RawData,0,$ptrSubject,$Subject.RawData.Length)
# создаём простой объект CRYPTOAPI_BLOB и указываем адрес наших данных в памяти и сколько
# байт наших данных
$ptrName = New-Object Quest.PowerGUI+CRYPTOAPI_BLOB -Property @{
    cbData = $Subject.RawData.Length;
    pbData = $ptrSubject
}

Всё, объект в $ptrName содержит данные о поле Subject и Issuer.

Генерация ключевой пары

Идём дальше по аргументам нашей функции. dwFlags мы пропускаем, мы его смело поставим в 0 и забудем как кошмарный сон. Далее идёт pKeyProvInfo. Здесь деваться некуда, а ключевую пару нужно генерировать и собирать структуру CRYPT_KEY_PROV_INFO. Для этого у нас есть функция CryptGenKey. Функция достаточно несложная:

BOOL WINAPI CryptGenKey(
  __in   HCRYPTPROV hProv,
  __in   ALG_ID Algid,
  __in   DWORD dwFlags,
  __out  HCRYPTKEY *phKey
);

В качестве первого аргумента указываем хэндл нашего криптопровайдера. AlgId у нас будет означать KeySpec, т.е. AT_Exchange или AT_Signature. Поскольку мы договорились, что будем делать сертификат для подписи, это будет AT_Signature — 0x2.

dwFlags у нас будет определять длину ключа и дополнительные флаги. Поскольку это тестовый сертификат, мы будем использовать флаг CRYPT_EXPORTABLE — 0x1. Этот аргумент мы слепим из 2-х половинок по 4 октета (чтобы в сумме получить 8 октетов). Первые 4 октета (старшие) указывают на длину ключа (1024 или 2048 бит), но записанных в шестнадцатеричной форме (т.е. 0x0400 или 0x0800, соответственно). А младшие 4 октета будут представлять собой флаги. Недостающие октеты заполняем нулями. Следовательно, dwFlags для ключа в 1024 бит у нас будет 0x04000001, а для 2048 бит — 0x08000001.

На выходе в последний аргумент мы получим хэндл на ключевую пару в памяти:

[↓] [vPodans] [IntPtr]$phKey = [IntPtr]::Zero
[↓] [vPodans] $Result = [Quest.PowerGUI]::CryptGenKey($phProv,2,0x08000001,[ref]$phKey)
[↓] [vPodans] $Result
True
[↓] [vPodans] $PrivateKey = New-Object Quest.PowerGUI+CRYPT_KEY_PROV_INFO -Property @{
>>     pwszContainerName = $pszContainer;
>>     pwszProvName = $Provider;
>>     dwProvType = 1;
>>     dwKeySpec = 2
>> }
>>
[↓] [vPodans] $PrivateKey


pwszContainerName : b2909b8f-4145-445a-8816-cc37c672a532
pwszProvName      : Microsoft Base Cryptographic Provider v1.0
dwProvType        : 1
dwFlags           : 0
cProvParam        : 0
rgProvParam       : 0
dwKeySpec         : 2



[↓] [vPodans] $phKey
37910656
[↓] [vPodans]

Сначала мы сгенерировали ключевую пару (открытый ключ длиной 2048 бит) и создали объект структуры CRYPT_KEY_PROV_INFO. Почему мы никуда не вписали хэндл на закртый ключ? Он сейчас не нужен по простой причине, что он создан в контейнере криптопровайдера и функция CertCreateSelfSignCertificate сможет самостоятельно найти ключ.

Я предлагаю на этом пока остановиться, поскольку мы сделали самый необходимый минимум. В принципе, уже сейчас можно вызывать CertCreateSelfSignCertificate и на выходе мы получим примитивный сертификат. Минус этого сертификата — в нём не будет расширений. А нам нужны будут расширения. Как минимум Enhanced Key Usage, Key Usage, Subject Key Identifier, Subject Alternative Name и Basic Constraints. Т.е. это будет по сути самым правильным самоподписанным сертификатом для цифровой подписи. Но об этом в следующий раз.


Share this article:

Comments:

Comments are closed.