Contents of this directory is archived and no longer updated.

Как вы знаете, .NET не имеет нативной поддержки для X.509 CRL и X.509 CTL (или STL — Security Trust List). Именно поэтому я в своё время написал свой прототип для объекта X509CRL2. Недавно мне потребовалось получить доступ к CTL из PowerShell.

Во-первых, что такое CTL? Это просто список данных (например, хешей сертификатов), который подписан доверенной стороной. Где они применяются? Например, в Microsoft Root Certificate Program, в которой участвуют различные коммерческие и государственные центры сертификации. Эти CA доверены в Windows по умолчанию. Сами сертификаты хранятся в нескольких местах:

  • Контейнер Third-Party Certification Authorities локального хранилища сертификатов;
  • Crypt32.dll (только в Windows Vista+);
  • Microsoft Update.

Список доверенных CA достаточно велик (более 300) и его состав контролируется через Certificate Trust List. Клиент Windows периодически скачивает с Windows Update этот самый CTL, в котором хранятся хеши всех доверенных корневых CA и сравнивает их с хешами сертификатов, установленных в Third-Party CAs. Все сертификаты, чьи хеши не представлены в CTL, выпиливаются из хранилища и к этим CA больше автоматического доверия нету. А если в списке есть хеш, но такого сертификата ВНЕЗАПНО нету, тогда клиент ещё раз обращается к Windows Update и скачивает оттуда сертификаты. Следует понимать, что этот CTL *не содержит* сами сертификаты, только их хеши и атрибуты (например, Friendly Name). Как я уже говорил, CTL защищается от подделок цифровой подписью.

И вот, мне приспичило получить вменяемый доступ к этому CTL в PowerShell. Задача оказалась достаточно лёгкой: CertCreateCTLContext. Всё очень похоже на работу с предыдущим скриптом X509CRL:

