0

I'm working on the deployment of our LetsEncrypt SSL certificates. I have cron jobs that run daily and renew the certificatse if needed using certbot. I also have a script that will deploy the certificate to the load balancer.

Certbot has a deploy callback when a cert is renewed. Certbot will call my script immediately after renewing a certificate. That seems to work fine -- mostly. However, if the deployment fails for any reason, I'd like it to try again the next day. It doesn't look like there is any functionality built into certbot for this.

How do I implement a script that has the following logic:

if [ /etc/letsencrypt/live/example.com/cert.pem != certfrom https://example.com/ ]
then
    example.com-cert-deploy.sh
fi

2 Answers2

2

You can use something like:

$ openssl s_client -showcerts -servername example.com -connect example.com:443 </dev/null

...which among other things will output the server certificate. You can parse that out, save it, and then diff with the other on-disk cert.

EEAA
  • 109,363
  • 18
  • 175
  • 245
  • When I run that: `unknown option -serverame` – Stephen Ostermiller Jun 19 '17 at 13:21
  • Typo...try again :) – EEAA Jun 19 '17 at 13:21
  • That shows good info, but it looks nothing like the contents of `cert.pem` – Stephen Ostermiller Jun 19 '17 at 13:22
  • Edit your question to provide both the output of the above command as well as your cert.pem. – EEAA Jun 19 '17 at 13:23
  • My cert.pem is my private certificate, I'm not putting that in my question. It only has PEM encoded stuff in it. I need a way to extract the same exact stuff from it as from the public facing server. Looks like I'd need to get the subject, issuer, and expiration dates out of both of them to compare – Stephen Ostermiller Jun 19 '17 at 13:24
  • 1
    Your certificate is surely not private - by definition the certificate *must* be public for it to be of any use. The contents of your server certificate *is* in the output of the above command. – EEAA Jun 19 '17 at 13:26
  • To be clear, the `s_client` command I gave you **does** "extract" the certificate from a public server, just like your browser does every time you visit a TLS-protected site. – EEAA Jun 19 '17 at 13:33
  • /etc/letsencrypt/live/example.com/cert.pem is the one that gets installed into the webserver, Anybody who gets their hands on it can run an SSL site for that domain name. – Stephen Ostermiller Jun 19 '17 at 13:35
  • With all due respect, @StephenOstermiller, you're incorrect and you have a fundamental misunderstanding on how TLS works. Two files are required for TLS. One is a key, which is truly private. That is your privkey.pem file, if you're using standard Let's Encrypt naming conventions. The other is your cert chain, which is your cert.pem. As I've mentioned, your certificate is *public*. Your **key** is not, and I'd never ask you to provide that. – EEAA Jun 19 '17 at 13:39
  • OK, that makes sense. I think I'm making some progress on this based on your command. It looks like `openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -fingerprint -noout` gives me a "fingerprint" for the local cert. And `echo | openssl s_client -showcerts -connect example.com:443 -servername example.com 2>&1 | openssl x509 -fingerprint -noout` gives me a fingerprint for the remote cert. Is it enough just to compare those? – Stephen Ostermiller Jun 19 '17 at 13:42
  • 1
    Yes, comparing the fingerprints should be sufficient for your purposes. – EEAA Jun 19 '17 at 13:42
0

It is enough to compare the "fingerprint" of the local certificate against the live certificate.

The two fingerprints can be obtained by the following two commands:

  • openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -fingerprint -noout
  • echo | openssl s_client -showcerts -connect example.com:443 -servername example.com 2>&1 | openssl x509 -fingerprint -noout

To make the comparison easier, I put them into a script with error checking. It accepts a domain name as an argument and exits with a failure status if the two differ. It can be used like:

if ! ./lets-encrypt-installed-test.sh --quiet example.com
then
    ./example.com-cert-deploy.sh
fi

Here is the full contents of the script:

#!/bin/sh

set -e 

quiet=0

for var in "$@"
do
    case "$var" in
        -q)
            quiet=1
            ;;
        --quiet)
            quiet=1
            ;;
        -*)
            echo "Unexpected argument $var" >&2
            exit 1
            ;;
        *.*)
            domain="$var"
            ;;
        *)
            echo "Expected argument $var" >&2
            exit 1
            ;;
    esac
done

if [ "z$domain" == "z" ]
then
    echo "Expected domain as parameter to $0" >&2
    exit 1
fi

lefile="/etc/letsencrypt/live/$domain/cert.pem"

if [ ! -e $lefile ]
then    
    echo "Lets Encrypt file does not exist: $lefile" >&2
    exit 1
fi

leprint=`openssl x509 -in $lefile -fingerprint -noout`

case "$leprint" in
    *Fingerprint*)
        ;;
    *)
        echo "No fingerprint from $lefile" >&2
        exit 1
        ;;
esac

liveprint=`echo | openssl s_client -showcerts -connect "$domain":443 -servername "$domain" 2>&1 | openssl x509 -fingerprint | grep -i fingerprint`

case "$liveprint" in
    *Fingerprint*)
        ;;
    *)
        echo "No fingerprint from SSL cert of https://$domain/" >&2
        exit 1
        ;;
esac

if [ "$leprint" != "$liveprint" ]
then
    if [ "$quiet" == "0" ]
    then
        echo "Fingerprints for local and remote SSL certificates differ:" >&2
        echo "$lefile: $leprint" >&2
        echo "https://$domain/: $liveprint" >&2
    fi
    exit 1
fi 

exit 0