Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion reference/http/tls.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,45 @@ Type: `string | undefined`

Default: `crypto.defaultCipherList`

Colon-separated list of allowed TLS cipher suites. When omitted, Node.js [default ciphers](https://nodejs.org/api/crypto.html#nodejs-crypto-constants) are used. See Node.js [Modifying the default TLS cipher suite](https://nodejs.org/api/tls.html#modifying-the-default-tls-cipher-suite) for more information.
Colon-separated list of allowed TLS cipher suites, optionally ending with an OpenSSL security level command such as `@SECLEVEL=0`. When omitted everywhere, Node.js [default ciphers](https://nodejs.org/api/crypto.html#nodejs-crypto-constants) are used. See Node.js [Modifying the default TLS cipher suite](https://nodejs.org/api/tls.html#modifying-the-default-tls-cipher-suite) for more information.

`ciphers` may be set at the top level, on individual `tls` array entries, or on certificates added through the operations API. A TLS listener has exactly one effective cipher string, so Harper composes these sources — see [Cipher Suites and Security Level](#cipher-suites-and-security-level).

## Cipher Suites and Security Level

<VersionBadge type="changed" version="v5.2.0" />

Every configured `ciphers` source is now honored on the Node.js (OpenSSL) TLS path; previously only `tls.ciphers` (or the first array entry's) took effect and all others were silently ignored. TLS listeners on the Bun runtime (BoringSSL) do not apply this resolution — BoringSSL has no `@SECLEVEL` concept.

A TLS listener has a single effective cipher string: OpenSSL applies the cipher list — and any `@SECLEVEL` command in it — from the configuration the listener was created with. The security level is connection-wide: it constrains cipher algorithms, curves, signature algorithms, DH sizes, and certificate key and signature strength for every connection on the listener, including client-certificate chain verification. A per-certificate value cannot take effect on its own, so Harper resolves one effective string per listener from every relevant source:

- The listener's own `tls` section, in priority order: `operationsApi.tls` for the Operations API listener, then the root `tls` section — an object's `ciphers` directly, and an array's relevant entries. The same relevance rules as certificate records apply to array entries (matching `uses`, no `uses`, or the legacy generic `https`; certificate-authority entries only for mTLS-verifying listeners), so an entry scoped to another listener — for example an Operations-API-only entry — does not alter the application listener.
- Certificate records (including certificates added with the `add_certificate` operation) whose `uses` matches the listener, plus records with no `uses` and the legacy generic `https` use.
- Certificate authority entries and records participate only for listeners that verify client certificates (mTLS) — a CA's required security level matters exactly when its chains are being verified, and does not relax listeners without mTLS.

The resolution composes two separable parts:

- **Cipher suites** come from the highest-priority source that specifies any — a CA entry cannot replace or broaden the listener's configured suite list.
- **Security level** is the minimum explicit `@SECLEVEL` across all relevant sources. Sources without an explicit `@SECLEVEL` keep the runtime default. Because the level is connection-wide, composing in a low level (for example `@SECLEVEL=0` required by one legacy CA) relaxes the strength requirements for every connection on that listener, not just the chains that needed it.

For example, a listener configured with strict suites plus a legacy client CA whose chain requires a relaxed level:

```yaml
http:
securePort: 9927
mtls: true

tls:
- certificate: ~/hdb/keys/certificate.pem
privateKey: ~/hdb/keys/privateKey.pem
ciphers: HIGH:!aNULL
- certificateAuthority: ~/hdb/keys/legacy-client-ca.pem
ciphers: DEFAULT@SECLEVEL=0
```

resolves to `HIGH:!aNULL@SECLEVEL=0` — the configured suites are preserved while the relaxed security level lets legacy chains verify, for example client certificate chains containing SHA-1 signatures on a leaf or intermediate certificate, which otherwise fail verification on modern OpenSSL. A directly trusted root's own self-signature is not checked, so a SHA-1-self-signed root alone does not require a relaxed level.

Whenever values are composed across sources or a suite list cannot be applied, Harper logs a warning describing what was used and what was ignored. The effective cipher string is fixed when the listener starts: if a later certificate change (such as `add_certificate` with a `ciphers` value) alters the resolved value, Harper logs a warning that a restart is required to apply it.

## Enabling HTTPS

Expand Down