Wednesday, March 3, 2010

Checking SSL certificate values with Powershell

For anyone that needs to check SSL certificates in a simple way from Powershell, I created something for this purpose a while back. It works for most SSL connections using .NET code and will throw exceptions if the name on the cert you provide is not valid, or the cert is expired.

Check-sslcert.ps1 (Updated Jan 15, 2013)


#Requires -version 2.0
 
param(
 [parameter(mandatory=$true,helpmessage="IP address or hostname to resolve remote system")][string]$ipaddr,
 [parameter(mandatory=$true,helpmessage="TCP port number that SSL application is listening on")][int]$port,
 [parameter(helpmessage="Hostname on certificate")][string]$myhostname=$ipaddr,
 [parameter(helpmessage="Verbose")][alias('fulldetail')][switch]$V
 
)


function stripcomma([string]$tempstring) {
 write-debug "In Function StripComma $($tempstring)"
 return $tempstring.replace(',',';') 
 
}

function convertoid([string]$oid) {
 write-debug "In function ConvertToOID: $($oid)"
 #strip off oid component common to all crypto types
 $oidstr = $oid.replace("1.2.840.113549.1.","")
 
 #pull out first number
 $firstval = $oidstr.substring(0,$oidstr.indexof('.'))
 
 #pull out second number for more detail
 $sub = $oidstr.substring(2)
 if ($sub.indexof('.') -gt 0) {
  $sub = $sub.substring(0,$sub.indexof('.')) 
 }
 
 if ($firstval -eq "1") {
  $format = "PKCS-1"
  switch ($sub) {
   "1" { return ($format + " RSA Encryption") }
   "2" { return ($format + " MD2 with RSA") }
   "3" { return ($format + " rsadsi md4 with RSA")}
   "4" { return ($format + " MD5 with RSA") }
   "5" { return ($format + " SHA-1 with RSA") }
   "6" { return ($format + " rsaOAEPEncryptionSet")}
   "11" { return ($format + " sha256 with RSA") }
  }
 } elseif ($firstval -eq "5") {
  $format = "RSA PKCS5"
  switch ($sub) {
   "1" { return ($format + " rsadsi pbe with MD2 DES-CBC")}
   "3" { return ($format + " rsadsi pbe with MD5 DES-CBC")}
   "4" { return ($format + " pbe with MD2 and RC2_CBC")}
   "6" { return ($format + " pbe with MD5 and RC2_CBC")}
   "9" { return ($format + " pbe with MD5 and XOR")}
   "10" { return ($format + " pbe with SHA1 and DES-CBC")}
   "11" { return ($format + " pbe with SHA1 and RC2_CBC")}
   "12" { return ($format + " id-PBKDF2 key derivation function")}
   "13" { return ($format + " id-PBES2  PBES2 encryption")}
   "14" { return ($format + " id-PBMAC1 message auth scheme")}
   
  }
 } elseif ($firstval -eq "7" ) {
  $format = "PKCS-7"
  switch ($sub) {
   "1" { return ($format + " data")}
   "2" { return ($format + " signed data")}
   "3" { return ($format + " enveloped data")}
   "4" { return ($format + " signed and enveloped data")}
   "5" { return ($format + " digested data")}
   "6" { return ($format + " encrypted data")}
  }
 } elseif ($firstval -eq "12") {
  return ("PKCS-12")
 } elseif ($firstval -eq "15") {
  return ("PKCS-15") 
 } else {
  return $oid 
 }
   
 
}

######
#MAIN#
######

#open TCP connection
try {
 $conn = new-object system.net.sockets.tcpclient($ipaddr,$port) 
 
 try {
  #create ssl stream on existing tcp connection
  $stream = new-object system.net.security.sslstream($conn.getstream())
  #send hostname on cert to try SSL negotiation
  $stream.authenticateasclient($myhostname) 
  
  $cert = $stream.get_remotecertificate()
  $cert2 = New-Object system.security.cryptography.x509certificates.x509certificate2($cert)    #can get much more information with this class    

  $validto = [datetime]::Parse($cert.getexpirationdatestring())
  $validfrom = [datetime]::Parse($cert.geteffectivedatestring())
  
  if ($V) {
   new-object psobject -property @{ 
    Connection = "Success"
    Machine = $ipaddr
    CertFormat = ($cert.getformat())
    CertExpiration = $validto
    CertIssueDate = $validfrom
    CertIssuer = ($cert.get_issuer())
    SerialNumber = ($cert.getserialnumberstring())
    CertSubject = (stripcomma $cert.get_subject())
    CertType =  (convertoid $cert.getkeyalgorithm())
   }
  } else {
   #non verbose
   New-Object psobject -Property @{
    Connection = "Success"
    Machine = $ipaddr
    CertExpiration = $validto
   }
  }

 } catch {
  #if SSL connection failed, cert may be invalid or name on cert didn't match, fails either way
  throw $_
 } finally {
  Write-Debug "In finally: closing connection"
  $conn.close()
 }
} catch {
 Write-Verbose "Error occurred connecting to $($ipaddr)"
 New-Object PSObject -Property @{
  Machine = $ipaddr
  Connection = "Failure"
  Status = $_.exception.innerexception.message
 }
 
}

