Monitor your HTTPS certificate expiry with this script

Shobhit🎈✨ - May 19 '18 - - Dev Community

Last year's new years eve, I got a call from my client. They said their website was infected by a virus and no one can access it.

Now, my client runs a juice shop, and had no idea about how the web technically works, so I discarded the "virus" issue but he said site can't be accessed so I fired up Firefox in my phone and I saw the Your connection is not secure page.

Ever since Let's Encrypt came out of beta, I've used it to convert all my and my clients' sites to secure connections via HTTPS. I had set up a cron as instructed by certbot to renew the certificates regularly, but it used to fail every once in a while because I didn't update the python packages, or something like that. Let's Encrypt is kind enough to send a mail before expiring, but initial installations were done by an employee.

Since it was 31st of December, I was at a party and no where near my machine with which I could ssh into the server and slay this beast. I had JuiceSSH on my phone using which I SSHed into the server (thank god I had added my keys as authorized), updated the packages and renewed the certificates.

All this while people around me were counting backwards from 10.

Not taking a pledge

This wasn't even the first time this was happening so I decided then and there that I am going to solve this problem for myself. I carefully chose not to make it my new year's resolution otherwise I'll be telling the same story next year.

So on 1st January, I wrote a Ruby script (more about which in a minute) and kept improving it to catch all sorts of red flags with HTTPS installations including if a certificate is revoked, affected by the Symantec mess, is self-signed or is past/near the expiry date, validating the chain of trust, etc. The script eventually converted into a nice little piece of web app called Monitor Certificates, but let's get back to talking about the script now.

Getting the certificate

First thing you'll need to do is procure the certificate from a domain. Surprisingly, this was difficult to achieve. I am a big fan of HTTParty for making requests with Ruby, but unfortunately I couldn't find a way to get it to return certificates.

So I went one layer down the abstraction and used Net::HTTP. To get a certificate, you have to run



require 'net/http'
require 'openssl'

domain_name = "example.com"

uri = URI::HTTPS.build(host: domain_name)
response = Net::HTTP.start(uri.host, uri.port, :use_ssl => true)
cert = response.peer_cert


Enter fullscreen mode Exit fullscreen mode

This gives you an object of certificate in the cert variable. This variable is of the type OpenSSL::X509::Certificate. These are called X.509 certificates, and they are the standard for public key certificates.

X.509 certificates give us a lot of details about the certificate including who issued it, when it was issued, when it will expire, how to find if it is revoked, and a plethora of other useful information.

Finding the expiry dates

To find the date when the certificate will be invalid, you just need to call cert.not_after. This returns a Time object which tells us when the certificate will expire.

So if you want to be notified everyday for 2 weeks before the certificate is supposed to expire, your script will look like -



require 'net/http'
require 'openssl'

domain_name = "example.com"

uri = URI::HTTPS.build(host: domain_name)
response = Net::HTTP.start(uri.host, uri.port, :use_ssl => true)
cert = response.peer_cert

two_weeks = 14 * 86400 # 14 * One Day

if Time.now + two_weeks > cert.not_after
  # send reminders
end


Enter fullscreen mode Exit fullscreen mode

Reminding your self

You can use lots of things to send these reminders. I've now implemented SMS, Emails (to multiple people), Slack and Stride in my app.

I have some friends who use this script to push mobiles notifications to themselves (using Pusher), web notifications, geckoboard updates, adding to To-Do lists, adding to trello boards, etc. Any communication platform is fair game.

For example, if you're on a Mac, there's a neat little command you can use to send a notification to yourself.

So I'll incorporate that command in our script to send us notifications.



require 'net/http'
require 'openssl'

domain_name = "example.com"

uri = URI::HTTPS.build(host: domain_name)
response = Net::HTTP.start(uri.host, uri.port, :use_ssl => true)
cert = response.peer_cert

two_weeks = 14 * 86400 # 14 * One Day

if Time.now + two_weeks > cert.not_after
  time_remaining = (cert.not_after - Time.now)
  days_remaining = time_remaining / 86400

  `osascript -e 'display notification "Certificate for #{domain_name} will expire in #{days_remaining.to_i} days. Please renew soon." with title "#{domain_name}"'`
end


Enter fullscreen mode Exit fullscreen mode

I removed the if condition and ran the script and the output looks like this today

dev.to certificate expiry reminder

Now all you need to do is add it to your crontab and it'll keep reminding you when your certificates are getting close to renewal.

Conclusion

Obviously, the script that I've shown you isn't the most elegant piece of software but it gets the job done. If you want to monitor multiple domains, just turn the script into a method and run the method against each element of your array of domain_names.

We've just scratched the surface here but it's a solid start to build your monitoring infrastructure and more features here to make sure your certificate hasn't been revoked, or if it's a paid certificate, who is the issuer.

I have added all this and more in my app to help keep on top of HTTPS issues.

Hope this was helpful for people like me who keep breaking their Let's Encrypt automation, or forgetting that a cert is up for renewal and management forgot to tell you that they are getting mails from vendors to issue a new one. 😁

For the next post, would you like me to show you how browsers determine if a certificate they are receiving has been compromised or revoked?

Happy Coding!

. . . . . .