1// Package dane verifies TLS certificates through DNSSEC-verified TLSA records.
 
3// On the internet, TLS certificates are commonly verified by checking if they are
 
4// signed by one of many commonly trusted Certificate Authorities (CAs). This is
 
5// PKIX or WebPKI. With DANE, TLS certificates are verified through
 
6// DNSSEC-protected DNS records of type TLSA. These TLSA records specify the rules
 
7// for verification ("usage") and whether a full certificate ("selector" cert) is
 
8// checked or only its "subject public key info" ("selector" spki). The (hash of)
 
9// the certificate or "spki" is included in the TLSA record ("matchtype").
 
11// DANE SMTP connections have two allowed "usages" (verification rules):
 
12//   - DANE-EE, which only checks if the certificate or spki match, without the
 
13//     WebPKI verification of expiration, name or signed-by-trusted-party verification.
 
14//   - DANE-TA, which does verification similar to PKIX/WebPKI, but verifies against
 
15//     a certificate authority ("trust anchor", or "TA") specified in the TLSA record
 
16//     instead of the CA pool.
 
18// DANE has two more "usages", that may be used with protocols other than SMTP:
 
19//   - PKIX-EE, which matches the certificate or spki, and also verifies the
 
20//     certificate against the CA pool.
 
21//   - PKIX-TA, which verifies the certificate or spki against a "trust anchor"
 
22//     specified in the TLSA record, that also has to be trusted by the CA pool.
 
24// TLSA records are looked up for a specific port number, protocol (tcp/udp) and
 
25// host name. Each port can have different TLSA records. TLSA records must be
 
26// signed and verified with DNSSEC before they can be trusted and used.
 
28// TLSA records are looked up under "TLSA candidate base domains". The domain
 
29// where the TLSA records are found is the "TLSA base domain". If the host to
 
30// connect to is a CNAME that can be followed with DNSSEC protection, it is the
 
31// first TLSA candidate base domain. If no protected records are found, the
 
32// original host name is the second TLSA candidate base domain.
 
34// For TLS connections, the TLSA base domain is used with SNI during the
 
37// For TLS certificate verification that requires PKIX/WebPKI/trusted-anchor
 
38// verification (all except DANE-EE), the potential second TLSA candidate base
 
39// domain name is also a valid hostname. With SMTP, additionally for hosts found in
 
40// MX records for a "next-hop domain", the "original next-hop domain" (domain of an
 
41// email address to deliver to) is also a valid name, as is the "CNAME-expanded
 
42// original next-hop domain", bringing the potential total allowed names to four
 
43// (if CNAMEs are followed for the MX hosts).
 
46// todo: why is https://datatracker.ietf.org/doc/html/draft-barnes-dane-uks-00 not in use? sounds reasonable.
 
47// todo: add a DialSRV function that accepts a domain name, looks up srv records, dials the service, verifies dane certificate and returns the connection. for 
../rfc/7673 
62	"golang.org/x/exp/slog"
 
64	"github.com/mjl-/adns"
 
66	"github.com/mjl-/mox/dns"
 
67	"github.com/mjl-/mox/mlog"
 
68	"github.com/mjl-/mox/stub"
 
72	MetricVerify       stub.Counter = stub.CounterIgnore{}
 
73	MetricVerifyErrors stub.Counter = stub.CounterIgnore{}
 
77	// ErrNoRecords means no TLSA records were found and host has not opted into DANE.
 
78	ErrNoRecords = errors.New("dane: no tlsa records")
 
80	// ErrInsecure indicates insecure DNS responses were encountered while looking up
 
81	// the host, CNAME records, or TLSA records.
 
82	ErrInsecure = errors.New("dane: dns lookups insecure")
 
84	// ErrNoMatch means some TLSA records were found, but none can be verified against
 
85	// the remote TLS certificate.
 
86	ErrNoMatch = errors.New("dane: no match between certificate and tlsa records")
 
89// VerifyError is an error encountered while verifying a DANE TLSA record. For
 
90// example, an error encountered with x509 certificate trusted-anchor verification.
 
91// A TLSA record that does not match a TLS certificate is not a VerifyError.
 
92type VerifyError struct {
 
93	Err    error     // Underlying error, possibly from crypto/x509.
 
94	Record adns.TLSA // Cause of error.
 
97// Error returns a string explaining this is a dane verify error along with the
 
99func (e VerifyError) Error() string {
 
100	return fmt.Sprintf("dane verify error: %s", e.Err)
 
103// Unwrap returns the underlying error.
 
104func (e VerifyError) Unwrap() error {
 
108// Dial looks up DNSSEC-protected DANE TLSA records for the domain name and
 
109// port/service in address, checks for allowed usages, makes a network connection
 
110// and verifies the remote certificate against the TLSA records. If verification
 
111// succeeds, the verified record is returned.
 
113// Different protocols require different usages. For example, SMTP with STARTTLS
 
114// for delivery only allows usages DANE-TA and DANE-EE. If allowedUsages is
 
115// non-nil, only the specified usages are taken into account when verifying, and
 
116// any others ignored.
 
118// Errors that can be returned, possibly in wrapped form:
 
119//   - ErrNoRecords, also in case the DNS response indicates "not found".
 
120//   - adns.DNSError, potentially wrapping adns.ExtendedError of which some can
 
121//     indicate DNSSEC errors.
 
123//   - VerifyError, potentially wrapping errors from crypto/x509.
 
124func Dial(ctx context.Context, elog *slog.Logger, resolver dns.Resolver, network, address string, allowedUsages []adns.TLSAUsage, pkixRoots *x509.CertPool) (net.Conn, adns.TLSA, error) {
 
125	log := mlog.New("dane", elog)
 
127	// Split host and port.
 
128	host, portstr, err := net.SplitHostPort(address)
 
130		return nil, adns.TLSA{}, fmt.Errorf("parsing address: %w", err)
 
132	port, err := resolver.LookupPort(ctx, network, portstr)
 
134		return nil, adns.TLSA{}, fmt.Errorf("parsing port: %w", err)
 
137	hostDom, err := dns.ParseDomain(strings.TrimSuffix(host, "."))
 
139		return nil, adns.TLSA{}, fmt.Errorf("parsing host: %w", err)
 
143	// First follow CNAMEs for host. If the path to the final name is secure, we must
 
144	// lookup TLSA there first, then fallback to the original name. If the final name
 
145	// is secure that's also the SNI server name we must use, with the original name as
 
146	// allowed host during certificate name checks (for all TLSA usages other than
 
149	cnameAuthentic := true
 
150	for i := 0; ; i += 1 {
 
152			return nil, adns.TLSA{}, fmt.Errorf("too many cname lookups")
 
154		cname, cnameResult, err := resolver.LookupCNAME(ctx, cnameDom.ASCII+".")
 
155		cnameAuthentic = cnameAuthentic && cnameResult.Authentic
 
156		if !cnameResult.Authentic && i == 0 {
 
157			return nil, adns.TLSA{}, fmt.Errorf("%w: cname lookup insecure", ErrInsecure)
 
158		} else if dns.IsNotFound(err) {
 
160		} else if err != nil {
 
161			return nil, adns.TLSA{}, fmt.Errorf("resolving cname %s: %w", cnameDom, err)
 
162		} else if d, err := dns.ParseDomain(strings.TrimSuffix(cname, ".")); err != nil {
 
163			return nil, adns.TLSA{}, fmt.Errorf("parsing cname: %w", err)
 
171	if strings.HasSuffix(network, "4") {
 
173	} else if strings.HasSuffix(network, "6") {
 
176	ips, _, err := resolver.LookupIP(ctx, ipnetwork, cnameDom.ASCII+".")
 
177	// note: For SMTP with opportunistic DANE we would stop here with an insecure
 
178	// response. But as long as long as we have a verified original tlsa base name, we
 
179	// can continue with regular DANE.
 
181		return nil, adns.TLSA{}, fmt.Errorf("resolving ips: %w", err)
 
182	} else if len(ips) == 0 {
 
183		return nil, adns.TLSA{}, &adns.DNSError{Err: "no ips for host", Name: cnameDom.ASCII, IsNotFound: true}
 
186	// Lookup TLSA records. If resolving CNAME was secure, we try that first. Otherwise
 
187	// we try at the secure original domain.
 
192	var records []adns.TLSA
 
193	var result adns.Result
 
196		records, result, err = resolver.LookupTLSA(ctx, port, network, baseDom.ASCII+".")
 
197		// If no (secure) records can be found at the final cname, and there is an original
 
198		// name, try at original name.
 
200		if baseDom != hostDom && (dns.IsNotFound(err) || !result.Authentic) {
 
204		if !result.Authentic {
 
205			return nil, adns.TLSA{}, ErrInsecure
 
206		} else if dns.IsNotFound(err) {
 
207			return nil, adns.TLSA{}, ErrNoRecords
 
208		} else if err != nil {
 
209			return nil, adns.TLSA{}, fmt.Errorf("lookup dane tlsa records: %w", err)
 
214	// Keep only the allowed usages.
 
215	if allowedUsages != nil {
 
217		for _, r := range records {
 
218			for _, usage := range allowedUsages {
 
219				if r.Usage == usage {
 
226		records = records[:o]
 
227		if len(records) == 0 {
 
228			// No point in dialing when we know we won't be able to verify the remote TLS
 
230			return nil, adns.TLSA{}, fmt.Errorf("no usable tlsa records remaining: %w", ErrNoMatch)
 
234	// We use the base domain for SNI, allowing the original domain as well.
 
236	var moreAllowedHosts []dns.Domain
 
237	if baseDom != hostDom {
 
238		moreAllowedHosts = []dns.Domain{hostDom}
 
241	// Dial the remote host.
 
242	timeout := 30 * time.Second
 
243	if deadline, ok := ctx.Deadline(); ok && len(ips) > 0 {
 
244		timeout = time.Until(deadline) / time.Duration(len(ips))
 
246	dialer := &net.Dialer{Timeout: timeout}
 
249	for _, ip := range ips {
 
250		addr := net.JoinHostPort(ip.String(), portstr)
 
251		c, err := dialer.DialContext(ctx, network, addr)
 
253			dialErrs = append(dialErrs, err)
 
260		return nil, adns.TLSA{}, errors.Join(dialErrs...)
 
263	var verifiedRecord adns.TLSA
 
264	config := TLSClientConfig(log.Logger, records, baseDom, moreAllowedHosts, &verifiedRecord, pkixRoots)
 
265	tlsConn := tls.Client(conn, &config)
 
266	if err := tlsConn.HandshakeContext(ctx); err != nil {
 
268		return nil, adns.TLSA{}, err
 
270	return tlsConn, verifiedRecord, nil
 
273// TLSClientConfig returns a tls.Config to be used for dialing/handshaking a
 
274// TLS connection with DANE verification.
 
276// Callers should only pass records that are allowed for the intended use. DANE
 
277// with SMTP only allows DANE-EE and DANE-TA usages, not the PKIX-usages.
 
279// The config has InsecureSkipVerify set to true, with a custom VerifyConnection
 
280// function for verifying DANE. Its VerifyConnection can return ErrNoMatch and
 
281// additionally one or more (wrapped) errors of type VerifyError.
 
283// The TLS config uses allowedHost for SNI.
 
285// If verifiedRecord is not nil, it is set to the record that was successfully
 
287func TLSClientConfig(elog *slog.Logger, records []adns.TLSA, allowedHost dns.Domain, moreAllowedHosts []dns.Domain, verifiedRecord *adns.TLSA, pkixRoots *x509.CertPool) tls.Config {
 
288	log := mlog.New("dane", elog)
 
290		ServerName:         allowedHost.ASCII, // For SNI.
 
291		InsecureSkipVerify: true,
 
292		VerifyConnection: func(cs tls.ConnectionState) error {
 
293			verified, record, err := Verify(log.Logger, records, cs, allowedHost, moreAllowedHosts, pkixRoots)
 
294			log.Debugx("dane verification", err, slog.Bool("verified", verified), slog.Any("record", record))
 
296				if verifiedRecord != nil {
 
297					*verifiedRecord = record
 
300			} else if err == nil {
 
303			return fmt.Errorf("%w, and error(s) encountered during verification: %w", ErrNoMatch, err)
 
309// Verify checks if the TLS connection state can be verified against DANE TLSA
 
312// allowedHost along with the optional moreAllowedHosts are the host names that are
 
313// allowed during certificate verification (as used by PKIX-TA, PKIX-EE, DANE-TA,
 
314// but not DANE-EE). A typical connection would allow just one name, but some uses
 
315// of DANE allow multiple, like SMTP which allow up to four valid names for a TLS
 
316// certificate based on MX/CNAME/TLSA/DNSSEC lookup results.
 
318// When one of the records matches, Verify returns true, along with the matching
 
319// record and a nil error.
 
320// If there is no match, then in the typical case Verify returns: false, a zero
 
321// record value and a nil error.
 
322// If an error is encountered while verifying a record, e.g. for x509
 
323// trusted-anchor verification, an error may be returned, typically one or more
 
324// (wrapped) errors of type VerifyError.
 
326// Verify is useful when DANE verification and its results has to be done
 
327// separately from other validation, e.g. for MTA-STS. The caller can create a
 
328// tls.Config with a VerifyConnection function that checks DANE and MTA-STS
 
330func Verify(elog *slog.Logger, records []adns.TLSA, cs tls.ConnectionState, allowedHost dns.Domain, moreAllowedHosts []dns.Domain, pkixRoots *x509.CertPool) (verified bool, matching adns.TLSA, rerr error) {
 
331	log := mlog.New("dane", elog)
 
333	if len(records) == 0 {
 
334		MetricVerifyErrors.Inc()
 
335		return false, adns.TLSA{}, fmt.Errorf("verify requires at least one tlsa record")
 
338	for _, r := range records {
 
339		ok, err := verifySingle(log, r, cs, allowedHost, moreAllowedHosts, pkixRoots)
 
341			errs = append(errs, VerifyError{err, r})
 
346	MetricVerifyErrors.Inc()
 
347	return false, adns.TLSA{}, errors.Join(errs...)
 
350// verifySingle verifies the TLS connection against a single DANE TLSA record.
 
352// If the remote TLS certificate matches with the TLSA record, true is
 
353// returned. Errors may be encountered while verifying, e.g. when checking one
 
354// of the allowed hosts against a TLSA record. A typical non-matching/verified
 
355// TLSA record returns a nil error. But in some cases, e.g. when encountering
 
356// errors while verifying certificates against a trust-anchor, an error can be
 
357// returned with one or more underlying x509 verification errors. A nil-nil error
 
358// is only returned when verified is false.
 
359func verifySingle(log mlog.Log, tlsa adns.TLSA, cs tls.ConnectionState, allowedHost dns.Domain, moreAllowedHosts []dns.Domain, pkixRoots *x509.CertPool) (verified bool, rerr error) {
 
360	if len(cs.PeerCertificates) == 0 {
 
361		return false, fmt.Errorf("no server certificate")
 
364	match := func(cert *x509.Certificate) bool {
 
366		switch tlsa.Selector {
 
367		case adns.TLSASelectorCert:
 
369		case adns.TLSASelectorSPKI:
 
370			buf = cert.RawSubjectPublicKeyInfo
 
375		switch tlsa.MatchType {
 
376		case adns.TLSAMatchTypeFull:
 
377		case adns.TLSAMatchTypeSHA256:
 
378			d := sha256.Sum256(buf)
 
380		case adns.TLSAMatchTypeSHA512:
 
381			d := sha512.Sum512(buf)
 
387		return bytes.Equal(buf, tlsa.CertAssoc)
 
390	pkixVerify := func(host dns.Domain) ([][]*x509.Certificate, error) {
 
391		// Default Verify checks for expiration. We pass the host name to check. And we
 
392		// configure the intermediates. The roots are filled in by the x509 package.
 
393		opts := x509.VerifyOptions{
 
395			Intermediates: x509.NewCertPool(),
 
398		for _, cert := range cs.PeerCertificates[1:] {
 
399			opts.Intermediates.AddCert(cert)
 
401		chains, err := cs.PeerCertificates[0].Verify(opts)
 
406	case adns.TLSAUsagePKIXTA:
 
407		// We cannot get at the system trusted ca certificates to look for the trusted
 
408		// anchor. So we just ask Go to verify, then see if any of the chains include the
 
411		for _, host := range append([]dns.Domain{allowedHost}, moreAllowedHosts...) {
 
412			chains, err := pkixVerify(host)
 
413			log.Debugx("pkix-ta verify", err)
 
415				errs = append(errs, err)
 
418			// The chains by x509's Verify should include the longest possible match, so it is
 
420			for _, chain := range chains {
 
421				// If pkix verified, check if any of the certificates match.
 
422				for i := len(chain) - 1; i >= 0; i-- {
 
429		return false, errors.Join(errs...)
 
431	case adns.TLSAUsagePKIXEE:
 
432		// Check for a certificate match.
 
433		if !match(cs.PeerCertificates[0]) {
 
438		for _, host := range append([]dns.Domain{allowedHost}, moreAllowedHosts...) {
 
439			_, err := pkixVerify(host)
 
440			log.Debugx("pkix-ee verify", err)
 
444			errs = append(errs, err)
 
446		return false, errors.Join(errs...)
 
448	case adns.TLSAUsageDANETA:
 
449		// We set roots, so the system defaults don't get used. Verify checks the host name
 
450		// (set below) and checks for expiration.
 
451		opts := x509.VerifyOptions{
 
452			Roots: x509.NewCertPool(),
 
455		// If the full certificate was included, we must add it to the valid roots, the TLS
 
458		if tlsa.Selector == adns.TLSASelectorCert && tlsa.MatchType == adns.TLSAMatchTypeFull {
 
459			cert, err := x509.ParseCertificate(tlsa.CertAssoc)
 
461				log.Debugx("parsing full exact certificate from tlsa record to use as root for usage dane-trusted-anchor", err)
 
462				// Continue anyway, perhaps the servers sends it again in a way that the tls package can parse? (unlikely)
 
464				opts.Roots.AddCert(cert)
 
469		for _, cert := range cs.PeerCertificates {
 
471				opts.Roots.AddCert(cert)
 
477			// Trusted anchor was not found in TLS certificates so we won't be able to
 
482		// Trusted anchor was found, still need to verify.
 
484		for _, host := range append([]dns.Domain{allowedHost}, moreAllowedHosts...) {
 
485			opts.DNSName = host.ASCII
 
486			_, err := cs.PeerCertificates[0].Verify(opts)
 
490			errs = append(errs, err)
 
492		return false, errors.Join(errs...)
 
494	case adns.TLSAUsageDANEEE:
 
495		// 
../rfc/7250 is about raw public keys instead of x.509 certificates in tls
 
496		// handshakes. Go's crypto/tls does not implement the extension (see
 
497		// crypto/tls/common.go, the extensions values don't appear in the
 
498		// rfc, but have values 19 and 20 according to
 
499		// https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#tls-extensiontype-values-1
 
501		// questionable that this is commonly implemented. For now the world can probably
 
502		// live with an ignored certificate wrapped around the subject public key info.
 
506		// The whole point of this type is to have simple secure infrastructure that
 
507		// doesn't automatically expire (at the most inconvenient times).
 
508		return match(cs.PeerCertificates[0]), nil
 
511		// Unknown, perhaps defined in the future. Not an error.
 
512		log.Debug("unrecognized tlsa usage, skipping", slog.Any("tlsausage", tlsa.Usage))