Page 1 of 2 in the PowerShellCryptoAPI category Next Page

Как известно, когда запрос попадает в папку Pending Requests, администратор CA должен что-то с ним явно сделать — или одобрить или отклонить. Это можно сделать при помощи оснастки CetSrv.msc или при помощи PowerShell. Ещё можно через certutil, но речь сегодня не о нём. Если вспомнить предыдущие посты посвящённые CryptoAPI и PowerShell, можно вспомнить какие-то основные принципы. Как обычно, мы будем использовать интерфейс ICertAdmin2. Для аппрува соответствующих запросов необходимо воспользоваться методом ResubmitRequest(). Как вы видите, метод принимает 2 аргумента:

HRESULT ResubmitRequest(
   
[in] const BSTR strConfig,
   
[in] LONG RequestId,
    [out, retval] LONG *pDisposition
);
 

это конфигурационная строка CA вида: CAComputerName\CAName и номер запроса. И в ответ метод возвращает результат выполнения операции. Вот как это можно аккуратно сделать в PowerShell:

function Issue-PendingRequest {
[CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFomPipeline = $true)]
        [string]$CAConfig,
        [Parameter(Mandatory = $true)]
        [int]$RequestID
    )
    try {$CertAdmin = New-Object -ComObject CertificateAuthority.Admin}
    catch {Write-Warning "Unable to instantiate ICertAdmin2 object!"; return}
    try {
        $status = switch ($CertAdmin.ResubmitRequest($CAConfig,$RequestID)) {
            0 {"The request was not completed."}
            1 {"The request failed."}
            2 {"The request was denied"}
            3 {"The certificate was issued."}
            4 {"The certificate was issued separately."}
            5 {"The request was taken under submission."}
            6 {"The certificate is revoked."}
        }
    }
    catch {$_; return}
    Write-Host "Operation status for the request '$RequestID': $ststus"
}

Для отклонения запроса, следует воспользоваться методом DenyRequest(). Как и метод ResubmitRequest тоже принимает всего 2 аргумента, но кроме ошибок ничего не возвращает:

HRESULT DenyRequest(
    [in] const BSTR strConfig,
    [in] Long RequestId
);

И код будет очень похож на предыдущий:

function Deny-PendingRequest {
[CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFomPipeline = $true)]
        [string]$CAConfig,
        [Parameter(Mandatory = $true)]
        [int]$RequestID
    )
    try {$CertAdmin = New-Object -ComObject CertificateAuthority.Admin}
    catch {Write-Warning "Unable to instantiate ICertAdmin2 object!"; return}
    try {$CertAdmin.DenyRequest($CAConfig,$RequestID)}
    catch {$_; return}
    Write-Host "Successfully denied request '$RequestID'"
}

вот так легко можно программным способом управлять реквестами из папки Pending Requests без графических оснасток или certutil. Что касается certutil, я не уверен, что он сможет заапрувить или отклонить реквест на удалённом CA. У него есть параметр –config, но я не уверен, что он работает в данном случае. Плюс, когда я выложу в общий доступ свой PS модуль для PKI, эта операция будет ещё проще. Вам не придётся вручную набивать конфигурационную строку, а просто воспользоваться командой Get-CertificationAuthority.

Saturday, July 03, 2010 10:48:17 PM (FLE Daylight Time, UTC+03:00)   Comments [2]    

 

MSDN и Microsoft меня однажды доканает и я уйду проповедовать православныйбогомерзкий линукс.

А дело в том, что одна моя задумка с треском проваливалась из-за непонятного и тупого бага в интерфейсе ICertEncodeAltName. Это CryptoAPI интерфейс, который позволяет кодировать строковые значения для расширения Subject Alternative Name в ASN.1 DER encoded строку. На возню с ним я потратил почти неделю (чуть меньше, наверное) и только сегодня я смог найти все ответы. Хотя я это должен был сделать много раньше, но дико тупил на ровном месте. Итак, давайте посмотрим, что из себя представляет этот баг.

Для начала я покажу как собирается этот интерфейс:

$san = New-Object -ComObject CertificateAuthority.EncodeAltName

Далее, его надо инициализировать методом Reset() и в качестве аргумента метода указать размер объекта. Т.е. количество элементов, которые должны содержаться в расширении Subject Alternative Name. Если только один элемент, то и указываете число 1:

$san.Reset(1)

и теперь можно в него заряжать данные методом SetNameEntry():

$san.SetNameEntry(0,0x3,"Custom-Name")

Где 0 — индекс массива, в который надо записать строку, 0x3 — тип строки. В данном случае это DNS и последний аргумент указывает само значение строки. Полный список типов можно получить вот здесь: http://msdn.microsoft.com/en-us/library/aa374981(VS.85).aspx. Только учтите, что реально типы начинаются не с нуля (как по ссылке), а с единицы. т.е. 0x3 в нашем случае это DNS (2+1).

После того как мы загнали туда данные, их можно кодировать:

$san.Encode()

И давайте посмотрим, что у нас получилось:

[↓] [vPodans] $san = New-Object -ComObject CertificateAuthority.EncodeAltName [↓] [vPodans] $san.Reset(1) [↓] [vPodans] $san.SetNameEntry(0,0x3,"Custom-Name") [↓] [vPodans] $str = $san.Encode() [↓] [vPodans] $str ???????

выглядит прикольно. Но на самом деле это строка из символов, которые описаны не одним, а двумя байтами:

[↓] [vPodans] $str.ToCharArray() | %{[int][char]$_} 3376 2946 30019 29811 28015 20013 28001

Чтобы убедиться, что в каждом символе по 2 байта, мы преобразуем эти числа в их шестнадцатиричное (hex) представление:

[↓] [vPodans] $str.ToCharArray() | %{"{0:X4}" -f [int][char]$_} 0D30 0B82 7543 7473 6D6F 4E2D 6D61

видите, в каждом символе по 2 байта и все они записаны в little-endian последовательности. Т.е. первый байт находится справа, а не слева, как обычно. Давайте соберём эти байты в последовательную строку, переставив байты местами в каждой строке:

30 0D 82 0B 43 75 73 74 6F 6D 2D 4E 61 6D

И что же означают эти циферки-букавки? А означают они следующее:

  • 30 (48) — тип кодировки. 30 или 48 (в десятичном представлении) означает Constructed encoding.
  • 0D (13) — длину строки начиная со следующего байта. Остаточная длина должна быть 13 байт. Но моя математика говорит, что дальше не 13, а только 12 байт.
  • 82 (130) — тип закодированной строки. 130 означает DNS. 80 (128) — OtherName, 81 (129) — RFC822Name и т.д.
  • 0B (11) — размер строки начиная со следующего байта. Остаточная длина должна быть 11 байт. Но моя математика говорит, что их там не 11, а только 10 байт.
  • Остальные байты — сама строка, только в hex'е.

Куда-то пропал один очень важный байт, из-за чего сервер CA начинал доставлять шлакоблоки при виде такой строки. В принципе, мы можем посмотреть, что у нас потерялось по дороге:

[↓] [vPodans] $str1 = "43 75 73 74 6F 6D 2D 4E 61 6D".split(" ") [↓] [vPodans] $str1 | %{$a += invoke-expression [char]0x$_} [↓] [vPodans] $a Custom-Nam

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

Что можно сделать? Вариантов куча. Как вы видите ASN.1 DER кодировка достаточно простая, её можно сделать самому. Либо использовать православные интерфейсы CertEnroll — IX509ExtensionAlternativeNames. На первый взгляд там багов не обнаружено :). Примеры использования данных интерфейсов можно посмотреть (а заодно и почитать) в моём буржуйском бложике: How to add FQDN to HP iLO request.

На сегодня всё. Спокночи.

Sunday, May 02, 2010 1:46:05 AM (FLE Daylight Time, UTC+03:00)   Comments [0]    

 

Самоподписанные сертификаты — это зло, за исключением сертификатов корневых CA. Я об этом говорил, говорю и буду говорить. Но в данном случае мы не преследуем цель создания самоподписанного сертификата. Нас по сути будет интересовать немного другое — рассмотрение принципа, который заложен во многих популярных тулзах как MakeCert или OpenSSL. Лично я не фанат ни первого, ни второго по своим сугубо личным причинам. Но, кроме этих двоих есть ещё утилита CertReq.exe, которая достаточно православная и вряд ли ей грозит вымирание (а жаль). Вобщем, сегодня предлагаю ещё раз поковырять CryptoAPI.

Как мы уже знаем, CryptoAPI обладает большим количеством всяческих COM интерфейсов, при помощи которых мы можем работать практически с любыми аспектами цифровых сертификатов. Некоторые из них бажные, а некоторые — не очень :), но функционал у них впечатляющий. В настоящее время существует 2 основных набора API, которые реализуют клиентскую часть энроллмента — XEnroll и CertEnroll. Первый доступен только в системах начиная с Windows 2000 и до Windows Server 2003 включительно. В более новых версиях XEnroll был вырезан вместе с CAPICOM'ом полностью (куски CAPICOM'а ещё можно найти в висте) за ненадобностью. Семейство интерфейсов CertEnroll было значительно переработано и расширено, что делает его крайне гибким. Я не буду рассказывать про XEnroll, потому что это неинтересно и трупов пинать нехорошо.

Многие считают, что CryptoAPI — это очень сложно. Я могу возразить им. Я не программист совсем, но могу достаточно свободно их использовать. Нашей отправной точкой будет MSDN по адресу: Certificate Enrollment API Reference. Эта секция содержит всё самое необходимое — описание интерфейсов и перечисления. И самый первый интерфейс, который мы видим — IX509Enrollment. Этот интерфейс реализует нечто промежуточное между клиентом и сервером. Мы можем по описанию найти то, что нам нужно, а именно первую секцию — Out-of-band-enrollment. И мы видим, что для него надо сначала вызвать метод CreateRequest(). Но прежде чем вызывать метод, нам надо создать форму сертификата, на основе которой будет создан запрос. Как я уже упоминал, мы будем делать самоподписанный сертификат, поэтому следующий интерфейс подойдёт нам как нельзя кстати — IX509CertificateRequestCertificate2. Вот давайте с него и начнём.

Все указанные здесь и далее интерфейсы являются COM интерфейсами семейства X509Enrollment и эти объекты создаются следующим образом:

$Cert = New-Object -ComObject X509Enrollment.CX509CertificateRequestCertificate.1

Примечание: как строятся такие команды? Поскольку это COM интерфейс, первую букву I в названии интерфейса меняем на букву C. Далее, если мы видим цифру 2 в конце названия интерфейса, в команде мы ставим точку и пишем число на единцу меньшее. Вот такие нехитрые правила.

Прежде чем его начать использовать, нам надо инициализировать его. К сожалению документация на MSDN далеко не полная, поэтому будем искать нужные методы через PowerShell и командлет Get-Member:

[↓] [vPodans] $cert | gm -MemberType methods TypeName: System.__ComObject#{728ab35a-217d-11da-b2a4-000e7bbb2b09} Name MemberType Definition ---- ---------- ---------- CheckPublicKeySignature Method void CheckPublicKeySignature (IX509PublicKey) CheckSignature Method void CheckSignature (Pkcs10AllowedSignatureTypes) Encode Method void Encode () GetCspStatuses Method ICspStatuses GetCspStatuses (X509KeySpec) GetInnerRequest Method IX509CertificateRequest GetInnerRequest (InnerRequestLevel) Initialize Method void Initialize (X509CertificateEnrollmentContext) InitializeDecode Method void InitializeDecode (string, EncodingType) InitializeFromCertificate Method void InitializeFromCertificate (X509CertificateEnrollmentContext, string... InitializeFromPrivateKey Method void InitializeFromPrivateKey (X509CertificateEnrollmentContext, IX509Pr... InitializeFromPrivateKeyTemplate Method void InitializeFromPrivateKeyTemplate (X509CertificateEnrollmentContext,... InitializeFromPublicKey Method void InitializeFromPublicKey (X509CertificateEnrollmentContext, IX509Pub... InitializeFromTemplate Method void InitializeFromTemplate (X509CertificateEnrollmentContext, IX509Enro... InitializeFromTemplateName Method void InitializeFromTemplateName (X509CertificateEnrollmentContext, string) IsSmartCard Method bool IsSmartCard () ResetForEncode Method void ResetForEncode () [↓] [vPodans]

Из всех методов нам по сути доступен только InitializeFromPrivateKey(), поскольку остальные методы инициализации требуют наличие доступа к Certification Authority. Посмотрим что требуется для этого метода:

[↓] [vPodans] $cert | gm -MemberType methods | ?{$_.name -eq "InitializeFromPrivateKey"} | select definition Definition ---------- void InitializeFromPrivateKey (X509CertificateEnrollmentContext, IX509PrivateKey, string) [↓] [vPodans]

В качестве аргументов метода нам надо указать контекст энроллмента и объект закрытого ключа. Значения контекста находятся здесь: X509CertificateEnrollmentContext (просто включаете поиск на MSDN по названию перечисления). В качестве контекста мы можем выбрать контекст текущего пользователя или компьютера (остальное нас сейчас не волнует совсем). Контекст пользователя имеет значение 0x1. Так и запишем. Но этого мало. Надо ещё создать объект закрытого ключа:

$PrivateKey = New-Object -ComObject X509Enrollment.CX509PrivateKey

Этот интерфейс позволяет задавать различные параметры закрытого ключа, но мы обойдёмся лишь самым необходимым:

# во-первых надо указать CSP. Выберем самый простой криптопровайдер
$PrivateKey.ProviderName = "Microsoft Base Cryptographic Provider v1.0"
# закрытый ключ будет использоваться для подписи (Digital Signature)
# http://msdn.microsoft.com/en-us/library/aa379409(VS.85).aspx
$PrivateKey.KeySpec = 0x2
# длина вполне стандартная
$PrivateKey.Length = 1024
# ключ будем хранить в пользовательском хранилище
$PrivateKey.MachineContext = 0x0
# и генерируем ключ
$PrivateKey.Create()

Ура! Мы сгенерировали ключ. Теперь вернёмся к предыдущему интерфейсу и инициализируем его из закрытого ключа:

$Cert.InitializeFromPrivateKey(0x1,$PrivateKey,"")

Мы указываем контекст текущего пользователя и объект закрытого ключа. Там есть ещё один аргумент, который называется String. Я не знаю, что они этим хотели сказать, поэтому оставляем пустую строку. А теперь вернёмся к интерфейсу IX509CertificateRequestCertificate2 и посмотрим, что мы можем сделать сейчас. Например, используя свойства NotBefore и NotAfter мы зададим срок действия сертификата. Например, 1 год с сегодняшнего дня:

$Cert.NotBefore = [datetime]::Now
$Cert.NotAfter = $Cert.NotBefore.AddDays(365)

Теперь нам надо добавить следующие свойства: EncancedKeyUsage (т.е. для каких целей вообще будет использоваться сертификат), Subject (на кого будет выписан сертификат) и Issuer (кто выдал этот сертификат). Поскольку у нас самоподписанный сертификат, поле Subject и Issuer будут одинаковые. На MSDN'е не хватает документации по свойствам Issuer и Subject, но у нас есть поиск, который нас приведёт сюда: IX500DistinguishedName. Поля Subject и Issuer должны заполняться в формате Distinguished Name и доступные префиксы для DN достаточно понятно расписаны в таблице. Нам нужно как-то активировать этот объект. Методов для инициализации здесь нет, поэтому будем использовать метод Encode().

Лирическое отступление: в подавляющем большинстве случаев вы не можете присваивать значения свойствам объектов после создания самих объектов. Предварительно их надо «активировать» одним из двух способов. Если у объекта есть метод Initialize или производное от него, необходимо сначала воспользоваться одним из доступных методов инициализации. Если объект не содержит явных методов инициализации, нужно воспользоваться методом Encode, который кодирует объект или строку в ASN.1 DER строку и инициализирует объект. Единственным исключением из этого правила являются коллекции объектов. Они как правило используют метод Add() для добавления уже инициализированных объектов.

Уже с главной страницы IX500DistinguishedName видно, что Encode кодирует строку, которая записана в DN формате. Поэтому вызываем этот метод:

$SubjectDN.Encode("CN=Some Subject,DC=lucernepublishing,DC=COM", 0x0)

После строки нужно ещё указать флаг, в котором указана строка DN. Ставим дефолтный флаг. Теперь у нас готово поле Subject и Issuer (как мы договаривались, они будут одинаковые). Давайте их прицепим к нашему шаблону сертификата:

$Cert.Subject = $SubjectDN
$Cert.Issuer = $Cert.Subject

Что нам осталось сделать? Нам надо создать расширение Enchanced Key Usage. Для этого нам надо использовать следующий интерфейс: IX509ExtensionEnhancedKeyUsage:

$EKU = New-Object -ComObject X509Enrollment.CX509ExtensionEnhancedKeyUsage

Данный объект инициализируется из коллекции объектов IObjectIds. Давайте создадим эту коллекцию:

$OIDs = New-Object -ComObject X509Enrollment.CObjectIDs

В эту коллекцию с использованием метода Add() надо добавить один или несколько объектов IObjectId, каждый из которых представляет конкретное предназначение сертификата. Например, Server Authentication, Client Authentication, Smart Card Logon, Secure e-mail и т.д. Но мы сделаем сертификат для Code Signing. OID этого EKU = 1.3.6.1.5.5.7.3.3. Вот и сделаем его:

# создаём объект IObjectID
$OID = New-Object -ComObject X509Enrollment.CObjectID
# инициализируем его с использованием Code Signing
$OID.InitializeFromValue("1.3.6.1.5.5.7.3.3")
# добавляем наш OID в коллекцию OID'ов
$OIDs.Add($OID)
# добавляем коллекцию в объект IX509ExtensionEnhancedKeyUsage
$EKU.InitializeEncode($OIDs)

объект EKU у нас готов, теперь его надо добавить в наш шаблон сертификата. Поскольку это не стандартное поле сертификата, а расширение, добавляем этот объект в свойство X509Extensions, которое является аналогом интерфейса IX509Extensions и, который в свою очередь, является коллекцией расширений. Поэтому добавляем наше расширение методом Add():

$Cert.X509Extensions.Add($EKU)

Всё, мы собрали все минимально необходимые поля и расширения:

[↓] [vPodans] $cert Type : 4 EnrollmentContext : 1 Silent : False ParentWindow : UIContextMessage : SuppressDefaults : False ClientId : CspInformations : System.__ComObject HashAlgorithm : System.__ComObject AlternateSignatureAlgorithm : False TemplateObjectId : PublicKey : System.__ComObject PrivateKey : System.__ComObject NullSigned : False ReuseKey : False Subject : System.__ComObject CspStatuses : System.__ComObject SmimeCapabilities : False SignatureInformation : System.__ComObject KeyContainerNamePrefix : lp CryptAttributes : X509Extensions : System.__ComObject CriticalExtensions : System.__ComObject SuppressOids : System.__ComObject Issuer : System.__ComObject NotBefore : 16.04.2010 18:25:22 NotAfter : 16.04.2011 18:25:22 SignerCertificate : PolicyServer : Template : [↓] [vPodans]

