Get a server's SSL/TLS certificate using "openssl s_client"

After a while I figured it out: this particular load balancer was configured to use only TLSv1.2, which the version of openssl included in OS X (0.9.8) does not understand. I installed a newer version of openssl (>= 1.0.1) using homebrew so this works:

/usr/local/opt/openssl/bin/openssl s_client -showcerts -connect lb.example.com:443

I am trying to get the SSL/TLS certificate for one of our load balancers (Netscaler) using:

 openssl s_client -showcerts -connect lb.example.com:443

If its a modern configuration (some hand waiving on what that means), use:

openssl s_client -connect lb.example.com:443 -tls1 -servername lb.example.com | \
openssl x509 -text -noout

CONNECTED(00000003)
write to 0x7fec7af0abf0 [0x7fec7b803a00] (130 bytes => 130 (0x82))
0000 - 80 80 01 03 01 00 57 00-00 00 20 00 00 39 00 00
...

It looks like there's some extra preamble at byte 0 and 1. At byte 2, there should be a record type. At byte 3 and 4 there should be a version number. Bytes 5 and 6 should be a 16-bit length of the payload.

Here's a working example:

$ openssl s_client -connect www.googl.com:443 -tls1 -servername www.googl.com -debug
CONNECTED(00000005)
write to 0x7f7fe1c1fa30 [0x7f7fe2022000] (132 bytes => 132 (0x84))
0000 - 16 03 01 00 7f 01 00 00-7b 03 01 71 c0 12 35 98
...

From above, the record type is at position 0, and its value is 0x16. 0x16 is the Handshake type. The record layer version are the next two bytes at positions 2 and 3. Their values are 0x03 0x01. The length of the payload is 0x007f.

Also see RFC 5246, The Transport Layer Security (TLS) Protocol Version 1.2, page 18:

6.2.1.  Fragmentation

   The record layer fragments information blocks into TLSPlaintext
   records carrying data in chunks of 2^14 bytes or less.  Client
   message boundaries are not preserved in the record layer (i.e.,
   multiple client messages of the same ContentType MAY be coalesced
   into a single TLSPlaintext record, or a single message MAY be
   fragmented across several records).

      struct {
          uint8 major;
          uint8 minor;
      } ProtocolVersion;

      enum {
          change_cipher_spec(20), alert(21), handshake(22),
          application_data(23), (255)
      } ContentType;

      struct {
          ContentType type;
          ProtocolVersion version;
          uint16 length;
          opaque fragment[TLSPlaintext.length];
      } TLSPlaintext;

Your problem could be the old SSLv2 compatible record type. Or it could be a down level version of OpenSSL from, say 0.9.5 or 0.9.8. Its hard to say, and we probably need more information.

More information would include OS; OpenSSL version; if you attempted to replace the platform's version of OpenSSL with your own version of OpenSSL; if there's a firewall or "web inspect" box or other middleware goodness running; and what the server receives.


Using -servername lb.example.com doesn't help, and our sysadmin told me our load balancers don't use SNI anyway.

This sounds kind of unusual. But its an extension to TLS, so its ignored if its not used (and won't produce a fatal alert).

The rule of thumb in 2016: always use TLS 1.0 or above, and always use SNI.