Alternative TLS implementation in Go
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

227 lines
5.7 KiB

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