Monday, March 21, 2011

PowerShell example for LdapSessionOptions.VerifyServerCertificate

 

I’ve started switching most of my management scripts over to PowerShell.  I previously had written a small C# command-line tool that would display the certificate expiration date of a Domain Controller’s LDAPS certificate.  This utility was based on Joe Kaplan’s sample.  As this utility was called as part of a much larger and more complex VBScript it only made sense to incorporate this functionality directly into PowerShell as well.  However, figuring out how to get PowerShell to deal with the VerifyServerCertificateCallback object was a more complex an undertaking than I had anticipated, and there were several times I almost gave up and kept the certificate date check as an external utility.  However, I did eventually figure it out and thought I’d share since there are no specific examples anywhere and few examples about PowerShell and callbacks in general.

Within the .NET Framework, System.DirectoryServices.Protocols provides comparatively raw access to the LDAP APIs.  In Joe’s example, the VerifyServerCertificate property is assigned to a new VerifyServerCertificateCallback object, which itself is a function that returns either True or False based on whatever logic one wants to employ.

Most of the examples I found centered on web-server SSL certificates and ServerCertificateValidationCallback and were based on either C# which has no issues with callbacks, like Joe’s, or PowerShell v1 which had to do lots of unnatural things to use the callback.

It turns out there are two important items to know about callbacks and PowerShell v2.  First, callbacks are implemented as a scriptblock.  Second, access to the callback parameters are provided via the args array.

Add-Type -AssemblyName System.DirectoryServices.Protocols
$LDAPId = New-Object System.DirectoryServices.Protocols.LdapDirectoryIdentifier(($DC + ":636"), $true, $false)
$LDAPConnection = New-Object System.DirectoryServices.Protocols.LdapConnection($LDAPId)
$LDAPConnection.Credential = New-Object System.Net.NetworkCredential("", "")
$LDAPConnection.AuthType = [system.directoryservices.protocols.authtype]::anonymous
$LDAPConnection.SessionOptions.SecureSocketLayer = $true
$LDAPConnection.SessionOptions.VerifyServerCertificate = {
    $MyCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $args[1]
    $DCCertDate = $MyCert.NotAfter
    $true
}
Try {
    $LDAPConnection.Bind()
} Catch {
    $DCCertDate = "No Certificate Found"
}

During execution, when the Bind method is called, the scriptblock attached to the VerifyServerCertificate property is called.  The LdapConnection parameter for the VerifyServerCertificateCallback delegate is contained in args[0] and the X509Certificate in args[1].  In this particular example I use the callback certificate to create a new certificate object, store the expiration date and return true, which allows the connection to succeed.