6 cryptorand "crypto/rand"
18 "github.com/mjl-/mox/mlog"
21var pkglog = mlog.New("tlsrpt", nil)
24 "organization-name": "Company-X",
26 "start-datetime": "2016-04-01T00:00:00Z",
27 "end-datetime": "2016-04-01T23:59:59Z"
29 "contact-info": "sts-reporting@company-x.example",
30 "report-id": "5065427c-23d3-47ca-b6e0-946ea0e8c4be",
34 "policy-string": ["version: STSv1","mode: testing",
35 "mx: *.mail.company-y.example","max_age: 86400"],
36 "policy-domain": "company-y.example",
37 "mx-host": ["*.mail.company-y.example"]
40 "total-successful-session-count": 5326,
41 "total-failure-session-count": 303
44 "result-type": "certificate-expired",
45 "sending-mta-ip": "2001:db8:abcd:0012::1",
46 "receiving-mx-hostname": "mx1.mail.company-y.example",
47 "failed-session-count": 100
49 "result-type": "starttls-not-supported",
50 "sending-mta-ip": "2001:db8:abcd:0013::1",
51 "receiving-mx-hostname": "mx2.mail.company-y.example",
52 "receiving-ip": "203.0.113.56",
53 "failed-session-count": 200,
54 "additional-information": "https://reports.company-x.example/report_info ? id = 5065427 c - 23 d3# StarttlsNotSupported "
56 "result-type": "validation-failure",
57 "sending-mta-ip": "198.51.100.62",
58 "receiving-ip": "203.0.113.58",
59 "receiving-mx-hostname": "mx-backup.mail.company-y.example",
60 "failed-session-count": 3,
61 "failure-reason-code": "X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED"
67var tlsrptMessage = strings.ReplaceAll(`From: tlsrpt@mail.sender.example.com
68Date: Fri, May 09 2017 16:54:30 -0800
69To: mts-sts-tlsrpt@example.net
70Subject: Report Domain: example.net
71Submitter: mail.sender.example.com
72Report-ID: <735ff.e317+bf22029@example.net>
73TLS-Report-Domain: example.net
74TLS-Report-Submitter: mail.sender.example.com
76Content-Type: multipart/report; report-type="tlsrpt";
77 boundary="----=_NextPart_000_024E_01CC9B0A.AFE54C00"
78Content-Language: en-us
80This is a multipart message in MIME format.
82------=_NextPart_000_024E_01CC9B0A.AFE54C00
83Content-Type: text/plain; charset="us-ascii"
84Content-Transfer-Encoding: 7bit
86This is an aggregate TLS report from mail.sender.example.com
88------=_NextPart_000_024E_01CC9B0A.AFE54C00
89Content-Type: application/tlsrpt+json
90Content-Transfer-Encoding: 8bit
91Content-Disposition: attachment;
92 filename="mail.sender.example!example.com!1013662812!1013749130.json.gz"
96------=_NextPart_000_024E_01CC9B0A.AFE54C00--
99// Message without multipart.
100var tlsrptMessage2 = strings.ReplaceAll(`From: tlsrpt@mail.sender.example.com
101To: mts-sts-tlsrpt@example.net
102Subject: Report Domain: example.net
103Report-ID: <735ff.e317+bf22029@example.net>
104TLS-Report-Domain: example.net
105TLS-Report-Submitter: mail.sender.example.com
107Content-Type: application/tlsrpt+json
108Content-Transfer-Encoding: 8bit
109Content-Disposition: attachment;
110 filename="mail.sender.example!example.com!1013662812!1013749130.json.gz"
115func TestReport(t *testing.T) {
118 var report ReportJSON
119 dec := json.NewDecoder(strings.NewReader(reportJSON))
120 dec.DisallowUnknownFields()
121 if err := dec.Decode(&report); err != nil {
122 t.Fatalf("parsing report: %s", err)
125 if _, err := ParseMessage(pkglog.Logger, strings.NewReader(tlsrptMessage)); err != nil {
126 t.Fatalf("parsing TLSRPT from message: %s", err)
129 if _, err := ParseMessage(pkglog.Logger, strings.NewReader(tlsrptMessage2)); err != nil {
130 t.Fatalf("parsing TLSRPT from message: %s", err)
133 if _, err := ParseMessage(pkglog.Logger, strings.NewReader(strings.ReplaceAll(tlsrptMessage, "multipart/report", "multipart/related"))); err != ErrNoReport {
134 t.Fatalf("got err %v, expected ErrNoReport", err)
137 if _, err := ParseMessage(pkglog.Logger, strings.NewReader(strings.ReplaceAll(tlsrptMessage, "application/tlsrpt+json", "application/json"))); err != ErrNoReport {
138 t.Fatalf("got err %v, expected ErrNoReport", err)
141 files, err := os.ReadDir("../testdata/tlsreports")
143 t.Fatalf("listing reports: %s", err)
145 for _, file := range files {
146 f, err := os.Open("../testdata/tlsreports/" + file.Name())
148 t.Fatalf("open %q: %s", file, err)
150 if _, err := ParseMessage(pkglog.Logger, f); err != nil {
151 t.Fatalf("parsing TLSRPT from message %q: %s", file.Name(), err)
157func TestTLSFailureDetails(t *testing.T) {
158 const alert70 = "tls-remote-alert-70-protocol-version-not-supported"
160 test := func(expResultType ResultType, expReasonCode string, client func(net.Conn) error, server func(net.Conn)) {
163 cconn, sconn := net.Pipe()
169 t.Fatalf("expected tls error")
172 resultType, reasonCode := TLSFailureDetails(err)
173 if resultType != expResultType || !(reasonCode == expReasonCode || expReasonCode == alert70 && reasonCode == "tls-remote-alert-70") {
174 t.Fatalf("got %v %v, expected %v %v", resultType, reasonCode, expResultType, expReasonCode)
178 newPool := func(certs ...tls.Certificate) *x509.CertPool {
179 pool := x509.NewCertPool()
180 for _, cert := range certs {
181 pool.AddCert(cert.Leaf)
186 // Expired certificate.
187 expiredCert := fakeCert(t, "localhost", true)
188 test(ResultCertificateExpired, "",
189 func(conn net.Conn) error {
190 config := tls.Config{ServerName: "localhost", RootCAs: newPool(expiredCert)}
191 return tls.Client(conn, &config).Handshake()
193 func(conn net.Conn) {
194 config := tls.Config{Certificates: []tls.Certificate{expiredCert}}
195 tls.Server(conn, &config).Handshake()
199 // Hostname mismatch.
200 okCert := fakeCert(t, "localhost", false)
201 test(ResultCertificateHostMismatch, "", func(conn net.Conn) error {
202 config := tls.Config{ServerName: "otherhost", RootCAs: newPool(okCert)}
203 return tls.Client(conn, &config).Handshake()
205 func(conn net.Conn) {
206 config := tls.Config{Certificates: []tls.Certificate{okCert}}
207 tls.Server(conn, &config).Handshake()
211 // Not signed by trusted CA.
212 test(ResultCertificateNotTrusted, "", func(conn net.Conn) error {
213 config := tls.Config{ServerName: "localhost", RootCAs: newPool()}
214 return tls.Client(conn, &config).Handshake()
216 func(conn net.Conn) {
217 config := tls.Config{Certificates: []tls.Certificate{okCert}}
218 tls.Server(conn, &config).Handshake()
222 // We don't support the right protocol version.
223 test(ResultValidationFailure, alert70, func(conn net.Conn) error {
224 config := tls.Config{ServerName: "localhost", RootCAs: newPool(okCert), MinVersion: tls.VersionTLS10, MaxVersion: tls.VersionTLS10}
225 return tls.Client(conn, &config).Handshake()
227 func(conn net.Conn) {
228 config := tls.Config{Certificates: []tls.Certificate{okCert}, MinVersion: tls.VersionTLS12}
229 tls.Server(conn, &config).Handshake()
233 // todo: ideally a test for tls-local-alert-*
235 // Remote is not speaking TLS.
236 test(ResultValidationFailure, "tls-record-header-error", func(conn net.Conn) error {
237 config := tls.Config{ServerName: "localhost", RootCAs: newPool(okCert)}
238 return tls.Client(conn, &config).Handshake()
240 func(conn net.Conn) {
241 go io.Copy(io.Discard, conn)
242 buf := make([]byte, 128)
244 _, err := conn.Write(buf)
252 // Context deadline exceeded during handshake.
253 test(ResultValidationFailure, "io-timeout-during-handshake",
254 func(conn net.Conn) error {
255 config := tls.Config{ServerName: "localhost", RootCAs: newPool(okCert)}
256 ctx, cancel := context.WithTimeout(context.Background(), 1)
258 return tls.Client(conn, &config).HandshakeContext(ctx)
260 func(conn net.Conn) {},
263 // Timeout during handshake.
264 test(ResultValidationFailure, "io-timeout-during-handshake",
265 func(conn net.Conn) error {
266 config := tls.Config{ServerName: "localhost", RootCAs: newPool(okCert)}
267 conn.SetDeadline(time.Now())
268 return tls.Client(conn, &config).Handshake()
270 func(conn net.Conn) {},
273 // Closing connection during handshake.
274 test(ResultValidationFailure, "connection-closed-during-handshake", func(conn net.Conn) error {
275 config := tls.Config{ServerName: "localhost", RootCAs: newPool(okCert)}
276 return tls.Client(conn, &config).Handshake()
278 func(conn net.Conn) {
284// Just a cert that appears valid.
285func fakeCert(t *testing.T, name string, expired bool) tls.Certificate {
286 notAfter := time.Now()
288 notAfter = notAfter.Add(-time.Hour)
290 notAfter = notAfter.Add(time.Hour)
293 privKey := ed25519.NewKeyFromSeed(make([]byte, ed25519.SeedSize)) // Fake key, don't use this for real!
294 template := &x509.Certificate{
295 SerialNumber: big.NewInt(1), // Required field...
296 DNSNames: []string{name},
297 NotBefore: time.Now().Add(-time.Hour),
300 localCertBuf, err := x509.CreateCertificate(cryptorand.Reader, template, template, privKey.Public(), privKey)
302 t.Fatalf("making certificate: %s", err)
304 cert, err := x509.ParseCertificate(localCertBuf)
306 t.Fatalf("parsing generated certificate: %s", err)
308 c := tls.Certificate{
309 Certificate: [][]byte{localCertBuf},
316func FuzzParseMessage(f *testing.F) {
318 f.Fuzz(func(t *testing.T, s string) {
319 ParseMessage(pkglog.Logger, strings.NewReader(s))