openssl

A CLI Method to Check SSL Certificate Expiration Date

I know that browser does this automatically, but it might come in handy if you need to check the expiration date of a SSL certificate through CLI. The key is openssl, OpenSSL command line tool.

1
2
3
$ echo | openssl s_client -connect example.com:443 2> /dev/null | \
openssl x509 -noout -enddate
notAfter=Nov 28 12:00:00 2018 GMT

The command is consisted of two parts:

  • Retrieve SSL certificate from the server
  • Extract the expiration date data

The openssl program is a command line tool for using the various cryptography functions of OpenSSL’s crypto library from the shell. It can be used for[^1]

  • Creation and management of private keys, public keys and parameters
  • Public key cryptographic operations
  • Creation of X.509 certificates, CSRs and CRLs
  • Calculation of Message Digests
  • Encryption and Decryption with Ciphers
  • SSL/TLS Client and Server Tests
  • Handling of S/MIME signed or encrypted mail
  • Time Stamp requests, generation and verification

What we need here is to perform SSL/TLS Client and Server Tests.

s_client is one of the standard commands of openssl command line tool:

This implements a generic SSL/TLS client which can establish a transparent connection to a remote server speaking SSL/TLS. It’s intended for testing purposes only and provides only rudimentary interface functionality but internally uses mostly all functionality of the OpenSSL ssl library.[^1]

Dig deeper into s_client command:

The s_client command implements a generic SSL/TLS client which connects to a remote host using SSL/TLS. It is a very useful diagnostic tool for SSL servers.[^2]

Option -connect host:port:

This specifies the host and optional port to connect to. If not specified then an attempt is made to connect to the local host on port 4433.[^2]

And the format is:

1
$ openssl s_client -connect servername:443 > data

If a connection is established, openssl enters interactive mode:

If a connection is established with an SSL server then any data received from the server is displayed and any key presses will be sent to the server. When used interactively (which means neither -quiet nor -ign_eof have been given), the session will be renegotiated if the line begins with an R, and if the line begins with a Q or if end of file is reached, the connection will be closed down.[^2]

To quit, type Q or <ctr>+d (EOF).

1
2
3
4
5
6
7
$ openssl s_client -connect example.com:443 > /tmp/example.com
depth=1 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert SHA2 High Assurance
Server CA
verify error:num=20:unable to get local issuer certificate
verify return:0
Q
DONE

