Caddy RA-TLS Module
A Caddy TLS issuance module that produces RA-TLS certificates for Confidential Computing (Intel TDX, AMD SEV-SNP).
The RA-TLS for Caddy is a Caddy tls.issuance module (ra_tls) that produces RA-TLS certificates for Confidential Computing. It turns any Caddy instance running inside a Confidential VM into an attested HTTPS server — no custom client-side code required.
Repository: github.com/Privasys/ra-tls-caddy
Supported Backends
| Backend | TEE | Status |
|---|---|---|
tdx | Intel TDX | Supported |
sev-snp | AMD SEV-SNP | Planned |
The hardware-specific logic is abstracted behind the Attester interface (attester.go). Each backend lives in its own file (e.g. attester_tdx.go) and registers itself via init().
Why Caddy + Confidential VMs
| Benefit | Description |
|---|---|
| Ease of deployment | Caddy is a mature, production-grade web server. Adding RA-TLS is a single module — no code changes to your application. |
| Unmodified Linux | TDX/SEV protect an entire VM, so your application runs as a normal Linux process. No SGX SDK, no special build toolchain. |
| Standard Caddyfile | Configure RA-TLS with a few lines in the Caddyfile, just like any other TLS issuer. |
| Reverse proxy | Caddy can reverse-proxy to any backend service running in the same Confidential VM, adding RA-TLS attestation to existing applications. |
Extended Certificate
The module serves an X.509 certificate extended with an attribute containing the TEE quote. For Intel TDX the attestation evidence is embedded in extension OID 1.2.840.113741.1.5.5.1.6:
X509v3 extensions:
...
1.2.840.113741.1.5.5.1.6:
<hex dump of the TDX quote — ~8000 bytes of attestation evidence>See RA-TLS for details on how RA-TLS certificates work across both Enclave OS and Caddy.
Architecture
┌──────────────────────────────────────────────┐
│ Confidential VM (TDX / SEV) │
│ │
│ ┌────────────────────────────────────────┐ │
│ │ Caddy Server │ │
│ │ │ │
│ │ ┌──────────────┐ ┌───────────────┐ │ │
│ │ │ RA-TLS │ │ Reverse Proxy │ │ │
│ │ │ Issuance │ │ / File Server │ │ │
│ │ │ Module │ │ / Application │ │ │
│ │ └──────┬───────┘ └───────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────────────┐ │ │
│ │ │ configfs-tsm │ │ │
│ │ │ /sys/kernel/config/ │ │ │
│ │ │ tsm/report │ │ │
│ │ └──────────────────────┘ │ │
│ └────────────────────────────────────────┘ │
│ │
│ Hardware Memory Encryption │
└──────────────────────────────────────────────┘The TDX backend uses the Linux kernel's configfs-tsm interface (/sys/kernel/config/tsm/report) to request quotes — no special SDK or library needed.
How It Works
The module supports two attestation paths:
Deterministic (Issue Path)
- Key Generation — An ECDSA P-256 key pair is generated inside the TEE.
- Attestation —
ReportData = SHA-512( SHA-256(DER public key) || creation_time ), wherecreation_timeisNotBeforetruncated to 1-minute precision ("2006-01-02T15:04Z"). The quote is obtained from the configured backend. - Certificate — An X.509 certificate is signed by a user-provided intermediary CA (private PKI), embedding the attestation evidence in a backend-specific extension OID. The PEM output includes the full chain (leaf + CA cert).
Certificates are cached by CertMagic and auto-renewed. A verifier reproduces the ReportData from the certificate alone.
Challenge-Response (GetCertificate Path)
When a TLS client sends an RA-TLS challenge in its ClientHello (extension 0xffbb):
- Ephemeral Key — A fresh ECDSA P-256 key pair is generated.
- Attestation —
ReportData = SHA-512( SHA-256(DER public key) || nonce ), binding the quote to the client's challenge. - Certificate — A very short-lived (5 min) certificate is signed by the same CA, with the quote embedded.
This certificate is not cached — each challenge produces a unique response.
Note: Challenge-response attestation requires the Privasys/go fork (initial commit) which adds
ClientHelloInfo.RATLSChallengetocrypto/tls. The code referenceshello.RATLSChallengedirectly and will not compile with standard Go. An upstream PR is open at golang/go#77714.
Requirements
- Linux host running inside a Confidential VM.
- Backend-specific support:
tdx— Kernel configfs-tsm (/sys/kernel/config/tsm/report).
- An intermediary CA certificate and private key (private PKI).
- Privasys/go fork (
ratlsbranch — initial commit) and xcaddy.
Building
First, build the Go fork:
# Install official Go (needed as bootstrap compiler)
wget https://go.dev/dl/go1.24.0.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.24.0.linux-amd64.tar.gz
rm go1.24.0.linux-amd64.tar.gz
# Clone and build the Privasys Go fork
git clone -b ratls https://github.com/Privasys/go.git ~/go-ratls
export GOROOT_BOOTSTRAP=/usr/local/go
cd ~/go-ratls/src && ./make.bash
export GOROOT=~/go-ratls
export PATH=$GOROOT/bin:$HOME/go/bin:$PATHThen build Caddy with the RA-TLS module:
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
git clone https://github.com/Privasys/ra-tls-caddy.git
cd ra-tls-caddy/src
xcaddy build --with github.com/Privasys/ra-tls-caddy=.Note: The
=.suffix tells xcaddy to use the local directory as the module source. The import path before=must match themoduledirective ingo.mod. If you are working from a fork, updatego.modaccordingly.
For full deployment guides on specific cloud providers, see the Guides section.
Configuration
Caddyfile
example.com {
tls {
issuer ra_tls {
backend tdx
ca_cert /path/to/intermediate-ca.crt
ca_key /path/to/intermediate-ca.key
}
}
respond "Hello from a Confidential VM!"
}JSON Configuration
{
"apps": {
"tls": {
"automation": {
"policies": [
{
"subjects": ["example.com"],
"issuers": [
{
"module": "ra_tls",
"backend": "tdx",
"ca_cert_path": "/path/to/intermediate-ca.crt",
"ca_key_path": "/path/to/intermediate-ca.key"
}
]
}
]
}
}
}
}Inspecting the Certificate
Once Caddy is running, inspect the RA-TLS certificate with standard tools:
# Retrieve and display the full certificate
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -textTo save the certificate for programmatic verification:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -outform PEM > ratls-cert.pem
openssl asn1parse -in ratls-cert.pemKey Sensitivity
The ECDSA private key is generated inside the TEE and protected by hardware memory encryption at runtime. However, CertMagic will PEM-encode and persist it via its storage backend. To prevent writing it to unencrypted disk:
- Use an encrypted filesystem or volume for Caddy's data directory.
- Or configure a secrets-manager storage module.
Verification
A relying party verifying an RA-TLS certificate should:
Deterministic Path
- Validate the certificate chain back to the trusted root CA.
- Extract the attestation evidence from the backend-specific extension OID (e.g.
1.2.840.113741.1.5.5.1.6for TDX). - Verify the evidence against the hardware vendor's attestation infrastructure.
- Read the certificate's
NotBeforefield, format it as"2006-01-02T15:04Z"(UTC, minute precision). - Compute
SHA-512( SHA-256(DER public key) || formatted_time_string )and confirm it matches the quote'sReportData. - Check the quote's measurement registers against expected values.
Challenge-Response Path
- Validate the certificate chain back to the trusted root CA.
- Extract the attestation evidence from the backend-specific extension OID.
- Verify the evidence against the hardware vendor's attestation infrastructure.
- Compute
SHA-512( SHA-256(DER public key) || original_nonce )using the nonce sent in the ClientHello. - Confirm the result matches the quote's
ReportData— this proves freshness. - Check the quote's measurement registers against expected values.
See RA-TLS Clients for verification libraries in Python, Go, Rust, TypeScript, and C#.
Adding a New Backend
- Create
src/attester_<name>.goimplementing theAttesterinterface. - Register it in an
init()function:RegisterAttester("<name>", func() Attester { return new(MyAttester) }) - The new backend is immediately available via
backend <name>in the Caddyfile.