DEV Community

Heddi Nabbisen
Heddi Nabbisen

Posted on • Originally published at obsd.solutions

OpenBSD acme-client For Let's Encrypt Certificates

Summary

OpenBSD's acme-client

acme-client is the default Automatic Certificate Management Environment (ACME) client on OpenBSD, installed at the same time when the OS is.

To configure acme-client.conf is easy and enables us to get certificates with Let's Encrypt or another.

The basic usage is:

# # configure
# nvim /etc/acme-client.conf

# # get or update (for renewal) certificate
# acme-client <domain>

# # revoke certificate
# acme-client -r <domain>
Enter fullscreen mode Exit fullscreen mode

How about CertBot

Besides, we know there is another option. Yes, CertBot by EFF (Electronic Frontier Foundation), a very popular client. Although we can get it via pkg_add certbot, there was sometimes a problem around permissions on OpenBSD when renewing the certificate.

Environment

  • OS: OpenBSD 7.0
  • ACME client: OpenBSD acme-client
  • Certificate Authority: Let's Encrypt
  • Web server: OpenBSD httpd

How to use

Configure acme-client.conf

Well, let's see the conf file which is officially provided by OpenBSD.

$ cat /etc/examples/acme-client.conf
Enter fullscreen mode Exit fullscreen mode

There are clear examples:

#
# $OpenBSD: acme-client.conf,v 1.4 2020/09/17 09:13:06 florian Exp $
#
authority letsencrypt {
    api url "https://acme-v02.api.letsencrypt.org/directory"
    account key "/etc/acme/letsencrypt-privkey.pem"
}

authority letsencrypt-staging {
    api url "https://acme-staging-v02.api.letsencrypt.org/directory"
    account key "/etc/acme/letsencrypt-staging-privkey.pem"
}

authority buypass {
    api url "https://api.buypass.com/acme/directory"
    account key "/etc/acme/buypass-privkey.pem"
    contact "mailto:me@example.com"
}

authority buypass-test {
    api url "https://api.test4.buypass.no/acme/directory"
    account key "/etc/acme/buypass-test-privkey.pem"
    contact "mailto:me@example.com"
}

domain example.com {
    alternative names { secure.example.com }
    domain key "/etc/ssl/private/example.com.key"
    domain full chain certificate "/etc/ssl/example.com.fullchain.pem"
    sign with letsencrypt
}
Enter fullscreen mode Exit fullscreen mode

Let's copy it:

$ # well, you can see there is none by default
$ ls /etc/acme-client*

$ doas cp -p /etc/examples/acme-client.conf /etc/
Enter fullscreen mode Exit fullscreen mode

Then edit:

$ doas nvim /etc/acme-client.conf
Enter fullscreen mode Exit fullscreen mode

like below:

- domain example.com {
-   alternative names { secure.example.com }
-   domain key "/etc/ssl/private/example.com.key"
-   domain full chain certificate "/etc/ssl/example.com.fullchain.pem"
-   sign with letsencrypt
- }
+ domain your.domain {    
+   #alternative names { secure.example.com }    
+   domain key "/etc/ssl/private/your.domain.key"    
+   domain full chain certificate "/etc/ssl/your.domain.fullchain.pem"    
+   sign with letsencrypt    
+ }
Enter fullscreen mode Exit fullscreen mode

OK. your.domain is now ready for "sign with letsencrypt".

Start OpenBSD httpd as web server

Be sure to httpd listens on egress:

server "your.domain" {
    listen on egress port 80
    # ...
}
Enter fullscreen mode Exit fullscreen mode

Well, you may also find the example of httpd.conf in /etc/examples/.

Of course, it has to be started:

$ # case disabled:
$ doas rcctl start -f httpd
$ # case enabled:
$ doas rcctl restart httpd
Enter fullscreen mode Exit fullscreen mode

Request certificate

Just run the single command below, and you will get the sweet certificate:

$ doas acme-client -v your.domain
Enter fullscreen mode Exit fullscreen mode

Well, -v is for verbose and optional.

There are other methods. How to update it:

$ # the same command above
$ doas acme-client your.domain
Enter fullscreen mode Exit fullscreen mode

Moreover, how to revoke it:

$ doas acme-client -r your.domain
Enter fullscreen mode Exit fullscreen mode

Notes

Periodical update is required

Remember Let's Encrypt certificate lasts only 90 days. You have to update it periodically.

One way to do so is to use cron.

Let's Encrypt has their Rate Limits

Be careful about that Let's Encrypt has several rate limits.

For example,

  • "Certificates per Registered Domain". It is 50 per week.

Also,

  • "Duplicate Certificate". You can renew the same certificate up to 4 times (totally 5 times) per week.

Here is their documentation on it.

Side story: The history and the changelog

acme-client first appeared in 6.1.

In 6.5 or below (to 6.1), you had to use more options to get certificate:

$ doas acme-client -ADv your.domain
Enter fullscreen mode Exit fullscreen mode