Dump the session data:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
$ cat /tmp/example.com
CONNECTED(00000003)
---
Certificate chain
0 s:/C=US/ST=California/L=Los Angeles/O=Internet Corporation for Assigned Names and Numbers/OU=Technology/CN=www.example.org
i:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 High Assurance Server CA
1 s:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 High Assurance Server CA
i:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV Root CA
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIF8jCCBNqgAwIBAgIQDmTF+8I2reFLFyrrQceMsDANBgkqhkiG9w0BAQsFADBw
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMS8wLQYDVQQDEyZEaWdpQ2VydCBTSEEyIEhpZ2ggQXNz
dXJhbmNlIFNlcnZlciBDQTAeFw0xNTExMDMwMDAwMDBaFw0xODExMjgxMjAwMDBa
MIGlMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEUMBIGA1UEBxML
TG9zIEFuZ2VsZXMxPDA6BgNVBAoTM0ludGVybmV0IENvcnBvcmF0aW9uIGZvciBB
c3NpZ25lZCBOYW1lcyBhbmQgTnVtYmVyczETMBEGA1UECxMKVGVjaG5vbG9neTEY
MBYGA1UEAxMPd3d3LmV4YW1wbGUub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAs0CWL2FjPiXBl61lRfvvE0KzLJmG9LWAC3bcBjgsH6NiVVo2dt6u
Xfzi5bTm7F3K7srfUBYkLO78mraM9qizrHoIeyofrV/n+pZZJauQsPjCPxMEJnRo
D8Z4KpWKX0LyDu1SputoI4nlQ/htEhtiQnuoBfNZxF7WxcxGwEsZuS1KcXIkHl5V
RJOreKFHTaXcB1qcZ/QRaBIv0yhxvK1yBTwWddT4cli6GfHcCe3xGMaSL328Fgs3
jYrvG29PueB6VJi/tbbPu6qTfwp/H1brqdjh29U52Bhb0fJkM9DWxCP/Cattcc7a
z8EXnCO+LK8vkhw/kAiJWPKx4RBvgy73nwIDAQABo4ICUDCCAkwwHwYDVR0jBBgw
FoAUUWj/kK8CB3U8zNllZGKiErhZcjswHQYDVR0OBBYEFKZPYB4fLdHn8SOgKpUW
5Oia6m5IMIGBBgNVHREEejB4gg93d3cuZXhhbXBsZS5vcmeCC2V4YW1wbGUuY29t
ggtleGFtcGxlLmVkdYILZXhhbXBsZS5uZXSCC2V4YW1wbGUub3Jngg93d3cuZXhh
bXBsZS5jb22CD3d3dy5leGFtcGxlLmVkdYIPd3d3LmV4YW1wbGUubmV0MA4GA1Ud
DwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdQYDVR0f
BG4wbDA0oDKgMIYuaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItaGEtc2Vy
dmVyLWc0LmNybDA0oDKgMIYuaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTIt
aGEtc2VydmVyLWc0LmNybDBMBgNVHSAERTBDMDcGCWCGSAGG/WwBATAqMCgGCCsG
AQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAECAjCB
gwYIKwYBBQUHAQEEdzB1MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy
dC5jb20wTQYIKwYBBQUHMAKGQWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9E
aWdpQ2VydFNIQTJIaWdoQXNzdXJhbmNlU2VydmVyQ0EuY3J0MAwGA1UdEwEB/wQC
MAAwDQYJKoZIhvcNAQELBQADggEBAISomhGn2L0LJn5SJHuyVZ3qMIlRCIdvqe0Q
6ls+C8ctRwRO3UU3x8q8OH+2ahxlQmpzdC5al4XQzJLiLjiJ2Q1p+hub8MFiMmVP
PZjb2tZm2ipWVuMRM+zgpRVM6nVJ9F3vFfUSHOb4/JsEIUvPY+d8/Krc+kPQwLvy
ieqRbcuFjmqfyPmUv1U9QoI4TQikpw7TZU0zYZANP4C/gj4Ry48/znmUaRvy2kvI
l7gRQ21qJTK5suoiYoYNo3J9T+pXPGU7Lydz/HwW+w0DpArtAaukI8aNX4ohFUKS
wDSiIIWIWJiJGbEeIO0TIFwEVWTOnbNl/faPXpk5IRXicapqiII=
-----END CERTIFICATE-----
subject=/C=US/ST=California/L=Los Angeles/O=Internet Corporation for Assigned Names and Numbers/OU=Technology/CN=www.example.org
issuer=/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 High Assurance Server CA ---
No client certificate CA names sent
---
SSL handshake has read 3393 bytes and written 421 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES128-GCM-SHA256
Session-ID: C828441A824CE7B0F6A74BBE890AB23727445EAE8521E19F438E679C39E969B1
Session-ID-ctx:
Master-Key: 38BE4F754FBCB5F41650AD91AA5588ACD88B75D7939487052D9FD2790476E7C6D2512A6451A3FC102958488BF173CB54
Key-Arg : None
PSK identity: None
PSK identity hint: None
SRP username: None
TLS session ticket lifetime hint: 300 (seconds)
TLS session ticket:
0000 - 83 70 c4 28 23 ee 9c 9e-87 1b 96 bf 44 76 ee d3 .p.(#.......Dv..
0010 - 45 c9 be ee a5 c5 42 49-c9 08 35 10 ba 79 03 b4 E.....BI..5..y..
0020 - 46 99 9a f2 d3 7b b5 f2-ad 9e 10 5c 7a 61 c3 0e F....{.....\za..
0030 - e0 09 aa 7a 5e 2a 2e bb-42 6a 08 18 16 ae 56 66 ...z^*..Bj....Vf
0040 - 11 0c 96 1a 4a 20 9f 50-6d f7 e2 53 00 75 6f 07 ....J .Pm..S.uo.
0050 - 7f 94 bf 4a 5f e1 f6 3b-d5 b7 6c 11 bc 33 7b 10 ...J_..;..l..3{.
0060 - 78 e3 81 a0 0b 83 25 d6-e6 a5 64 90 59 24 a6 e9 x.....%...d.Y$..
0070 - 9b b6 4b be 9e 42 1b 03-e0 d7 76 e9 32 87 3e 0d ..K..B....v.2.>.
0080 - 3d 09 09 32 18 fd 04 63-93 fe 33 9f 47 50 d4 c1 =..2...c..3.GP..
0090 - e1 a9 21 cc 67 30 ea 03-7f c1 ee 2a 54 02 c8 11 ..!.g0.....*T...
Start Time: 1475971200
Timeout : 300 (sec)
Verify return code: 20 (unable to get local issuer certificate)
---

To avoid the interactive mode, we can pipe an empty string into the command:

1
$ echo | openssl s_client -connect example.com:443 > /tmp/example.com 2> /dev/null

Now we have retrieved the SSL certificate from the server. Next, extract the expiration date. This is done by using the standard command x509:

Calculate Hashes for All OpenSSL Digest Algorithms

Based on the following Node.js statement:

1
require('crypto').createHash(algorithm).update('secret', 'utf8').digest('base64');

On a Ubuntu 14.04 system with OpenSSL:

1
2
$ openssl version
OpenSSL 1.0.1f 6 Jan 2014

List available digest algorithms:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
$ openssl list-message-digest-algorithms
DSA
DSA-SHA
DSA-SHA1 => DSA
DSA-SHA1-old => DSA-SHA1
DSS1 => DSA-SHA1
MD4
MD5
RIPEMD160
RSA-MD4 => MD4
RSA-MD5 => MD5
RSA-RIPEMD160 => RIPEMD160
RSA-SHA => SHA
RSA-SHA1 => SHA1
RSA-SHA1-2 => RSA-SHA1
RSA-SHA224 => SHA224
RSA-SHA256 => SHA256
RSA-SHA384 => SHA384
RSA-SHA512 => SHA512
SHA
SHA1
SHA224
SHA256
SHA384
SHA512
DSA
DSA-SHA
dsaWithSHA1 => DSA
dss1 => DSA-SHA1
ecdsa-with-SHA1
MD4
md4WithRSAEncryption => MD4
MD5
md5WithRSAEncryption => MD5
ripemd => RIPEMD160
RIPEMD160
ripemd160WithRSA => RIPEMD160
rmd160 => RIPEMD160
SHA
SHA1
sha1WithRSAEncryption => SHA1
SHA224
sha224WithRSAEncryption => SHA224
SHA256
sha256WithRSAEncryption => SHA256
SHA384
sha384WithRSAEncryption => SHA384
SHA512
sha512WithRSAEncryption => SHA512
shaWithRSAEncryption => SHA
ssl2-md5 => MD5
ssl3-md5 => MD5
ssl3-sha1 => SHA1
whirlpool

Calculate hashes for each and every uniquely listed algorithm:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
$ openssl list-message-digest-algorithms | \
cut -d ' ' -f 1 | \
xargs -n 1 -I {} node -e "algorithm = '{}'; \
digest = require('crypto').createHash('{}').update('secret', 'utf8').digest('base64'); \
console.log(algorithm, digest)" | \
sort -u
DSA 5en6G6MezRroT3XKqkdPOmY/BfQ=
DSA-SHA1 5en6G6MezRroT3XKqkdPOmY/BfQ=
DSA-SHA1-old 5en6G6MezRroT3XKqkdPOmY/BfQ=
DSA-SHA 5en6G6MezRroT3XKqkdPOmY/BfQ=
dsaWithSHA1 5en6G6MezRroT3XKqkdPOmY/BfQ=
dss1 5en6G6MezRroT3XKqkdPOmY/BfQ=
DSS1 5en6G6MezRroT3XKqkdPOmY/BfQ=
ecdsa-with-SHA1 5en6G6MezRroT3XKqkdPOmY/BfQ=
md4WithRSAEncryption Z9Pa/vY/8AYDru83ac+/DQ==
MD4 Z9Pa/vY/8AYDru83ac+/DQ==
md5WithRSAEncryption Xr4ilOzQ4PCOq3aQ0qbuaQ==
MD5 Xr4ilOzQ4PCOq3aQ0qbuaQ==
ripemd160WithRSA zZi/AgLvB+OOh/a9lEXl5zMeLHg=
RIPEMD160 zZi/AgLvB+OOh/a9lEXl5zMeLHg=
ripemd zZi/AgLvB+OOh/a9lEXl5zMeLHg=
rmd160 zZi/AgLvB+OOh/a9lEXl5zMeLHg=
RSA-MD4 Z9Pa/vY/8AYDru83ac+/DQ==
RSA-MD5 Xr4ilOzQ4PCOq3aQ0qbuaQ==
RSA-RIPEMD160 zZi/AgLvB+OOh/a9lEXl5zMeLHg=
RSA-SHA1-2 5en6G6MezRroT3XKqkdPOmY/BfQ=
RSA-SHA1 5en6G6MezRroT3XKqkdPOmY/BfQ=
RSA-SHA224 lcf7ypKsUIOv2mKlZKPQFPw7cskUDjy5nqa/Eg==
RSA-SHA256 K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols=
RSA-SHA384 WKd1ukESvjAFrkQHznV9iP2nHUBJe7gCbsrFTU4//HIyzo3jq1rLMK45dg/ufFPt
RSA-SHA512 vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==
RSA-SHA f72qWouzjoc6vqk8vf5NBIVHFXs=
SHA1 5en6G6MezRroT3XKqkdPOmY/BfQ=
sha1WithRSAEncryption 5en6G6MezRroT3XKqkdPOmY/BfQ=
SHA224 lcf7ypKsUIOv2mKlZKPQFPw7cskUDjy5nqa/Eg==
sha224WithRSAEncryption lcf7ypKsUIOv2mKlZKPQFPw7cskUDjy5nqa/Eg==
SHA256 K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols=
sha256WithRSAEncryption K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols=
sha384WithRSAEncryption WKd1ukESvjAFrkQHznV9iP2nHUBJe7gCbsrFTU4//HIyzo3jq1rLMK45dg/ufFPt
SHA384 WKd1ukESvjAFrkQHznV9iP2nHUBJe7gCbsrFTU4//HIyzo3jq1rLMK45dg/ufFPt
SHA512 vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==
sha512WithRSAEncryption vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==
SHA f72qWouzjoc6vqk8vf5NBIVHFXs=
shaWithRSAEncryption f72qWouzjoc6vqk8vf5NBIVHFXs=
ssl2-md5 Xr4ilOzQ4PCOq3aQ0qbuaQ==
ssl3-md5 Xr4ilOzQ4PCOq3aQ0qbuaQ==
ssl3-sha1 5en6G6MezRroT3XKqkdPOmY/BfQ=
whirlpool 4GG4emdK44gOFZq1XtNdbF6Kau+sarCDBKUFiAGNN3soY5uxX97t8AbVfkX3tCmObf786vfJLIJqcI/m0RVusw==

Sharpening the Ax Before Chopping Down a Tree

I was helping to examine a server that was impacted by Heartbleed. According to the developer who was patching the server, he had updated the OpenSSL library to the following:

1
2
3
4
5
6
7
8
9
10
11
12
$ openssl version -a
OpenSSL 1.0.1g 7 Apr 2014
built on: Fri Apr 18 11:04:34 EDT 2014
platform: linux-x86_64
options: bn(64,64) rc4(16x,int) des(idx,cisc,16,int) idea(int)
blowfish(idx)
compiler: gcc -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H
-Wa,--noexecstack -m64 -DL_ENDIAN -DTERMIO -O3 -Wall -DOPENSSL_IA32_SSE2
-DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m
-DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM
-DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM
OPENSSLDIR: "/usr/ssl"

And the developer claimed: “According to http://heartbleed.com/. OpenSSL 1.0.1g is NOT vulnerable. Also I have restarted all services on this server.”

So, OpenSSL has been updated and the all services have been restarted, but why does the problem still persist?

I took a look at the command history he ran:

1
2
3
4
5
6
7
8
9
10
wget http://www.openssl.org/source/openssl-1.0.1g.tar.gz
ls
tar xvzf openssl-1.0.1g.tar.gz
cd openssl-1.0.1g/
sudo ./config --prefix=/usr
sudo make
sudo make install
exit
openssl version -a
sudo reboot

The OpenSSL library has been built from the source, which is fine, but the problem is that the Nginx server was still using the old library distributed by Ubuntu:

1
2
3
4
$ ldd `which nginx` | grep ssl
libssl.so.1.0.0 => /lib/x86_64-linux-gnu/libssl.so.1.0.0 (0x00007fafe82a3000)
$ strings /lib/x86_64-linux-gnu/libssl.so.1.0.0 | grep '^OpenSSL '
OpenSSL 1.0.1c 10 May 2012

In effect, there were two versions of OpenSSL library installed in the system, one was built from the source, and another one was managed by dpkg:

1
2
3
4
$ dpkg -l openssl
||/ Name Version Architecture
+++-=============================-===================-===================
ii openssl 1.0.1c-4ubuntu8.2 amd64

However, the bigger problem is the version of the operating system:

1
2
3
4
5
6
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 13.04
Release: 13.04
Codename: raring

Ubuntu 13.04 is not supported anymore according to https://wiki.ubuntu.com/Releases. The developer probably issued apt-get upgrade, but nothing to be updated, because Ubuntu stopped supporting the release. Therefore, no security update. And Ubuntu 13.04 is not listed in Ubuntu Security Notice USN-2165-1. So, the developer opted for building the library from the source. After installation from the source, the binary openssl was overridden by the source build, and the command openssl version showed the latest and patched version 1.0.1g.

To fix the problem, we need to reinstall the package first:

$ sudo apt-get install --reinstall openssl

Now, this will revert control back to apt-get and overwrite the binary /usr/bin/openssl:

1
2
3
4
5
6
7
$ openssl version -a
OpenSSL 1.0.1c 10 May 2012
built on: Wed Jan 8 20:51:55 UTC 2014
platform: debian-amd64
options: bn(64,64) rc4(16x,int) des(idx,cisc,16,int) blowfish(idx)
compiler: cc -fPIC -DOPENSSL_PIC -DZLIB -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -m64 -DL_ENDIAN -DTERMIO -g -O2 -fstack-protector --param=ssp-buffer-size=4 -Wformat -Werror=format-security -D_FORTIFY_SOURCE=2 -Wl,-Bsymbolic-functions -Wl,-z,relro -Wa,--noexecstack -Wall -DOPENSSL_NO_TLS1_2_CLIENT -DOPENSSL_MAX_TLS1_2_CIPHER_LENGTH=50 -DMD32_REG_T=int -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM
OPENSSLDIR: "/usr/lib/ssl"

And then we must perform the distribution upgrade to the latest long term support version, in order to continue receiving updates.

The lesson I have learned from this is that if you are going the wrong direction, no matter how hard you work, you are not going to make it. Make sure to take the initial investment, and really understand the true cause of the problem before attempting to resolve the issue. And don’t blindly follow the procedure. Understand it first, and adapt to your specific situation. As Abraham Lincoln once said:

“If I have nine hours to chop down a tree, I’d spend the first six sharpening my ax.”