Saturday, February 15, 2020

Using acme.sh to generate a Let's Encrypt certificate

Like almost any individual on Earth involved a little bit in open-source, I use Let's Encrypt to generate my web certificates.

The first client I used for this was acme-client. It was conveniently available as FreeBSD's security/acme-client port but it has been removed. The reason, as far as I understand, is a bit silly: acme-client has been imported in OpenBSD and is now maintained there. The code on the original website is not updated anymore so the port has been removed. (It's a bit more work to maintain a tool from OpenBSD as a port, as someone needs to manually extract it from time to time and store it somewhere, I guess that's the fundamental reason.)

Anyway I had to find something else and after a bit of googling, I found a replacement: acme.sh, which is packaged as security/acme.sh. At first this is a bit scary (7000 lines of shell script, but well people have reported it works well and it's simple, so I gave it a try.

So here is how set it up (note that the ordering is important):

  1. First you need to configure you web server so that Let's Encrypt's service can read the challenge and verify the domain you claim is indeed yours. In my case, with Nginx:
        # This host only exist on port 443. Just accept Let's Encrypt challenges
        # in plain text and redirect anything else to the port 443.
        server {
            listen       80;
            server_name  foo.chchile.org;
            access_log   /var/log/nginx/foo-access.log;
            error_log    /var/log/nginx/foo-error.log info;
            allow all;
    
            root   /var/empty;
    
            location ^~ /.well-known/acme-challenge/ {
                alias /usr/local/www/acme/.well-known/acme-challenge/;
            }
    
            location / {
                return  301 https://foo.chchile.org$request_uri;
            }
        }
        
  2. Then you can run acme.sh to issue the certificate. It's really straightforward:
    # /usr/local/sbin/acme.sh  --issue -d foo.chchile.org -w /usr/local/www/acme
                            
    [Sat Feb 15 21:40:18 UTC 2020] Create account key ok.
    [Sat Feb 15 21:40:18 UTC 2020] Registering account                                                                                           
    [Sat Feb 15 21:40:19 UTC 2020] Registered                                                                                                    
    [Sat Feb 15 21:40:19 UTC 2020] ACCOUNT_THUMBPRINT='...'
    [Sat Feb 15 21:40:19 UTC 2020] Creating domain key
    [Sat Feb 15 21:40:19 UTC 2020] The domain key is here: /root/.acme.sh/foo.chchile.org/foo.chchile.org.key                        [Sat Feb 15 21:40:19 UTC 2020] Single domain='foo.chchile.org'                                                                         [Sat Feb 15 21:40:19 UTC 2020] Getting domain auth token for each domain                             
    [Sat Feb 15 21:40:20 UTC 2020] Getting webroot for domain='foo.chchile.org'
    [Sat Feb 15 21:40:20 UTC 2020] Verifying: foo.chchile.org
    [Sat Feb 15 21:40:24 UTC 2020] Success
    [Sat Feb 15 21:40:24 UTC 2020] Verify finished, start to sign.
    [Sat Feb 15 21:40:24 UTC 2020] Lets finalize the order, Le_OrderFinalize: https://acme-v02.api.letsencrypt.org/acme/finalize/78264375/2343222
    453
    [Sat Feb 15 21:40:25 UTC 2020] Download cert, Le_LinkCert: https://acme-v02.api.letsencrypt.org/acme/cert/04236a2f1bd00075bda7c3ed8bb9d2953b0
    0
    [Sat Feb 15 21:40:25 UTC 2020] Cert success.
    -----BEGIN CERTIFICATE-----
    [...]
    -----END CERTIFICATE-----
    [Sat Feb 15 21:40:25 UTC 2020] Your cert is in  /root/.acme.sh/foo.chchile.org/foo.chchile.org.cer 
    [Sat Feb 15 21:40:25 UTC 2020] Your cert key is in  /root/.acme.sh/foo.chchile.org/foo.chchile.org.key 
    [Sat Feb 15 21:40:25 UTC 2020] The intermediate CA cert is in  /root/.acme.sh/foo.chchile.org/ca.cer 
    [Sat Feb 15 21:40:25 UTC 2020] And the full chain certs is there:  /root/.acme.sh/foo.chchile.org/fullchain.cer 
        
  3. Now, let's configure the desired certificate paths in Nginx (there's not there yet, this will be the next step):
       server {
            listen       443 ssl;
            server_name  foo.chchile.org;
            access_log   /var/log/nginx/foo-access.log;
            error_log    /var/log/nginx/foo-error.log debug;
    
            ssl_certificate      /usr/local/etc/ssl/acme/foo.chchile.org/fullchain.pem;
            ssl_certificate_key  /usr/local/etc/ssl/acme/private/foo.chchile.org/privkey.pem;
    
            ssl_session_timeout  5m;
    
            ssl_protocols  TLSv1.1 TLSv1.2;
            ssl_ciphers  HIGH:!aNULL:!MD5;
            ssl_prefer_server_ciphers   on;
    
            [...]
        }
        
  4. It's almost done. acme.sh uses /root/.acme.sh as its work space. The certificate and the key are there but it's not advised to use them as is since the internal layout may chance in the future. So you need to install (read "copy") the certificate:
    # /usr/local/sbin/acme.sh --install-cert -d foo.chchile.org \
      --key-file /usr/local/etc/ssl/acme/private/foo.chchile.org/privkey.pem \
      --cert-file /usr/local/etc/ssl/acme/foo.chchile.org/cert.pem \
      --fullchain-file /usr/local/etc/ssl/acme/foo.chchile.org/fullchain.pem \
      --reloadcmd "service nginx restart"
    [Sat Feb 15 21:42:24 UTC 2020] Installing cert to:/usr/local/etc/ssl/acme/foo.chchile.org/cert.pem
    [Sat Feb 15 21:42:24 UTC 2020] Installing key to:/usr/local/etc/ssl/acme/private/foo.chchile.org/privkey.pem
    [Sat Feb 15 21:42:24 UTC 2020] Installing full chain to:/usr/local/etc/ssl/acme/foo.chchile.org/fullchain.pem
    [Sat Feb 15 21:42:24 UTC 2020] Run reload cmd: service nginx restart
    Performing sanity check on nginx configuration:
    nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok
    nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful
    Stopping nginx.
    Waiting for PIDS: 29714.
    Performing sanity check on nginx configuration:
    nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok
    nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful
    Starting nginx.
    [Sat Feb 15 21:42:24 UTC 2020] Reload success
        
  5. And finally, set up a cron job to renew the certificate whenever it's needed. That's where acme.sh is neat, everything you typed above has been recorded in /root/acme.sh, so the crontab(5) line just looks like:
    0       0       *       *       *       root    /usr/local/sbin/acme.sh --cron > /dev/null
        

Blog re-purpose

It's a been a long time since my last post.

My life has changed a lot since then and I don't have much time anymore to play with my computer for fun, even less to write articles to share my knowledge and learning. Hence from now on, when I will post something this will be mostly as a memo for myself for future use. Since it's public, I will still try to make the posts look decent but the style will be much more terse.

So why keep posting at all? I doubt I have any reader anyway. But maybe sometimes someone will be desperate enough to go to Google's 10th result page and will come across my blog. If this can help this soul, I'll be more than happy!