# ACME Mini‑CA – Documentation This repository contains a **minimal‑viable ACME Certificate Authority** written in Go.\ It is *not* a full replacement for production CAs such as Boulder/PEBBLE, but it demonstrates every building‑block you need to issue X.509 certificates via [RFC 8555](https://datatracker.ietf.org/doc/html/rfc8555): - **ACME endpoints** (`newAccount`, `newOrder`, `challenge`, `finalize`, `certificate`, `revokeCert`) - **Challenge types**: `http‑01` *(default)* and `dns‑01` *(optional)* - **JWS request verification** – supports both *JWK* and *KID* modes - **OCSP responder** and **CRL** generation (optional) - **MySQL persistence** for all entities (accounts, orders, nonces, certs …) - **Pluggable DNS provider** API to publish/remove `_acme‑challenge.` TXT records --- ## 1  Architecture ```text ┌─────────────┐ HTTP ┌──────────────┐ │ ACME Client│ ──────────────►│ ACME Server │ └─────────────┘ │ (this repo) │ ├─── MySQL │ ├─── OCSP / CRL │ └──────────────┘ ▲ │ REST ┌───────┴────────┐ │ DNS Provider │ (optional) └────────────────┘ ``` *The server may sit behind a reverse‑proxy for TLS termination; however **`http‑01`** challenges are exposed directly by the embedded **`autocert.Manager`**.* --- ## 2  Build & Run ```bash # clone & build $ go build -o acme-ca . # set mandatory secrets export CA_CERT_PATH="./ca_cert.pem" export CA_KEY_PATH="./ca_key.pem" # run with sensible defaults $ ./acme-ca ``` ### 2.1  Environment variables | Variable | Default | Description | | --------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------ | | `PORT` | `8080` | HTTP listener port | | `MYSQL_DSN` | `root:root@tcp(localhost:3306)/acme?parseTime=true&multiStatements=true` | DSN in *Go SQL* format | | `ACME_ALLOWED_DOMAIN` | `example.com` | Hostname whitelist for `autocert` HTTP handler | | `DNS01_ENABLED` | `false` | When `true` the server issues additional `dns‑01` challenges | | `DNS_PROVIDER_URL` | *(empty)* | Base URL of your DNS provider’s TXT API | | `DNS_PROVIDER_TOKEN` | *(empty)* | Bearer token used in `Authorization` header | | `OCSP_ENABLED` | `false` | Expose `GET /ocsp/{serial}` endpoint | | `CRL_ENABLED` | `false` | Expose `GET /crl` endpoint | | `JWS_VERIFY_ENABLED` | `true` | Toggle signature verification (disable for local dev) | > **Note**   All optional modules can be enabled/disabled independently. ### 2.2  Database schema `init()` automatically executes the following DDL (simplified): ```sql nonces(id PK, created) accounts(id PK, jwk JSON) orders(id PK, payload JSON, status, finalize_url) authzs(id PK, order_id FK, identifier, status, payload JSON) challenges(id PK, authz_id FK, type, token, status, payload JSON) certs(id PK, serial, der LONGBLOB, revoked_at) ``` Add indices as required (e.g. `serial`, `token`). --- ## 3  ACME API surface | Method / Path | Purpose | | ------------------------------ | ---------------------------------------------- | | `HEAD /acme/new-nonce` | Issue fresh nonce (always) | | `POST /acme/new-account` | Register account – payload must be JWS‑signed | | `POST /acme/new-order` | Create order → returns `authz`/`finalize` URLs | | `GET /acme/authz/{id}` | Poll authorization status | | `POST /acme/challenge/{id}` | Client signals that challenge token is ready | | `POST /acme/finalize/{order}` | Upload CSR (base64url DER) | | `GET /acme/certificate/{id}` | Retrieve PEM certificate | | `POST /acme/revoke-cert` | Revoke certificate (base64url DER) | ### 3.1  Challenge flow 1. **Server → Client** `newOrder` response contains an `authz` URL. 2. **Client → Server** `GET /acme/authz/{id}` to fetch challenge details. 3. **Client** publishes token: - **http‑01**: `http:///.well-known/acme-challenge/{token}` = key‑auth - **dns‑01**: TXT `_acme-challenge.` = base64url(sha256(token)) - When `DNS01_ENABLED=true` the server automatically calls *DNS Provider* API. 4. **Client → Server** `POST /acme/challenge/{id}` signals readiness. 5. Server validates (**dns‑01** simply trusts provider) and sets *valid*. 6. Client uploads CSR via `finalize`, server issues certificate. ### 3.2  DNS Provider contract ```http POST DELETE Authorization: Bearer Content-Type: application/json { "fqdn": "_acme-challenge.example.com", "token": "" } ``` Return `200` to acknowledge; all other codes are logged only (non‑blocking). --- ## 4  OCSP & CRL - **OCSP** ↠ `GET /ocsp/{serial}` – returns DER `application/ocsp-response`.\ Serial numbers are looked‑up in `certs.revoked_at`. Status =`Revoked` if timestamp present. - **CRL**  ↠ `GET /crl` – regenerates PEM‑CRL on each request. Enable with the respective ENV flags. --- ## 5  JWS verification When `JWS_VERIFY_ENABLED=true` every ACME POST body must be a [JOSE JWS](https://datatracker.ietf.org/doc/html/rfc7515) JSON serialization. - **New‑Account**: payload includes a `jwk` → key is saved under new `account.id`. - **Subsequent requests**: use `kid` header pointing to `account` URL. - Nonce‑reuse or invalid signatures return `4xx`. --- ## 6  Security & Hardening checklist | Area | Recommendation | | --------------- | ---------------------------------------------------------------------------------- | | **CA key** | Store in HSM / KMS, *never* plaintext. | | **TLS** | Run behind reverse‑proxy (Nginx, Traefik) or enable `tls.Config` on `http.Server`. | | **Rate‑limits** | Use middleware (e.g. `golang.org/x/time/rate`). | | **Audit** | Stream structured logs to SIEM; DB triggers for cert changes. | | **Backup** | Full MySQL dumps; optionally S3 for issued certs. | | **Monitoring** | Export Prometheus metrics: issued / revoked / OCSP hits. | --- ## 7  Testing A quick smoke‑test with **Pebble** (Let’s Encrypt dev CA): ```bash # run Pebble $ docker run --rm -p 14000:14000 letsencrypt/pebble -config /test/config.json # point Pebble at your Mini‑CA PEBBLE\_ALTERNATE\_ROOTS= export PEBBLE\_V2\_CA="http://localhost:8080/acme" # use `lego` client $ lego -m you@example.com --pem --server http://localhost:8080/acme --accept-tos -d sub.stadt-hilden.de run ``` --- ## 8  Future improvements - OCSP stapling & cache headers - PostgreSQL + GORM migrations - Automatic key‑rollover for CA - ACME‑v3 *STAR* certificates (short‑term re‑use) - Proper background validator for `dns‑01` via `dig TXT` lookup --- © 2025 — Released under the **Apache‑2.0** License