> ## Documentation Index
> Fetch the complete documentation index at: https://wiki.krkn.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Certificates

> SSL/TLS certificate management, Let's Encrypt automation, and Cloudflare Origin CA integration

# SSL/TLS Certificates

Aegis manages SSL/TLS certificates for all proxy hosts. It supports three certificate sources: **Let's Encrypt** (automatic ACME), **Cloudflare Origin CA** (API-integrated), and **Custom** (manual PEM upload). Private keys are encrypted at rest using AES-256-GCM.

<Frame>
  <img src="https://mintcdn.com/krakentechllc/QCb-hD7jjEnD8u8M/images/image-39.png?fit=max&auto=format&n=QCb-hD7jjEnD8u8M&q=85&s=5c2a623bac293b390d9f133e35a8feba" alt="Image" width="2934" height="1750" data-path="images/image-39.png" />
</Frame>

***

## Certificate Sources

| Source                   | Mode                                   | Description                                                               |
| ------------------------ | -------------------------------------- | ------------------------------------------------------------------------- |
| **Let's Encrypt**        | `letsencrypt`                          | Automatic certificate issuance and renewal via ACME HTTP-01 challenge     |
| **Cloudflare Origin CA** | `custom` (source: `cloudflare_origin`) | Origin certificates issued through the Cloudflare API                     |
| **Custom Upload**        | `custom`                               | Manual PEM-encoded certificate and key upload                             |
| **Self-Signed Fallback** | automatic                              | Generated automatically when no other certificate is available for a host |

***

## Let's Encrypt (ACME)

Aegis uses Go's `autocert` library to obtain and renew Let's Encrypt certificates automatically via the ACME HTTP-01 challenge.

### How It Works

1. Set the proxy host's SSL mode to `letsencrypt` in the admin UI
2. Aegis validates that the domain is configured as a proxy host (host policy check)
3. When a TLS handshake arrives for that domain, `autocert` requests a certificate from Let's Encrypt
4. The HTTP-01 challenge is served on port 80 (the HTTP proxy listener handles `/.well-known/acme-challenge/` paths)
5. The certificate is cached in the autocert cache directory (default `.cache/ssl`)
6. Renewal happens automatically before expiration

### Requirements

* Port 80 must be reachable from the internet for HTTP-01 challenge validation
* The domain must resolve to the Aegis server's public IP
* The proxy host must have `ssl_mode` set to `letsencrypt`

### Configuration

| Setting               | Default      | Description                                           |
| --------------------- | ------------ | ----------------------------------------------------- |
| `AEGIS_SSL_CACHE_DIR` | `.cache/ssl` | Directory for autocert's file-based certificate cache |

```bash theme={null}
# Start with custom SSL cache directory
AEGIS_SSL_CACHE_DIR=/var/lib/aegis/ssl aegis run
```

***

## Cloudflare Origin CA

Aegis can request Cloudflare Origin CA certificates directly through the Cloudflare API. This is ideal when Aegis sits behind Cloudflare's proxy, where Let's Encrypt HTTP-01 challenges may not reach the origin server.

### How It Works

1. Aegis generates a CSR (Certificate Signing Request) with the requested hostnames
2. The CSR is submitted to Cloudflare's Origin CA API (`/client/v4/certificates`)
3. Cloudflare issues a signed certificate (valid up to 15 years)
4. Aegis stores the certificate and encrypted private key in SQLite
5. The certificate is loaded into the TLS configuration for SNI-based selection

### Authentication Methods

| Method          | Header                           | Description                                                                   |
| --------------- | -------------------------------- | ----------------------------------------------------------------------------- |
| `api_token`     | `Authorization: Bearer <token>`  | Cloudflare API Token with Origin CA permissions                               |
| `origin_ca_key` | `X-Auth-User-Service-Key: <key>` | Cloudflare Origin CA Key (found in the Cloudflare dashboard under API Tokens) |

### Request Options

