Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

RFC Support Reference

This page documents every RFC that is relevant to Akāmu, explaining what each one specifies, which parts are implemented, and — for RFCs that are intentionally not implemented — why.

Summary

SpecificationTitleStatus
CA/B Forum BRCA/Browser Forum Baseline Requirements v2.xPartial
dns-persist-01Let’s Encrypt Persistent DNS ChallengeFull
draft-ietf-acme-profiles-01ACME Certificate ProfilesFull
RFC 9964ML-DSA for JSON Object Signing and Encryption (JOSE) and CBOR Object Signing and Encryption (COSE)Full
draft-ietf-lamps-pq-composite-sigs / draft-reddy-tls-composite-mldsaML-DSA Composite TLS Signature SchemesPartial (provisional code points)
RFC 7807Problem Details for HTTP APIsFull
RFC 8555Automatic Certificate Management Environment (ACME)Full
RFC 8659DNS Certification Authority Authorization (CAA)Full
RFC 8657CAA Extensions: accounturi and validationmethodsFull
RFC 8737ACME TLS-ALPN-01 Challenge ExtensionFull
RFC 8738ACME IP Identifier ValidationFull
RFC 8739ACME Short-Term, Automatically Renewed (STAR) CertificatesFull
RFC 8823ACME Extensions for S/MIME CertificatesFull
RFC 9444ACME for SubdomainsFull
RFC 9773ACME Renewal Information (ARI)Full
RFC 9799ACME Extensions for .onion Special-Use Domain NamesFull
RFC 5280X.509 Certificate and CRL ProfileFull
RFC 6960Online Certificate Status Protocol (OCSP)Full
RFC 9115ACME Profile for Delegated CertificatesFull
RFC 9447ACME Challenges Using an Authority TokenFull
RFC 9448ACME TNAuthList Authority TokenFull
draft-ietf-acme-authority-token-jwtclaimconACME Authority Token: JWTClaimConstraintsFull
RFC 9538ACME Delegation Metadata for CDNINot implemented
RFC 9891ACME DTN Node ID Validation (Experimental)Not considered

RFC 8555 — Core ACME

RFC 8555 is the foundation. It defines the full ACME protocol: the HTTP API, the JSON object model, the JWS (JSON Web Signature) authentication scheme, and the challenge validation framework.

What it covers

SectionFeatureStatus
§7.1Directory (GET /acme/directory)Yes
§7.2Nonces (HEAD /acme/new-nonce, GET /acme/new-nonce)Yes
§7.3Account creation and management (/acme/new-account, /acme/account/{id})Yes
§7.3.4externalAccountRequired enforcementYes
§7.4Order management (/acme/new-order, /acme/order/{id})Yes
§7.4.1Pre-authorization (POST /acme/new-authz)Yes
§7.1.3Honour order notBefore / notAfter in issued certificatesYes
§7.5Authorizations (/acme/authz/{id})Yes
§7.5.1Challenge response (/acme/chall/{authz}/{type})Yes
§7.4 finalizeCertificate issuance (/acme/order/{id}/finalize)Yes
§7.4.2Certificate download (/acme/cert/{id})Yes
§7.6Certificate revocation (/acme/revoke-cert)Yes
§7.3.5Account key rollover (/acme/key-change)Yes
§8.3http-01 challenge validationYes
§8.4dns-01 challenge validationYes

Pre-authorization (newAuthz)

Pre-authorization lets a client prove domain control ahead of any specific order. Once pre-authorized, the client can request multiple certificates for that domain (or its subdomains, if subdomainAuthAllowed is set) without repeating the challenge for each order.

POST /acme/new-authz
Content-Type: application/jose+json

payload: {
  "identifier": { "type": "dns", "value": "example.com" }
}

The response is identical to a reactive authorization created by newOrder.

External Account Binding

When server.external_account_required = true, every newAccount request must include an externalAccountBinding field. Requests without it are rejected with urn:ietf:params:acme:error:externalAccountRequired (HTTP 403).

EAB keys can be provisioned in two ways:

Static provisioning — keys are declared in the TOML configuration under [server.eab_keys] and loaded into the database at startup:

[server]
external_account_required = true

[server.eab_keys]
"kid-1" = "c2VjcmV0LWhtYWMta2V5LWJ1ZmZlcg"   # base64url-encoded raw key bytes
"kid-2" = "YW5vdGhlci1rZXktaGVyZQ"

GSSAPI self-service derivation — when [server].eab_master_secret is set, authenticated clients call GET /acme/eab (authenticating via Authorization: Negotiate or via a trusted reverse proxy supplying X-Remote-User). The server derives deterministic (kid, hmac_key) pairs using HKDF-SHA-256 (RFC 5869) keyed by (master_secret, principal), stores them in the eab_keys table on first request, and returns them to the client:

[server]
external_account_required = true
eab_master_secret = "<base64url-encoded 32-byte secret>"   # see configuration reference

The response JSON is {"principal":"…","kid":"…","hmac_key":"…","alg":"HS256"}. The client uses the returned kid and hmac_key to construct the externalAccountBinding JWS for newAccount. Once a kid has been consumed by an account registration, re-fetching GET /acme/eab for the same principal returns HTTP 409 Conflict.

Regardless of the provisioning method, the server performs full HMAC verification per RFC 8555 §7.3.4: it checks the kid, validates the algorithm and URL, verifies the HMAC signature, and confirms the EAB payload contains the account public key. Account creation and EAB key consumption happen atomically so that a key can never be used more than once even under concurrent requests.

Certificate validity window

If the newOrder request includes notBefore and/or notAfter fields, the issued certificate’s validity period will honour them, subject to the CA’s configured validity_days limit and a 5-minute clock-skew grace on notBefore.


RFC 8659 — CAA DNS Resource Record

RFC 8659 requires a CA to look up DNS Certification Authority Authorization (CAA) records before issuing a certificate. A domain owner can publish CAA records to restrict which CAs are allowed to issue certificates for that domain.

