How to scan SSL/TLS sites.

The other day, I hit a conundrum.

We couldn’t make LDAPS connections to a couple of domain controllers. A quick “TS” over to the systems in question indicated that we had a correct certificate in place, and that it was valid, but when we connected using “LDP” over port 636, we would be told that the certificate exchange wasn’t allowed to finish.

A look at the event viewer showed us that the certificate had expired. That’s ludicrous, though because the certificates show in the certificate manager as valid.

The only thing we could figure out is that we must have been running into the problem specified in KB 321051 – “How to enable LDAP over SSL…” – or at least, the last issue on “Pre-SP3 SSL certificate caching issue”. Sure, we’re on SP4, but that’s the only thing we could figure out.

To test it further, we had to view the certificate, and the brutal method we chose was to open up Internet Explorer and open https://192.168.0.1:636 – where “192.168.0.1″ was the address of the DC whose certificate we wanted to view.

An error comes up on screen when you do this, because the IP address doesn’t match the name. You want this error, because it allows you to press “View Certificate”.

Sure enough, the certificate that came up was old, and expired. And, just as the article assured us we shouldn’t have to do on a Windows 2000 SP4 box, a re-boot fixed the server to using the right certificate.

If you’re thinking like we were, your next though will be “gee, I wonder how many of our other servers have this problem?” Obviously, we would be hard-pressed to scan all of our servers using an Internet Explorer error dialog, so it was time to write a program to do it for us.

Less than an hour later, I had the program I like to call “SSLScan”.

Sure, it’s in C# .NET 2.0, but it’s probably the shortest piece of SSL code I’ve written that wasn’t completely densely obfuscated.

using System;
using System.Net;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Authentication;
using System.Collections;
using System.Security.Cryptography.X509Certificates;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;

namespace SSLScan
{
    class Program
    {
        static int port = 443; // We choose to default to HTTPS.
        // Other examples – LDAPS is 636.

