Как вы знаете, .NET не имеет нативной поддержки для X.509 CRL и X.509 CTL (или STL — Security Trust List). Именно поэтому я в своё время написал свой прототип для объекта X509CRL2. Недавно мне потребовалось получить доступ к CTL из PowerShell.
Во-первых, что такое CTL? Это просто список данных (например, хешей сертификатов), который подписан доверенной стороной. Где они применяются? Например, в Microsoft Root Certificate Program, в которой участвуют различные коммерческие и государственные центры сертификации. Эти CA доверены в Windows по умолчанию. Сами сертификаты хранятся в нескольких местах:
Список доверенных 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.
Comments: