Root CA missing in TLS handshake leading to delivery problems with DANE

Issue Description

Dear stalwart support,

I have problems receiving mails from some providers implementing outbound DANE. The problem has been troubleshooted on their side and the issue stems from stalwart using the combination of DANE-TA with SPKI(1) but not sending the root certificate.

Expected Behavior

When using DANE-TA with SPKI(1) instead of Full(0) the corresponding root certificate must be sent by the mail server (as else it cannot calculate and check the public key hash).

The exact issue is described here: RFC 7671 - The DNS-Based Authentication of Named Entities (DANE) Protocol: Updates and Operational Guidance

Actual Behavior

stalwart only sends the server certificate and intermediates, but not the root CA

Reproduction Steps

Try to receive mail from a provider with strict DANE checking

Stalwart Version

v0.16.x

Installation Method

Binary (Linux)

Database Backend

RocksDB

Blob Storage

Filesystem

Search Engine

Internal

Directory Backend

OIDC

Additional Context

Please consider also sending the Root CA on TLS connections or remove DANE-TA from the default DANE DNS records.
Having both DANE-EE and DANE-TS is redundant, as with DANE-EE the server certificate alone is already strictly matched to the TLSA record and there seems to be no reason to use both at the same time, they serve different purposes.

DANE-EE strictly matches to the server certificate public key offered by the server and SNI has to be ignored. It makes it easy if you have a single infrastructure serving lots of different DANE endpoints as you can use the same certificate for all endpoints. With DANE-EE certificate validity and SAN are strictly ignored, only the public key is checked.

DANE-TA is used to have a validated chain of trust (SNI is used, certificate validity and SAN are checked up to the Root CAs public keys) but up to a root of trust that you choose instead of the sender. It is for infrastructures with complex architecture using different servers which would make it impractical to share a key between so many endpoints.

Thanks for the great work you do on stalwart, I really appreciate this project.

I have reviewed the documentation and FAQ and confirm that my issue is NOT addressed there.

on

I have searched this support forum (open and closed topics) and confirm this is not a duplicate.

on

I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.

on

Let me research how Stalwart handles TLS certificate chain construction and DANE-TA to give an accurate answer.

This is very helpful. The source code clearly shows the TLSA record generation logic. Now I can see the exact behavior and confirm the issue.

Looking at lines ~330-360 of records.rs, Stalwart iterates over the certificate chain and generates:

  • DaneEe (DANE-EE, usage 3) for cert_num == 0 (the leaf/server certificate)
  • DaneTa (DANE-TA, usage 2) for all subsequent certs (intermediates)

Both use TlsaSelector::Spki (selector 1 = SubjectPublicKeyInfo hash). The key issue the user raises is: when a DANE-TA(2) + SPKI(1) record is published, RFC 7671 §5.2.2 requires that the trust anchor certificate itself be included in the TLS handshake, because the verifier needs it to compute and check the SPKI hash. Stalwart does not include the root CA in its TLS certificate chain – it only sends the leaf + intermediates.

This is a real, confirmed bug: Stalwart generates DANE-TA/SPKI(1) TLSA records for intermediate (and potentially root) certificates in the chain, but does not send those certificates in the TLS handshake, making DANE-TA validation impossible for strict validators.


This is a confirmed bug in how Stalwart generates and advertises TLSA records. Here is what the source shows:

Root cause: When generating TLSA records (DnsRecordType::Tlsa branch in crates/common/src/network/dns/records.rs), Stalwart iterates over every certificate in the loaded chain. The first cert (index 0) gets DaneEe (usage 3), and every subsequent cert gets DaneTa (usage 2). Both use TlsaSelector::Spki (selector 1). However, the TLS handshake only sends the leaf certificate plus any intermediates that were explicitly loaded – it does not send the root CA. Per RFC 7671 §5.2.2, when DANE-TA(2) + SPKI(1) is published, the trust anchor certificate must be included in the TLS handshake so the remote side can hash its SubjectPublicKeyInfo. Strict DANE validators (like the Trend Micro gateway you encountered) correctly reject connections where the trust anchor cert is absent.

Short-term workaround: You have two practical options:

  1. Manually edit your TLSA DNS records to remove the DANE-TA(2) records that Stalwart auto-generated, leaving only the DANE-EE(3) record for the leaf certificate. DANE-EE alone is sufficient and, as you correctly note, they serve different purposes – there is no benefit to publishing both simultaneously for most single-server deployments.
  2. Only load the leaf certificate in your Stalwart TLS configuration (without the intermediate chain), so no DANE-TA records are generated. This is not recommended from a general TLS standpoint but would prevent the DANE-TA mismatch.

Proper fix (needs a code change): Stalwart should either (a) include the full chain including the root CA in the TLS handshake when DANE-TA/SPKI records are published, or (b) stop generating DANE-TA records for certs it cannot guarantee will be sent in the handshake. A human maintainer will need to address this.

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

I looked into this against RFC 7671 and RFC 7672, and I believe what you are seeing is expected behavior rather than a defect:

A TLS server sends exactly the certificate chain it has been configured with. By long-standing convention, that chain contains the leaf (server) certificate plus any intermediates, but not the root CA, because validators are expected to already hold the root in their trust store. This is also how ACME-issued chains are structured: they terminate at the intermediate and never include the self-signed root. So a server presenting “leaf plus intermediates, but no root” is the normal, correct case, not something Stalwart is stripping out.

The passage you cited, RFC 7671 Section 5.2.2, is in fact a requirement on the party publishing the TLSA records, not on the server software. It says that whoever publishes a digest-based or SPKI DANE-TA(2) record MUST ensure the corresponding server is configured to also send that trust anchor certificate in its handshake. RFC 7672 repeats this for SMTP: if you rely on a DANE-TA(2) record, you must include that trust anchor in the chain you present, even when it is a self-signed root. In other words, the obligation is to keep your published records and your served chain in agreement. Publishing a DANE-TA record that points at a certificate the server does not send is the misconfiguration the RFC is warning against, and unfortunately that is what is happening here: the record references your root CA, but the root CA is not part of the chain being served, so a strict validator like the Trend Micro gateway correctly rejects it.

It is worth highlighting what Stalwart’s automatic DNS management actually publishes, because it already follows this rule. For each certificate that is genuinely present in the served chain, it generates a record: a DANE-EE (3 1 1) record for the leaf, and a DANE-TA (2 1 1) record for each additional certificate that is actually sent. It will never auto-publish a TLSA record for a certificate that is not in the handshake. So if you let Stalwart manage the records, you cannot end up in this inconsistent state. The mismatch arises specifically because a DANE-TA record was published by hand for the root, which the server does not transmit.

For the fix, you have two good options, and I would strongly recommend the first:

Publish DANE-EE (3 1 1) records instead. RFC 7672 explicitly states that SMTP TLSA records SHOULD, in most cases, be DANE-EE(3) SPKI(1) SHA2-256(1). These match the leaf key directly, need no chain gymnastics, survive certificate renewals cleanly (especially with ACME), and avoid this whole class of problem. As you noted yourself, for a single self-managed infrastructure the DANE-TA record is redundant.

If you genuinely want to keep a DANE-TA record pointing at your root, then you must add the root CA certificate to the chain file you configure, so that it is actually sent during the handshake. Be aware this is fragile with ACME-managed certificates, since those chains do not include the root, which is another reason the DANE-EE approach is preferable.

So in short, no code change is needed on the server side. The records and the served chain simply need to agree, and the cleanest way to achieve that is to publish 3 1 1 records, which is also the RFC’s general recommendation.

Thank you for the explanation.

I did not see that the DANE-TA 2 1 1 record published by automatic DNS management points to the intermediate certificate and naively assumed that it points to the root.

So the issue brought up by the vendor is bogus and it should actually work right now.

However I still want to improve my configuration to make sure that no confusion can come up on what to validate.

Is it possible to configure stalwarts automatic dns management to only publish DANE-EE records and omit DANE-TA?

Configuring which TLSA records are published is not supported. What you can do is delete the EE record once Stalwart publishes it. If your concern is ACME renewals, you can enable reusing the private key (a recent option added to AcmeProvider on v0.16.10) so the EE record does not change on renewals.