Давным давно я мечтал об удобном (ну или не очень, но чтобы стандартном) способе работы с CRL'ами в PowerShell. Ленивые разрабы .NET'а как не чесались, так и до сих пор не чешутся по этому поводу, хотя, задача весьма востребованная. Я несколько раз пытался подойти к решению этого вопроса самостоятельно. Но незнание чего-то не позволяло этого достичь. Но не так давно в качестве практического упражнения в ASN.1 я написал вот такой страшный парсер: Basic CRL parser for PowerShell. Это даже не концепт, а просто отработка навыков работы с ASN.1. Но в виду его неуниверсальности, розовомедленности его даже стыдно запускать и показывать как он работает. Неделю назад я снова вспомнил об этой теме и решил подойти к вопросу более основательно и написал код с использованием p/invoke неуправляемого кода и с блек-джеком и шлюхами, который используется стандартыми Windows инструментами для работы с CRL объектами. Я не буду рассказывать об истории его создания и как он там внутри работает, потому что это лишено всякого смысла. Взамен я предлагаю рабочий код, который вы можете использовать в собственных целях:
##################################################################### # Get-CRL.ps1 # Version 1.0 # # Retrieves CRL object from a file or a DER-encoded byte array. # # Vadims Podans (c) 2011 # http://www.sysadmins.lv/ ##################################################################### #requires -Version 2.0 function Get-CRL { <# .Synopsis Retrieves CRL object from a file or a DER-encoded byte array. .Description Retrieves CRL object from a file or a DER-encoded byte array. .Parameter Path Specifies the path to a file. .Parameter RawCRL Specifies a pointer to a DER-encoded CRL byte array. .Example Get-CRL C:\Custom.crl Returns X509CRL2 object from a specified file .Example $Raw = [IO.FILE]::ReadAllBytes("C:\Custom.crl") Get-CRL -RawCRL $Raw Returns X509CRL2 object from a DER-encoded byte array. .Outputs System.Security.Cryptography.X509Certificates.X509CRL2 .NOTES Author: Vadims Podans Blog : http://en-us.sysadmins.lv #> [OutputType('System.Security.Cryptography.X509Certificates.X509CRL2')] [CmdletBinding(DefaultParameterSetName='FileName')] param( [Parameter(ParameterSetName = "FileName", Mandatory = $true, Position = 0, ValueFromPipeline = $true)] [string]$Path, [Parameter(ParameterSetName = "RawData", Mandatory = $true, Position = 0)] [Byte[]]$RawCRL ) #region content parser switch ($PsCmdlet.ParameterSetName) { "FileName" { if ($(Get-Item $Path -ErrorAction Stop).PSProvider.Name -ne "FileSystem") { throw {"File either does not exist or not a file object"} } if ($(Get-Item $Path -ErrorAction Stop).Extension -ne ".crl") { throw {"File is not valid CRL file"} } $Content = Get-Content $Path if ($Content[0] -eq "-----BEGIN X509 CRL-----") { [Byte[]]$cBytes = [Convert]::FromBase64String($(-join $Content[1..($Content.Count - 2)])) } elseif ($Content[0][0] -eq "M") { [Byte[]]$cBytes = [Convert]::FromBase64String($(-join $Content)) } else { [Byte[]]$cBytes = [IO.File]::ReadAllBytes($Path) } } "RawData" {[Byte[]]$cBytes = $RawCRL} } #endregion $signature = @" [DllImport("CRYPT32.DLL", CharSet = CharSet.Auto, SetLastError = true)] public static extern int CertCreateCRLContext( int dwCertEncodingType, byte[] pbCrlEncoded, int cbCrlEncoded ); [DllImport("CRYPT32.DLL", SetLastError = true)] public static extern Boolean CertFreeCRLContext( IntPtr pCrlContext ); [DllImport("CRYPT32.DLL", CharSet = CharSet.Auto, SetLastError = true)] public static extern int CertNameToStr( int dwCertEncodingType, ref CRYPTOAPI_BLOB pName, int dwStrType, System.Text.StringBuilder psz, int csz ); [DllImport("CRYPT32.DLL", CharSet = CharSet.Auto, SetLastError = true)] public static extern IntPtr CertFindExtension( [MarshalAs(UnmanagedType.LPStr)]String pszObjId, int cExtensions, IntPtr rgExtensions ); [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct CRL_CONTEXT { public int dwCertEncodingType; public byte[] pbCrlEncoded; public int cbCrlEncoded; public IntPtr pCrlInfo; public IntPtr hCertStore; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct CRL_INFO { public int dwVersion; public CRYPT_ALGORITHM_IDENTIFIER SignatureAlgorithm; public CRYPTOAPI_BLOB Issuer; public Int64 ThisUpdate; public Int64 NextUpdate; public int cCRLEntry; public IntPtr rgCRLEntry; public int cExtension; public IntPtr rgExtension; } [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 int cbData; public IntPtr pbData; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct CRL_ENTRY { public CRYPTOAPI_BLOB SerialNumber; public Int64 RevocationDate; public int cExtension; public IntPtr rgExtension; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct CERT_EXTENSION { [MarshalAs(UnmanagedType.LPStr)]public String pszObjId; public Boolean fCritical; public CRYPTOAPI_BLOB Value; } "@ Add-Type @" using System; using System.Security; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; namespace System { namespace Security { namespace Cryptography { namespace X509Certificates { public class X509CRL2 { public int Version; public string Type; public X500DistinguishedName IssuerDN; public string Issuer; public DateTime ThisUpdate; public DateTime NextUpdate; public Oid SignatureAlgorithm; public X509ExtensionCollection Extensions; public X509CRLEntry[] RevokedCertificates; public byte[] RawData; } public class X509CRLEntry { public string SerialNumber; public DateTime RevocationDate; public int ReasonCode; public string ReasonMessage; } } } } } "@ try {Add-Type -MemberDefinition $signature -Namespace PKI -Name CRL} catch {throw "Unable to load required types"} #region Variables [IntPtr]$pvContext = [IntPtr]::Zero [IntPtr]$rgCRLEntry = [IntPtr]::Zero [IntPtr]$pByte = [IntPtr]::Zero [byte]$bByte = 0 [IntPtr]$rgExtension = [IntPtr]::Zero $ptr = [IntPtr]::Zero $Reasons = @{1="Key compromise";2="CA Compromise";3="Change of Affiliation";4="Superseded";5="Cease Of Operation"; 6="Hold Certificiate";7="Privilege Withdrawn";10="aA Compromise"} #endregion # retrive CRL context and CRL_CONTEXT structure $pvContext = [PKI.CRL]::CertCreateCRLContext(65537,$cBytes,$cBytes.Count) if ($pvContext.Equals([IntPtr]::Zero)) {throw "Unable to retrieve context"} $CRL = New-Object System.Security.Cryptography.X509Certificates.X509CRL2 # void first marshaling operation, because it throws unexpected exception try {$CRLContext = [Runtime.InteropServices.Marshal]::PtrToStructure([IntPtr]$pvContext,[PKI.CRL+CRL_CONTEXT])} catch {} $CRLContext = [Runtime.InteropServices.Marshal]::PtrToStructure([IntPtr]$pvContext,[PKI.CRL+CRL_CONTEXT]) $CRLInfo = [Runtime.InteropServices.Marshal]::PtrToStructure($CRLContext.pCrlInfo,[PKI.CRL+CRL_INFO]) $CRL.Version = $CRLInfo.dwVersion + 1 $CRL.Type = "Base CRL" $CRL.RawData = $cBytes $CRL.SignatureAlgorithm = New-Object Security.Cryptography.Oid $CRLInfo.SignatureAlgorithm.pszObjId $CRL.ThisUpdate = [datetime]::FromFileTime($CRLInfo.ThisUpdate) $CRL.NextUpdate = [datetime]::FromFileTime($CRLInfo.NextUpdate) $csz = [PKI.CRL]::CertNameToStr(65537,[ref]$CRLInfo.Issuer,3,$null,0) $psz = New-Object text.StringBuilder $csz $csz = [PKI.CRL]::CertNameToStr(65537,[ref]$CRLInfo.Issuer,3,$psz,$csz) $CRL.IssuerDN = New-Object Security.Cryptography.X509Certificates.X500DistinguishedName $psz $CRL.Issuer = $CRL.IssuerDN.Format(0) $rgCRLEntry = $CRLInfo.rgCRLEntry if ($CRLInfo.cCRLEntry -ge 1) { for ($n = 0; $n -lt $CRLInfo.cCRLEntry; $n++) { $Entry = New-Object System.Security.Cryptography.X509Certificates.X509CRLEntry $SerialNumber = "" $CRLEntry = [Runtime.InteropServices.Marshal]::PtrToStructure($rgCRLEntry,[PKI.CRL+CRL_ENTRY]) $pByte = $CRLEntry.SerialNumber.pbData $SerialNumber = "" for ($m = 0; $m -lt $CRLEntry.SerialNumber.cbData; $m++) { $bByte = [Runtime.InteropServices.Marshal]::ReadByte($pByte) $SerialNumber = "{0:x2}" -f $bByte + $SerialNumber $pByte = [int]$pByte + [Runtime.InteropServices.Marshal]::SizeOf([byte]) } $Entry.SerialNumber = $SerialNumber $Entry.RevocationDate = [datetime]::FromFileTime($CRLEntry.RevocationDate) $CRLReasonCode = "" [IntPtr]$rcExtension = [PKI.CRL]::CertFindExtension("2.5.29.21",$CRLEntry.cExtension,$CRLEntry.rgExtension) if (!$rcExtension.Equals([IntPtr]::Zero)) { $CRLExtension = [Runtime.InteropServices.Marshal]::PtrToStructure($rcExtension,[PKI.CRL+CERT_EXTENSION]) $pByte = $CRLExtension.Value.pbData $bBytes = $null for ($m = 0; $m -lt $CRLExtension.Value.cbData; $m++) { $bByte = [Runtime.InteropServices.Marshal]::ReadByte($pByte) [Byte[]]$bBytes += $bByte $pByte = [int]$pByte + [Runtime.InteropServices.Marshal]::SizeOf([byte]) } $Entry.ReasonCode = $bBytes[2] $Entry.ReasonMessage = $Reasons[$Entry.ReasonCode] } $CRL.RevokedCertificates += $Entry $rgCRLEntry = [int]$rgCRLEntry + [Runtime.InteropServices.Marshal]::SizeOf([PKI.CRL+CRL_ENTRY]) } } $rgExtension = $CRLInfo.rgExtension if ($CRLInfo.cExtension -ge 1) { $Exts = New-Object Security.Cryptography.X509Certificates.X509ExtensionCollection for ($n = 0; $n -lt $CRLInfo.cExtension; $n++) { $ExtEntry = [Runtime.InteropServices.Marshal]::PtrToStructure($rgExtension,[PKI.CRL+CERT_EXTENSION]) [IntPtr]$rgExtension = [PKI.CRL]::CertFindExtension($ExtEntry.pszObjId,$CRLInfo.cExtension,$CRLInfo.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]) } if ($exts | ?{$_.Oid.Value -eq "2.5.29.27"}) {$CRL.Type = "Delta CRL"} $CRL.Extensions = $Exts } $CRL [void][PKI.CRL]::CertFreeCRLContext($pvContext) }
И, собственно, его вывод:
[↓] [vPodans] Get-CRL .\Desktop\pica-1.crl Version : 2 Type : Base CRL IssuerDN : System.Security.Cryptography.X509Certificates.X500DistinguishedName Issuer : CN=Sysadmins LV Internal Class 1 SubCA-1, OU=Information Systems, O=Sysadmins LV, C=LV ThisUpdate : 22.02.2011 19:22:27 NextUpdate : 26.02.2011 19:42:27 SignatureAlgorithm : System.Security.Cryptography.Oid Extensions : {System.Security.Cryptography.Oid, System.Security.Cryptography.Oid, System.Security.Cryptography .Oid, System.Security.Cryptography.Oid...} RevokedCertificates : {System.Security.Cryptography.X509Certificates.X509CRLEntry, System.Security.Cryptography.X509Cer tificates.X509CRLEntry, System.Security.Cryptography.X509Certificates.X509CRLEntry, System.Securi ty.Cryptography.X509Certificates.X509CRLEntry...} RawData : {48, 130, 3, 39...} [↓] [vPodans]
И некоторые внутренности:
[↓] [vPodans] $crl = Get-CRL .\Desktop\pica-1.crl [↓] [vPodans] $crl.Extensions[0].format(0) KeyID=1b fa 5e 73 2d 67 13 5c ce d3 0e e6 e8 7a a9 60 8c 0b 63 fc [↓] [vPodans] $crl.Extensions[4].format(1) [1]Freshest CRL Distribution Point Name: Full Name: URL=http://www.sysadmins.lv/pki/pica-1+.crl [↓] [vPodans] $crl.RevokedCertificates SerialNumber RevocationDate ReasonCode ReasonMessage ------------ -------------- ---------- ------------- 3bfe8e77000000000078 27.12.2010 19:32:00 0 163c8142000000000072 27.11.2010 23:27:00 5 Cease Of Operation 14d70748000000000071 27.11.2010 23:27:00 5 Cease Of Operation 411726e0000000000054 04.08.2010 21:26:00 0 1cee2e2000000000002b 01.05.2010 15:32:00 0 2ee0af5a000000000021 24.04.2010 22:25:00 0 [↓] [vPodans] $crl.GetType().FullName System.Security.Cryptography.X509Certificates.X509CRL2 [↓] [vPodans]
Comments: