Skip to content

TLS

Mail travelling between servers is only as private as the connection that carries it. SMTP between servers starts unencrypted, and the sending server upgrades the connection with STARTTLS when the receiving server offers it. An attacker on the network path can defeat this upgrade, for example by removing the offer (Transport security). TLS strategies decide how much encryption and authentication Stalwart insists on when it delivers, and therefore how it balances protection against the ability to reach every server.

Two mechanisms let a recipient domain publish that its servers support TLS, and how senders must authenticate them. DANE publishes the servers’ certificates in TLSA records, in a DNS zone signed with DNSSEC. MTA-STS publishes a policy over HTTPS. A TLS strategy decides how strictly Stalwart applies each of these, and plain STARTTLS, when it delivers a message.

Each strategy is an MtaTlsStrategy object (found in the WebUI under Settings › MTA › Outbound › TLS Strategies). The tls expression of the outbound strategy selects one for each remote host. A new installation has two strategies:

  • default, which validates certificates
  • invalid-tls, which accepts invalid certificates, and which the default expression selects when the previous attempt failed with a TLS error

startTls, dane and mtaSts each take one of three values. By default, startTls is optional, dane is optional and mtaSts is optional. The following table shows what each value means for each mechanism:

ValuestartTlsdanemtaSts
optionalUse STARTTLS when the server offers it; otherwise deliver unencrypted.Look up TLSA records for MX hosts in DNSSEC-signed zones and, when they exist, require a certificate that matches them.Fetch the domain’s policy and, when its mode is enforce, deliver only to the hosts it lists, over validated TLS.
requireDeliver only when STARTTLS succeeds.Deliver only to hosts with valid TLSA records.Deliver only when a policy can be fetched, and apply it.
disableNever use TLS.Make no TLSA lookups.Fetch no policies.

With all three at optional, Stalwart applies every policy that a recipient domain publishes, and falls back to opportunistic TLS for the domains that publish none. This is what RFC 7672 and RFC 8461 expect of a sending server.

optional does not mean that a published policy can be ignored. A domain’s published policy is binding even under optional: when its TLSA records exist, or its MTA-STS policy is in enforce mode, a host that fails them is not delivered to.

require goes further, and is intended for selected destinations, such as a partner whose servers are known to support DANE. Applied to all mail, it fails delivery to the many domains that publish neither DANE nor MTA-STS. The require value of dane also cannot be combined with relay routes.

A sender can also ask for encryption for an individual message, with the REQUIRETLS parameter (RFC 8689). Such a message is delivered only over an encrypted connection, whatever the strategy. Stalwart does not apply the other requirements that RFC 8689 places on a sending server. The server certificate is validated only as the TLS strategy and the route allow (see Certificate validation). A server that does not advertise REQUIRETLS receives the message without the parameter, and no non-delivery report is sent.

The following strategy requires TLS, DANE and MTA-STS for a partner:

{
"name": "partner-strict",
"startTls": "require",
"dane": "require",
"mtaSts": "require"
}

The strategy is selected for the partner’s domain with the following tls expression. The MTA-STS setting is taken from the evaluation made for the recipient domain, before the MX hosts are known (Strategies). The condition therefore tests rcpt_domain, not mx:

{
"tls": {
"match": {
"0": {"if": "rcpt_domain == 'partner.example'", "then": "'partner-strict'"},
"1": {"if": "retry_num > 0 && last_error == 'tls'", "then": "'invalid-tls'"}
},
"else": "'default'"
}
}

DANE depends on the DNS resolver. It needs a resolver that validates DNSSEC and can be reached over TCP. At start-up, Stalwart tests the configured resolver. When the resolver cannot validate DNSSEC, DANE is disabled and the warning The configured DNS resolver cannot validate DNSSEC. DANE has been disabled to avoid deferring mail. Ensure the resolver is DNSSEC-capable and reachable over TCP. is reported. DANE describes the requirements.

Certificate validation decides whether Stalwart only encrypts the connection, or also confirms that it is talking to the right server. allowInvalidCerts is disabled by default. In that state, a server whose certificate does not validate, because it is self-signed, expired or issued for another name, fails the TLS handshake, and the delivery attempt fails.

Many mail servers on the internet present such certificates. For this reason the default outbound strategy retries a failed TLS delivery with the invalid-tls strategy. That strategy sets allowInvalidCerts to true, so it encrypts the connection without authenticating the server. This matches the opportunistic TLS that most sending servers use when no DANE or MTA-STS policy applies.

Published policies take precedence over this setting. allowInvalidCerts is ignored for hosts covered by an MTA-STS policy in enforce mode, and for hosts authenticated through DANE: their certificates are always validated.

Some servers cannot be reached over TLS at all. Stalwart’s TLS implementation does not support SSL, TLS 1.0 and 1.1, or obsolete cipher suites, so a server that offers nothing else fails the handshake even with allowInvalidCerts. To reach such a server, a strategy with startTls set to disable delivers without encryption. The following tls expression falls back to it after two failed TLS attempts:

{
"tls": {
"match": {
"0": {"if": "retry_num > 1 && last_error == 'tls'", "then": "'no-tls'"},
"1": {"if": "retry_num > 0 && last_error == 'tls'", "then": "'invalid-tls'"}
},
"else": "'default'"
}
}

The no-tls strategy that the expression names disables STARTTLS:

{
"name": "no-tls",
"startTls": "disable"
}

The conditions are evaluated in order, so the more specific one comes first. Otherwise the invalid-tls branch would match every retry after a TLS error, and the no-tls branch would never be reached.

TLS adds steps to a delivery, and two timeouts keep a slow remote server from holding those steps open:

FieldDefaultLimit
tlsTimeout3 minutesThe TLS handshake.
mtaStsTimeout5 minutesFetching an MTA-STS policy over HTTPS.

Each step of a delivery is logged as an event, so the log shows where a TLS delivery failed. The failures are logged at the info level:

  • delivery.start-tls-unavailable: the server does not offer STARTTLS
  • delivery.start-tls-error and delivery.implicit-tls-error: the handshake failed
  • dane.authentication-failure and dane.tlsa-record-invalid
  • mta-sts.not-authorized: the host is not in the domain’s policy
  • mta-sts.policy-fetch-error and mta-sts.invalid-policy

The routine steps are logged at the debug level only. Among them are delivery.attempt-start, delivery.connect, delivery.start-tls, delivery.completed, dane.tlsa-record-not-dnssec-signed, mta-sts.policy-fetch, mta-sts.authorized, mta-sts.policy-not-found and tls-rpt.record-fetch. Following a delivery step by step therefore requires a tracer at that level (Telemetry).

The error of the last attempt is also recorded with each recipient of a queued message (Queue management).