acme-proxy 0.2.0

An ACME (RFC 8555) server that issues from a local CA, relays to an upstream CA, or delegates to a script
Documentation
# TLS-ALPN-01

Defined by **RFC 8737**. The client proves control by answering a TLS handshake
on port 443 with a special self-signed certificate, rather than by serving
anything over HTTP.

Its appeal is operational: validation happens entirely within the TLS layer, so
a host that terminates TLS but serves no plain HTTP at all can still be
validated, and nothing needs to be routed on port 80.

## How it works

1. The client is given a random `token` and computes the key authorization as
   usual.
2. It generates a **self-signed** certificate that contains:
   - a single `dNSName` SAN equal to the identifier being validated, and
   - a **critical** `id-pe-acmeIdentifier` extension (OID
     `1.3.6.1.5.5.7.1.31`) whose value is `SHA256(keyAuthorization)`.
3. It arranges for that certificate to be presented when a handshake arrives
   with SNI set to the identifier **and** ALPN protocol `acme-tls/1`.
4. `acme-proxy` performs that handshake and inspects the certificate it gets
   back.

The proof is verified without ever completing an application-layer exchange: the
certificate presented during the handshake *is* the answer.

## What `acme-proxy` checks

- Exactly **one** `dNSName` SAN, matching the identifier. Not "at least one".
- The `id-pe-acmeIdentifier` extension is present and marked **critical**, as
  RFC 8737 requires.
- Its value equals the expected digest, compared in **constant time**.

The presented certificate is otherwise untrusted by design — it is self-signed
and issued by the entity being challenged, so there is no chain to validate.

## Configuration

```toml
[challenge]
enabled = ["tls-alpn-01"]

[challenge.tls_alpn_01]
port = 443
```

### Reference

**`port`** (`Integer`) — *Default: `443` | Env: `ACME_PROXY_CHALLENGE__TLS_ALPN_01__PORT`*

Port the validation handshake connects to. RFC 8737 fixes this at 443 for the
public Internet; it is configurable here for internal deployments that cannot
bind it.

## Client support

Support is thinner than for the other two types:

- **lego** implements a responder (`--tls`). This is what the project's
  end-to-end suite uses.
- **certbot** and **acme.sh** do **not** implement a `tls-alpn-01` responder.
  Listing this type is harmless for them — they simply pick another enabled type
  — but it cannot be their only option.
- Servers that manage their own certificates, such as Caddy and Traefik,
  generally do support it natively.

If `tls-alpn-01` is the only entry in `challenge.enabled`, certbot and acme.sh
clients will not be able to complete an order at all.

## Limitations

- **No wildcards.** Use [`dns-01`]dns_01.md.
- Requires inbound connectivity from `acme-proxy` to the client on `port`, the
  same constraint as [`http-01`]http_01.md.
- The responder must serve the ACME certificate **only** for the `acme-tls/1`
  ALPN protocol, and its normal certificate otherwise. Serving it
  unconditionally would break ordinary traffic to that host for the duration.

## Troubleshooting

| Event | Meaning |
| --- | --- |
| `challenge_tls_alpn_01_loaded` | The handshake completed and a certificate was obtained. |
| `challenge_tls_alpn_01_matched` | The certificate carried the right digest. Validation passed. |
| `challenge_validation_failed` | Handshake failed, or the certificate did not satisfy the checks above. |

A handshake that fails outright usually means the responder did not negotiate
`acme-tls/1` — many TLS servers simply fall through to their normal certificate
when they do not recognise the ALPN protocol, and the certificate then has no
`id-pe-acmeIdentifier` extension to find.