How Akāmu implements it

Before issuing any certificate, Akāmu queries CAA records for each DNS identifier in the order:

  1. It starts at the requested domain (e.g., sub.example.com) and walks up the DNS tree (example.com, com) until it finds a CAA record set or exhausts the tree.
  2. If no CAA records are found anywhere, issuance proceeds (unconstrained domain).
  3. If a CAA record set is found, Akāmu checks whether any issue record (or issuewild record for wildcard certs) contains one of the CA’s configured domain names (server.caa_identities).
  4. If none match, issuance is denied with urn:ietf:params:acme:error:caa (HTTP 403).

Configuration

[server]
caa_identities = ["acme.example.com"]

When caa_identities is empty (the default), CAA checking is disabled entirely.

Example CAA record

A domain owner who trusts only this Akāmu instance would publish:

example.com. IN CAA 0 issue "acme.example.com"

To also allow wildcard certificates:

example.com. IN CAA 0 issuewild "acme.example.com"

IP identifiers are not subject to CAA checking (CAA is a DNS mechanism).


RFC 8657 — CAA accounturi and validationmethods

RFC 8657 extends CAA with two optional parameters that give domain owners finer-grained control:

  • accounturi — Restricts issuance to a specific ACME account URI.
  • validationmethods — Restricts issuance to specific challenge types (e.g., only dns-01).

validationmethods

When Akāmu finds a matching issue or issuewild CAA record that contains a validationmethods parameter, it checks whether the challenge type used to validate the order appears in the list. If not, issuance is denied.

Example:

; Only allow dns-01 for this CA
example.com. IN CAA 0 issue "acme.example.com; validationmethods=dns-01"

With this record, an http-01-validated order for example.com would be denied at finalization time.

accounturi

