Home | History | Annotate | Download | only in tls
      1 // Copyright 2013 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package tls
      6 
      7 import (
      8 	"bufio"
      9 	"encoding/hex"
     10 	"errors"
     11 	"flag"
     12 	"fmt"
     13 	"io"
     14 	"io/ioutil"
     15 	"net"
     16 	"strconv"
     17 	"strings"
     18 	"sync"
     19 )
     20 
     21 // TLS reference tests run a connection against a reference implementation
     22 // (OpenSSL) of TLS and record the bytes of the resulting connection. The Go
     23 // code, during a test, is configured with deterministic randomness and so the
     24 // reference test can be reproduced exactly in the future.
     25 //
     26 // In order to save everyone who wishes to run the tests from needing the
     27 // reference implementation installed, the reference connections are saved in
     28 // files in the testdata directory. Thus running the tests involves nothing
     29 // external, but creating and updating them requires the reference
     30 // implementation.
     31 //
     32 // Tests can be updated by running them with the -update flag. This will cause
     33 // the test files. Generally one should combine the -update flag with -test.run
     34 // to updated a specific test. Since the reference implementation will always
     35 // generate fresh random numbers, large parts of the reference connection will
     36 // always change.
     37 
     38 var update = flag.Bool("update", false, "update golden files on disk")
     39 
     40 // recordingConn is a net.Conn that records the traffic that passes through it.
     41 // WriteTo can be used to produce output that can be later be loaded with
     42 // ParseTestData.
     43 type recordingConn struct {
     44 	net.Conn
     45 	sync.Mutex
     46 	flows   [][]byte
     47 	reading bool
     48 }
     49 
     50 func (r *recordingConn) Read(b []byte) (n int, err error) {
     51 	if n, err = r.Conn.Read(b); n == 0 {
     52 		return
     53 	}
     54 	b = b[:n]
     55 
     56 	r.Lock()
     57 	defer r.Unlock()
     58 
     59 	if l := len(r.flows); l == 0 || !r.reading {
     60 		buf := make([]byte, len(b))
     61 		copy(buf, b)
     62 		r.flows = append(r.flows, buf)
     63 	} else {
     64 		r.flows[l-1] = append(r.flows[l-1], b[:n]...)
     65 	}
     66 	r.reading = true
     67 	return
     68 }
     69 
     70 func (r *recordingConn) Write(b []byte) (n int, err error) {
     71 	if n, err = r.Conn.Write(b); n == 0 {
     72 		return
     73 	}
     74 	b = b[:n]
     75 
     76 	r.Lock()
     77 	defer r.Unlock()
     78 
     79 	if l := len(r.flows); l == 0 || r.reading {
     80 		buf := make([]byte, len(b))
     81 		copy(buf, b)
     82 		r.flows = append(r.flows, buf)
     83 	} else {
     84 		r.flows[l-1] = append(r.flows[l-1], b[:n]...)
     85 	}
     86 	r.reading = false
     87 	return
     88 }
     89 
     90 // WriteTo writes Go source code to w that contains the recorded traffic.
     91 func (r *recordingConn) WriteTo(w io.Writer) {
     92 	// TLS always starts with a client to server flow.
     93 	clientToServer := true
     94 
     95 	for i, flow := range r.flows {
     96 		source, dest := "client", "server"
     97 		if !clientToServer {
     98 			source, dest = dest, source
     99 		}
    100 		fmt.Fprintf(w, ">>> Flow %d (%s to %s)\n", i+1, source, dest)
    101 		dumper := hex.Dumper(w)
    102 		dumper.Write(flow)
    103 		dumper.Close()
    104 		clientToServer = !clientToServer
    105 	}
    106 }
    107 
    108 func parseTestData(r io.Reader) (flows [][]byte, err error) {
    109 	var currentFlow []byte
    110 
    111 	scanner := bufio.NewScanner(r)
    112 	for scanner.Scan() {
    113 		line := scanner.Text()
    114 		// If the line starts with ">>> " then it marks the beginning
    115 		// of a new flow.
    116 		if strings.HasPrefix(line, ">>> ") {
    117 			if len(currentFlow) > 0 || len(flows) > 0 {
    118 				flows = append(flows, currentFlow)
    119 				currentFlow = nil
    120 			}
    121 			continue
    122 		}
    123 
    124 		// Otherwise the line is a line of hex dump that looks like:
    125 		// 00000170  fc f5 06 bf (...)  |.....X{&?......!|
    126 		// (Some bytes have been omitted from the middle section.)
    127 
    128 		if i := strings.IndexByte(line, ' '); i >= 0 {
    129 			line = line[i:]
    130 		} else {
    131 			return nil, errors.New("invalid test data")
    132 		}
    133 
    134 		if i := strings.IndexByte(line, '|'); i >= 0 {
    135 			line = line[:i]
    136 		} else {
    137 			return nil, errors.New("invalid test data")
    138 		}
    139 
    140 		hexBytes := strings.Fields(line)
    141 		for _, hexByte := range hexBytes {
    142 			val, err := strconv.ParseUint(hexByte, 16, 8)
    143 			if err != nil {
    144 				return nil, errors.New("invalid hex byte in test data: " + err.Error())
    145 			}
    146 			currentFlow = append(currentFlow, byte(val))
    147 		}
    148 	}
    149 
    150 	if len(currentFlow) > 0 {
    151 		flows = append(flows, currentFlow)
    152 	}
    153 
    154 	return flows, nil
    155 }
    156 
    157 // tempFile creates a temp file containing contents and returns its path.
    158 func tempFile(contents string) string {
    159 	file, err := ioutil.TempFile("", "go-tls-test")
    160 	if err != nil {
    161 		panic("failed to create temp file: " + err.Error())
    162 	}
    163 	path := file.Name()
    164 	file.WriteString(contents)
    165 	file.Close()
    166 	return path
    167 }
    168