Hi folks! Today I want to demonstrate some useful stuff with CryptoAPI and PowerShell to extract CDP, AIA and OCSP URLs from a digital certificate.
The start point for us is CryptGetObjectUrl function:
BOOL WINAPI CryptGetObjectUrl( __in LPCSTR pszUrlOid, __in LPVOID pvPara, __in DWORD dwFlags, __out PCRYPT_URL_ARRAY pUrlArray, __inout DWORD *pcbUrlArray, __out PCRYPT_URL_INFO pUrlInfo, __inout DWORD *pcbUrlInfo, __reserved LPVOID pvReserved );
as a pszUrlOid argument we will use the following constants: URL_OID_CERTIFICATE_ISSUER
(from AIA extension) and URL_OID_CERTIFICATE_OCSP_AND_CRL_DIST_POINT
. Constant values (as per Wincrypt.h) are:
URL_OID_CERTIFICATE_ISSUER = 1
;URL_OID_CERTIFICATE_CRL_DIST_POINT = 2
;URL_OID_CERTIFICATE_ONLY_OCSP = 13
.pvPara
accepth the handle to a certificate. X509Certificate2 object contains Handle property (which we will use).
In dwFlags
we specify CRYPT_GET_URL_FROM_EXTENSION
constant (0x2) to instruct the function to extract URLs from certificate extensions.
pUrlArray
will contains a byte array that represents little-endian Unicode strings. pcbUrlArray
will contain a number of bytes to use for pUrlArray
byte array. The rest arguments are not required for our task. Let's try to p/invoke this function in C# language:
$signature = @" [DllImport("cryptnet.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool CryptGetObjectUrl( int pszUrlOid, IntPtr pvPara, int dwFlags, byte[] pUrlArray, ref int pcbUrlArray, IntPtr pUrlInfo, ref int pcbUrlInfo, int pvReserved ); "@ Add-Type -MemberDefinition $signature -Namespace PKI -Name Cryptnet
Now, we need to define one helper function which will decode byte array to a little-endian Unicode string:
function ConvertTo-DERString ([byte[]]$bytes) { $SB = New-Object System.Text.StringBuilder $bytes1 = $bytes | %{"{0:X2}" -f $_} for ($n = 0; $n -lt $bytes1.count; $n = $n + 2) { [void]$SB.Append([char](Invoke-Expression 0x$(($bytes1[$n+1]) + ($bytes1[$n])))) } $SB.ToString() }
Before we starting CryptoAPI function usage, you need to note that the function MUST be called twice per operation: first time to determine resulting byte array length, and when the buffer is created, the second function call will write actual data to the buffer.
As an example, we will use the certificate from login.live.com. If you are using Internet Explorer 7+ on Windows Vista+, it is not easy to copy the certificate from the web browser, because Internet Explorer Protected Mode prevents that. But don't worry, you can use my handy tool: Test remote web server SSL certificate. Just save Certificate.Handle property to a variable. Say:
$pvPara = (Test-WebServerSSL -URL login.live.com).Certificate
Ok, we now are ready to route:
function Get-CertificateURL { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [Security.Cryptography.X509Certificates.X509Certificate2]$Cert ) $signature = @" [DllImport("cryptnet.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool CryptGetObjectUrl( int pszUrlOid, IntPtr pvPara, int dwFlags, byte[] pUrlArray, ref int pcbUrlArray, IntPtr pUrlInfo, ref int pcbUrlInfo, int pvReserved ); "@ Add-Type -MemberDefinition $signature -Namespace PKI -Name Cryptnet function ConvertTo-DERString ([byte[]]$bytes) { $SB = New-Object System.Text.StringBuilder $bytes1 = $bytes | %{"{0:X2}" -f $_} for ($n = 0; $n -lt $bytes1.count; $n = $n + 2) { [void]$SB.Append([char](Invoke-Expression 0x$(($bytes1[$n+1]) + ($bytes1[$n])))) } $SB.ToString() } # create synthetic object to store resulting URLs $URLs = New-Object psobject -Property @{ CDP = $null; AIA = $null; OCSP = $null; } $pvPara = $Cert.Handle # process only if Handle is not zero. if (!$Cert.Handle.Equals([IntPtr]::Zero)) { # loop over each URL type: AIA, CDP and OCSP foreach ($id in 1,2,13) { # initialize reference variables $pcbUrlArray = 0 $pcbUrlInfo = 0 # call CryptGetObjectUrl to get required buffer size. The function returns True if succeeds and False otherwise if ([PKI.Cryptnet]::CryptGetObjectUrl($id,$pvPara,2,$null,[ref]$pcbUrlArray,[IntPtr]::Zero,[ref]$pcbUrlInfo,$null)) { # create buffers to receive the data $pUrlArray = New-Object byte[] -ArgumentList $pcbUrlArray $pUrlInfo = [Runtime.InteropServices.Marshal]::AllocHGlobal($pcbUrlInfo) # call CryptGetObjectUrl to receive decoded URLs to the buffer. [void][PKI.Cryptnet]::CryptGetObjectUrl($id,$pvPara,2,$pUrlArray,[ref]$pcbUrlArray,$pUrlInfo,[ref]$pcbUrlInfo,$null) # convert byte array to a single string $URL = ConvertTo-DERString $pUrlArray # parse unicode string to remove extra insertions switch ($id) { 1 { $URL = $URL.Split("`0",[StringSplitOptions]::RemoveEmptyEntries) $URLs.AIA = $URL[4..($URL.Length - 1)] } 2 { $URL = $URL.Split("`0",[StringSplitOptions]::RemoveEmptyEntries) $URLs.CDP = $URL[4..($URL.Length - 1)] } 13 { $URL = $URL -split "ocsp:" $URLs.OCSP = $URL[1..($URL.Length - 1)] | %{$_ -replace [char]0} } } # free unmanaged buffer [void][Runtime.InteropServices.Marshal]::FreeHGlobal($pUrlInfo) } else {Write-Warning "No Urls"} } $URLs } }
And example:
[↓] [vPodans] $cert = (Test-WebServerSSL login.live.com).Certificate [↓] [vPodans] Get-CertificateURL $cert | fl AIA : {http://EVSecure-aia.verisign.com/EVSecure2006.cer} CDP : {http://EVSecure-crl.verisign.com/EVSecure2006.crl} OCSP : http://EVSecure-ocsp.verisign.com [↓] [vPodans] $cert = (Test-WebServerSSL www.startcom.org).Certificate [↓] [vPodans] Get-CertificateURL $cert | fl AIA : {http://www.startssl.com/certs/sub.class4.server.ca.crt} CDP : {http://www.startssl.com/crt4-crl.crl, http://crl.startssl.com/crt4-crl.crl} OCSP : http://ocsp.startssl.com/sub/class4/server/ca [↓] [vPodans]
Post your comment:
Comments: