maddy

Fork https://github.com/foxcpp/maddy

git clone git://git.lin.moe/go/maddy.git

  1/*
  2Maddy Mail Server - Composable all-in-one email server.
  3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
  4
  5This program is free software: you can redistribute it and/or modify
  6it under the terms of the GNU General Public License as published by
  7the Free Software Foundation, either version 3 of the License, or
  8(at your option) any later version.
  9
 10This program is distributed in the hope that it will be useful,
 11but WITHOUT ANY WARRANTY; without even the implied warranty of
 12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13GNU General Public License for more details.
 14
 15You should have received a copy of the GNU General Public License
 16along with this program.  If not, see <https://www.gnu.org/licenses/>.
 17*/
 18
 19package remote
 20
 21import (
 22	"crypto/tls"
 23	"crypto/x509"
 24	"time"
 25
 26	"github.com/foxcpp/maddy/framework/dns"
 27	"github.com/foxcpp/maddy/framework/exterrors"
 28)
 29
 30// Used to override verification time for DANE-TA tests.
 31var verifyDANETime time.Time
 32
 33// verifyDANE checks whether TLSA records require TLS use and match the
 34// certificate and name used by the server.
 35//
 36// overridePKIX result indicates whether DANE should make server authentication
 37// succeed even if PKIX/X.509 verification fails. That is, if InsecureSkipVerify
 38// is used and verifyDANE returns overridePKIX=true, the server certificate
 39// should trusted.
 40func verifyDANE(recs []dns.TLSA, connState tls.ConnectionState) (overridePKIX bool, err error) {
 41	tlsErr := &exterrors.SMTPError{
 42		Code:         550,
 43		EnhancedCode: exterrors.EnhancedCode{5, 7, 1},
 44		Message:      "TLS is required but unsupported or failed (enforced by DANE)",
 45		TargetName:   "remote",
 46		Misc: map[string]interface{}{
 47			"remote_server": connState.ServerName,
 48		},
 49	}
 50
 51	// See https://tools.ietf.org/html/rfc7672#section-2.2 for requirements of
 52	// TLS discovery.
 53	// We assume upstream resolver will generate an error if the DNSSEC
 54	// signature is bogus so this case is "DNSSEC-authenticated denial of existence".
 55	if len(recs) == 0 {
 56		return false, nil
 57	}
 58
 59	// Require TLS even if all records are not usable, per Section 2.2 of RFC 7672.
 60	if !connState.HandshakeComplete {
 61		return false, tlsErr
 62	}
 63
 64	// Ignore invalid records.
 65	var (
 66		eeRecs []dns.TLSA
 67		taRecs []dns.TLSA
 68	)
 69	for _, rec := range recs {
 70		switch rec.MatchingType {
 71		case 0, 1, 2:
 72		default:
 73			continue
 74		}
 75		switch rec.Selector {
 76		case 0, 1:
 77		default:
 78			continue
 79		}
 80
 81		switch rec.Usage {
 82		case 2:
 83			taRecs = append(taRecs, rec)
 84		case 3:
 85			eeRecs = append(eeRecs, rec)
 86		default:
 87			continue
 88		}
 89	}
 90
 91	// Authentication is not required if all records are unusable, see
 92	// RFC 7672 Section 2.1.1.
 93	if len(eeRecs) == 0 && len(taRecs) == 0 {
 94		return false, nil
 95	}
 96
 97	for _, rec := range eeRecs {
 98		if rec.Verify(connState.PeerCertificates[0]) == nil {
 99			// https://tools.ietf.org/html/rfc7672#section-3.1.1
100			// - SAN/CN are not considered.
101			// - Expired certificates are fine too.
102			return true, nil
103		}
104	}
105
106	// Don't bother building a temporary certificate pool if there are no
107	// records to check.
108	if len(taRecs) == 0 {
109		return true, &exterrors.SMTPError{
110			Code:         550,
111			EnhancedCode: exterrors.EnhancedCode{5, 7, 0},
112			Message:      "No matching TLSA records",
113			TargetName:   "remote",
114			Misc: map[string]interface{}{
115				"remote_server": connState.ServerName,
116			},
117		}
118	}
119
120	// Collect certificates presented by the server as possible intermediates.
121	// Add all certificates from the chain that match any record to the root
122	// pool.
123	opts := x509.VerifyOptions{
124		DNSName:       connState.ServerName,
125		Intermediates: x509.NewCertPool(),
126		Roots:         x509.NewCertPool(),
127		CurrentTime:   verifyDANETime,
128	}
129	for _, cert := range connState.PeerCertificates {
130		root := false
131		for _, rec := range taRecs {
132			if cert.IsCA && rec.Verify(cert) == nil {
133				opts.Roots.AddCert(cert)
134				root = true
135			}
136		}
137		if !root {
138			opts.Intermediates.AddCert(cert)
139		}
140	}
141
142	// ... then run the standard X.509 verification. This will verify that the
143	// server certificate chains to any of asserted TA certificates.
144	if _, err := connState.PeerCertificates[0].Verify(opts); err == nil {
145		return true, nil
146	}
147
148	// There are valid records, but none matched.
149	return false, &exterrors.SMTPError{
150		Code:         550,
151		EnhancedCode: exterrors.EnhancedCode{5, 7, 0},
152		Message:      "No matching TLSA records",
153		TargetName:   "remote",
154		Misc: map[string]interface{}{
155			"remote_server": connState.ServerName,
156		},
157	}
158}