In 6.6, acme-client became to able to communicate with the v02 Let's Encrypt API. Therefore, -A and -D have not been necessary any more. Nice improvement.

Troubleshooting: When the challenge failed

You might sometimes see such errors and, sadly, fail:

acme-client: /etc/ssl/private/your.domain: generated RSA domain key
acme-client: https://acme-v02.api.letsencrypt.org/directory: directories
acme-client: acme-v02.api.letsencrypt.org: DNS: xxx.xxx.xxx.xxx
acme-client: dochngreq: https://acme-v02.api.letsencrypt.org/acme/authz-v3/84303399960
acme-client: challenge, token: (...), uri: https://acme-v02.api.letsencrypt.org/acme/chall-v3/84303399960/MQGXJQ, status: 0
acme-client: /var/www/acme/(...): created
acme-client: https://acme-v02.api.letsencrypt.org/acme/chall-v3/84303399960/MQGXJQ: challenge
acme-client: order.status 0
acme-client: dochngreq: https://acme-v02.api.letsencrypt.org/acme/authz-v3/84303399960
acme-client: challenge, token: (...), uri: https://acme-v02.api.letsencrypt.org/acme/chall-v3/84303399960/MQGXJQ, status: -1
acme-client: order.status -1
acme-client: dochngreq: https://acme-v02.api.letsencrypt.org/acme/authz-v3/84303399960
acme-client: Invalid response from http://your.domain/.well-known/acme-challenge/(...) [2606:4700:3031::6815:3ae6]: \\"\\u003c!DOCTYPE html\\u003e\\\\n\\u003chtml\\u003e\\\\n\\u003chead\\u003e\\\\n\\u003cmeta charset=\\\\\\"utf-8\\\\\\"\\u003e\\\\n\\u003ctitle\\u003e404 Not Found\\u003c/title\\u003e\\\\n\\u003cstyle type=\\\\\\"text/css\\\\\\"\\u003e\\u003c!--\\\\nbody { background-\\"
acme-client: bad exit: netproc(87417): 1
Enter fullscreen mode Exit fullscreen mode

It is 404 aka page-is-missing error. In other cases, it may 403 aka access-is-denied.

They are because your web server returned "bad response". Let's mitigate it by properly mapping response challenges:

  server "your.domain" {
    listen on egress port 80
+   # acme-client
+   location "/.well-known/acme-challenge/*" {
+       root "/acme"
+       request strip 2
+   }
    # ...
  }
Enter fullscreen mode Exit fullscreen mode

Then restart httpd:

$ # case disabled:
$ doas rcctl -f restart httpd
$ # case enabled:
$ doas rcctl restart httpd
Enter fullscreen mode Exit fullscreen mode

It must be fixed.

# acme-client -v your.domain
Enter fullscreen mode Exit fullscreen mode

I hope you meet success like this:

acme-client: https://acme-v02.api.letsencrypt.org/directory: directories
acme-client: acme-v02.api.letsencrypt.org: DNS: xxx.xxx.xxx.xxx
acme-client: dochngreq: https://acme-v02.api.letsencrypt.org/acme/authz-v3/84304202350
acme-client: challenge, token: (...), uri: https://acme-v02.api.letsencrypt.org/acme/chall-v3/84304202350/NVmlRA, status: 0
acme-client: /var/www/acme/(...): created
acme-client: https://acme-v02.api.letsencrypt.org/acme/chall-v3/84304202350/NVmlRA: challenge
acme-client: order.status 0
acme-client: dochngreq: https://acme-v02.api.letsencrypt.org/acme/authz-v3/84304202350
acme-client: challenge, token: (...), uri: https://acme-v02.api.letsencrypt.org/acme/chall-v3/84304202350/NVmlRA, status: 2
acme-client: order.status 1
acme-client: https://acme-v02.api.letsencrypt.org/acme/finalize/58118741/68735636600: certificate
acme-client: order.status 3
acme-client: https://acme-v02.api.letsencrypt.org/acme/cert/(...): certificate
acme-client: /etc/ssl/your.domain: created
Enter fullscreen mode Exit fullscreen mode

Conclusion

You have got the Let's Encrypt certificate! All what to do is add it to httpd.conf.

  server "your.domain" {
    listen on * port 80
    # acme-client
    location "/.well-known/acme-challenge/*" {
        root "/acme"
        request strip 2
    }
+   block return 301 "https://$SERVER_NAME$REQUEST_URI"
  }
+ server "your.domain" {
+   listen on * tls port 443
+   tls {
+       certificate "/etc/ssl/your.domain.fullchain.pem"
+       key     "/etc/ssl/private/your.domain.key"
+   }
+   # acme-client
+   location "/.well-known/acme-challenge/*" {
+       root "/acme"
+       request strip 2
+   }
+   # ...
+ }
Enter fullscreen mode Exit fullscreen mode

Here, I replaced egress with * to apply also to lo. It is optional.

Your TLS connection based on encryption aka HTTPS is completed.

Happy connecting securely🕊

Discussion (0)