Friday, September 10, 2010

Get Certificate Chain from any port with Powershell

Some time back I needed to dump the certificate chain from an LDAP server. I cobbled together a small function to connect to any SSL/TLS port and download the certificate chain.

The function takes 3 parameters, -Server, -Port and -ToBase64. I think all of them are self describing. Without the -ToBase64 the function returns a X509Certificate2 object from which you can get the Subject, Issuer, Thumbprint (...) fields.

Sample execution:

PS C:\> Get-CertificateChain -server lon001 -Port 636 -ToBase64 > c:\cert.cer

I used the function to collect all certificates from all our Domain Controller in our Active Directory domain, lo and behold, we had a few Domain Controllers with old certs. So it was very useful. Here's the function...



Function Get-CertificateChain {
param(
[string]$server=$(throw "Mandatory parameter -Server is missing."),
[int]$port=$(throw "Mandatory parameter -Port is missing."),
[switch]$ToBase64
)
$code=@"
using System;
using System.Collections;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Text;
using System.Security.Cryptography.X509Certificates;
using System.IO;
using System.Threading;
namespace CosmosKey.Powershell
{
  public class SslUtility
  {
    private static byte[] CertChain;
    private static object Lock = new object();
    private static Hashtable certificateErrors = new Hashtable();
    public static bool ValidateServerCertificate(
        object sender,
        X509Certificate certificate,
        X509Chain chain,
        SslPolicyErrors sslPolicyErrors)
    {
      byte[] data = certificate.Export(X509ContentType.Cert);
      lock (Lock)
      {
        CertChain = data;
        Monitor.Pulse(Lock);
      }
      return true;
    }
    public static byte[] GetCertificate(string serverName, int port)
    {
      TcpClient client = new TcpClient(serverName,port);
          SslStream sslStream = new SslStream(
          client.GetStream(),
          false,
          new RemoteCertificateValidationCallback (ValidateServerCertificate),
          null
        );
      try
      {
        lock (Lock)
        {
          sslStream.BeginAuthenticateAsClient(serverName,null,null);
          bool didTimeout = Monitor.Wait(Lock);
        }
      }
      finally
      {
        client.Close();
      }
      return CertChain;
    }
  }
}
"@
  Add-Type $code
  [byte[]]$certData = [CosmosKey.Powershell.SslUtility]::GetCertificate($server,$port)
  if($ToBase64){
    [convert]::ToBase64String($certData)
  } else {
    $cert = new-object System.Security.Cryptography.X509Certificates.X509Certificate2
    $cert.import($certData)
    $cert
  }
}

Please enjoy!

3 comments:

  1. Thanks so much for taking the time to post this! This was exactly what I was looking for and although there are a few other sites that mention this same approach this is the only all-inclusive example that I have seen of something like this. It was super helpful to me.

    ReplyDelete
  2. This was exactly what I was looking for and although there are a few other sites that mention this same approach this is the only all-inclusive example that I have seen of something like this. It was super helpful to me.

    Oxford Security

    ReplyDelete
  3. If you are getting error in Powershell Core
    Exception calling "Import" with "1" argument(s): "X509Certificate is immutable on this platform. Use the equivalent constructor instead."
    Then edit the last else clause from

    $cert = new-object System.Security.Cryptography.X509Certificates.X509Certificate2
    $cert.import($certData)
    $cert

    to

    new-object System.Security.Cryptography.X509Certificates.X509Certificate2(,$certData)

    ReplyDelete