When a matching issue or issuewild CAA record contains an accounturi parameter, Akāmu enforces it: the full ACME account URL of the requesting client (e.g. https://acme.example.com/acme/account/42) must match the parameter value exactly. If it does not match, the record is treated as non-authorizing and issuance is denied unless another record in the set authorizes it without an accounturi constraint.

Example:

; Only the named account may obtain a certificate from this CA
example.com. IN CAA 0 issue "acme.example.com; accounturi=https://acme.example.com/acme/account/42"

RFC 8737 — TLS-ALPN-01 Challenge

RFC 8737 defines the tls-alpn-01 challenge, which proves domain control by serving a specially crafted TLS certificate on port 443 using the ALPN protocol identifier acme-tls/1.

How it works

  1. Akāmu computes the SHA-256 of the key authorization.
  2. It opens a TLS connection to port 443 of the domain, advertising acme-tls/1 as the ALPN protocol.
  3. It verifies that the server presents a certificate with:
    • The domain as a dNSName SAN (exactly one SAN entry).
    • A critical id-pe-acmeIdentifier extension (OID 1.3.6.1.5.5.7.1.31) containing the SHA-256 hash of the key authorization as a DER OCTET STRING.
  4. For IP identifiers, the server connects directly to the IP address; the reverse-DNS name is used as the TLS SNI value.

Constraints

  • Port 443 must be reachable from the Akāmu server.
  • Wildcard identifiers cannot be validated with tls-alpn-01.
  • Both TLS 1.2 and TLS 1.3 are accepted.
  • RFC 8737 §3 requires exactly one SAN entry in the validation certificate. Certificates with multiple SANs are rejected.

RFC 8738 — IP Identifier Validation

RFC 8738 extends ACME to issue certificates for IP addresses (IPv4 and IPv6), not just domain names.

Supported identifier type

{ "type": "ip", "value": "192.0.2.1" }
{ "type": "ip", "value": "2001:db8::1" }

IPv4 values use dotted-decimal notation. IPv6 values use the compressed text representation defined in RFC 5952.

Supported challenge types for IP identifiers

ChallengeSupported
http-01Yes — connects directly to the IP; Host header is the IP address literal
tls-alpn-01Yes — connects to the IP; SNI uses the reverse-DNS name (e.g., 1.2.0.192.in-addr.arpa)
dns-01No — MUST NOT be used for IP identifiers per RFC 8738 §7
dns-persist-01No — DNS-based, not applicable to IP identifiers

RFC 8739 — ACME STAR

RFC 8739 (Short-Term, Automatically Renewed) allows a client to place a single order and receive a continuous stream of short-lived certificates without repeating domain validation. The CA automatically reissues each certificate before the previous one expires.

Use case

STAR is designed for scenarios where certificate revocation is unreliable. Instead of revoking a compromised certificate, the operator simply cancels the STAR order; the attacker’s window is limited to the remaining validity of the current short-lived certificate.

Another key use case is CDN delegation (see RFC 9115): the domain owner holds the STAR order and can revoke the CDN’s access at any time by canceling it.

Creating a STAR order

Include an auto-renewal object in the newOrder payload:

{
  "identifiers": [{ "type": "dns", "value": "example.com" }],
  "auto-renewal": {
    "start-date": "2025-01-01T00:00:00Z",
    "end-date":   "2025-12-31T00:00:00Z",
    "lifetime":   86400
  }
}
FieldRequiredDescription
end-dateYesThe latest date of validity of the last certificate issued (RFC 3339).
lifetimeYesValidity period of each certificate, in seconds.
start-dateNoThe earliest notBefore of the first certificate. Defaults to when the order becomes ready.
lifetime-adjustNoPre-dates each certificate’s notBefore by this many seconds (for clock-skew tolerance). Default: 0.
allow-certificate-getNoIf true, the rolling certificate URL can be fetched with an unauthenticated GET.

notBefore and notAfter must NOT be present in a STAR order.

Rolling certificate URL

After finalization, the order response includes a star-certificate URL instead of certificate:

{
  "status": "valid",
  "star-certificate": "https://acme.example.com/acme/cert/star/<order-id>"
}

GET /acme/cert/star/<order-id> always returns the currently active PEM certificate, along with Cert-Not-Before and Cert-Not-After HTTP headers matching the certificate’s validity window.

Canceling a STAR order

POST to the order URL with {"status": "canceled"} to stop automatic renewal:

POST /acme/order/<id>
{ "status": "canceled" }

Once canceled, the star-certificate endpoint returns HTTP 403 (autoRenewalCanceled). The currently active short-lived certificate continues to be usable until it expires naturally.

Server configuration

Advertise STAR capability in the directory by configuring minimum lifetime and maximum duration:

[server]
star_min_lifetime_secs = 86400     # 1 day minimum cert lifetime
star_max_duration_secs = 31536000  # 1 year maximum renewal period

When either field is set, the directory meta object includes the auto-renewal advertisement.


RFC 9444 — ACME for Subdomains

RFC 9444 allows a client to prove control of an ancestor domain (e.g., example.com) and then obtain certificates for any subdomain (e.g., api.example.com, www.example.com) without repeating the challenge for each one.

ancestorDomain in new orders

When placing an order for a subdomain, the client can declare which ancestor domain it controls:

{
  "identifiers": [
    {
      "type": "dns",
      "value": "api.example.com",
      "ancestorDomain": "example.com"
    }
  ]
}

Akāmu validates that ancestorDomain is a genuine ancestor (label-aligned DNS suffix) of the requested identifier. If accepted, the authorization challenge is issued against example.com rather than api.example.com.

subdomainAuthAllowed in pre-authorization

When pre-authorizing an ancestor domain, include subdomainAuthAllowed: true:

POST /acme/new-authz
{
  "identifier": { "type": "dns", "value": "example.com" },
  "subdomainAuthAllowed": true
}

The returned authorization object includes the same flag:

{
  "identifier": { "type": "dns", "value": "example.com" },
  "status": "valid",
  "subdomainAuthAllowed": true,
  ...
}

A client can reuse this authorization for any subsequent order that specifies ancestorDomain: "example.com".

Server advertisement

To advertise subdomain authorization support in the directory:

[server]
allow_subdomain_auth = true

This adds "subdomainAuthAllowed": true to the directory meta object.


RFC 8823 — S/MIME Certificates

RFC 8823 defines the email identifier type and the email-reply-00 challenge for issuing S/MIME end-user certificates. Proof of email address control is established via a DKIM-authenticated reply to a challenge email.

Identifier type

Orders may include {"type": "email", "value": "user@example.com"} identifiers. The server validates the format (non-empty local-part, non-empty domain, exactly one @, no wildcard prefix) and returns 400 unsupportedIdentifier for malformed addresses.

Challenge type

email-reply-00 is the only challenge offered for email identifiers. The challenge object includes a mandatory from field (the server’s validation address) in addition to the standard token and url fields:

{
  "type": "email-reply-00",
  "url": "https://acme.example.com/acme/chall/<id>",
  "status": "pending",
  "token": "<base64url(token-part2)>",
  "from": "acme-validation@example.com"
}

Two-channel token

The token is split across two channels per RFC 8823 §3:

  • token-part2 (≥128 bits): returned in the challenge JSON. The client stores it.
  • token-part1 (≥128 bits): sent by the server in the challenge email Subject: ACME: <base64url(token-part1)>. The client reads it from the email.

The client concatenates them: full_token = base64url(token-part1) || base64url(token-part2), then computes the key authorization and digest.

DKIM enforcement

RFC 8823 §3.2 requires that the DKIM d= tag on the reply email matches the domain of the From address. Akāmu enforces this via the webhook payload: dkim_domain must equal the domain portion of from, and dkim_status must be "pass".

DKIM verification itself is performed by the mail routing infrastructure (the webhook caller), not by Akāmu. The server trusts the dkim_domain and dkim_status fields in the webhook payload — secure HMAC authentication of the webhook endpoint is therefore essential.

Certificate requirements

Issued S/MIME certificates contain:

  • An rfc822Name Subject Alternative Name matching the validated email address.
  • The emailProtection Extended Key Usage (OID 1.3.6.1.5.5.7.3.4).

These are enforced at CSR validation time (the server rejects CSRs where the rfc822Name SANs do not match the authorized email identifiers).

Configuration

Requires [email_challenge] in the server configuration with enabled = true. See the email_challenge configuration reference and the challenges documentation for the full webhook payload format and send script interface.


RFC 9773 — ACME Renewal Information (ARI)

RFC 9773 defines the Renewal Information extension, which lets the server tell ACME clients when to renew their certificates — even before the certificate expires. This is useful when a CA needs to revoke and reissue certificates en masse (e.g., due to a key compromise or mis-issuance event).

Endpoints

GET /acme/renewal-info/<cert-id>

<cert-id> is the RFC 9773 certificate identifier: base64url(AKI keyIdentifier) "." base64url(DER-encoded serial number bytes).

The response includes a suggested renewal window:

{
  "suggestedWindow": {
    "start": "2025-03-15T00:00:00Z",
    "end":   "2025-03-20T00:00:00Z"
  }
}

The server includes a Retry-After header indicating how often to poll.

Renewal replacement

When placing a renewal order for a certificate that is being replaced, include the predecessor’s cert-id in the order:

{
  "identifiers": [...],
  "replaces": "<cert-id-of-predecessor>"
}

Akāmu validates that the predecessor cert belongs to the same account, marks it as replaced in the database at finalization, and returns an HTTP 409 (alreadyReplaced) if a replacement order has already been finalized.

Configuration

[server]
ari_retry_after_secs = 21600  # 6 hours between renewal-info polls (default)
# ari_explanation_url = "https://acme.example.com/docs/renewal-policy"  # optional

RFC 9799 — ACME for .onion Domains

RFC 9799 defines how ACME can issue certificates for Tor Hidden Services (.onion Special-Use Domain Names). These are not DNS names — the second-level label encodes the hidden service’s Ed25519 public key.

Supported challenges for .onion identifiers

ChallengeSupportedNotes
onion-csr-01YesKey validation via CSR; no Tor network access needed server-side
http-01ConditionalOnly offered when server.tor_connectivity_enabled = true
tls-alpn-01ConditionalOnly offered when server.tor_connectivity_enabled = true
dns-01NoMUST NOT be used for .onion identifiers

Tor connectivity configuration

RFC 9799 §4 prohibits offering http-01 or tls-alpn-01 for .onion identifiers unless the CA can actually reach the Tor network. By default, Akāmu offers only onion-csr-01. To enable the additional challenge types, set:

[server]
tor_connectivity_enabled = true

Only set this when the Akāmu server process can make outbound Tor connections to hidden services (e.g. via torsocks or a SOCKS5 proxy configured at the OS level).

onion-csr-01 challenge

onion-csr-01 is the recommended challenge type for .onion domains because it does not require the ACME server to connect to the Tor network. Proof of control comes from a cryptographic signature by the hidden service’s private key (the same key embedded in the .onion address).

Protocol:

  1. Akāmu returns a challenge object with type: "onion-csr-01", a token, and an authKey (the JWK thumbprint of the ACME account key).
  2. The client builds a CSR that:
    • Contains the .onion SAN.
    • Includes a cabf-onion-csr-nonce extension (OID 2.23.140.41) containing the key authorization (token.thumbprint).
    • Is signed with both the CSR subject key and the hidden service’s Ed25519 private key.
  3. The client POSTs {"csr": "<base64url-CSR-DER>"} to the challenge URL.
  4. Akāmu:
    • Extracts the 32-byte Ed25519 public key from the .onion address.
    • Verifies the cabf-onion-csr-nonce extension contains the correct key authorization.
    • Verifies the CSR signature using the extracted hidden-service public key.
    • If all checks pass, marks the authorization as valid.

Identifier format

Only v3 (Ed25519) .onion addresses are accepted. A v3 address has a 56-character base32 second-level label:

bbcweb3hytmzhn5d532owbu6oqadra5z3ar726vq5kgwwn6aucdccrad.onion

Version 2 addresses (16-character label) are rejected per RFC 9799 §2.


RFC 6960 — OCSP Responder

RFC 6960 defines the Online Certificate Status Protocol (OCSP), which allows relying parties to query a CA for the real-time revocation status of a specific certificate.

Endpoints

POST /ca/ocsp                   # body: DER OCSPRequest, Content-Type: application/ocsp-request
GET  /ca/ocsp/{request}         # {request}: base64url-encoded DER OCSPRequest (RFC 6960 §A.1)

Both endpoints return a signed OCSPResponse with Content-Type: application/ocsp-response. No authentication is required.

Status mapping

For each serial number in the OCSPRequest, the server looks up the certificate in the database:

DB stateCertStatus
Certificate not foundunknown
status = "revoked"revoked
Any other statusgood

The response is signed with the CA private key. The responder identity is byName using the CA’s subject Name DER.

Configuration

Set ocsp_url in [ca] to the public URL of the OCSP endpoint so the URL is embedded in issued certificates:

[ca]
ocsp_url = "http://acme.example.com/ca/ocsp"

See CRL and OCSP for the complete deployment guide.


RFC 5280 — X.509 Certificate Profile

RFC 5280 defines the structure of X.509 v3 certificates and Certificate Revocation Lists (CRLs). Akāmu issues certificates that conform to the RFC 5280 PKIX profile via the synta-certificate library.

Conformance includes:

  • Correct BasicConstraints (CA: false on end-entity certs).
  • SubjectKeyIdentifier and AuthorityKeyIdentifier extensions.
  • KeyUsage and ExtendedKeyUsage extensions.
  • SubjectAlternativeName extensions carrying dNSName (including .onion domains) or iPAddress.
  • CRL Distribution Points and OCSP Access Information when crl_url / ocsp_url are configured.

CA/B Forum Baseline Requirements

The CA/Browser Forum Baseline Requirements for TLS Server Certificates (BR) is not an RFC but a policy document maintained by the CA/Browser Forum and enforced by browser trust-store membership. Any CA intending to issue publicly-trusted TLS certificates must comply with it. Akāmu enforces several BR requirements at startup and at certificate-issuance time.

Compliance status

RequirementSectionDeadlineStatusImplementation
Maximum validity 200 days§6.3.22026-03-15Enforced (warning)Startup warning when ca.validity_days > 200
Maximum validity 100 days§6.3.22027-03-15Enforced (warning)Startup warning when ca.validity_days > 100
SHA-1 prohibited in signatures§7.1.3.2.12026-09-15Enforced (hard error)Startup hard error when ca.hash_alg is sha1 or sha-1
DNSSEC validation for DNS challenges§3.2.2.4, §3.2.2.8.12026-03-15Enforced by defaultserver.validate_dnssec (default true)
Pre-issuance linting§4.3.1.22025-03-15EnforcedEvery issued certificate is verified via synta-x509-verification before delivery
Multi-perspective validation§3.2.2.92025-03-15To do, not a priorityRequires validation from multiple network vantage points

§6.3.2 — Certificate Validity Period

The CA/B Forum has progressively shortened the maximum certificate validity period:

  • 200 days — hard limit since 2026-03-15
  • 100 days — hard limit from 2027-03-15

Akāmu enforces these limits as startup warnings rather than hard errors, because the restriction applies only to publicly-trusted WebPKI certificates. Private or enterprise deployments may legitimately use longer validity periods when not chaining to a public root. The warning makes the misconfiguration visible without breaking private-CA use cases.

Configure ca.validity_days in your config.toml:

[ca]
validity_days = 90   # ≤ 100 is fully compliant through 2027-03-15

§7.1.3.2.1 — SHA-1 Sunset

SHA-1 signatures in certificates and CRLs are prohibited from 2026-09-15. Akāmu enforces this as a startup hard error: if ca.hash_alg is set to sha1 or sha-1, the server refuses to start with an explicit error message citing the BR section.

Compliant hash algorithms: sha256, sha384, sha512.

§3.2.2.4 / §3.2.2.8.1 — DNSSEC Validation

DNS-based challenge validation (dns-01, dns-persist-01) and CAA record checking must use DNSSEC-validated answers as of 2026-03-15.

Akāmu enables DNSSEC validation by default. The behaviour is controlled by server.validate_dnssec:

[server]
validate_dnssec = true   # default — required for BR compliance

Set validate_dnssec = false only for testing environments or private deployments where the DNS infrastructure is not DNSSEC-signed. Disabling DNSSEC makes the server non-compliant with CA/B Forum BR and ineligible for public WebPKI inclusion.

§4.3.1.2 — Pre-Issuance Linting

CAs must programmatically verify every certificate before signing and delivering it, using a linting tool that checks structural and policy conformance. Akāmu satisfies this requirement by running the synta-x509-verification policy engine against every issued certificate immediately after signing and before delivering it to the client.

The linter checks:

  • X.509 version = v3
  • Serial number: ≤ 20 octets, positive integer
  • Validity window present and well-formed
  • SPKI algorithm on the WebPKI allowlist (no SHA-1, no weak RSA)
  • RSA keys: minimum 2048 bits; EC keys: named curves only
  • Signature algorithm on the WebPKI allowlist (includes ML-DSA / composite post-quantum)
  • AuthorityKeyIdentifier extension present
  • BasicConstraints: cA=FALSE on end-entity certificates
  • CA signature is cryptographically valid over the certificate body

If linting fails, the certificate is not delivered and the order moves to the invalid state with an internal error. The malformed certificate is never exposed to the client.

§3.2.2.9 — Multi-Perspective Issuance Corroboration (MPIC)

As of 2025-03-15, CAs are required to validate domain control from multiple network vantage points — at minimum two remote perspectives in addition to the primary validation — to mitigate BGP hijacking attacks against ACME challenge responses.

To do, not a priority. Satisfying this requirement demands either integration with a set of geographically distributed MPIC agents or reliance on an external MPIC service. Akāmu is intended for private and enterprise deployments where the network topology is controlled; public CAs using Akāmu as a backend must implement MPIC at the infrastructure layer until this is supported natively.


RFC 7807 — Problem Details for HTTP APIs

RFC 7807 defines a JSON format for HTTP error responses. All Akāmu error responses use this format with Content-Type: application/problem+json:

{
  "type":   "urn:ietf:params:acme:error:malformed",
  "detail": "JWS url mismatch: got '...', expected '...'",
  "status": 400
}

All ACME-specific error URNs are defined in RFC 8555 §6.7 and its extensions.


Let’s Encrypt dns-persist-01

The dns-persist-01 specification is a non-standard ACME challenge type published by Let’s Encrypt. Unlike the standard dns-01 challenge, which requires a fresh DNS TXT record for every renewal, dns-persist-01 uses a single long-lived TXT record that remains in place across renewals. This eliminates the need to modify DNS on every certificate renewal cycle.

How it differs from dns-01

Propertydns-01dns-persist-01
TXT record name_acme-challenge.<domain>_validation-persist.<domain>
Record changes per renewalRequiredNot required
Token in recordYes (changes each time)No
Record format<key-auth>"<issuer-domain>; accounturi=<uri>[; policy=wildcard][; persistUntil=<ISO8601Z>]"
Wildcard supportRequires explicit policy=wildcard parameter

Configuration

[server]
dns_persist_issuer_domains = "acme.example.com"

When dns_persist_issuer_domains is set, the server offers dns-persist-01 as an additional challenge type alongside http-01, dns-01, and tls-alpn-01. Without it, the challenge type is not advertised.

TXT record format

The domain owner publishes (and keeps permanently):

_validation-persist.example.com. IN TXT "acme.example.com; accounturi=https://acme.example.com/acme/account/abc123"

Optional extensions:

  • policy=wildcard — authorizes wildcard certificate issuance.
  • persistUntil=2026-12-31T00:00:00Z — caps the record’s validity. After this date, the record must be renewed.

Validation

Akāmu queries the _validation-persist.<domain> TXT record, verifies the issuer domain matches one of the configured dns_persist_issuer_domains, and checks that the accounturi matches the requesting ACME account URL. If both match, the authorization is marked valid.


draft-ietf-acme-profiles-01

draft-ietf-acme-profiles-01 defines a mechanism for an ACME server to advertise named certificate profiles and for clients to request a specific profile when placing an order. This moves policy selection from CSR extensions and post-issuance inspection into the order object itself, making the server’s issuance policy explicit and machine-readable.

What it adds

FeatureLocationStatus
meta.profiles in directoryGET /acme/directoryYes
profile field in newOrder payloadPOST /acme/new-orderYes
profile field in order responseGET/POST /acme/order/{id}Yes
invalidProfile error typeAll order and finalize endpointsYes
Finalize-time profile re-validationPOST /acme/order/{id}/finalizeYes

Directory advertisement

When [profiles] providers are configured, the directory meta includes a profiles object mapping each profile name to its description:

"meta": {
  "profiles": {
    "tlsserver":  "Standard TLS server certificate",
    "clientauth": "Client authentication certificate"
  }
}

Requesting a profile in newOrder

Clients include the profile field in the newOrder payload:

{
  "identifiers": [{ "type": "dns", "value": "example.com" }],
  "profile": "tlsserver"
}

The server validates that the requested profile is loaded in the registry. If not, it returns:

{
  "type": "urn:ietf:params:acme:error:invalidProfile",
  "status": 400,
  "detail": "profile 'unknown-profile' is not served by any configured provider"
}

The profile field is echoed back in every subsequent order response so that clients can confirm which profile applies.

Default profile auto-selection

When a newOrder request omits the profile field and a profile named "default" exists in the registry, the server automatically applies "default" and echoes it in the order response. This means clients that do not specify a profile will receive "profile": "default" in the order JSON rather than an absent field, giving operators a clean way to enforce a baseline policy without requiring client-side changes.

If no "default" profile is configured and the client omits profile, the order is issued under the CA’s built-in defaults (no profile applied).

Finalize-time enforcement

At finalize time the server reads the profile registry once and uses the result for both authorization and certificate parameter construction. Per-profile authorization checks (allowed_identifiers, auth_hook, require_account_grant) run before CSR validation so that authorization failures are reported before the server expends effort parsing and validating the CSR.

The server resolves the profile’s CertificateParameters (key usage bits, EKU OIDs, validity, CRL/OCSP URLs, certificate policies) and issues the certificate with those exact extension values. If the profile is no longer loaded (e.g. removed since the order was placed), the request is rejected with invalidProfile.

Configuration

[profiles.providers.local]
type = "builtin"

[profiles.providers.local.profiles.tlsserver]
description   = "Standard TLS server certificate"
validity_days = 90
key_usage     = ["digital_signature", "key_encipherment"]
eku           = ["server_auth"]

See Certificate Profiles for the full configuration reference including Dogtag and IPA providers. When no providers are configured, the profile field in newOrder is accepted but ignored — the server issues under its default policy.


RFC 9964 — ML-DSA for JOSE and COSE

RFC 9964 defines how ML-DSA (Module-Lattice-Based Digital Signature Algorithm, formerly CRYSTALS-Dilithium, standardized in FIPS 204) keys and signatures are represented in JOSE (JSON Object Signing and Encryption) and COSE (CBOR Object Signing and Encryption). Akāmu implements it for ACME account key authentication, meaning ACME clients can register an ML-DSA key pair and sign every subsequent ACME request with it.

JWK key type: AKP

ML-DSA keys use the key type "AKP" (Algorithm Key Pair). Unlike classical key types, the algorithm is encoded inside the JWK itself (not only in the JWS protected header), so the alg field is required in the JWK:

{
  "kty": "AKP",
  "alg": "ML-DSA-65",
  "pub": "<base64url-encoded raw public key bytes>"
}
JWK fieldRequiredDescription
ktyYesAlways "AKP" for ML-DSA keys
algYes"ML-DSA-44", "ML-DSA-65", or "ML-DSA-87"
pubYesBase64url-encoded raw public key bytes (no padding)
privNo32-byte seed (private key); never sent to the server and ignored if present

Supported variants

AlgorithmFIPS 204 parameter setPublic key sizeSignature sizeOID (SPKI)
ML-DSA-44Parameter set 2 (k=4, l=4)1312 bytes2420 bytes2.16.840.1.101.3.4.3.17
ML-DSA-65Parameter set 3 (k=6, l=5)1952 bytes3309 bytes2.16.840.1.101.3.4.3.18
ML-DSA-87Parameter set 5 (k=8, l=7)2592 bytes4627 bytes2.16.840.1.101.3.4.3.19

JWK thumbprint

Per RFC 9964 §6, the JWK thumbprint for an AKP key is the SHA-256 hash of the following canonical JSON object with members in lexicographic order:

{"alg":"ML-DSA-65","kty":"AKP","pub":"<base64url-key>"}

This is the same SHA-256 / base64url procedure as RFC 7638, applied to the three required members alg, kty, and pub (in that order).

Signature format

ML-DSA signatures in JOSE are raw bytes as defined by FIPS 204 §7.2. They are not DER-encoded. The server validates the signature length before attempting verification and returns HTTP 400 if the length does not match the declared algorithm.

The signing context MUST be an empty byte string per RFC 9964 §4. Signature failures return HTTP 401 Unauthorized.

ACME client integration notes

An ACME client registering with an ML-DSA key must:

  1. Generate an ML-DSA key pair (any of the three variants).
  2. Construct the AKP JWK from the raw public key bytes (base64url-encode them into pub).
  3. Include the JWK in the new-account protected header (the jwk field).
  4. Sign all ACME requests with the ML-DSA private key using an empty context string.
  5. Set alg in the JWS protected header to match the JWK’s alg field.

Existing ACME clients designed for classical algorithms require ML-DSA support in their underlying JOSE library. There is no server-side configuration to enable or disable ML-DSA; the feature is always available.


draft-ietf-lamps-pq-composite-sigs / draft-reddy-tls-composite-mldsa

draft-ietf-lamps-pq-composite-sigs defines the X.509/PKIX OIDs for hybrid ML-DSA+classical composite signature algorithms (sub-arcs 37–54 under the id-CompositeSig arc). The TLS 1.3 SignatureScheme code points for use in CertificateVerify are defined in the companion draft draft-reddy-tls-composite-mldsa.

CA signing keys (draft-ietf-lamps-pq-composite-sigs-19)

All 18 composite ML-DSA variants defined in sub-arcs 37–54 are supported as CA signing keys. When ca.key_type is set to a composite variant (e.g. "composite-mldsa65-ecdsa-p384-sha512"), Akāmu generates a composite CA key and issues all end-entity certificates with that composite signature. Issued certificates pass pre-issuance lint via synta-x509-verification.

Requires OpenSSL 3.5 or later (same requirement as pure ML-DSA keys). The full list of 18 supported variants and their OID sub-arcs is documented in the ca.key_type configuration reference.

Mutual TLS client authentication (draft-reddy-tls-composite-mldsa)

Composite ML-DSA schemes also appear in the TLS CertificateVerify message when a client presents a certificate signed with a composite ML-DSA scheme. The 11 composite scheme code points implemented for mTLS are:

Code pointScheme
0x0901MLDSA44-ECDSA-P256-SHA256
0x0902MLDSA44-RSA2048-PKCS15-SHA256
0x0903MLDSA44-RSA2048-PSS-SHA256
0x0904MLDSA44-Ed25519-SHA512
0x0905MLDSA65-ECDSA-P256-SHA512
0x0906MLDSA65-ECDSA-P384-SHA512
0x0907MLDSA65-RSA3072-PKCS15-SHA512
0x0908MLDSA65-RSA3072-PSS-SHA512
0x0909MLDSA65-Ed25519-SHA512
0x090AMLDSA87-ECDSA-P384-SHA512
0x090CMLDSA87-Ed448-SHAKE256

Stability warning

All OID sub-arcs (37–54) and all SignatureScheme code points (0x090x) are provisional pending IANA allocation. They may change as the drafts advance toward RFC publication. Before deploying to production, verify the current draft version against the values listed above.



RFC 9115 — ACME Profile for Delegated Certificates

RFC 9115 defines a three-party ACME delegation model in which an Identifier Owner (IdO) pre-authorizes a Name Delegation Consumer (NDC) to obtain certificates for the IdO’s domain names. The CA enforces a JSON CSR template that constrains what the NDC may request. Akāmu implements both roles: it acts as the IdO-facing ACME CA (serving NDC clients) and as an IdO ACME client that drives the upstream CA leg automatically.

Roles

RoleDescription
IdOThe domain owner. Creates delegation objects (CSR templates + CNAME maps) and holds the STAR or regular order on Akāmu.
NDCThe delegate (e.g., a CDN PoP). Discovers the delegation URL via the IdO’s account, submits a new-order referencing it, and finalizes with a CSR that satisfies the template.
Upstream CAAn external ACME CA that issues to the IdO. Akāmu drives this leg automatically using [delegation_upstream].

What it adds to the ACME API

FeatureRFC 9115 sectionStatus
delegation-enabled in directory meta§2.3.1Yes — when server.delegation_enabled = true
allow-certificate-get in directory meta§2.3.5Yes — when server.allow_certificate_get = true
delegations URL in account object§2.3.2Yes — appears when delegation_enabled = true
POST /acme/delegations/{account_id} — list delegations§2.3.2Yes
POST /acme/delegation/{id} — fetch one delegation§2.3.3Yes
"delegation" field in new-order payload§2.3.4Yes
"allow-certificate-get" field in new-order payload§2.3.5Yes
Delegation orders start in ready status (no challenge/authz flow)§2.3.4Yes
"authorizations": [] on delegation orders§2.3.4Yes
CSR template validation at finalize§4Yes
Unauthenticated GET /acme/cert/{id} when allow_cert_get = 1§2.3.5Yes

CSR template format (RFC 9115 §4)

The delegation object’s csr_template field is a JSON object that constrains what an NDC may put in its CSR:

{
  "keyTypes": [{"type": "EC", "curve": "P-256"}],
  "subject": {
    "commonName": {},
    "organization": "ExampleCorp"
  },
  "extensions": {
    "subjectAltName": {},
    "keyUsage": ["digitalSignature"],
    "extendedKeyUsage": ["1.3.6.1.5.5.7.3.1"]
  }
}

Field value semantics:

ValueMeaning
{}MandatoryWildcard — the field MUST be present in the CSR
nullOptionalWildcard — the field MAY be present in the CSR
"ExampleCorp"Literal — the field must equal this exact value
absentThe field is forbidden in the CSR

Akāmu validates the CSR against the stored template at finalize time. CSRs that violate the template are rejected with urn:ietf:params:acme:error:badCSR.

Server configuration (IdO-server role)

[server]
# Enable the delegation API surface and advertise it in the directory.
delegation_enabled      = true

# Advertise and allow unauthenticated GET of delegation order certificates.
allow_certificate_get   = true

When delegation_enabled = true, the directory meta object includes "delegation-enabled": true and every account response includes a "delegations" URL. The delegation endpoints become active:

POST /acme/delegations/{account_id}   — list delegations (POST-as-GET)
POST /acme/delegation/{id}             — fetch one delegation object (POST-as-GET)

When allow_certificate_get = true, the directory meta also includes "allow-certificate-get": true, and orders placed with "allow-certificate-get": true in their payload allow the NDC (or any bearer) to fetch the certificate with an unauthenticated GET.

Upstream CA configuration (IdO-client role)

The [delegation_upstream] section configures Akāmu to act as an ACME client toward an upstream CA. A background task polls orders whose status = 'processing' and a non-null delegation_id, drives the upstream ACME flow (account registration, order creation, dns-01 challenge, finalize), and stores the resulting certificate URL back on the order.

[delegation_upstream]
# ACME directory URL of the upstream CA.
directory_url = "https://upstream-ca.example.com/acme/directory"

# PEM file containing the ACME account key for the upstream CA.
account_key_file = "/etc/akamu/upstream-acme.key.pem"

# Contact email(s) used when registering the upstream account.
contacts = ["mailto:admin@example.com"]

# Challenge type for the upstream authz flow.  Only "dns-01" is supported.
challenge_solver = "dns-01"

# Executable that deploys the dns-01 TXT record.
# Called with env_clear(); receives CERTBOT_DOMAIN and CERTBOT_VALIDATION.
challenge_deploy_script = "/etc/akamu/upstream-dns-deploy.sh"

# Optional cleanup script called after the authz transitions to valid.
# Receives CERTBOT_DOMAIN, CERTBOT_VALIDATION, and CERTBOT_AUTH_OUTPUT="".
# challenge_cleanup_script = "/etc/akamu/upstream-dns-cleanup.sh"

# Polling interval for the upstream order status (seconds). Default: 10.
# poll_interval_secs = 10

The deploy script is invoked after Akāmu has triggered the challenge at the upstream CA. The cleanup script is called once the authorization has transitioned to valid — not immediately after the deploy script, which allows the TXT record to remain in place long enough for the upstream CA’s validators to query it.

Admin API — delegation CRUD

Delegations are managed through the Admin API. The delegation_enabled config flag must be set; the [admin] section must be configured with at least one operator.

MethodPathRole required
GET/admin/delegationsany authenticated role
GET/admin/delegations?account_id={id}any authenticated role
POST/admin/delegationsca_operations, administrator
GET/admin/delegations/{id}any authenticated role
PUT/admin/delegations/{id}ca_operations, administrator
DELETE/admin/delegations/{id}ca_operations, administrator

DELETE returns 409 Conflict when one or more orders still reference the delegation.

The CSR template syntax is validated at write time (POST and PUT). A malformed template is rejected with 400 Bad Request before it reaches the database.

Every write operation emits a structured audit event: delegation.create (POST), delegation.update (PUT), or delegation.delete (DELETE). These events are queryable via GET /admin/audit and the akamuctl audit --type delegation.* filter.

Delegation management is also available through akamuctl delegation — see akamuctl — Admin CLI for the full command reference.

Delegation order lifecycle

stateDiagram-v2
    direction LR
    [*] --> ready : new-order (with delegation URL)
    ready --> processing : finalize (NDC submits CSR)
    processing --> valid : upstream CA issues cert
    processing --> invalid : CSR template mismatch or upstream failure
    valid --> [*]
    invalid --> [*]

Delegation orders skip the pending state and the challenge/authorization flow entirely. The authorizations array in the order response is always empty. The order transitions from ready to processing when the NDC calls finalize, and from processing to valid when the background upstream task has retrieved the certificate from the upstream CA.


RFC 9447 — ACME Challenges Using an Authority Token

RFC 9447 defines the tkauth-01 ACME challenge type. Instead of a network probe, the client proves control of the identifier by presenting a signed JWT (an authority token) issued by an external Token Authority (TA). This enables ACME automation for identifier types — such as telephone numbers — that cannot be validated by http-01 or dns-01.

The authority token is a compact JWT carrying an atc claim that binds:

  • tktype — the identifier type (e.g., "TNAuthList")
  • tkvalue — the identifier value (base64url-encoded DER)
  • fingerprint — the ACME account’s JWK thumbprint
  • ca — must be absent or false (CA-cert issuance not supported)

Akāmu validates the TA’s signing certificate chain against a locally-configured set of trust anchors, verifies the JWT signature and expiry, and enforces one-time use via a JTI replay-prevention cache.

What it adds to the ACME API

FeatureStatus
tkauth-01 challenge typeYes
tkauth-type field in challenge objectYes — always "atc"
token-authority hint in challenge objectYes — optional, from tkauth.token_authority_url
x5u cert fetch for TA signing certYes
x5c inline cert for TA signing certYes
JTI replay preventionYes — database-backed tkauth_jti_cache table
Automatic JTI cache pruningYes — background task, interval from tkauth.jti_prune_interval_secs

Configuration

[tkauth]
enabled                 = true
trusted_ta_ca_files     = ["/etc/akamu/ta-root.pem"]
token_authority_url     = "https://ta.example.com"   # optional hint
max_validity_secs       = 3600
jti_prune_interval_secs = 3600

trusted_ta_ca_files must list one or more PEM files containing the CA certificates that sign Token Authority certificates. The signing cert presented in the authority token (via x5u or x5c) must chain to one of these anchors.

JTI cache management

Expired JTI entries accumulate over time. The background task prunes them automatically. Operators can also trigger manual pruning via:

akamuctl tkauth prune-jti
akamuctl tkauth prune-jti --dry-run   # count without deleting

Or via the Admin API:

POST /admin/tkauth/prune-jti
POST /admin/tkauth/prune-jti?dry_run=true

RFC 9448 — ACME TNAuthList Authority Token

RFC 9448 defines the TNAuthList ACME identifier type and its use with the RFC 9447 tkauth-01 challenge for STIR/SHAKEN telephone number automation. The identifier value is a base64url-encoded DER-encoded TNAuthorizationList structure as defined in RFC 8226.

When a new-order request contains a TNAuthList identifier, Akāmu creates a tkauth-01 challenge. The client obtains a signed authority token from the Token Authority — attesting that the account holds the telephone number authority — and submits it in the challenge response.


draft-ietf-acme-authority-token-jwtclaimcon

draft-ietf-acme-authority-token-jwtclaimcon defines a second RFC 9447 profile for the JWTClaimConstraints identifier type. The identifier value is a base64url-encoded DER-encoded JWTClaimConstraints ASN.1 structure (from RFC 8226), constraining which PASSporT claims may appear on issued certificates.

The tkauth-01 validation is identical to RFC 9448 — the only differences are the identifier type string ("JWTClaimConstraints") and the corresponding atc.tktype value in the authority token. Akāmu validates these generically; no separate configuration is required beyond enabling [tkauth].

An order may contain both TNAuthList and JWTClaimConstraints identifiers simultaneously. Each gets its own authorization and tkauth-01 challenge; all authorizations must be valid before the order may be finalized.


Not implemented

RFC 9538 — ACME Delegation Metadata for CDNI

Extends RFC 9115 for CDN Interconnection (CDNI) scenarios where multiple CDN tiers chain certificate delegation.

Not implemented. RFC 9115 single-tier delegation is fully supported (see above). Chained multi-tier delegation across CDN interconnects as defined in RFC 9538 is not yet implemented.

RFC 9891 — ACME DTN Node ID Validation (Experimental)

An experimental RFC that defines a bundleEID identifier type and a Bundle Protocol (BP) challenge for validating Delay-Tolerant Networking node identities.

Not considered. Experimental status; targets space/satellite networks using the Bundle Protocol (RFC 9171), outside the scope of Akāmu’s target deployments.