        static void Main(string[] args)
        {
            //
            //  SSLScan – scan a list of SSL servers, connecting to each
            // in turn, and asking for their certificate.
            // Then display certificate information.
            //
            // We assume that each server responds to an SSL ClientHello
            // immediately upon connection – this will not work with some
            // SSL servers, for instance FTPS servers, that require some
            // sort of command(s) to be issued prior to sending the SSL
            // negotiation.
            //
            // For now, the only argument is the name of the file used to list
            // the hosts – or you can feed it as stdin.
            //
            switch (args.Length)
            {
                case 0:
                    Usage();
                    Console.Error.WriteLine(“Reading from keyboard”);
                    TestConnectionsFromStream(new System.IO.StreamReader(
System.Console.OpenStandardInput())); break; case 1: Console.Error.WriteLine(“Reading from file {0}”, args[0]); TestConnectionsFromStream(new System.IO.StreamReader(args[0])); break; default: Usage(); break; } } private static void TestConnectionsFromStream(
System.IO.StreamReader streamReader) { string server; bool blank = false; bool interactive = !streamReader.BaseStream.CanSeek; char[] delimiters = { ‘:’ }; string[] elements; do { if (blank) Console.WriteLine(); if (interactive) Console.Error.Write(“\nServerName[:PortNumber] > “); server = streamReader.ReadLine(); if (server == null || // end of file – no more. (interactive && server.Length==0)) // Interactive – blank line ends. break; if (server.StartsWith(“//”)) continue; // ignore comments – go to next server. if (server.Contains(“:”)) { elements = server.Split(delimiters); server = elements[0]; port = Convert.ToInt32(elements[1]); } try { TestConnectionToSite(server,port); Console.WriteLine(); } catch (Exception e) { Console.Error.WriteLine(“Exception on site {0}”, server); Console.Error.WriteLine(e.Message); } blank = true; } while (true); // Forever – or until “break” hit. } public static bool ValidateServerCertificate( object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { Console.WriteLine(“Subject: {0}”,certificate.Subject); Console.WriteLine(“Issuer : {0}”,certificate.Issuer); Console.WriteLine(“Serial : {0}”,certificate.GetSerialNumberString()); Console.WriteLine(“Expires: {0}”, certificate.GetExpirationDateString()); X509Certificate2 cert2 = new X509Certificate2(certificate); if (cert2.NotAfter < DateTime.Now) Console.WriteLine(“*** Certificate has expired!!!”); else if (cert2.NotAfter.AddDays(-30.0) < DateTime.Now) Console.WriteLine(“*** Certificate expires in thirty days or less!!!”); if (sslPolicyErrors == SslPolicyErrors.None) return true; Console.WriteLine(“Certificate error: {0}”, sslPolicyErrors); // Reject the SSL connection return false; } private static void TestConnectionToSite(string serverName, int port) { Console.Error.WriteLine(“Connecting to server: ” + serverName + “:” + port.ToString()); TcpClient client = new TcpClient(serverName, port); Console.Error.WriteLine(“Client connected.”); // Create an SSL stream that will enclose the client’s stream. SslStream sslStream = new SslStream( client.GetStream(), false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null ); // The server name must match the name on the server certificate. try { sslStream.AuthenticateAsClient(serverName); } catch (AuthenticationException e) { // Uncomment this lot of code if you want to see the exceptions. // Console.WriteLine(“Exception: {0}”, e.Message); // if (e.InnerException != null) // { // Console.WriteLine(“Inner exception: {0}”, e.InnerException.Message); // } // Console.WriteLine(“Authentication failed – closing the connection.”); client.Close(); return; } client.Close(); } static void Usage() { Console.Error.WriteLine(“Usage:”); Console.Error.WriteLine( “SSLScan [filename] – if filename is blank, reads from stdin.”); Console.Error.WriteLine( “Scans multiple systems with an SSL connection,” + ” listing the certificates.”); Console.Error.WriteLine( “Sites are specified as host:port – the ‘:port’ part” + ” is optional, and if not”); Console.Error.WriteLine( “specified, will default to the previous port value,” + ” or the default of ” + port.ToString()); } } }

10 thoughts on “How to scan SSL/TLS sites.”

  1. Guess your post got truncated….

    Wouldn’t WinInet do the job here and throw the error you wanted?

    You’d have to use a thread pool though to scan the servers unless you used async wininet (which is pretty ugly).

    And of course it would work only for HTTPS servers, not FTPS afaik.

  2. Thanks for the note on truncation – hopefully it’s fixed now. Note to self – when posting code with angle-brackets (less-than, greater-than signs), don’t preview before posting.
    I could have used WinInet, perhaps, but of course then it wouldn’t have been extensible to supporting FTPS in future, and I wanted to see how the new SslStream class works in .NET.
    I wish that they’d called it TlsStream, or at least created some sort of alias to that, as TLS is the new name for SSL.

  3. Right…thanks for fixing that.

    Looks like pretty simple code, as you said…..I’m not a .NET programmer myself but I do see its utility now for the kind of task outlined above / RAD of a kind.

  4. I am not sure what I am missing but I had two issues with your code:

    1- I was not able to find the code for TestConnectionToSite method
    2- There was no use of the method VlidateServerCertificate

    I have the same issue where I need to scan all avalable DCs in a domain to check for certificate experation. I was hoping to use your code as a starting point

    Thanks

    Issam

  5. Try it now – obviously, when updating this site to the newest version of Community Server, the code sample got horked again. I have since unhorked it (literally by pressing “Post” again).

  6. Thanks for the information.
    I would like to start TLS session for imap and pop on normal port 143 (imap) and 110(pop).
    i was not able to do the same.
    http://rfc.net/rfc2595.txt\
    i was trying folloiwg command using socket.
    it got stuck at “starttls” command.
    Example: C: a001 CAPABILITY
    S: * CAPABILITY IMAP4rev1 STARTTLS LOGINDISABLED
    S: a001 OK CAPABILITY completed
    C: a002 STARTTLS
    S: a002 OK Begin TLS negotiation now

    C: a003 CAPABILITY
    S: * CAPABILITY IMAP4rev1 AUTH=EXTERNAL
    S: a003 OK CAPABILITY completed
    C: a004 LOGIN joe password
    S: a004 OK LOGIN completed

    Any help on this would be great.
    thanks
    urvish
    urvishshelat@yahoo.co.in

  7. In between the lines creating a new SslStream object, and calling AuthenticateAsClient , you would insert the appropriate lines to write the CAPABILITY and STARTTLS commands, and to read the responses.
    Once you read the “OK…” in response to the STARTTLS command, you can authenticate as an SSL client, and send further commands and responses which will be encrypted.
    There should be a number of samples available for demonstrating how to communicate using SSL / TLS.

  8. Thanks alung.
    same i have done.but i always works first time.second time if u run immediately it fails at “sslStream.AuthenticateAsClient(serverName);” stating that “handshake failed due bad packet format.” same code is working fine for SSL multiple times wihtout closing application.(window).
    I realy got stuck here..no clue…
    regards,
    Urvish

  9. Not having developed any larger apps in .NET yet, I can’t say that I can offer a really good answer – but any time you’re talking about a failure “the second and subsequent times”, you have to look at “did I close the first one properly?”

    “Close”, is of course, a concept, not a function or operation – it could mean that you didn’t call a “Close()” function, or it could mean that you didn’t finish receiving the last piece of data, or you didn’t drop from SSL down to plain-text, and now you want to negotiate back up to SSL when you’re already there.

    Apply some thought to this in your own case, and you’ll probably find out what’s wrong – otherwise, you might want to post your question in one of the .NET newsfroups.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>