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]

Share this article:

Comments:


Post your comment:

Please, solve this little equation and enter result below. Captcha