function Get-CTL {
[CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [Byte[]]$RawData
    )
Add-Type @"
using System.Security.Cryptography;

namespace System.Security.Cryptography.X509Certificates {
    public class X509CTL {
        public int Version;
        public OidCollection SubjectUsage;
        public String SequenceNumber;
        public DateTime ThisUpdate;
        public Oid SubjectAlgorithm;
        public Object[] Entries;
        public X509ExtensionCollection Extensions;
        public IntPtr Handle;
        public Byte[] RawData;
    }
    public class X509CTLEntry {
        public String Thumbprint;
        public X509Attribute[] Attributes;
        public IntPtr Certificate;
    }
    public class X509Attribute {
        public Oid OID;
        public Byte[] RawData;
    }
}
"@
$cryptsignature = @"
[DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CertCreateCTLContext(
    UInt32 dwMsgAndCertEncodingType,
    Byte[] pbCtlEncoded,
    UInt32 cbCtlEncoded
);
[DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern Boolean CertFreeCTLContext(
    IntPtr pCtlContext
);
[DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CertFindExtension(
    [MarshalAs(UnmanagedType.LPStr)]
    String pszObjId,
    UInt32 cExtensions,
    IntPtr rgExtensions
);
"@
$wincryptsignature = @"
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct CTL_CONTEXT {
    public UInt32 dwMsgAndCertEncodingType;
    public IntPtr pbCtlEncoded;
    public UInt32 cbCtlEncoded;
    public IntPtr pCtlInfo;
    public IntPtr hCertStore;
    public IntPtr hCryptMsg;
    public IntPtr pbCtlContent;
    public UInt32 cbCtlContent;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct CTL_INFO {
    public UInt32 dwVersion;
    public CTL_USAGE SubjectUsage;
    public CRYPTOAPI_BLOB ListIdentifier;
    public CRYPTOAPI_BLOB SequenceNumber;
    public UInt64 ThisUpdate;
    public UInt64 NextUpdate;
    public CRYPT_ALGORITHM_IDENTIFIER SubjectAlgorithm;
    public UInt32 cCTLEntry;
    public IntPtr rgCTLEntry;
    public UInt32 cExtension;
    public IntPtr rgExtension;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct CTL_USAGE {
    public UInt32 cUsageIdentifier;
    public IntPtr rgpszUseageIdentifier;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct CTL_ENTRY {
    public CRYPTOAPI_BLOB SubjectIdentifier;
    public UInt32 cAttribute;
    public IntPtr rgAttribute;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct CRYPT_ATTRIBUTE {
    [MarshalAs(UnmanagedType.LPStr)]
    public String pszObjId;
    public UInt32 cValue;
    public IntPtr rgValue;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct CRYPT_ALGORITHM_IDENTIFIER {
    [MarshalAs(UnmanagedType.LPStr)]
    public String pszObjId;
    public CRYPTOAPI_BLOB Parameters;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct CRYPTOAPI_BLOB {
    public UInt32 cbData;
    public IntPtr pbData;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct CERT_EXTENSION {
    [MarshalAs(UnmanagedType.LPStr)]
    public String pszObjId;
    public Boolean fCritical;
    public CRYPTOAPI_BLOB Value;
}
"@
    $WarningPreference = "SilentlyContinue"
    Add-Type -MemberDefinition $cryptsignature -Namespace PKI -Name Crypt32
    Add-Type -MemberDefinition $wincryptsignature -Namespace PKI.Structs -Name Wincrypt
    [IntPtr]$CTLContext = [IntPtr]::Zero
    $CTLContext = [PKI.Crypt32]::CertCreateCTLContext(65537,$RawData,$RawData.Length)
    if (!$CTLContext.Equals([IntPtr]::Zero)) {
        $CTLContextStructure = [Runtime.InteropServices.Marshal]::PtrToStructure($CTLContext,[PKI.Structs.Wincrypt+CTL_CONTEXT])
        $CTLInfo = [Runtime.InteropServices.Marshal]::PtrToStructure($CTLContextStructure.pCtlInfo,[PKI.Structs.Wincrypt+CTL_INFO])
        # CTL usage
        $rgpszUseageIdentifier = $CTLInfo.SubjectUsage.rgpszUseageIdentifier
        $oids = New-Object Security.Cryptography.OidCollection
        for ($m = 0; $m -lt $CTLInfo.SubjectUsage.cUsageIdentifier; $m++) {
            $pszOid = [Runtime.InteropServices.Marshal]::ReadIntPtr($rgpszUseageIdentifier)
            [void]$oids.Add([Runtime.InteropServices.Marshal]::PtrToStringAnsi($pszOid))
            $rgpszUseageIdentifier = [int]$rgpszUseageIdentifier + [Runtime.InteropServices.Marshal]::SizeOf([PKI.Structs.Wincrypt+CTL_USAGE])
        }
        $bytes = New-Object byte[] -ArgumentList $CTLInfo.SequenceNumber.cbData
        [Runtime.InteropServices.Marshal]::Copy($CTLInfo.SequenceNumber.pbData,$bytes,0,$CTLInfo.SequenceNumber.cbData)
        [array]::Reverse($bytes)
        $SequenceNumber = -join ($bytes | %{"{0:x2}" -f $_})
        $ctl = New-Object System.Security.Cryptography.X509Certificates.X509CTL -Property @{
            Version = $CTLInfo.dwVersion + 1;
            SubjectUsage = $oids;
            SequenceNumber = $SequenceNumber
            ThisUpdate = [datetime]::FromFileTime($CTLInfo.ThisUpdate);
            SubjectAlgorithm = $CTLInfo.SubjectAlgorithm.pszObjId;
            Handle = $CTLContext;
        }
        $entries = @()
        if ($CTLInfo.cCTLEntry -gt 0) {
            $rgCTLEntry = $CTLInfo.rgCTLEntry
            for ($n = 0; $n -lt $CTLInfo.cCTLEntry; $n++) {
                $entry = New-Object System.Security.Cryptography.X509Certificates.X509CTLEntry
                $EntryStructure = [Runtime.InteropServices.Marshal]::PtrToStructure($rgCTLEntry,[PKI.Structs.Wincrypt+CTL_ENTRY])
                $bytes = New-Object byte[] -ArgumentList ($EntryStructure.SubjectIdentifier.cbData)
                [System.Runtime.InteropServices.Marshal]::Copy($EntryStructure.SubjectIdentifier.pbData,$bytes,0,$EntryStructure.SubjectIdentifier.cbData)
                $entry.Thumbprint = -join ($bytes | %{"{0:X2}" -f $_})
                $attribs = @()
                if ($EntryStructure.cAttribute -gt 0) {
                    $rgAttribute = $EntryStructure.rgAttribute
                    for ($m = 0; $m -lt $EntryStructure.cAttribute; $m++) {
                        $attrib = New-Object System.Security.Cryptography.X509Certificates.X509Attribute
                        $AttribStructure = [Runtime.InteropServices.Marshal]::PtrToStructure($rgAttribute,[PKI.Structs.Wincrypt+CRYPT_ATTRIBUTE])
                        $attrib.OID = $AttribStructure.pszObjId
                        #if ($AttribStructure.cValue -gt 1) {Write-Host more}
                        $blob = [Runtime.InteropServices.Marshal]::PtrToStructure($AttribStructure.rgValue, [PKI.Structs.Wincrypt+CRYPTOAPI_BLOB])
                        $bytes = New-Object byte[] -ArgumentList $blob.cbData
                        [Runtime.InteropServices.Marshal]::Copy($blob.pbData,$bytes,0,$blob.cbData)
                        $attrib.RawData = $bytes
                        $attribs += $attrib
                        $rgAttribute = [int]$rgAttribute + [Runtime.InteropServices.Marshal]::SizeOf([PKI.Structs.Wincrypt+CRYPT_ATTRIBUTE])
                    }
                }
                $entry.Attributes = $attribs
                
                $entries += $entry
                $rgCTLEntry = [int]$rgCTLEntry + [Runtime.InteropServices.Marshal]::SizeOf([PKI.Structs.Wincrypt+CTL_ENTRY])
            }
        }
        $rgExtension = $CTLInfo.rgExtension
        if ($CTLInfo.cExtension -ge 1) {
            $Exts = New-Object Security.Cryptography.X509Certificates.X509ExtensionCollection
            for ($n = 0; $n -lt $CTLInfo.cExtension; $n++) {
                $ExtEntry = [Runtime.InteropServices.Marshal]::PtrToStructure($rgExtension,[PKI.CRL+CERT_EXTENSION])
                [IntPtr]$rgExtension = [PKI.CRL]::CertFindExtension($ExtEntry.pszObjId,$CTLInfo.cExtension,$CTLInfo.rgExtension)
                $pByte = $ExtEntry.Value.pbData
                $bBytes = $null
                for ($m = 0; $m -lt $ExtEntry.Value.cbData; $m++) {
                    [byte[]]$bBytes += [Runtime.InteropServices.Marshal]::ReadByte($pByte)
                    $pByte = [int]$pByte + [Runtime.InteropServices.Marshal]::SizeOf([byte])
                }
                $ext = New-Object Security.Cryptography.X509Certificates.X509Extension $ExtEntry.pszObjId, @([Byte[]]$bBytes), $ExtEntry.fCritical
                [void]$Exts.Add($ext)
                $rgExtension = [int]$rgExtension + [Runtime.InteropServices.Marshal]::SizeOf([PKI.CRL+CERT_EXTENSION])
            }
            $CTL.Extensions = $Exts
        }
        $ctl.Entries = $entries
        $ctl
        [void][PKI.Crypt32]::CertFreeCTLContext($CTLContext)
    } else {Write-Error -Category InvalidData -ErrorId InvalidDataException -Message "The data is not valid CTL."}
}

И вот, как примерно выглядит вывод:

[↓] [vPodans] $raw = [io.file]::ReadAllBytes(".\Desktop\authroot.stl")
[↓] [vPodans] $ctl = Get-CTL $raw
[↓] [vPodans] $ctl


Version          : 1
SubjectUsage     : {Root List Signer}
SequenceNumber   : 1401ccf1d2032b8314
ThisUpdate       : 23.02.2012 4:22:38
SubjectAlgorithm : System.Security.Cryptography.Oid
Entries          : {System.Security.Cryptography.X509Certificates.X509CTLEntry, System.Security.Cryptography.X509Certif
                   icates.X509CTLEntry, System.Security.Cryptography.X509Certificates.X509CTLEntry, System.Security.Cry
                   ptography.X509Certificates.X509CTLEntry...}
Extensions       :
Handle           : 40415264
RawData          :



[↓] [vPodans]

В свойстве Entries находится массив элементов, каждый из которых представляет собой хеш и набор атрибутов конкретного сертификата:

[↓] [vPodans] $ctl.Entries.Length
347
[↓] [vPodans] $ctl.Entries[0]

Thumbprint                              Attributes                                                          Certificate
----------                              ----------                                                          -----------
CDD4EEAE6000AC7F40C3802C171E30148030... {System.Security.Cryptography.Oid, S...                                       0


[↓] [vPodans] $ctl.Entries[0].Attributes

OID                                                         RawData
---                                                         -------
System.Security.Cryptography.Oid                            {4, 14, 48, 12...}
System.Security.Cryptography.Oid                            {4, 16, 240, 196...}
System.Security.Cryptography.Oid                            {4, 20, 14, 172...}
System.Security.Cryptography.Oid                            {4, 32, 136, 93...}
System.Security.Cryptography.Oid                            {4, 74, 77, 0...}


[↓] [vPodans]

В X509CTLEntry я добвил свойство Certificate, которое в идеале (сейчас там всегда нули) должно содержать ссылку на сертификат (хэндл). Но я пока не придумал, как это лучше сделать. Дело в том, что в Windows Vista и выше, в контейнере Third-Party CAs установлено всего несколько сертификатов. Остальные доставляются в хранилище из Windows Update или crypt32.dll по требованию. Дополнительно, здесь есть ещё 2 проблемы, которые следует решить:

1) В структуре CTL_CONTEXT у нас есть свойство hCertStore, которое содержит хэндл на хранилище сертификатов (враппер — X509Store). Но что-то сертификатов я там не нашёл.

2) Можно распотрошить ресурсы crypt32.dll (сертификаты там хранятся в виде сериализированного хранилища) и достать оттуда список всех доверенных сертификатов (хотя, он может быть не совсем актуальным). Беда в том, что если надёргать хэндлы из этих сертификатов, они сразу станут недействительными, как только закроете serialized store. Т.е. скорее всего для каждого сертификата придётся дуплицировать хэндлы и как-то их потом очищать.

Вобщем, тут есть над чем подумать. Нарпример, можно доставить форматтер, который бы отформатировал атрибуты сертификатов во что-то читабельное (сейчас они хранятся только в виде массива байтов). Можно попробовать прикрутить CryptFormatObject. Но это уже частности, потому что это всё-таки, только прототип для CTL в PowerShell.


Share this article:

Comments:

Comments are closed.