| Field                | Default      | Description                                                                     |
| -------------------- | ------------ | ------------------------------------------------------------------------------- |
| `hostnames`          | (required)   | Domain names for the certificate (e.g., `["app.example.com", "*.example.com"]`) |
| `request_type`       | `origin-rsa` | Key type — `origin-rsa` (RSA 2048) or `origin-ecc` (ECDSA P-256)                |
| `requested_validity` | `5475`       | Certificate validity in days (default: 15 years)                                |
| `zone_id`            | (optional)   | Cloudflare Zone ID (for audit/tracking)                                         |

### API Endpoint

```text theme={null}
POST /api/v1/certificates/cloudflare-origin
```

```json theme={null}
{
  "auth_method": "api_token",
  "api_token": "your-cloudflare-api-token",
  "hostnames": ["app.example.com", "*.example.com"],
  "request_type": "origin-rsa",
  "requested_validity": 5475
}
```

**Response:**

```json theme={null}
{
  "status": "created",
  "id": 1,
  "cloudflare_id": "cf-cert-id",
  "expires_on": "2041-03-23T00:00:00Z",
  "requested_zone": ""
}
```

The private key is generated locally, never sent to Cloudflare, and stored encrypted (AES-256-GCM) in SQLite.

***

## Custom Certificate Upload

Upload any PEM-encoded certificate and private key pair through the admin UI or API.

### API Endpoint

```text theme={null}
POST /api/v1/certificates
```

```json theme={null}
{
  "domains": ["app.example.com"],
  "cert_pem": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
  "key_pem": "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----"
}
```

### Validation

* The certificate and key must form a valid X.509 key pair
* The leaf certificate is parsed to extract the issuer, expiration, and subject
* The private key is encrypted with AES-256-GCM before storage

***

## Assigning Certificates to Hosts

Certificates are assigned to proxy hosts through the host configuration:

### Let's Encrypt

Set `ssl_mode` to `letsencrypt` on the proxy host. No certificate ID is needed — autocert handles issuance automatically.

```json theme={null}
{
  "domain": "app.example.com",
  "ssl_mode": "letsencrypt",
  "force_https": true
}
```

### Custom / Cloudflare Origin

Set `ssl_mode` to `custom` and either:

* **Inline PEM** — provide `ssl_cert_pem` and `ssl_key_pem` directly in the host update (Aegis stores and encrypts them automatically)
* **Certificate ID** — reference an existing certificate by `ssl_cert_id`

```json theme={null}
{
  "domain": "app.example.com",
  "ssl_mode": "custom",
  "ssl_cert_id": 1,
  "force_https": true
}
```

***

## Cloudflare Edge Certificates

In addition to Origin CA certificates (which live on the origin server), Aegis can request **Cloudflare Edge Certificates** through the Cloudflare API. Edge certificates are served by Cloudflare's edge network on behalf of your domain — useful when you need Cloudflare to provision and serve a publicly trusted certificate (e.g., for advanced certificate packs, custom hostnames, or alternative CAs).

### API Endpoint

```text theme={null}
POST /api/v1/certificates/cloudflare-edge
```

```json theme={null}
{
  "api_token": "your-cloudflare-api-token",
  "zone_id": "your-zone-id",
  "hostnames": ["app.example.com", "*.example.com"],
  "certificate_authority": "lets_encrypt",
  "validation_method": "txt",
  "validity_days": 90
}
```

### Options

| Field                   | Default        | Description                                                       |
| ----------------------- | -------------- | ----------------------------------------------------------------- |
| `api_token`             | (required)     | Cloudflare API token with SSL and Certificates Write permissions  |
| `zone_id`               | (required)     | Cloudflare Zone ID                                                |
| `hostnames`             | (required)     | Domain names for the edge certificate                             |
| `certificate_authority` | `lets_encrypt` | CA to issue the edge cert: `lets_encrypt`, `google`, or `ssl_com` |
| `validation_method`     | `txt`          | Domain validation method: `txt`, `http`, or `email`               |
| `validity_days`         | `90`           | Certificate validity in days                                      |
| `cloudflare_branding`   | (optional)     | Enable Cloudflare branding on the certificate                     |

