danecheck: DANE SMTP validator

[ bsd3, network, program ] [ Propose Tags ] [ Report a vulnerability ]

danecheck is a diagnostic tool that verifies the DANE TLSA security (RFC 7672) of an email domain's SMTP delivery path. For the given domain it follows the MX records, looks up A/AAAA addresses for each MX host, queries TLSA records at _25._tcp.<mx> (with the RFC 7672 CNAME extension to a TLSA base name), connects on port 25, performs STARTTLS, and reports whether each peer's certificate chain matches the TLSA RRset. . Output is streamed in BIND-style record form with per-IP diagnostics: the DNS chain for each lookup, the negotiated TLS version, cipher and key-exchange group, the presented peer names, and a depth-by-depth view of the certificate chain with TLSA match/mismatch annotations. The process exit code reflects whether the domain validated cleanly. . See the README.md for usage examples and command-line options.


[Skip to Readme]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 1.0.0.0
Dependencies attoparsec (>=0.14.4 && <0.15), base (>=4.20 && <5), bytestring (>=0.10 && <0.13), clock (>=0.8 && <0.9), containers (>=0.6 && <0.9), crypton (>=1.0 && <1.2), crypton-asn1-encoding (>=0.9 && <0.11), crypton-asn1-types (>=0.3 && <0.5), crypton-x509 (>=1.7 && <1.10), crypton-x509-store (>=1.7 && <1.10), crypton-x509-validation (>=1.7 && <1.10), data-default-class (>=0.1 && <0.3), dnsbase (>=1.0.2 && <1.1), hostname (>=1.0 && <1.1), idna2008 (>=1.0 && <1.1), iproute (>=1.6 && <1.8), network (>=3.0 && <3.3), optparse-applicative (>=0.14 && <0.20), ram (>=0.19 && <0.23), streaming (>=0.2 && <0.3), streaming-bytestring (>=0.2 && <0.4), text (>=1.2 && <2.2), time-hourglass (>=0.2 && <0.4), tls (>=2.4.3 && <2.5), transformers (>=0.5 && <0.7), unix (>=2.7 && <2.9), unix-time (>=0.3 && <0.6) [details]
Tested with ghc ==9.10.3, ghc ==9.12.4, ghc ==9.14.1
License BSD-3-Clause
Author Viktor Dukhovni (with contributions from Peter Duchovni)
Maintainer postfix-users@dukhovni.org
Uploaded by ietfdane at 2026-06-09T14:26:14Z
Category Network
Home page https://github.com/vdukhovni/danecheck
Bug tracker https://github.com/vdukhovni/danecheck/issues
Source repo head: git clone https://github.com/vdukhovni/danecheck.git
Distributions
Executables danecheck
Downloads 1 total (1 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs not available [build log]
All reported builds failed as of 2026-06-09 [all 2 reports]

Readme for danecheck-1.0.0.0

[back to package description]

Check DANE TLSA security of an email domain

Features

  • Test the local resolver configuration by verifying the validity of the root zone DNSKEY and SOA RRSets.

  • Test the DNSSEC delegation chain of a given TLD (DS, DNSKEY and SOA), or just DNSKEY and SOA when checking the root zone itself.

  • Check whether an email domain is fully protected (across all of its MX hosts) by DANE TLSA records, and whether these match the actual certificate chains seen at each IP address of each MX host.

  • Perform certificate chain verification at a time offset from the current time to ensure that certificates are not about to expire too soon.

A non-zero exit status is returned if any DNS lookups fail or if the MX records or MX hosts are in an unsigned zone, or if for one of the MX hosts no associated secure TLSA records are found. A non-zero exit status is also returned if any of the attempted SMTP connections fail to complete a TLS handshake whose certificate chain matches the TLSA records.

By default danecheck makes one TLS connection per IP and offers the TLS library's full default set of signature algorithms, letting the server pick. Hosts that publish both ECDSA and RSA certificates are uncommon, and when only one is correctly bound to a TLSA record it is almost always RSA — so testing whichever cert the server prioritises is usually sufficient.

Where both certificate kinds are in use and you want to probe them independently, the --sigalgs option restricts (and orders) the signature-algorithm groups offered to the server. The argument is a comma-separated, case-insensitive list of named groups drawn from rsa, ecdsa and eddsa:

  • --sigalgs ecdsa   offer only ECDSA (P-256/P-384/P-521 with SHA-2)
  • --sigalgs rsa     offer only RSA (RSA-PSS for TLS 1.3, RSA-PKCS#1 for TLS 1.2)
  • --sigalgs eddsa   offer only Ed25519 and Ed448
  • --sigalgs ecdsa,rsa    offer ECDSA first, fall back to RSA
  • --sigalgs any (or omit the option)   the TLS library default

Servers that don't support any of the offered algorithms will fail the TLS handshake, which is the intended diagnostic — it confirms that the requested cert kind isn't available at that peer.

Synopsis

For an overview of the danecheck command-line interface, run danecheck --help.

When scanning the root domain, what's checked is secure retrieval of the root DNSKEY and SOA RRSets. Similarly, when scanning a top-level domain, what's checked is secure retrieval of its DS, DNSKEY and SOA records. For all other domains, MX records, address records and TLSA records are retrieved and must be DNSSEC signed.

Each MX host is expected to have TLSA records, an SMTP connection is made to each address of each such MX host (with the '-A' option connections are also made to MX hosts that don't have DNSSEC-signd TLSA records). A TLS handshake is performed to retrieve the host's certificate chain, which is verified against the DNS TLSA RRs. If anything is unavailable, insecure or wrong, a non-zero exit code is returned.

The '-D' option can be used zero or more times to skip SMTP connections to MX hosts that are expected to be down. The '-U' option inverts the action of the '-D' option, and connects to only those MX hosts that are specified via the '-D' option (none if no such hosts are specified).

Some "reserved" MX host addresses are assumed invalid and not tested by default. The command will report a non-zero exit status when these are encountered. The reserved addresses are the address blocks from the IANA IPv4 and IPv6 special purpose address registries:

these include, for example, the RFC1918 private IPv4 ranges, and should not appear among the addresses of MX hosts of internet-facing email domains. If you're testing a non-public domain on an internal network, you can use the -R option to enable connections to reserved addresses.

Building the software

Prerequisite: A working GHC toolchain

danecheck is written in Haskell and depends only on libraries available from Hackage; no external C libraries are required.

The recommended way to install a Haskell toolchain is via ghcup:

$ curl --proto '=https' --tlsv1.2 -sSf \
      https://get-ghcup.haskell.org | sh

Once ghcup is installed, fetch GHC and cabal-install:

$ ghcup install ghc 9.12.4
$ ghcup set     ghc 9.12.4
$ ghcup install cabal recommended

danecheck is tested against GHC 9.10.3, 9.12.4 and 9.14.1; any of these will work. Use whichever your system already provides, or the most recent one if you're starting fresh.

Disk space note: each GHC toolchain installed by ghcup is substantial — a single GHC takes roughly 2.2–3.0 GB under ~/.ghcup/ghc/<version>/. By comparison the cabal package store for danecheck and its transitive dependencies is around 470 MB under ~/.cabal/store/<ghc-version>-<hash>/. Plan for around 3 GB of free space for a fresh single-GHC setup, more if you install multiple GHC versions. Old GHC versions can be removed via ghcup rm ghc <version> when no longer needed.

GHC 9.14.1 dependency bounds: a couple of danecheck's transitive dependencies have not yet relaxed their upper bounds to admit the base, containers and time versions shipped with GHC 9.14.1. Two install scenarios behave differently:

  • Building from a source checkout (e.g. after git clone): the cabal.project shipped in this repository already carries the necessary allow-newer: base, containers, time stanza, and Cabal applies it automatically. Nothing to do.

  • Installing from Hackage with cabal install danecheck: Cabal does not consult the package's own cabal.project during a Hackage install. Pass the override on the command line yourself:

      $ cabal install danecheck --allow-newer=base,containers,time
    

    or add the same line to your own cabal.project / cabal config.

GHC 9.10.3 and 9.12.4 build cleanly in either scenario without any workaround.

Compile and install danecheck

From the top of the source tree:

$ cabal update
$ cabal build danecheck
$ exe=$(cabal -v0 list-bin danecheck)
$ strip "$exe"
$ install "$exe" ~/.local/bin    # or any directory on your PATH

For a self-contained install from Hackage (no source checkout required), cabal install danecheck will fetch the released version and place the binary under the path configured by cabal's installdir setting (typically ~/.cabal/bin).

Getting Started

Choose a working DNSSEC-validating resolver

It is assumed that your system has a working DNSSEC-validating resolver (BIND 9, unbound or similar) running locally and listening on the loopback interface at UDP and TCP at 127.0.0.1:53.

By default the system's /etc/resolv.conf file is ignored and the default nameserver list consists of just "127.0.0.1". If you want to specify a different validating resolver, use the -n option to select an alternate IP address. The /etc/resolv.conf nameserver list can be selected via the -N option.

Check that the software and resolver are working

Assuming the installation directory is ~/.local/bin:

$ PATH=$HOME/.local/bin:$PATH
$ danecheck || printf "ERROR: root zone record validation failed\n" >&2

This should output a validated copy of the root zone SOA RR and not print the ERROR message. For example:

$ danecheck
. IN SOA a.root-servers.net. nstld@verisign-grs.com. 2026060900 1800 900 604800 86400 ; NoError AD=1

The trailing ; NoError AD=1 comment on each output line indicates the rcode and DNSSEC AD bit of the response. A validated answer shows NoError AD=1; failures or insecure answers (AD=0) are diagnosed in place.

Validated DS and DNSKEY records are not printed: their successful retrieval is implicit in the SOA line being secure and present. Only failing or insecure lookups along the validation chain produce diagnostic output, so the steady-state success case is intentionally brief.

The . between the first and second DNS labels of the SOA contact mailbox field is displayed as an @ sign, since some domains have literal . characters in the localpart (first label) of the address. The trailing . is not stripped from the domain part of the address.

Check your TLD

If your domain's ancestor TLD is not DNSSEC signed (still the case for some ccTLD domains), then DNSSEC will not be used for your domain either, except from resolvers that have configured a custom trust-anchor for your domain or one of its ancestor domains. When checking the DNSSEC status of a TLD danecheck walks the DS, DNSKEY and SOA records. As with the root, validated DS and DNSKEY records are not printed; only the SOA confirmation appears in the success case:

$ danecheck org
org. IN SOA a0.org.afilias-nst.info. hostmaster.donuts.email. 1781006459 7200 900 1209600 3600 ; NoError AD=1

Checking your own domain

With your resolver tested for working root zone security and DNSSEC working for your TLD, you can proceed to regularly test your own domain. Example:

$ domain=ietf.org
$ danecheck ietf.org
ietf.org. IN MX 0 mail2.ietf.org. ; NoError AD=1
mail2.ietf.org. IN A 166.84.6.31 ; NoError AD=1
mail2.ietf.org. IN AAAA 2602:f977:800:f7f6::1 ; NoError AD=1
_25._tcp.mail2.ietf.org. IN TLSA 3 1 1 810e86ff280553ec895b7f35132a3e919f9aa0517b181645492cd56c8bc2e67a ; NoError AD=1
  mail2.ietf.org[166.84.6.31]: pass: TLSA match: depth = 0, name = *.ietf.org
    TLS = TLS1.3 with TLS_CHACHA20_POLY1305_SHA256,X25519,PubKeyALG_EC
    name = *.ietf.org
    depth = 0
      Issuer CommonName = E8
      Issuer Organization = Let's Encrypt
      notBefore = 2026-04-17T08:18:28Z
      notAfter = 2026-07-16T08:18:27Z
      Subject CommonName = *.ietf.org
      pkey sha256 [matched] <- 3 1 1 810e86ff280553ec895b7f35132a3e919f9aa0517b181645492cd56c8bc2e67a
    depth = 1
      Issuer CommonName = ISRG Root X1
      Issuer Organization = Internet Security Research Group
      notBefore = 2024-03-13T00:00:00Z
      notAfter = 2027-03-12T23:59:59Z
      Subject CommonName = E8
      Subject Organization = Let's Encrypt
      pkey sha256 [nomatch] <- 2 1 1 885bf0572252c6741dc9a52f5044487fef2a93b811cdedfad7624cc283b7cdd5
  mail2.ietf.org[2602:f977:800:f7f6::1]: pass: TLSA match: depth = 0, name = *.ietf.org
    TLS = TLS1.3 with TLS_CHACHA20_POLY1305_SHA256,X25519,PubKeyALG_EC
    name = *.ietf.org
    depth = 0
      Issuer CommonName = E8
      Issuer Organization = Let's Encrypt
      notBefore = 2026-04-17T08:18:28Z
      notAfter = 2026-07-16T08:18:27Z
      Subject CommonName = *.ietf.org
      pkey sha256 [matched] <- 3 1 1 810e86ff280553ec895b7f35132a3e919f9aa0517b181645492cd56c8bc2e67a
    depth = 1
      Issuer CommonName = ISRG Root X1
      Issuer Organization = Internet Security Research Group
      notBefore = 2024-03-13T00:00:00Z
      notAfter = 2027-03-12T23:59:59Z
      Subject CommonName = E8
      Subject Organization = Let's Encrypt
      pkey sha256 [nomatch] <- 2 1 1 885bf0572252c6741dc9a52f5044487fef2a93b811cdedfad7624cc283b7cdd5

If the exit code indicates failure you should check the output for:

  • DNS Failures
    • Any failed DNS queries (not NoError or NODATA) or insecure answers (AD=0)
    • Non-existent MX hosts or TLSA records
  • SMTP failures
    • Failures to connect to an MX host at one or more of its IP addresses
    • Rejected or timed-out SMTP commands
    • Lack of STARTTLS support
    • Failure to complete the TLS handshake
  • Chain verification failures
    • Failure to find matching TLSA records
    • Name check failure with DANE-TA(2) TLSA records
    • Certificate expiration with DANE-TA(2) TLSA records

Skipping out-of-service MX hosts

If some of your MX hosts are down and you want to verify the certificate chains of only the remaining hosts, you can specify the --down (-D) option one or more times to skip SMTP tests for those hosts. Their DNS security (including presence of TLSA records) is still tested and is still required for the overall check to succeed. Use --upside-down (-U) to invert the sense of -D and probe only the listed hosts, leaving the rest unchecked.

Common failure reasons

  • Certificates not matching TLSA records
  • No SMTP service on a subset of MX host IP addresses
  • STARTTLS not offered
  • TLSA Lookups ServFail
  • Invalid MX hostname (only MX hosts with "LDH" labels are considered valid).
  • MX target resolving to an IANA special-purpose IPv4 or IPv6 address (RFC1918 private space, loopback, 6to4 of any of the above, etc.) — overridable with -R for testing on internal networks