RFC 7672 and Stalwart automatic TLSA generation: why 2 1 1, how rollover works, and is it configurable?

Your question

Environment

  • Stalwart Mail Server: v0.16.26
  • Linux / Docker
  • Automatic certificate and DNS management enabled (DKIM and TLSA only)

Question

I am reviewing the TLSA records automatically generated by Stalwart for my SMTP MX hosts.

Stalwart currently publishes an RRset in this form:

2 1 1 <CA/intermediate SPKI SHA-256>
2 1 1 <CA/intermediate SPKI SHA-256>
2 1 1 <CA/intermediate SPKI SHA-256>
3 1 1 <leaf certificate SPKI SHA-256>

I have verified that DNSSEC validation succeeds and that the 3 1 1 record exactly matches the SPKI SHA-256 hash of the certificate actually presented by the SMTP server.

I also found the previous forum discussions explaining that Stalwart intentionally generates 3 1 1 for the end-entity certificate and 2 1 1 for additional certificates in the presented chain, so I understand that the 2 1 1 records are intentional rather than accidental.

However, I would like to clarify a few details about the design and configuration.

1. Why does Stalwart use 2 1 1 for DANE-TA?

RFC 7672 permits SPKI selector 1 for DANE-TA, but recommends 2 0 1 when DANE-TA is used, while 3 1 1 is generally recommended for DANE-EE.

Some external validators, for example Mailhardener, therefore flag the automatically generated 2 1 1 records as non-recommended/invalid for SMTP.

Is there a particular reason Stalwart prefers:

2 1 1

over:

2 0 1

for the additional certificates in the chain?

I am not reporting an interoperability failure here — I understand that 2 1 1 is permitted by the RFC. I am mainly interested in the rationale behind the choice.

2. Which certificates are used for the 2 1 1 records?

Does Stalwart generate one DANE-TA record for every non-leaf certificate actually sent in the SMTP TLS handshake, or is there additional logic determining which chain certificates become TLSA records?

For example, if the presented chain changes because the ACME CA changes its intermediate certificate, will the corresponding TLSA RRset automatically change as well?

3. How is certificate/TLSA rollover handled?

When the certificate chain changes, how does Stalwart avoid a temporary DANE mismatch caused by DNS caching?

Does it perform a sequence similar to:

publish new TLSA records
→ wait for the previous TTL
→ switch to the new certificate/chain
→ later remove obsolete TLSA records

or is rollover handled differently?

This is probably the part I am most interested in, since an incorrect ordering could temporarily prevent delivery from DANE-validating MTAs.

4. Is the generated TLSA profile configurable?

Is there currently any supported configuration option to control which TLSA records automatic DNS management publishes?

For example, can an administrator choose:

3 1 1 only

or use:

2 0 1 + 3 1 1

instead of the default 2 1 1 + 3 1 1 set, while still retaining automatic certificate and DNS management?

If this is currently fixed behaviour, would configurability of the TLSA publication profile be considered appropriate as a feature request?

Based on the source code (not just docs), here is what’s actually happening in v0.16.26:

1. Why 2 1 1 and not 2 0 1 for DANE-TA

Stalwart’s TLSA generator (crates/common/src/network/dns/records.rs) builds the TLSA set by iterating over every certificate in the chain Stalwart would present and hashing the certificate’s SubjectPublicKeyInfo (SPKI) with SHA‑256:

for (cert_num, cert) in key.cert.iter().enumerate() {
    ...
    let cert_usage = if cert_num == 0 { DaneEe } else { DaneTa };
    let cert_data = sha2::Sha256::digest(parsed_cert.subject_pki.raw);
    ...
    DnsRecord::TLSA(TLSARecord {
        cert_usage,
        selector: TlsaSelector::Spki,
        matching: TlsaMatching::Sha256,
        cert_data: cert_data.to_vec(),
    })
}

Selector (Spki = 1) and matching type (Sha256 = 1) are hardcoded and applied uniformly to every certificate in the chain, regardless of whether its usage field ends up 3 (DANE-EE, leaf) or 2 (DANE-TA, chain certs). There is no separate code path that switches to full-certificate hashing (selector 0) for the CA/intermediate entries, which is what would produce 2 0 1. This is a design choice, not a bug: it lets the same generation logic (hash the SPKI) apply to the whole chain, but it does mean the 2 1 1 records diverge from the 2 0 1 convention some validators (including Mailhardener) expect per RFC 7672’s non-normative recommendation.

2. Which certificates become 2 1 1 records

Every non-leaf certificate actually present in key.cert (the certificate chain Stalwart serves) gets its own 2 1 1 TLSA record — not just the immediate issuer. If an ACME CA rotates its intermediate (e.g., a cross-signed to a new chain), the new chain is picked up automatically and the TLSA RRset is regenerated to match whatever certificates Stalwart is now serving, since the generator always walks the currently loaded cert chain rather than a cached/fixed value.

3. Rollover handling

There is no TTL-aware staged rollover (publish-new → wait-TTL → switch → remove-old). Looking at the ACME renewal flow (crates/common/src/network/acme/renew.rs): the new certificate is installed and made active in the registry first (reload_registry), and only after that does it enqueue a DnsManagement task to regenerate and publish the TLSA RRset for the new certificate. So the certificate switch happens before the TLSA update is published — there’s a brief window where the live certificate may not yet match the currently published TLSA records (and vice versa, old cached TLSA records from resolvers may briefly be stale relative to the new cert) until the DNS task completes and any DNS propagation/TTL on the resolver’s side expires. There’s no explicit pre-publish-and-wait step baked into the renewal logic.

4. Configurability of the TLSA profile

No. The usage/selector/matching-type combination is fixed in code (DaneEe/DaneTa with Spki+Sha256 always), and there’s no exposed setting (in the DnsManagementProperties/MtaTlsStrategy schema or elsewhere) to choose a different profile such as leaf-only 3 1 1 or 2 0 1 + 3 1 1. This isn’t covered as a configuration option in the current docs or schema, so if you need a different TLSA profile today you’d need to manage DNS manually for that domain rather than use automatic DNS management.

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.

2 1 1 wasn’t a deliberate choice. Stalwart publishes one record for each certificate in the chain it serves on that name: the leaf as 3 1 1 and every certificate above it as DANE-TA, all with SPKI and SHA-256. RFC 7671 recommends selector 0 for DANE-TA, so 2 0 1 would be the better choice there, and I’ll change it.

On rollover, renewal installs the new certificate and starts serving it first, and only then replaces the whole TLSA set without pre-publishing anything, so for one TTL (300 seconds by default) plus propagation, senders with the old records cached see a mismatch. The 2 1 1 records only cover that gap if the new chain has the same intermediate, which Let’s Encrypt doesn’t guarantee. Setting reuseKey to true on the ACME provider (AcmeProvider | Stalwart) keeps the leaf key across renewals, so 3 1 1 stays valid, which is the usual way to run DANE with ACME.

The record set itself isn’t configurable. If you want a proper two-TTL rollover or EE-only records, remove TLSA from the records Stalwart publishes for the domain and manage those by hand. I’ve noted a TLSA profile setting as a feature request, and the docs should describe all of this, which they currently don’t.