Edge certificates are managed by Cloudflare — Aegis submits the order and returns the Cloudflare certificate ID and status. The actual certificate is served by Cloudflare's edge, not stored locally.

***

## Local Certificate Authority (Trust Bundle)

Aegis generates its own **local Certificate Authority** on first run. This CA is used to sign self-signed certificates for the admin UI and proxy fallback TLS. The CA is stored in the SSL cache directory alongside the certificates it issues.

### CA Files

| File                       | Purpose                                       | Validity |
| -------------------------- | --------------------------------------------- | -------- |
| `aegis-local-ca.crt`       | CA certificate (public)                       | 10 years |
| `aegis-local-ca.key`       | CA private key (ECDSA P-256)                  | —        |
| `admin-ui-self-signed.crt` | Admin UI TLS certificate (signed by CA)       | 1 year   |
| `admin-ui-self-signed.key` | Admin UI TLS key                              | —        |
| `proxy-self-signed.crt`    | Proxy fallback TLS certificate (signed by CA) | 1 year   |
| `proxy-self-signed.key`    | Proxy fallback TLS key                        | —        |

All self-signed certificates are issued by the local CA, so trusting the single CA certificate on your OS or browser automatically trusts all Aegis-generated certificates.

### Why Trust the CA?

When you first access the Aegis admin UI at `https://127.0.0.1:9443`, your browser will show a certificate warning because the self-signed certificate is not trusted by default. You can either click through the warning each time, or trust the Aegis local CA once to permanently eliminate the warning.

The same applies to the proxy fallback certificate — if a proxy host has no Let's Encrypt or custom certificate configured, Aegis serves a self-signed certificate signed by the local CA.

### Downloading the Trust Bundle

Aegis provides a downloadable trust bundle containing the CA certificate and installation instructions for every major platform:

```text theme={null}
GET /api/v1/settings/admin-trust-bundle
```

This returns a ZIP file containing:

| File                 | Description                            |
| -------------------- | -------------------------------------- |
| `aegis-local-ca.crt` | The CA certificate in PEM format       |
| `README.txt`         | Step-by-step installation instructions |

### Installing the CA Certificate

**macOS:**

```bash theme={null}
# Add to system keychain
sudo security add-trusted-cert -d -r trustRoot \
  -k /Library/Keychains/System.keychain aegis-local-ca.crt
```

**Windows:**

```powershell theme={null}
# Import to Trusted Root Certification Authorities
certutil -addstore -f "Root" aegis-local-ca.crt
```

**Linux (Debian/Ubuntu):**

```bash theme={null}
sudo cp aegis-local-ca.crt /usr/local/share/ca-certificates/aegis-local-ca.crt
sudo update-ca-certificates
```

**Linux (RHEL/Fedora):**

```bash theme={null}
sudo cp aegis-local-ca.crt /etc/pki/ca-trust/source/anchors/aegis-local-ca.crt
sudo update-ca-trust
```

**Browser (Firefox — uses its own trust store):**

1. Open Settings -> Privacy & Security -> Certificates -> View Certificates
2. Import `aegis-local-ca.crt` under the Authorities tab
3. Check "Trust this CA to identify websites"

After installation, the browser/OS will trust all certificates signed by the Aegis local CA without showing warnings.

### Certificate Renewal

| Certificate         | Validity | Renewal                                                         |
| ------------------- | -------- | --------------------------------------------------------------- |
| Local CA            | 10 years | Regenerated only if deleted from the cache directory            |
| Admin UI cert       | 1 year   | Automatically regenerated on next startup if expired or missing |
| Proxy fallback cert | 1 year   | Automatically regenerated on next startup if expired or missing |

If a signed certificate is no longer issued by the current CA (e.g., the CA was regenerated), Aegis detects this and re-issues the certificate automatically.