Теперь мы можем превращать наш шаблон сертификата в настоящй сертификат.

Лирическое отступление: а что такое запрос в техническом смысле? На самом деле запрос ничем не отличается от сертификата. Когда вы запрашиваете сертификат у CA, клиент использует эти же интерфейсы для генерации запроса. При этом получается самый настоящий самоподписанный сертификат, где Subject и Issuer одинаковые и равны имени текущего пользователя или компьютера, а так же содержит все необходимые расширения. Сам запрос подписывается закрытым ключом, который мы сгенерировали. По большому счёту, его уже можно использовать как настоящий самоподписанный сертификат. Если его отправить на сервер CA, то последний просто подменяет значения необходимых полей (как Issuer, в котором он ставит себя) и расширений, удаляет старую подпись и подписывает сертификат новой подписью. Вы можете убедиться в этом очень просто. Сгенерируйте запрос для сертификата, откройте оснастку Certificates и разверните секцию Certificate Enrollment Requests. Там будет этот самый запрос в виде уже готового сертификата. Просто там он ждёт, пока какой-нибудь CA не подпишет его.

Давайте вернёмся в самое начало текущего поста и вспомним про «исходный предмет» — IX509Enrollment. Вот этот интерфейс нам сконвертирует шаблон сертификата в настоящий сертификат с использованием метода CreateRequest(). Но прежде чем использовать метод, нам надо инициализировать объект:

$Request = New-Object -ComObject X509Enrollment.CX509enrollment
$Request.InitializeFromRequest($Cert)

И генерируем файл запроса, который ничем не отличается от самоподписанного сертификата:

$endCert = $Request.CreateRequest(0x0)

В аргументах метода указываем кодировку согласно этой страничке: EncodingType Enumeration. Мы выбираем Base64 с заголовками. $endCert будет содержать сам сертификат (открытую его часть). Фактически запрос хранится в контейнере Certificate Enrollment Requests. Поскольку этот интерфейс не был задуман специально для самоподписанных сертификатов мы проходим стандартную процедуру установки сертификата. Мы просто берём открытую часть нашего же сертификата и устанавливаем её. Вот, кстати, как он выглядит:

[↓] [vPodans] $endcert -----BEGIN CERTIFICATE----- MIICaTCCAdKgAwIBAgIQEzCS/mFIxLBAiGjz7+n0dDANBgkqhkiG9w0BAQUFADBP MRMwEQYKCZImiZPyLGQBGRYDQ09NMSEwHwYKCZImiZPyLGQBGRYRbHVjZXJuZXB1 Ymxpc2hpbmcxFTATBgNVBAMMDFNvbWUgU3ViamVjdDAeFw0xMDA0MTgxMTI5MDla Fw0xMTA0MTgxMTI5MDlaME8xEzARBgoJkiaJk/IsZAEZFgNDT00xITAfBgoJkiaJ k/IsZAEZFhFsdWNlcm5lcHVibGlzaGluZzEVMBMGA1UEAwwMU29tZSBTdWJqZWN0 MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBGa+PnrhnOFO5+76c5zX5/+xh Kb2hUYl/pRuIKzYcqrmkvqjpPK/McusibT1h70emUkED0TSZsAlSivdIFK6WSxn6 HsTCaGIHhyOSKAvzQkBsZ74BPEydGT5LiX0+MOTyxwFAHhb+bqfbkdkXqUSkJAHK Z6p+fgX8uaJkKjL/kwIDAQABo0YwRDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNV HQ4EFgQUzIFwoRTY6KjUiqmjWjSjlkxbNncwDgYDVR0PAQH/BAQDAgeAMA0GCSqG SIb3DQEBBQUAA4GBACizodCpl/cF3OGLUx8HVag0yhr1e1P8+CLPc31FmCPAY1CO T0yxyJPoafkbXKRjclevNJdvxE3ys9fyYigFUhgswh3oWmjanDaatPKa0kE4147k SQHvN8JP20KeDDCJBk/FbS3xCn3jTix90ddzTa1uFoqBbBNbKOaDHIrqypTY -----END CERTIFICATE----- [↓] [vPodans]

Система приклеит этот сертификат к шаблону сертификата и переложит его уже в контейнер Personal:

$Request.InstallResponse(0x2,$endCert,0x0,"")

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

  • Генерировать тестовый самоподписанный сертификат для подписи скриптов PowerShell
  • Устанавливать сертификат с закрытым ключом в контейнер Personal
  • Устанавливать открытую часть сертификата в Trusted Root CAs для обеспечения доверия этому сертификату
  • Устанавливать открытую часть сертификата в Trusted Publishers для задания явного доверия цифровым подписям, сделанные этим сертификатом.
#####################################################################
# Create PowerShell cert.ps1
# Version 1.0
#
# Creates self-signed signing certificate and install it to certificate store
#
# Note: Requires at least Windows Vista. Windows XP/Windows Server 2003
# are not supported.
#
# Vadims Podans (c) 2010
# http://www.sysadmins.lv/
#####################################################################
#requires -Version 2.0