8 comments:

  1. This was helpful thanks for posting it! I ended up just using import-csv to parse a csv file and check those entries with a call to your script.

    I also have to figure out how to use code boxes on my page like you do, that's much cleaner.

    Thanks again!

    ReplyDelete
  2. I tried this script but I always end up in the try/catch section.
    The script seems to break at

    $stream.authenticateasclient($myhostname)

    Has anybody an idea how to exactly call this script

    .\Check-sslcert.ps1 server.mydomain.com 443 mypcname.mydomain.com -fulldetail:$true

    Help appreciated....

    ReplyDelete
  3. Hi Oliver,

    I haven't done a great job of naming the parameters for the script. Maybe I'll get back to it and try to think of better names. The Help Message for each should cover it in more detail. Basically

    -ipaddr is any IP or computer name (that will resolve in dns or other method) in order to open the TCP connection to a remote machine

    -Port is the TCP port number that the SSL enabled application is listening on (remote machine)

    -myhostname is the hostname on the certificate. Sometimes this does not machine the remote machine name (-ipaddr), for example, if you have two nodes of a load balanced web application and you wanted to check each node individually. The certificate may have the name of a load balancer DNS alias instead of the specific node name.

    Hope this helps

    ReplyDelete
  4. For a quick example demo:

    PS C:\Users\nathan> .\check-sslcert.ps1 -ipaddr www.google.com -port 443 -myhostname
    www.google.com

    Connection CertExpiration Machine
    ---------- -------------- -------
    Success 8/6/2013 3:43:27 AM www.google.com


    PS C:\Users\nathan> .\check-sslcert.ps1 -ipaddr www.google.com -port 443 -myhostname
    www.google.com -v


    Machine : www.google.com
    SerialNumber : 188DF90B00000000780B
    CertSubject : CN=www.google.com; O=Google Inc; L=Mountain View;
    S=California; C=US
    CertExpiration : 8/6/2013 3:43:27 AM
    CertIssueDate : 3/1/2013 8:15:52 PM
    CertFormat : X509
    CertType : PKCS-1 RSA Encryption
    Connection : Success
    CertIssuer : CN=Google Internet Authority, O=Google Inc, C=US

    PS C:\Users\nathan> .\check-sslcert.ps1 -ipaddr www.google.com -port 443 -v


    Machine : www.google.com
    SerialNumber : 188DF90B00000000780B
    CertSubject : CN=www.google.com; O=Google Inc; L=Mountain View;
    S=California; C=US
    CertExpiration : 8/6/2013 3:43:27 AM
    CertIssueDate : 3/1/2013 8:15:52 PM
    CertFormat : X509
    CertType : PKCS-1 RSA Encryption
    Connection : Success
    CertIssuer : CN=Google Internet Authority, O=Google Inc, C=US

    (when myhostname param is not provided the script uses the -ipaddr param as the hostname on the cert. Used for when the machine name and certification host name both match)

    ReplyDelete
  5. And just a few extra examples of where the hostname and name on certificate may not match. Dell.com has an SSL cert with many subject alternate names for it to validation true on. Here is an example with a valid SAN, and one that isn't there:

    PS C:\Users\nathan> .\check-sslcert.ps1 -ipaddr www.d
    ell.com -port 443 -myhostname i.dell.com

    Connection CertExpiration Machine
    ---------- -------------- -------
    Success 22/3/2013 3:28:46 AM www.dell.com


    PS C:\Users\nathan> .\check-sslcert.ps1 -ipaddr www.d
    ell.com -port 443 -myhostname server.dell.com

    Connection Status Machine
    ---------- ------ -------
    Failure The remote certificate ... www.dell.com

    ReplyDelete
  6. Nathan,

    How can I determine public key length? For instance we're upgrading all of our certs to 2048-bit. How can I verify compliance?

    Thanks,

    Hank

    ReplyDelete
  7. Great job Nathan. Oliver thanks for asking; I appreciated the examples too.

    ReplyDelete
  8. For public key length add to the verbose section under if ($V)
    CertKeySize = $cert2.PublicKey.key.KeySize

    ReplyDelete