***

## SNI Certificate Selection

When a TLS handshake arrives, Aegis selects the certificate in this order:

1. **Host binding** — if the proxy host has an `ssl_cert_id`, use that certificate
2. **Domain match** — check all stored custom certificates for a domain match
3. **Autocert** — attempt Let's Encrypt certificate retrieval (for hosts with `ssl_mode: letsencrypt`)
4. **Self-signed fallback** — generate a self-signed certificate covering all configured domains

This ensures every TLS connection receives a certificate, even if the preferred source is unavailable.

***

## Private Key Encryption

All private keys are encrypted at rest using AES-256-GCM:

* **Key source** — provided via `AEGIS_SSL_KEY` (64-char hex string) or auto-generated on first run
* **Auto-generated keys** are stored in the SQLite `settings` table
* **Each key** gets a unique random nonce (GCM nonce size)
* **Decryption** happens only when loading certificates into memory at startup or after a reload

***

## Certificate Lifecycle

| Event                               | Behavior                                                                            |
| ----------------------------------- | ----------------------------------------------------------------------------------- |
| **Host created with Let's Encrypt** | Certificate requested on first TLS handshake                                        |
| **Host created with custom cert**   | Certificate stored immediately, proxy reloaded                                      |
| **Cloudflare Origin requested**     | CSR generated, sent to Cloudflare API, cert stored                                  |
| **Certificate approaches expiry**   | Let's Encrypt: auto-renewed by autocert. Custom/Cloudflare: manual renewal required |
| **Certificate deleted**             | Only allowed if not assigned to any host. Proxy reloaded after deletion             |
| **Host SSL mode changed**           | Certificate binding updated, proxy reloaded                                         |

***

## Host SSL Settings

Each proxy host supports these SSL-related settings:

| Setting                   | Description                                         |
| ------------------------- | --------------------------------------------------- |
| `ssl_mode`                | `none`, `letsencrypt`, or `custom`                  |
| `ssl_cert_id`             | Reference to a stored certificate (for custom mode) |
| `force_https`             | 301 redirect HTTP to HTTPS                          |
| `hsts_enabled`            | Enable Strict Transport Security header             |
| `hsts_max_age`            | HSTS max-age in seconds                             |
| `hsts_include_subdomains` | Include subdomains in HSTS                          |
| `hsts_preload`            | Add preload directive to HSTS                       |
| `tls_min_version`         | Minimum TLS version (default: TLS 1.2)              |

***

## API Reference

| Method   | Path                                     | Description                                                    |
| -------- | ---------------------------------------- | -------------------------------------------------------------- |
| `GET`    | `/api/v1/certificates`                   | List all stored certificates (domains, source, issuer, expiry) |
| `POST`   | `/api/v1/certificates`                   | Upload a custom certificate (PEM cert + key)                   |
| `DELETE` | `/api/v1/certificates/{id}`              | Delete a certificate (fails if assigned to a host)             |
| `POST`   | `/api/v1/certificates/cloudflare-origin` | Request a Cloudflare Origin CA certificate                     |
| `POST`   | `/api/v1/certificates/cloudflare-edge`   | Order a Cloudflare Edge certificate                            |
| `GET`    | `/api/v1/settings/admin-trust-bundle`    | Download the local CA trust bundle (ZIP)                       |

### Certificate Object

| Field        | Type    | Description                                         |
| ------------ | ------- | --------------------------------------------------- |
| `id`         | integer | Certificate ID                                      |
| `domains`    | array   | Domain names covered by the certificate             |
| `source`     | string  | `custom`, `cloudflare_origin`, or `letsencrypt`     |
| `issuer`     | string  | Certificate issuer common name                      |
| `expires_at` | string  | ISO 8601 expiration timestamp                       |
| `auto_renew` | boolean | Whether the certificate was marked for auto-renewal |
| `created_at` | string  | ISO 8601 creation timestamp                         |