function New-SigningCert {
<#
.Synopsis
    Creates self-signed signing certificate and install it to certificate store
.Description
    This function generates self-signed certificate with some pre-defined and
    user-definable settings. User may elect to perform complete certificate
    installation, by installing generated certificate to Trusted Root Certification
    Authorities and Trusted Publishers containers in *current user* store.
    
.Parameter Subject
    Specifies subject for certificate. This parameter must be entered in X500
    Distinguished Name format. Default is: CN=PowerShell User, OU=Test Signing Cert.

.Parameter KeyLength
    Specifies private key length. Due of performance and security reasons, only
    1024 and 2048 bit are supported. by default 1024 bit key length is used.

.Parameter NotBefore
    Sets the date in local time on which a certificate becomes valid. By default
    current date and time is used.

.Parameter NotAfter
    Sets the date in local time after which a certificate is no longer valid. By
    default certificate is valid for 365 days.

.Parameter Force
    If Force switch is asserted, script will prepare certificate for use by adding
    it to Trusted Root Certification Authorities and Trusted Publishers containers
    in current user certificate store. During certificate installation you will be
    prompted to confirm if you want to add self-signed certificate to Trusted Root
    Certification Authorities container.
#>
[CmdletBinding()]
    param (
        [string]$Subject = "CN=PowerShell User, OU=Test Signing Cert",
        [int][ValidateSet("1024", "2048")]$KeyLength = 1024,
        [datetime]$NotBefore = [DateTime]::Now,
        [datetime]$NotAfter = $NotBefore.AddDays(365),
        [switch]$Force
    )
    
    $OS = (Get-WmiObject Win32_OperatingSystem).Version
    if ($OS[0] -lt 6) {
        Write-Warning "Windows XP, Windows Server 2003 and Windows Server 2003 R2 are not supported!"
        return
    }
    # while all certificate fields MUST be encoded in ASN.1 DER format
    # we will use CryptoAPI COM interfaces to generate and encode all necessary
    # extensions.
    
    # create Subject field in X.500 format using the following interface:
    # http://msdn.microsoft.com/en-us/library/aa377051(VS.85).aspx
    $SubjectDN = New-Object -ComObject X509Enrollment.CX500DistinguishedName
    $SubjectDN.Encode($Subject, 0x0)
    
    # define CodeSigning enhanced key usage (actual OID = 1.3.6.1.5.5.7.3.3) from OID
    # http://msdn.microsoft.com/en-us/library/aa376784(VS.85).aspx
    $OID = New-Object -ComObject X509Enrollment.CObjectID
    $OID.InitializeFromValue("1.3.6.1.5.5.7.3.3")
    # while IX509ExtensionEnhancedKeyUsage accept only IObjectID collection
    # (to support multiple EKUs) we need to create IObjectIDs object and add our
    # IObjectID object to the collection:
    # http://msdn.microsoft.com/en-us/library/aa376785(VS.85).aspx
    $OIDs = New-Object -ComObject X509Enrollment.CObjectIDs
    $OIDs.Add($OID)
    
    # now we create Enhanced Key Usage extension, add our OID and encode extension value
    # http://msdn.microsoft.com/en-us/library/aa378132(VS.85).aspx
    $EKU = New-Object -ComObject X509Enrollment.CX509ExtensionEnhancedKeyUsage
    $EKU.InitializeEncode($OIDs)
    
    # generate Private key as follows:
    # http://msdn.microsoft.com/en-us/library/aa378921(VS.85).aspx
    $PrivateKey = New-Object -ComObject X509Enrollment.CX509PrivateKey
    $PrivateKey.ProviderName = "Microsoft Base Cryptographic Provider v1.0"
    # private key is supposed for signature: http://msdn.microsoft.com/en-us/library/aa379409(VS.85).aspx
    $PrivateKey.KeySpec = 0x2
    $PrivateKey.Length = $KeyLength
    # key will be stored in current user certificate store
    $PrivateKey.MachineContext = 0x0
    $PrivateKey.Create()
    
    # now we need to create certificate request template using the following interface:
    # http://msdn.microsoft.com/en-us/library/aa377124(VS.85).aspx
    $Cert = New-Object -ComObject X509Enrollment.CX509CertificateRequestCertificate
    $Cert.InitializeFromPrivateKey(0x1,$PrivateKey,"")
    $Cert.Subject = $SubjectDN
    $Cert.Issuer = $Cert.Subject
    $Cert.NotBefore = $NotBefore
    $Cert.NotAfter = $NotAfter
    $Cert.X509Extensions.Add($EKU)
    # completing certificate request template building
    $Cert.Encode()
    
    # now we need to process request and build end certificate using the following
    # interface: http://msdn.microsoft.com/en-us/library/aa377809(VS.85).aspx
    
    $Request = New-Object -ComObject X509Enrollment.CX509enrollment
    # process request
    $Request.InitializeFromRequest($Cert)
    # retrievecertificate encoded in Base64.
    $endCert = $Request.CreateRequest(0x1)
    # install certificate to user store
    $Request.InstallResponse(0x2,$endCert,0x1,"")
    
    if ($Force) {
        # convert Bas64 string to a byte array
         [Byte[]]$bytes = [System.Convert]::FromBase64String($endCert)
        foreach ($Container in "Root", "TrustedPublisher") {
            # open Trusted Root CAs and TrustedPublishers containers and add
            # certificate
            $x509store = New-Object Security.Cryptography.X509Certificates.X509Store $Container, "CurrentUser"
            $x509store.Open([Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
            $x509store.Add([Security.Cryptography.X509Certificates.X509Certificate2]$bytes)
            # close store when operation is completed
            $x509store.Close()
        }
    }
}

С виду кажется сложно, но на самом деле тут ничего сложного нет совсем. Просто представьте себе сертификат как большую матрёшку, в которую вы вкладываете другие маленькие матрёшки, которые представляют собой поля и расширения сертификатов. Начинаете собирать самые маленькие матрёшки, вкладываете в более большие и в конечном итоге собираете настоящий сертификат. Хоть документация на MSDN не очень полная, используя командлет Get-Member вы можете восполнить этот пробел.

Sunday, April 18, 2010 12:46:11 PM (FLE Daylight Time, UTC+03:00)   Comments [0]    

 

В продолжении темы исследования COM интерфейсов CryptoAPI для управления службами Certificate Services предлагаю теперь разобрать вопросы KRA — Key Recovery Agents. Сначала напомню уже изученные темы по CryptoAPI:

И на сегодня наш план выглядит достаточно интересно:

  • Получение списка доступных KRA в лесу и просмотр их сертификатов
  • Получение списка KRA назначенных на сервере CA;
  • Назначение новых KRA для CA.

Получение списка доступных KRA в лесу

KRA используется для архивирования закрытых ключей сертификатов в БД CA. В случае если пользователь уничтожил свой закрытый ключ он может его восстановить. Для этого нужно обратиться к администратору CA для извлечения BLOB (Binary Large OBject) в файл. После чего агент восстановления (KRA) использует свой закрытый ключ для расшифровки закрытого ключа из BLOB'а. Учитывая, что я и не только уже рассматривали этот вопрос, я не буду пересказывать принцип работы Key Archival:

Все сертификаты KRA публикуются в forest naming configuration context партиции AD и реплицируется между всеми контроллерами в лесу. Публикация просисходит в точке:

CN=KRA, CN=Public Key Services, CN=Services, CN=Configuration, DC=ForestRootDomain

PS C:\> $ldap = [ADSI]"LDAP://CN=KRA, CN=Public Key Services, CN=Services, CN=Configuration, DC=contoso,dc=com" PS C:\> $ldap distinguishedName ----------------- {CN=KRA,CN=Public Key Services,CN=Services,CN=Configuration,DC=contoso,DC=com} PS C:\> $kra = $ldap.psbase.children | %{$_} PS C:\> $kra distinguishedName ----------------- {CN=contoso-DC2-CA,CN=KRA,CN=Public Key Services,CN=Services,CN=Configuration,DC=contoso,DC=com} {CN=Contoso CA,CN=KRA,CN=Public Key Services,CN=Services,CN=Configuration,DC=contoso,DC=com}

Мы получили список CA в лесу и посмотрев каждый из них можем посмотреть сколько KRA сертификатов выпустил каждый CA:

PS C:\> $kra[1] | fl * objectClass : {top, msPKI-PrivateKeyRecoveryAgent} cn : {Contoso CA} userCertificate : {48 130 6 104 48 130 5 80 160 3 2 1 2 2 10 21 29 22 96 0 0 0 0 0 4 48 13 6 9 42 134 72 134 247 13 1 1 5 5 0 48 67 49 19 48 17 6 10 9 146 38 137 147 242 44 100 1 25 22 3 99 111 109 49 23 48 21 6 10 9 146 38 137 147 242 44 100 1 25 22 7 99 111 110 116 111 115 111 49 19 48 17 6 3 85 4 3 19 10 67 111 110 116 111 115 111 32 67 65 48 30 23 13 48 57 48 50 49 53 49 53 48 53 50 53 9 0 23 13 49 49 48 50 49 53 49 53 48 53 50 53 90 48 86 49 19 48 17 6 10 9 146 38 137 147 242 44 100 1 25 22 3 99 111 109 49 23 48 21 6 10 9 146 38 137 147 242 44 100 1 25 22 7 99 111 110 116 <...>

Свойство UserCertificate в виде массива может содержать сертификаты KRA в виде байтового массива. Например, указанный CA выпустил 2 сертификата KRA:

PS C:\> $kra[1].usercertificate.count 2

И обратившись к индексам этого свойства можно получить конкретные сертификаты: $kra[1].usercertificate[0]. Если CA не выпускал сертификаты для KRA, то свойство UserCertificate будет содержать число ноль:

PS C:\> $kra[0] | fl * objectClass : {top, msPKI-PrivateKeyRecoveryAgent} cn : {contoso-DC2-CA} userCertificate : {0} <...>

Поэтому для получения всех сертификатов KRA нужно пройтись по всем членам CN=KRA и выбрать из них ненулевые значения свойства UserCertificate. Если сертификатов больше, чем 1, то они будут храниться в этом свойстве в виде массива. Чтобы посмотреть сам сертификат, вы можете просто сохранить этот массив байтов в файл:

PS C:\> [System.IO.File]::WriteAllBytes("C:\kra.cer",$kra[1].usercertificate[1]) PS C:\> & .\kra.cer

Получение списка KRA назначенных на сервере CA

А теперь вернёмся к интерфейсу ICertAdmin2. С его помощью мы можем посмотреть количество агентов восстановления, которые назначены на CA и посмотреть их сертификаты. Однако, этот интерфейс имеет какой-то баг с PropID, которые связаны с KRA, о которых я писал в предыдущей статье, поэтому для получения этих данных мы будем использовать интерфейс ICertRequest3.

PS C:\> $CertRequest = New-Object -ComObject CertificateAuthority.Request.1 PS C:\> $CertRequest.GetCAProperty("dc1\contoso ca",0x18,0,1,0) 1 PS C:\> $CertRequest.GetCAProperty("dc2\contoso-dc2-ca",0x18,0,1,0) 0 PS C:\> $CertRequest.GetCAProperty("dc1\contoso ca",0x19,0,1,0) 1 PS C:\> $CertRequest.GetCAProperty("dc2\contoso-dc2-ca",0x19,0,1,0) 0

Мы видим, что на сервере DC1 назначен 1 агент восстановления, а на DC2 ни одного. Сегодня мы добавим агентов восстановления на DC2.

Примечание: чем отличаются KRACERTUSEDCOUNT и KRACERTCOUNT? KRACERTCOUNT показывает сколько всего KRA назначено на конкретном сервере CA. А KRACERTUSEDCOUNT показывает сколько из них будет использовать CA при архивации каждого закрытого ключа сертификата и это число может быть меньше, чем общее количество сертификатов KRA. В таком случае CA рандомно выбирает KRACERTUSEDCOUNT сертификатов и шифрует ими закрытый ключ. Например, KRACERTCOUNT = 5, а KRACERTUSEDCOUNT = 3, то CA при архивации ключа выберет рандомно 3 сертификата KRA из списка и зашифрует ими ключ. И только эти 3 агента восстановления могут восстановить ключ, а остальные 2 уже не смогут.

Получение списка KRA назначенных на сервере CA

Чтобы посмотреть сами сертификаты KRA, мы должны воспользоваться PropID = CR_PROP_KRACERT (0x1A).

И вот что мы увидим:

PS C:\> $CertAdmin.GetCAProperty("dc1\contoso ca",0x1A,0,3,0) -----BEGIN CERTIFICATE----- MIIGaDCCBVCgAwIBAgIKFR0WYAAAAAAABDANBgkqhkiG9w0BAQUFADBDMRMwEQYK CZImiZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHY29udG9zbzETMBEGA1UE AxMKQ29udG9zbyBDQTAeFw0wOTAyMTUxNTA1MjVaFw0xMTAyMTUxNTA1MjVaMFYx EzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdjb250b3NvMQ4w DAYDVQQDEwVVc2VyczEWMBQGA1UEAxMNQWRtaW5pc3RyYXRvcjCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBAJG7T5yMninhrkXFQU8WGUr5BYYUeOz10Rkn <...>

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

$CertAdmin.GetCAProperty("dc1\contoso ca",0x1A,$Index,3,0)

Этот сертификат можно спокойно записать в файл и через GUI просмотреть его свойства. Но вообще это не родной формат сертификата KRA для данного интерфейса, поскольку добавлять сертификаты KRA на сервере CA нужно в ASN1 DER-encoded string формате. Т.е. Flags должен быть не 0, а 2:

$CertAdmin.GetCAProperty("dc1\contoso ca",0x1A,$Index,3,2)

и увидим примерно такое:

PS C:\> $kra = $CertAdmin.GetCAProperty("dc1\contoso ca",0x1a,0,3,2) PS C:\> $kra ?????A???` ?????c?♣??????????????????T???????????????????????????????????????T???????????????????????????????????????? ??ca♣??????☺??????????????????????????????????????????????????????????????????????????????????????????????????????????? ??????????????????????☺????????Є?????????????????☻???????☻??????????????ЎЖ?????????????CA??????Ё????????????h??A??????? ??????????????????????????????????????????????╣???????????????????????????????????????????????????????????????????????? ?????????????????????C?cЁ??????C??????????????????????????????????????????????????????????????????????????????????????? ???????????????????????????????????????Х???CA?????Ё???????CA?????Б????CA??????????????????????a???☺???????????????????? ????????????????????????????????????c?????????????????????????????????????H╝??????????????????{?????????????

выглядит весьма уныло, но вот в таком формате и надо записывать сертификаты агентов восстановления в CA.

Назначение новых KRA для CA

Когда мы рассмотрели основные моменты связанные с агентами восстановления в контексте CryptoAPI, мы можем приступить к финальной части — назначению новых агентов восстановления. В принципе, здесь нет ничего сложного, поскольку для добавления KRA нужно сделать 5 вещи:

  1. Получить массив байтов, которые бы представляли сертификат агента восстановления откуда угодно. Хоть из AD, хоть из файла сертификата;
  2. Используя специальный конвертер, разобрать этот массив на пары байтов, сконвертировать эти пары в little-endian hex строку, получить десятичное представление этой hex-строки и получить итоговую символьную строку;
  3. Записать эту строку используя метод SetCAProperty();
  4. Задать новое количество используемых агентов восстановления;
  5. Перезапустить службу CA.

Первый этап у нас уже пройден, поскольку у нас уже есть такой массив в AD. Если сертификата агента восстановления нет в AD, то можно его получить из файла:

$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import(".\desktop\kra.cer")
$bytes = $cert.RawData

и переменная $bytes будет содержать такой же байтовый массив.

А вот с конвертером чуть сложнее. Я не нашёл ни одного готового конвертера, который бы выполнял все шаги из пункта 2, поэтому я написал свой конвертер с блек-джеком и шлюхами:

function ConvertTo-DERstring ([byte[]]$bytes) {
    # создаём объект StringBuilder, который нам будет собирать конечную символьную строку
    $SB = New-Object System.Text.StringBuilder
    # преобразовываем каждый байт в его hex значение
    $bytes1 = $bytes | %{"{0:X2}" -f $_}
    # циклом перебираем каждую пару байт
    for ($n = 0; $n -lt $bytes1.count; $n = $n + 2) {
        # переставляем элементы пары местами, чтобы получить little-endian hex-строку
        # после чего получаем десятичное представление этой строки и получаем Unicode
        # символ, который соответствует этому десятичному значению.
        [void]$SB.Append([char](Invoke-Expression 0x$(($bytes1[$n+1]) + ($bytes1[$n]))))
    }
    $SB.ToString()
}

А дальше уже по накатанной дороге:

[Administrator] # создаём COM объект ICertAdmin2 [Administrator] $CertAdmin = new-object -com certificateauthority.admin.1 [Administrator] # выбираем все Enterprise CA [Administrator] $ldap = [ADSI]"LDAP://CN=KRA, CN=Public Key Services, CN=Services, CN=Configuration, DC=contoso,dc=com" [Administrator] $kra = $ldap.psbase.children | %{$_} [Administrator] # сразу конвертируем все сертификаты в DER-encoded string [Administrator] $kra1 = ConvertTo-DERstring $kra[1].usercertificate[0] [Administrator] $kra2 = ConvertTo-DERstring $kra[1].usercertificate[1] [Administrator] # можем убедиться на месте, что строка имеет ожидаемый вид [Administrator] $kra2 ?????A???E ?????c?♣??????????????????T???????????????????????????????????????T???????????????????????????????????????? ??ca♣??????☺????????????????????????????K?????????????????????????????????????????????т???????????????????????????????? ??????????????????????☺????????Є?????????????????☻???????☻??????????????ЎЖ?????????????CA??????Ё????????????h??A??????? ??????????????????????????????????????????????╣???????????????????????????????????????????????????????????????????????? ?????????????????????C?cЁ??????C??????????????????????????????????????????????????????????????????????????????????????? ???????????????????????????????????????Х???CA?????Ё???????CA?????Б????CA??????????????????????a???☺???????????????????? ???????????????????????????????????????????????????????????????????????????????????????????????????????????? [Administrator] # и записываем первую строку сертификат агента восстанвовления в нулевой индекс [Administrator] $CertAdmin.SetCAProperty("dc2\contoso-dc2-ca",0x1a,0,3,$kra1) [Administrator] # все последующие строки сертификатов записываются в последующие индексы массива [Administrator] $CertAdmin.SetCAProperty("dc2\contoso-dc2-ca",0x1a,1,3,$kra2) [Administrator] # используем PropID = KRACERTUSEDCOUNT (0x18), чтобы сказать сколько у нас будет использоваться агентов [Administrator] # я буду их использовать все [Administrator] $CertAdmin.SetCAProperty("dc2\contoso-dc2-ca",0x18,0,1,2) [Administrator] # перезапускаем службу CertSvc [Administrator] $wmi = gwmi win32_service -comp dc2 -filter "name='certsvc'" [Administrator] [void]$wmi.StopService() [Administrator] [void]$wmi.StartService() [Administrator] # и проверяем нашу работу: [Administrator] $CertReq = new-object -com certificateauthority.request.1 [Administrator] $CertReq.GetCAProperty("dc2\contoso-dc2-ca",0x18,0,1,0) 2 [Administrator] $CertReq.GetCAProperty("dc2\contoso-dc2-ca",0x19,0,1,0) 2 [Administrator]

всё.

Friday, November 20, 2009 6:40:36 PM (FLE Standard Time, UTC+02:00)   Comments [0]    

 

Update 05.12.2009: разработчики подтвердили баг в ICertAdmin2 интерфейсе. Проблема заключается в том, что после первого запроса указанных PropID они кешируются и при смене контекста конфигурации (Certification Authority), этот кеш не очищается.


Прежде чем продолжать исследование CryptoAPI COM интерфейсов, предлагаю посмотреть несколько полезных ссылок и поговорить о багах в интерфейсе ICertAdmin2.

При использовании интерфейса ICertAdmin2, ICertRequest2D (этот интерфейс мы ещё посмотрим в будущих постах) мы используем различные аргументы для получения свойст CA с использованием метода GetCAProerty(). И вот их где мы можем получить числовые значения этих аргументов:

А теперь поговорим о багах. Как выяснилось, ICertAdmin2::GetCAProperty(), который определён в библиотеке certadm.dll имеет баги с PropID, которые связаны с количественной информацией. Будь то количество сертификатов CA, количество агентов восстановления ключей (Key Recovery Agents) и т.д. И вот в чём это выражается:

  • DC1 — корневой CA с именем Contoso CA. Имеет 2 сертификата CA и одного назначенного агента восстановления;
  • DC2 — подчинённый CA с именем Contoso-DC2-CA. Имеет 1 сертификат CA и ни одного назначенного агента восстановления.

Мы будем получать данные как с локального CA, так и с удалённого CA запуская код с каждого сервера. Кратко об используемых PropID:

  • 0x0B — количество сертификатов CA;
  • 0x19 — общее количество агентов восстановления на CA;
  • 0x18 — общее количество используемых агентов восстановления;

И вот какие результаты мы видим, когда собираем эти свойства с сервера DC1:

[Administrator] # создаём COM объек: [Administrator] $CertAdmin = New-Object -com CertificateAuthority.Admin.1 [Administrator] # получаем список сертификатов CA на локальном серве [Administrator] # мы видим 2 сертификата, как положено [Administrator] $certadmin.GetCAProperty("dc1\contoso ca",0xb,0,1,0) 2 [Administrator] # и он то же самое показывает и для удалённого сервера, хотя там всего 1 сертификат [Administrator] $certadmin.GetCAProperty("dc2\contoso-dc2-ca",0xb,0,1,0) 2 [Administrator] # ладно, смотрим, сколько всего KRA у нас на локальном сервере. Их 1, как на самом деле [Administrator] $certadmin.GetCAProperty("dc1\contoso ca",0x19,0,1,0) 1 [Administrator] # используемых сертификатов тоже 1. [Administrator] $certadmin.GetCAProperty("dc1\contoso ca",0x18,0,1,0) 1 [Administrator] # как я уже говорил, на DC2 нет агентов восстановления, хотя метод показывает их по одному в обоих случаях [Administrator] $certadmin.GetCAProperty("dc2\contoso-dc2-ca",0x19,0,1,0) 1 [Administrator] $certadmin.GetCAProperty("dc2\contoso-dc2-ca",0x18,0,1,0) 1 [Administrator]

собираем ту же информацию, запуская код с сервера DC2:

PS C:\> # создаём COM объект: PS C:\> $CertAdmin = New-Object -com CertificateAuthority.Admin.1 PS C:\> # получаем список сертификатов CA с локального сервера. PS C:\> # их мы видим ровно 1, как и должно быть PS C:\> $certadmin.GetCAProperty("dc2\contoso-dc2-ca",0xb,0,1,0) 1 PS C:\> # в предыдущем примере мы видели, что на DC1 2 сертификата CA. PS C:\> # проверим, так ли это? PS C:\> $certadmin.GetCAProperty("dc1\contoso ca",0xb,0,1,0) 1 PS C:\> # wow! обломс, метод возвращает тоже 1. А сколько у нас агентов восстановления? PS C:\> # и снова облом. На сервере DC1 нет агентов восстановления (хотя предыдущий пример говорит об обратном): PS C:\> $certadmin.GetCAProperty("dc1\contoso ca",0x19,0,1,0) 0 PS C:\> $certadmin.GetCAProperty("dc1\contoso ca",0x18,0,1,0) 0

а теперь посмотрим на результаты с использованием того же метода и тех же PropID, но с использованием интерфейса ICertRequest, который определён в библиотеке certcli.dll. Те же тесты уже возвращают правильные результаты:

с DC1:

[Administrator] $request = New-Object -com CertificateAuthority.Request.1 [Administrator] $request.GetCAProperty("dc1\contoso ca",0xb,0,1,0) 2 [Administrator] $request.GetCAProperty("dc2\contoso-dc2-ca",0xb,0,1,0) 1 [Administrator] $request.GetCAProperty("dc1\contoso ca",0x19,0,1,0) 1 [Administrator] $request.GetCAProperty("dc1\contoso ca",0x18,0,1,0) 1 [Administrator] $request.GetCAProperty("dc2\contoso-dc2-ca",0x19,0,1,0) 0 [Administrator] $request.GetCAProperty("dc2\contoso-dc2-ca",0x18,0,1,0) 0 [Administrator]

с DC2:

PS C:\> $request = New-Object -com CertificateAuthority.Request.1 PS C:\> $request.GetCAProperty("dc2\contoso-dc2-ca",0xb,0,1,0) 1 PS C:\> $request.GetCAProperty("dc1\contoso ca",0xb,0,1,0) 2 PS C:\> $request.GetCAProperty("dc1\contoso ca",0x19,0,1,0) 1 PS C:\> $request.GetCAProperty("dc1\contoso ca",0x18,0,1,0) 1 PS C:\> $request.GetCAProperty("dc2\contoso-dc2-ca",0x19,0,1,0) 0 PS C:\> $request.GetCAProperty("dc2\contoso-dc2-ca",0x18,0,1,0) 0 PS C:\>

Поэтому следует с осторожностью относиться к выводу ICertAdmin2::GetCAProperty(), а ещё лучше — использовать этот метод, который реализован в интерфейсе ICertRequest::GetCAProperty(). Чем это вызвано я пока не знаю, но задал я задал вопрос нужным людям, поэтому я дам знать в случае ответа на вопрос.

Monday, November 16, 2009 6:04:48 PM (FLE Standard Time, UTC+02:00)   Comments [0]    

 

Page 1 of 2 in the PowerShellCryptoAPI category Next Page
 · 
All content © 2008 - 2010, Vadims Podāns
"Spaces" Theme provided by: Vadims Podāns
About


E-mail - Send mail to the author(s)
Live Messenger -
My former blog -
For english language visitors
Библиотека
Календарик
<September 2010>
SunMonTueWedThuFriSat
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789

Карта расположения посетителей
Favorites





Disclaimer
Вся информация на сайте предоставляется на условиях «как есть», без предоставления каких-либо гарантий и прав.

При использовании материалов c данного сайта ссылка на оригинальный источник обязательна.