1/*2Maddy Mail Server - Composable all-in-one email server.3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors45This program is free software: you can redistribute it and/or modify6it under the terms of the GNU General Public License as published by7the Free Software Foundation, either version 3 of the License, or8(at your option) any later version.910This program is distributed in the hope that it will be useful,11but WITHOUT ANY WARRANTY; without even the implied warranty of12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the13GNU General Public License for more details.1415You should have received a copy of the GNU General Public License16along with this program. If not, see <https://www.gnu.org/licenses/>.17*/1819package remote2021import (22 "crypto/tls"23 "crypto/x509"24 "time"2526 "github.com/foxcpp/maddy/framework/dns"27 "github.com/foxcpp/maddy/framework/exterrors"28)2930// Used to override verification time for DANE-TA tests.31var verifyDANETime time.Time3233// verifyDANE checks whether TLSA records require TLS use and match the34// certificate and name used by the server.35//36// overridePKIX result indicates whether DANE should make server authentication37// succeed even if PKIX/X.509 verification fails. That is, if InsecureSkipVerify38// is used and verifyDANE returns overridePKIX=true, the server certificate39// 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 }5051 // See https://tools.ietf.org/html/rfc7672#section-2.2 for requirements of52 // TLS discovery.53 // We assume upstream resolver will generate an error if the DNSSEC54 // signature is bogus so this case is "DNSSEC-authenticated denial of existence".55 if len(recs) == 0 {56 return false, nil57 }5859 // Require TLS even if all records are not usable, per Section 2.2 of RFC 7672.60 if !connState.HandshakeComplete {61 return false, tlsErr62 }6364 // Ignore invalid records.65 var (66 eeRecs []dns.TLSA67 taRecs []dns.TLSA68 )69 for _, rec := range recs {70 switch rec.MatchingType {71 case 0, 1, 2:72 default:73 continue74 }75 switch rec.Selector {76 case 0, 1:77 default:78 continue79 }8081 switch rec.Usage {82 case 2:83 taRecs = append(taRecs, rec)84 case 3:85 eeRecs = append(eeRecs, rec)86 default:87 continue88 }89 }9091 // Authentication is not required if all records are unusable, see92 // RFC 7672 Section 2.1.1.93 if len(eeRecs) == 0 && len(taRecs) == 0 {94 return false, nil95 }9697 for _, rec := range eeRecs {98 if rec.Verify(connState.PeerCertificates[0]) == nil {99 // https://tools.ietf.org/html/rfc7672#section-3.1.1100 // - SAN/CN are not considered.101 // - Expired certificates are fine too.102 return true, nil103 }104 }105106 // Don't bother building a temporary certificate pool if there are no107 // 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 }119120 // Collect certificates presented by the server as possible intermediates.121 // Add all certificates from the chain that match any record to the root122 // 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 := false131 for _, rec := range taRecs {132 if cert.IsCA && rec.Verify(cert) == nil {133 opts.Roots.AddCert(cert)134 root = true135 }136 }137 if !root {138 opts.Intermediates.AddCert(cert)139 }140 }141142 // ... then run the standard X.509 verification. This will verify that the143 // server certificate chains to any of asserted TA certificates.144 if _, err := connState.PeerCertificates[0].Verify(opts); err == nil {145 return true, nil146 }147148 // 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}