// Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package trs import ( "bufio" "encoding/hex" "errors" "flag" "fmt" "io" "io/ioutil" "net" "os/exec" "strconv" "strings" "sync" "testing" ) // TLS reference tests run a connection against a reference implementation // (OpenSSL) of TLS and record the bytes of the resulting connection. The Go // code, during a test, is configured with deterministic randomness and so the // reference test can be reproduced exactly in the future. // // In order to save everyone who wishes to run the tests from needing the // reference implementation installed, the reference connections are saved in // files in the testdata directory. Thus running the tests involves nothing // external, but creating and updating them requires the reference // implementation. // // Tests can be updated by running them with the -update flag. This will cause // the test files to be regenerated. Generally one should combine the -update // flag with -test.run to updated a specific test. Since the reference // implementation will always generate fresh random numbers, large parts of // the reference connection will always change. var ( update = flag.Bool("update", false, "update golden files on disk") opensslVersionTestOnce sync.Once opensslVersionTestErr error ) func checkOpenSSLVersion(t *testing.T) { opensslVersionTestOnce.Do(testOpenSSLVersion) if opensslVersionTestErr != nil { t.Fatal(opensslVersionTestErr) } } func testOpenSSLVersion() { // This test ensures that the version of OpenSSL looks reasonable // before updating the test data. if !*update { return } openssl := exec.Command("openssl", "version") output, err := openssl.CombinedOutput() if err != nil { opensslVersionTestErr = err return } version := string(output) if strings.HasPrefix(version, "OpenSSL 1.1.0") { return } println("***********************************************") println("") println("You need to build OpenSSL 1.1.0 from source in order") println("to update the test data.") println("") println("Configure it with:") println("./Configure enable-weak-ssl-ciphers enable-ssl3 enable-ssl3-method -static linux-x86_64") println("and then add the apps/ directory at the front of your PATH.") println("***********************************************") opensslVersionTestErr = errors.New("version of OpenSSL does not appear to be suitable for updating test data") } // recordingConn is a net.Conn that records the traffic that passes through it. // WriteTo can be used to produce output that can be later be loaded with // ParseTestData. type recordingConn struct { net.Conn sync.Mutex flows [][]byte reading bool } func (r *recordingConn) Read(b []byte) (n int, err error) { if n, err = r.Conn.Read(b); n == 0 { return } b = b[:n] r.Lock() defer r.Unlock() if l := len(r.flows); l == 0 || !r.reading { buf := make([]byte, len(b)) copy(buf, b) r.flows = append(r.flows, buf) } else { r.flows[l-1] = append(r.flows[l-1], b[:n]...) } r.reading = true return } func (r *recordingConn) Write(b []byte) (n int, err error) { if n, err = r.Conn.Write(b); n == 0 { return } b = b[:n] r.Lock() defer r.Unlock() if l := len(r.flows); l == 0 || r.reading { buf := make([]byte, len(b)) copy(buf, b) r.flows = append(r.flows, buf) } else { r.flows[l-1] = append(r.flows[l-1], b[:n]...) } r.reading = false return } // WriteTo writes Go source code to w that contains the recorded traffic. func (r *recordingConn) WriteTo(w io.Writer) (int64, error) { // TLS always starts with a client to server flow. clientToServer := true var written int64 for i, flow := range r.flows { source, dest := "client", "server" if !clientToServer { source, dest = dest, source } n, err := fmt.Fprintf(w, ">>> Flow %d (%s to %s)\n", i+1, source, dest) written += int64(n) if err != nil { return written, err } dumper := hex.Dumper(w) n, err = dumper.Write(flow) written += int64(n) if err != nil { return written, err } err = dumper.Close() if err != nil { return written, err } clientToServer = !clientToServer } return written, nil } func parseTestData(r io.Reader) (flows [][]byte, err error) { var currentFlow []byte scanner := bufio.NewScanner(r) for scanner.Scan() { line := scanner.Text() // If the line starts with ">>> " then it marks the beginning // of a new flow. if strings.HasPrefix(line, ">>> ") { if len(currentFlow) > 0 || len(flows) > 0 { flows = append(flows, currentFlow) currentFlow = nil } continue } // Otherwise the line is a line of hex dump that looks like: // 00000170 fc f5 06 bf (...) |.....X{&?......!| // (Some bytes have been omitted from the middle section.) if i := strings.IndexByte(line, ' '); i >= 0 { line = line[i:] } else { return nil, errors.New("invalid test data") } if i := strings.IndexByte(line, '|'); i >= 0 { line = line[:i] } else { return nil, errors.New("invalid test data") } hexBytes := strings.Fields(line) for _, hexByte := range hexBytes { val, err := strconv.ParseUint(hexByte, 16, 8) if err != nil { return nil, errors.New("invalid hex byte in test data: " + err.Error()) } currentFlow = append(currentFlow, byte(val)) } } if len(currentFlow) > 0 { flows = append(flows, currentFlow) } return flows, nil } // tempFile creates a temp file containing contents and returns its path. func tempFile(contents string) string { file, err := ioutil.TempFile("", "go-tls-test") if err != nil { panic("failed to create temp file: " + err.Error()) } path := file.Name() file.WriteString(contents) file.Close() return path }