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/sha256"
 23	"crypto/tls"
 24	"crypto/x509"
 25	"encoding/hex"
 26	"encoding/pem"
 27	"testing"
 28	"time"
 29
 30	"github.com/miekg/dns"
 31)
 32
 33// These certificates are related like this:
 34//
 35//	Root A -> Intermediate A -> Leaf A
 36//	Root B -> LeafB
 37var (
 38	rootA = `-----BEGIN CERTIFICATE-----
 39MIIBMDCB46ADAgECAhRDwag3n5CG90BEO87zEMAPejn6YTAFBgMrZXAwFjEUMBIG
 40A1UEAxMLVGVzdCBSb290IEEwHhcNMjAxMTI4MjExODA4WhcNMzAxMTI2MjExODA4
 41WjAWMRQwEgYDVQQDEwtUZXN0IFJvb3QgQTAqMAUGAytlcAMhADXMzcRec5ocluNR
 42ExnnNT7I5fmcpjf2P4ik5k0DJNbco0MwQTAPBgNVHRMBAf8EBTADAQH/MA8GA1Ud
 43DwEB/wQFAwMHBAAwHQYDVR0OBBYEFM5b/b1di1vA+YpMZcsF4K7N1LbaMAUGAytl
 44cANBAAZ0XTxBDjN9VGPqWjXrYqGPUqbjm4JD3PeHUB4YGH+MNTgeVIlU8qCLIXtM
 459kmAkCk7+j5G8p0gMjJMNygeuwE=
 46-----END CERTIFICATE-----`
 47	intermediateA = `-----BEGIN CERTIFICATE-----
 48MIIBWjCCAQygAwIBAgIUEOd619/8HC1pWXxaEpQ1vUZOe7wwBQYDK2VwMBYxFDAS
 49BgNVBAMTC1Rlc3QgUm9vdCBBMB4XDTIwMTEyODIxMTk0M1oXDTMwMTEyNjIxMTk0
 50M1owHjEcMBoGA1UEAxMTVGVzdCBJbnRlcm1lZGlhdGUgQTAqMAUGAytlcAMhAFgW
 51aZz5316olEIHn1Q4RTPd2u/EjN2bo+Cn3EmSlFxto2QwYjAPBgNVHRMBAf8EBTAD
 52AQH/MA8GA1UdDwEB/wQFAwMHBAAwHQYDVR0OBBYEFB0P00Qphygy+KgkI9tjihFD
 53ELxhMB8GA1UdIwQYMBaAFM5b/b1di1vA+YpMZcsF4K7N1LbaMAUGAytlcANBAJJH
 54zsS8ahEjdyRCNUlsPalZiKW8N3G0LnwdVKFhVfcCT+RTRcrMP7vjuWsbJyD5e7hu
 55z2eCI68xreLQlNySdQ0=
 56-----END CERTIFICATE-----`
 57	leafA = `-----BEGIN CERTIFICATE-----
 58MIIBjzCCAUGgAwIBAgIUONvbCs6r9zKFM3IAPRMdrNiJpNgwBQYDK2VwMB4xHDAa
 59BgNVBAMTE1Rlc3QgSW50ZXJtZWRpYXRlIEEwHhcNMjAxMTI4MjEyMTIyWhcNMzAx
 60MTI2MjEyMTIyWjAWMRQwEgYDVQQDEwtUZXN0IExlYWYgQTAqMAUGAytlcAMhABIj
 61W7gwY78RCWHs9eSIdy4x4MXjzdhZwgNSNHHCp5pAo4GYMIGVMAwGA1UdEwEB/wQC
 62MAAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBUGA1UdEQQOMAyCCm1h
 63ZGR5LnRlc3QwDwYDVR0PAQH/BAUDAweAADAdBgNVHQ4EFgQU9PFQCnG5fNpNPXUT
 648rCuylS6tVwwHwYDVR0jBBgwFoAUHQ/TRCmHKDL4qCQj22OKEUMQvGEwBQYDK2Vw
 65A0EAGdvHA4VLxpUeUu1Vjom2YX3MukPJG0a3/dB3HiAWWpxMgWfU+Ftie7noaNcI
 66oUW+M8my46dqN6oXSHU47/QjDg==
 67-----END CERTIFICATE-----`
 68	rootB = `-----BEGIN CERTIFICATE-----
 69MIIBMDCB46ADAgECAhRXD7xuPkipDyxyCtm8pZaxhuulaDAFBgMrZXAwFjEUMBIG
 70A1UEAxMLVGVzdCBSb290IEIwHhcNMjAxMTI4MjExODMwWhcNMzAxMTI2MjExODMw
 71WjAWMRQwEgYDVQQDEwtUZXN0IFJvb3QgQjAqMAUGAytlcAMhAPOIGJJh5jK8N/Vc
 72lLrFpysV+SiZjT1Cmt7hoFtMrlbTo0MwQTAPBgNVHRMBAf8EBTADAQH/MA8GA1Ud
 73DwEB/wQFAwMHBAAwHQYDVR0OBBYEFOLGYf4mkhKbZPwZKCv952tfz/KDMAUGAytl
 74cANBAOX2gb6ud8CAvOsCgw6uaRm0+jMDVZfkAkNuCIO6cJ/WYfdvuXYXu3e88SuI
 75gri++h118PomIzJ5PHAaCYsFPgQ=
 76-----END CERTIFICATE-----`
 77	leafB = `-----BEGIN CERTIFICATE-----
 78MIIBhzCCATmgAwIBAgIUR2bVQ/Cu4j7Td5TdbWd6Q0LEpOgwBQYDK2VwMBYxFDAS
 79BgNVBAMTC1Rlc3QgUm9vdCBCMB4XDTIwMTEyODIxMjE0M1oXDTMwMTEyNjIxMjE0
 80M1owFjEUMBIGA1UEAxMLVGVzdCBMZWFmIEIwKjAFBgMrZXADIQBiHCTUxF3UxPIV
 81M/o5OkTtmUrI7AInOvMa0dchU4iJXqOBmDCBlTAMBgNVHRMBAf8EAjAAMB0GA1Ud
 82JQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAVBgNVHREEDjAMggptYWRkeS50ZXN0
 83MA8GA1UdDwEB/wQFAwMHgAAwHQYDVR0OBBYEFPYZPubaAXyr6kXs3khqpMNfdHKK
 84MB8GA1UdIwQYMBaAFOLGYf4mkhKbZPwZKCv952tfz/KDMAUGAytlcANBABlOwVxE
 85h7vYmaMYoyOSF1GQiB0ZLsGUjrTNHDnv0+Xp8xG5Td5mGnBi/4Ehq39PdLrj2T7j
 863Xy0aiqdDomvwQY=
 87-----END CERTIFICATE-----`
 88)
 89
 90func parsePEMCert(blob string) *x509.Certificate {
 91	block, _ := pem.Decode([]byte(blob))
 92	cert, err := x509.ParseCertificate(block.Bytes)
 93	if err != nil {
 94		panic(err)
 95	}
 96	return cert
 97}
 98
 99func singleTlsaRecord(usage, matchType, selector uint8, cert string) dns.TLSA {
100	return dns.TLSA{
101		Hdr: dns.RR_Header{
102			Name:   "maddy.test.",
103			Class:  dns.ClassINET,
104			Rrtype: dns.TypeTLSA,
105			Ttl:    9999,
106		},
107		Usage:        usage,
108		MatchingType: matchType,
109		Selector:     selector,
110		Certificate:  cert,
111	}
112}
113
114func keySHA256(blob string) string {
115	cert := parsePEMCert(blob)
116	hash := sha256.Sum256(cert.RawSubjectPublicKeyInfo)
117	return hex.EncodeToString(hash[:])
118}
119
120func TestVerifyDANE(t *testing.T) {
121	verifyDANETime = time.Unix(1606600100, 0)
122	test := func(name string, recs []dns.TLSA, connState tls.ConnectionState, expectErr bool) {
123		t.Helper()
124		t.Run(name, func(t *testing.T) {
125			t.Helper()
126			_, err := verifyDANE(recs, connState)
127			if (err != nil) != expectErr {
128				t.Error("err:", err, "expectErr:", expectErr)
129			}
130		})
131	}
132
133	// RFC 7672, Section 2.2:
134	// An "insecure" TLSA RRset or DNSSEC-authenticated denial of existence
135	// of the TLSA records:
136	//    A connection to the MTA SHOULD be made using (pre-DANE)
137	// opportunistic TLS;
138	//
139	// "Insecure" TLSA RRset results in verifyDANE not being called at all,
140	// but for the latter (authenticated denial of existence) it is still
141	// called and should be tested for.
142	//
143	// More specific tests for TLSA RRset discovery (including CNAME
144	// shenanigans) are in dane_delivery_test.go.
145	test("no TLSA, TLS", []dns.TLSA{}, tls.ConnectionState{
146		HandshakeComplete: true,
147	}, false)
148	test("no TLSA, no TLS", []dns.TLSA{}, tls.ConnectionState{
149		HandshakeComplete: false,
150	}, false)
151
152	// RFC 7272, Section 2.2:
153	// A "secure" non-empty TLSA RRset where all the records are unusable:
154	//  Any connection to the MTA MUST be made via TLS, but authentication
155	//  is not required.
156	test("unusable TLSA, TLS", []dns.TLSA{
157		singleTlsaRecord(4, 1, 2, "whatever"),
158		singleTlsaRecord(4, 5, 2, "whatever"),
159		singleTlsaRecord(4, 1, 1, "whatever"),
160	}, tls.ConnectionState{
161		HandshakeComplete: true,
162		PeerCertificates:  []*x509.Certificate{parsePEMCert(leafA)},
163	}, false)
164	test("unusable TLSA, no TLS", []dns.TLSA{
165		singleTlsaRecord(4, 1, 2, "whatever"),
166	}, tls.ConnectionState{
167		HandshakeComplete: false,
168	}, true)
169
170	// RFC 7672, Section 2.2:
171	// A "secure" TLSA RRset with at least one usable record:  Any
172	//  connection to the MTA MUST employ TLS encryption and MUST
173	//  authenticate the SMTP server using the techniques discussed in the
174	//  rest of this document.
175	test("DANE-EE, non-self-signed", []dns.TLSA{
176		singleTlsaRecord(3, 1, 1, keySHA256(leafA)),
177	}, tls.ConnectionState{
178		HandshakeComplete: true,
179		PeerCertificates:  []*x509.Certificate{parsePEMCert(leafA)},
180	}, false)
181	test("DANE-EE, multiple records", []dns.TLSA{
182		singleTlsaRecord(3, 1, 1, keySHA256(leafB)),
183		singleTlsaRecord(3, 1, 1, keySHA256(leafA)),
184	}, tls.ConnectionState{
185		HandshakeComplete: true,
186		PeerCertificates:  []*x509.Certificate{parsePEMCert(leafA)},
187	}, false)
188	test("DANE-EE, self-signed", []dns.TLSA{
189		singleTlsaRecord(3, 1, 1, keySHA256(rootA)),
190	}, tls.ConnectionState{
191		HandshakeComplete: true,
192		PeerCertificates:  []*x509.Certificate{parsePEMCert(rootA)},
193	}, false)
194	test("DANE-TA, intermediate TA", []dns.TLSA{
195		singleTlsaRecord(2, 1, 1, keySHA256(intermediateA)),
196	}, tls.ConnectionState{
197		HandshakeComplete: true,
198		PeerCertificates: []*x509.Certificate{
199			parsePEMCert(leafA),
200			parsePEMCert(intermediateA),
201			parsePEMCert(rootA),
202		},
203	}, false)
204	test("DANE-TA, intermediate TA, mismatch", []dns.TLSA{
205		singleTlsaRecord(2, 1, 1, keySHA256(intermediateA)),
206	}, tls.ConnectionState{
207		HandshakeComplete: true,
208		PeerCertificates: []*x509.Certificate{
209			parsePEMCert(leafB),
210			parsePEMCert(rootB),
211		},
212	}, true)
213	test("DANE-TA, intermediate TA, multiple records", []dns.TLSA{
214		singleTlsaRecord(2, 1, 1, keySHA256(rootB)),
215		singleTlsaRecord(2, 1, 1, keySHA256(intermediateA)),
216		// Add multiple times to make sure that multiple records matching the
217		// same cert do not break anything.
218		singleTlsaRecord(2, 1, 1, keySHA256(intermediateA)),
219	}, tls.ConnectionState{
220		HandshakeComplete: true,
221		PeerCertificates: []*x509.Certificate{
222			parsePEMCert(leafA),
223			parsePEMCert(intermediateA),
224			parsePEMCert(rootA),
225		},
226	}, false)
227}