Let's Encrypt for IRC Networks: A Deployment Guide

I wrote this as a rough draft a while ago and forgot to publish it. I'm publishing it as is in the hope that it will be of use to IRC networks which would like to take advantage of Let's Encrypt.

Traditionally when IRC networks have offered TLS they have neglected to do so with CA-signed TLS certificates, instead using self-signed certificates. For this reason, many IRC clients don't even bother to try and validate the certificates used by IRC networks. Of course, since IRC networks are run for free and clients don't bother to verify certificates anyway, paying money to obtain certificates would be of dubious value.

However, with the availability of Let's Encrypt, this no longer need be the case. If you have a server accessible at some domain, you can get a certificate for that domain.

My own Let's Encrypt client, acmetool is suitable for a wide range of deployment scenarios. There are some quirks to common IRC network configuration, however, which make things a little more complicated.

Most IRC networks have a round-robin domain name such as irc.example.net, and specific server hostnames such as alpha.example.net. They may also have geographic domains such as irc.us.example.net or address family specific domains such as ipv6.irc.example.net.

It's easy for a server to obtain a certificate for itself for a name which always points to it and only it. However, for a server to obtain a certificate for irc.example.net is more difficult. Validation HTTP requests or TLS connections from an ACME service such as Let's Encrypt will often go to a different server, and for a network with many servers, there is not even necessarily any guarantee that the IP of the server will be returned in the round robin response.

There are, then, several possible methods of allowing individual servers in an IRC network to prove control of irc.example.net.

Methods

Method 1: HTTP with Redirection

An interesting feature of the ACME protocol's HTTP-based validation is that, while it always makes requests to http://DOMAIN/.well-known/acme-challenge/TOKEN, it does support redirects, including redirects to other domains, even to HTTPS URLs.

If irc.example.net resolves to potentially any server in the network, then every server could be configured to run a small HTTP server which redirects requests for paths under /.well-known/acme-challenge to a central host.

This central host could then respond to challenges appropriately. One option would be to have individual servers inform this central host about challenges and the correct responses before initiating the challenge. However, this requires a channel of communication between individual servers and some central machine, and this channel must be securely authenticated. If this authentication fails to be effective, arbitrary entities can obtain certificates for irc.example.net. This is a risky approach.

However, it turns out that the ACME HTTP challenge can be implemented statelessly, in a way that nominates the thumbprint of a given ACME account key.

Given a request path of /.well-known/acme-challenge/TOKEN, one is expected to respond with TOKEN.ACCOUNT-KEY-THUMBPRINT. This means that the server which responds to such a request can endorse one, and only one, account key in a fully stateless manner.

One option would be to have every server use the same account key, but this is dubious as it allows one server to impersonate another, making abuse hard to track. For example, if the operator for foo.example.net turned rogue, they could still issue certificates for alpha.example.net, and it would be difficult to identify who was responsible for the issuance of these certificates. It should be possible to securely remove individual servers from the network without requiring every other server to create a new account key.

A better option would be to have a preconfigured list of account keys on the central server. The server will need to know ahead of time which account key thumbprint should be endorsed for a given token; a simple protocol could be constructed whereby an individual server calls the central server with its token and account key thumbprint. The central server checks the thumbprint is on its list, so this protocol would not even need any authentication. If there is concern that this protocol may be misimplemented, for example by people lazily (and disastrously) accepting any account key thumbprint, the protocol can be revised to prevent this by presenting a hash of the account key thumbprint instead; when the central server then presents the account key thumbprint in its challenge response, this requires prior knowledge of the account key thumbprint, making the protocol resistant to misimplementation.

Method 2: DNS

DNS challenges can also be used. This requires the ability to update the zone files of nameservers in an automated fashion. Obviously an individual server must not be able to make arbitrary changes to a zone file. The only change a server should be allowed to make is to temporarily add a record of the form _acme-challenge.irc.example.net. IN TXT "..."

There are arbitrary ways this might be enabled. It is unlikely that most DNS servers supporting DNS UPDATE will allow this granularity of control. A simple HTTP protocol could be implemented allowing servers to update DNS in this constrained way. This protocol could be translated to DNS UPDATE internally, or any other internal protocol for modifying zone files.

There is once again the risk that a bad implementation of this protocol could accept any challenge value, thereby authorizing arbitrary entities. This is trickier to fix because the DNS challenge value is a hash of the (token, account key thumbprint) tuple, rather than the tuple itself; thus, only if the token (and preferably the account key thumbprint) is known can the correspondance of the challenge value to an approved account key thumbprint be ascertained. The protocol then should probably be based on specifying the token and the account key thumbprint (or a hash thereof) separately, and allowing the central server to construct the appropriate challenge value. A hash of the correct challenge value could be provided by the individual server, which would be verified by the central server to equal its own conclusion and may be useful for debugging.

Implementation using acmetool

acmetool supports the use of hook scripts to customize the challenge process. Typically, these are shell scripts, but they can be any executable program. They are supported for HTTP challenges, and they are the only way in which acmetool supports DNS challenges, due to the diversity of arrangements for modifying zonefiles remotely.

Implementing the HTTP method

Here is a rough example of an acmetool hook script to implement the HTTP method.

#!/bin/bash
set -e

case "$1" in
  challenge-http-start|challenge-http-stop)
    HOSTNAME="$1"
    TOKEN="$3"
    # The challenge body is passed on stdin and is of the form
    # TOKEN.ACCOUNT-KEY.
    ACCOUNT_KEY_THUMBPRINT="$(cut -d. -f2)"
    [ "$HOSTNAME" == "irc.example.net" ] || exit 42
    curl "https://central.example.net/$1" \
      -d "akth=$(echo "$ACCOUNT_KEY_THUMBPRINT" | sha256sum | cut -d' ' -f1)" \
      -d "token=$TOKEN"
  *) exit 42;;
esac

Remember that in order for this to work, all IRC servers must redirect HTTP requests, or at least HTTP requests for /.well-known/acme-challenge/, to the central server.

Implementing a backend for this would not be hard; you can use whatever you like. I might augment acmetool's built in HTTP redirector with such functionality; see the TODO list below.

Implementing the DNS method

Currently, acmetool passes the final challenge value to be placed in DNS to a hook script. It does not provide the individual token and account key thumbprint values used to construct that challenge value. I should rectify this.

TODO

(I'm always interested in information on how acmetool could be rendered amenable to new use cases. Feel free to create an issue or e. mail me with your experiences.)