blob: 36276be87a69b245da72bdf203f34e5fa64df7d3 [file] [log] [blame]
Adam Langley95c29f32014-06-20 12:00:00 -07001package main
2
3import (
4 "bytes"
David Benjamin407a10c2014-07-16 12:58:59 -04005 "crypto/x509"
Adam Langley95c29f32014-06-20 12:00:00 -07006 "flag"
7 "fmt"
8 "io"
Kenny Root7fdeaf12014-08-05 15:23:37 -07009 "io/ioutil"
Adam Langley95c29f32014-06-20 12:00:00 -070010 "net"
11 "os"
12 "os/exec"
David Benjamin884fdf12014-08-02 15:28:23 -040013 "path"
David Benjamin2bc8e6f2014-08-02 15:22:37 -040014 "runtime"
Adam Langley95c29f32014-06-20 12:00:00 -070015 "strings"
16 "sync"
17 "syscall"
18)
19
20var useValgrind = flag.Bool("valgrind", false, "If true, run code under valgrind")
21
David Benjamin025b3d32014-07-01 19:53:04 -040022const (
23 rsaCertificateFile = "cert.pem"
24 ecdsaCertificateFile = "ecdsa_cert.pem"
25)
26
27const (
28 rsaKeyFile = "key.pem"
29 ecdsaKeyFile = "ecdsa_key.pem"
30)
31
Adam Langley95c29f32014-06-20 12:00:00 -070032var rsaCertificate, ecdsaCertificate Certificate
33
34func initCertificates() {
35 var err error
David Benjamin025b3d32014-07-01 19:53:04 -040036 rsaCertificate, err = LoadX509KeyPair(rsaCertificateFile, rsaKeyFile)
Adam Langley95c29f32014-06-20 12:00:00 -070037 if err != nil {
38 panic(err)
39 }
40
David Benjamin025b3d32014-07-01 19:53:04 -040041 ecdsaCertificate, err = LoadX509KeyPair(ecdsaCertificateFile, ecdsaKeyFile)
Adam Langley95c29f32014-06-20 12:00:00 -070042 if err != nil {
43 panic(err)
44 }
45}
46
47var certificateOnce sync.Once
48
49func getRSACertificate() Certificate {
50 certificateOnce.Do(initCertificates)
51 return rsaCertificate
52}
53
54func getECDSACertificate() Certificate {
55 certificateOnce.Do(initCertificates)
56 return ecdsaCertificate
57}
58
David Benjamin025b3d32014-07-01 19:53:04 -040059type testType int
60
61const (
62 clientTest testType = iota
63 serverTest
64)
65
Adam Langley95c29f32014-06-20 12:00:00 -070066type testCase struct {
David Benjamin025b3d32014-07-01 19:53:04 -040067 testType testType
Adam Langley95c29f32014-06-20 12:00:00 -070068 name string
69 config Config
70 shouldFail bool
71 expectedError string
Adam Langleyac61fa32014-06-23 12:03:11 -070072 // expectedLocalError, if not empty, contains a substring that must be
73 // found in the local error.
74 expectedLocalError string
David Benjamin7e2e6cf2014-08-07 17:44:24 -040075 // expectedVersion, if non-zero, specifies the TLS version that must be
76 // negotiated.
77 expectedVersion uint16
Adam Langley80842bd2014-06-20 12:00:00 -070078 // messageLen is the length, in bytes, of the test message that will be
79 // sent.
80 messageLen int
David Benjamin025b3d32014-07-01 19:53:04 -040081 // certFile is the path to the certificate to use for the server.
82 certFile string
83 // keyFile is the path to the private key to use for the server.
84 keyFile string
David Benjamin1d5c83e2014-07-22 19:20:02 -040085 // resumeSession controls whether a second connection should be tested
86 // which resumes the first session.
87 resumeSession bool
David Benjamin325b5c32014-07-01 19:40:31 -040088 // flags, if not empty, contains a list of command-line flags that will
89 // be passed to the shim program.
90 flags []string
Adam Langley95c29f32014-06-20 12:00:00 -070091}
92
David Benjamin025b3d32014-07-01 19:53:04 -040093var testCases = []testCase{
Adam Langley95c29f32014-06-20 12:00:00 -070094 {
95 name: "BadRSASignature",
96 config: Config{
97 CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
98 Bugs: ProtocolBugs{
99 InvalidSKXSignature: true,
100 },
101 },
102 shouldFail: true,
103 expectedError: ":BAD_SIGNATURE:",
104 },
105 {
106 name: "BadECDSASignature",
107 config: Config{
108 CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
109 Bugs: ProtocolBugs{
110 InvalidSKXSignature: true,
111 },
112 Certificates: []Certificate{getECDSACertificate()},
113 },
114 shouldFail: true,
115 expectedError: ":BAD_SIGNATURE:",
116 },
117 {
118 name: "BadECDSACurve",
119 config: Config{
120 CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
121 Bugs: ProtocolBugs{
122 InvalidSKXCurve: true,
123 },
124 Certificates: []Certificate{getECDSACertificate()},
125 },
126 shouldFail: true,
127 expectedError: ":WRONG_CURVE:",
128 },
Adam Langleyac61fa32014-06-23 12:03:11 -0700129 {
David Benjamina8e3e0e2014-08-06 22:11:10 -0400130 testType: serverTest,
131 name: "BadRSAVersion",
132 config: Config{
133 CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA},
134 Bugs: ProtocolBugs{
135 RsaClientKeyExchangeVersion: VersionTLS11,
136 },
137 },
138 shouldFail: true,
139 expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
140 },
141 {
David Benjamin325b5c32014-07-01 19:40:31 -0400142 name: "NoFallbackSCSV",
Adam Langleyac61fa32014-06-23 12:03:11 -0700143 config: Config{
144 Bugs: ProtocolBugs{
145 FailIfNotFallbackSCSV: true,
146 },
147 },
148 shouldFail: true,
149 expectedLocalError: "no fallback SCSV found",
150 },
David Benjamin325b5c32014-07-01 19:40:31 -0400151 {
152 name: "FallbackSCSV",
153 config: Config{
154 Bugs: ProtocolBugs{
155 FailIfNotFallbackSCSV: true,
156 },
157 },
158 flags: []string{"-fallback-scsv"},
159 },
David Benjamin197b3ab2014-07-02 18:37:33 -0400160 {
161 testType: serverTest,
David Benjamin35a7a442014-07-05 00:23:20 -0400162 name: "ServerNameExtension",
David Benjamin197b3ab2014-07-02 18:37:33 -0400163 config: Config{
164 ServerName: "example.com",
165 },
166 flags: []string{"-expect-server-name", "example.com"},
167 },
David Benjamin35a7a442014-07-05 00:23:20 -0400168 {
169 testType: clientTest,
170 name: "DuplicateExtensionClient",
171 config: Config{
172 Bugs: ProtocolBugs{
173 DuplicateExtension: true,
174 },
175 },
176 shouldFail: true,
177 expectedLocalError: "remote error: error decoding message",
178 },
179 {
180 testType: serverTest,
181 name: "DuplicateExtensionServer",
182 config: Config{
183 Bugs: ProtocolBugs{
184 DuplicateExtension: true,
185 },
186 },
187 shouldFail: true,
188 expectedLocalError: "remote error: error decoding message",
189 },
David Benjamin7b030512014-07-08 17:30:11 -0400190 {
191 name: "ClientCertificateTypes",
192 config: Config{
193 ClientAuth: RequestClientCert,
194 ClientCertificateTypes: []byte{
195 CertTypeDSSSign,
196 CertTypeRSASign,
197 CertTypeECDSASign,
198 },
199 },
200 flags: []string{"-expect-certificate-types", string([]byte{
201 CertTypeDSSSign,
202 CertTypeRSASign,
203 CertTypeECDSASign,
204 })},
205 },
David Benjamin636293b2014-07-08 17:59:18 -0400206 {
207 name: "NoClientCertificate",
208 config: Config{
209 ClientAuth: RequireAnyClientCert,
210 },
211 shouldFail: true,
212 expectedLocalError: "client didn't provide a certificate",
213 },
David Benjamin1c375dd2014-07-12 00:48:23 -0400214 {
215 name: "UnauthenticatedECDH",
216 config: Config{
217 CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
218 Bugs: ProtocolBugs{
219 UnauthenticatedECDH: true,
220 },
221 },
222 shouldFail: true,
David Benjamine8f3d662014-07-12 01:10:19 -0400223 expectedError: ":UNEXPECTED_MESSAGE:",
David Benjamin1c375dd2014-07-12 00:48:23 -0400224 },
David Benjamin9c651c92014-07-12 13:27:45 -0400225 {
226 name: "SkipServerKeyExchange",
227 config: Config{
228 CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
229 Bugs: ProtocolBugs{
230 SkipServerKeyExchange: true,
231 },
232 },
233 shouldFail: true,
234 expectedError: ":UNEXPECTED_MESSAGE:",
235 },
David Benjamin1f5f62b2014-07-12 16:18:02 -0400236 {
David Benjamina0e52232014-07-19 17:39:58 -0400237 name: "SkipChangeCipherSpec-Client",
238 config: Config{
239 Bugs: ProtocolBugs{
240 SkipChangeCipherSpec: true,
241 },
242 },
243 shouldFail: true,
David Benjamin86271ee2014-07-21 16:14:03 -0400244 expectedError: ":HANDSHAKE_RECORD_BEFORE_CCS:",
David Benjamina0e52232014-07-19 17:39:58 -0400245 },
246 {
247 testType: serverTest,
248 name: "SkipChangeCipherSpec-Server",
249 config: Config{
250 Bugs: ProtocolBugs{
251 SkipChangeCipherSpec: true,
252 },
253 },
254 shouldFail: true,
David Benjamin86271ee2014-07-21 16:14:03 -0400255 expectedError: ":HANDSHAKE_RECORD_BEFORE_CCS:",
David Benjamina0e52232014-07-19 17:39:58 -0400256 },
David Benjamin42be6452014-07-21 14:50:23 -0400257 {
258 testType: serverTest,
259 name: "SkipChangeCipherSpec-Server-NPN",
260 config: Config{
261 NextProtos: []string{"bar"},
262 Bugs: ProtocolBugs{
263 SkipChangeCipherSpec: true,
264 },
265 },
266 flags: []string{
267 "-advertise-npn", "\x03foo\x03bar\x03baz",
268 },
269 shouldFail: true,
David Benjamin86271ee2014-07-21 16:14:03 -0400270 expectedError: ":HANDSHAKE_RECORD_BEFORE_CCS:",
271 },
272 {
273 name: "FragmentAcrossChangeCipherSpec-Client",
274 config: Config{
275 Bugs: ProtocolBugs{
276 FragmentAcrossChangeCipherSpec: true,
277 },
278 },
279 shouldFail: true,
280 expectedError: ":HANDSHAKE_RECORD_BEFORE_CCS:",
281 },
282 {
283 testType: serverTest,
284 name: "FragmentAcrossChangeCipherSpec-Server",
285 config: Config{
286 Bugs: ProtocolBugs{
287 FragmentAcrossChangeCipherSpec: true,
288 },
289 },
290 shouldFail: true,
291 expectedError: ":HANDSHAKE_RECORD_BEFORE_CCS:",
292 },
293 {
294 testType: serverTest,
295 name: "FragmentAcrossChangeCipherSpec-Server-NPN",
296 config: Config{
297 NextProtos: []string{"bar"},
298 Bugs: ProtocolBugs{
299 FragmentAcrossChangeCipherSpec: true,
300 },
301 },
302 flags: []string{
303 "-advertise-npn", "\x03foo\x03bar\x03baz",
304 },
305 shouldFail: true,
306 expectedError: ":HANDSHAKE_RECORD_BEFORE_CCS:",
David Benjamin42be6452014-07-21 14:50:23 -0400307 },
David Benjaminf3ec83d2014-07-21 22:42:34 -0400308 {
309 testType: serverTest,
310 name: "EarlyChangeCipherSpec-server-1",
311 config: Config{
312 Bugs: ProtocolBugs{
313 EarlyChangeCipherSpec: 1,
314 },
315 },
316 shouldFail: true,
317 expectedError: ":CCS_RECEIVED_EARLY:",
318 },
319 {
320 testType: serverTest,
321 name: "EarlyChangeCipherSpec-server-2",
322 config: Config{
323 Bugs: ProtocolBugs{
324 EarlyChangeCipherSpec: 2,
325 },
326 },
327 shouldFail: true,
328 expectedError: ":CCS_RECEIVED_EARLY:",
329 },
David Benjamind23f4122014-07-23 15:09:48 -0400330 {
David Benjamind23f4122014-07-23 15:09:48 -0400331 name: "SkipNewSessionTicket",
332 config: Config{
333 Bugs: ProtocolBugs{
334 SkipNewSessionTicket: true,
335 },
336 },
337 shouldFail: true,
338 expectedError: ":CCS_RECEIVED_EARLY:",
339 },
David Benjamin7e3305e2014-07-28 14:52:32 -0400340 {
David Benjamin7e3305e2014-07-28 14:52:32 -0400341 name: "FalseStart-SessionTicketsDisabled",
342 config: Config{
David Benjamind86c7672014-08-02 04:07:12 -0400343 CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
344 NextProtos: []string{"foo"},
David Benjamin7e3305e2014-07-28 14:52:32 -0400345 SessionTicketsDisabled: true,
346 },
347 flags: []string{
348 "-false-start",
349 "-select-next-proto", "foo",
350 },
351 },
David Benjamind86c7672014-08-02 04:07:12 -0400352 {
353 testType: serverTest,
354 name: "SendV2ClientHello",
355 config: Config{
356 // Choose a cipher suite that does not involve
357 // elliptic curves, so no extensions are
358 // involved.
359 CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA},
360 Bugs: ProtocolBugs{
361 SendV2ClientHello: true,
362 },
363 },
364 },
David Benjaminbef270a2014-08-02 04:22:02 -0400365 {
366 testType: serverTest,
367 name: "FallbackSCSV",
368 config: Config{
369 MaxVersion: VersionTLS11,
370 Bugs: ProtocolBugs{
371 SendFallbackSCSV: true,
372 },
373 },
374 shouldFail: true,
375 expectedError: ":INAPPROPRIATE_FALLBACK:",
376 },
377 {
378 testType: serverTest,
379 name: "FallbackSCSV-VersionMatch",
380 config: Config{
381 Bugs: ProtocolBugs{
382 SendFallbackSCSV: true,
383 },
384 },
385 },
David Benjamin98214542014-08-07 18:02:39 -0400386 {
387 testType: serverTest,
388 name: "FragmentedClientVersion",
389 config: Config{
390 Bugs: ProtocolBugs{
391 MaxHandshakeRecordLength: 1,
392 FragmentClientVersion: true,
393 },
394 },
395 shouldFail: true,
396 expectedError: ":RECORD_TOO_SMALL:",
397 },
Adam Langley95c29f32014-06-20 12:00:00 -0700398}
399
David Benjamin7e2e6cf2014-08-07 17:44:24 -0400400func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int) error {
David Benjamin1d5c83e2014-07-22 19:20:02 -0400401 var tlsConn *Conn
David Benjamin7e2e6cf2014-08-07 17:44:24 -0400402 if test.testType == clientTest {
David Benjamin1d5c83e2014-07-22 19:20:02 -0400403 tlsConn = Server(conn, config)
404 } else {
405 config.InsecureSkipVerify = true
406 tlsConn = Client(conn, config)
407 }
408
Adam Langley95c29f32014-06-20 12:00:00 -0700409 if err := tlsConn.Handshake(); err != nil {
410 return err
411 }
Kenny Root7fdeaf12014-08-05 15:23:37 -0700412
David Benjamin7e2e6cf2014-08-07 17:44:24 -0400413 if vers := tlsConn.ConnectionState().Version; test.expectedVersion != 0 && vers != test.expectedVersion {
414 return fmt.Errorf("got version %x, expected %x", vers, test.expectedVersion)
415 }
416
Kenny Root7fdeaf12014-08-05 15:23:37 -0700417 if messageLen < 0 {
418 // Read until EOF.
419 _, err := io.Copy(ioutil.Discard, tlsConn)
420 return err
421 }
422
Adam Langley80842bd2014-06-20 12:00:00 -0700423 if messageLen == 0 {
424 messageLen = 32
425 }
426 testMessage := make([]byte, messageLen)
427 for i := range testMessage {
428 testMessage[i] = 0x42
429 }
Adam Langley95c29f32014-06-20 12:00:00 -0700430 tlsConn.Write(testMessage)
431
432 buf := make([]byte, len(testMessage))
433 _, err := io.ReadFull(tlsConn, buf)
434 if err != nil {
435 return err
436 }
437
438 for i, v := range buf {
439 if v != testMessage[i]^0xff {
440 return fmt.Errorf("bad reply contents at byte %d", i)
441 }
442 }
443
444 return nil
445}
446
David Benjamin325b5c32014-07-01 19:40:31 -0400447func valgrindOf(dbAttach bool, path string, args ...string) *exec.Cmd {
448 valgrindArgs := []string{"--error-exitcode=99", "--track-origins=yes", "--leak-check=full"}
Adam Langley95c29f32014-06-20 12:00:00 -0700449 if dbAttach {
David Benjamin325b5c32014-07-01 19:40:31 -0400450 valgrindArgs = append(valgrindArgs, "--db-attach=yes", "--db-command=xterm -e gdb -nw %f %p")
Adam Langley95c29f32014-06-20 12:00:00 -0700451 }
David Benjamin325b5c32014-07-01 19:40:31 -0400452 valgrindArgs = append(valgrindArgs, path)
453 valgrindArgs = append(valgrindArgs, args...)
Adam Langley95c29f32014-06-20 12:00:00 -0700454
David Benjamin325b5c32014-07-01 19:40:31 -0400455 return exec.Command("valgrind", valgrindArgs...)
Adam Langley95c29f32014-06-20 12:00:00 -0700456}
457
David Benjamin325b5c32014-07-01 19:40:31 -0400458func gdbOf(path string, args ...string) *exec.Cmd {
459 xtermArgs := []string{"-e", "gdb", "--args"}
460 xtermArgs = append(xtermArgs, path)
461 xtermArgs = append(xtermArgs, args...)
Adam Langley95c29f32014-06-20 12:00:00 -0700462
David Benjamin325b5c32014-07-01 19:40:31 -0400463 return exec.Command("xterm", xtermArgs...)
Adam Langley95c29f32014-06-20 12:00:00 -0700464}
465
David Benjamin1d5c83e2014-07-22 19:20:02 -0400466func openSocketPair() (shimEnd *os.File, conn net.Conn) {
Adam Langley95c29f32014-06-20 12:00:00 -0700467 socks, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
468 if err != nil {
469 panic(err)
470 }
471
472 syscall.CloseOnExec(socks[0])
473 syscall.CloseOnExec(socks[1])
David Benjamin1d5c83e2014-07-22 19:20:02 -0400474 shimEnd = os.NewFile(uintptr(socks[0]), "shim end")
Adam Langley95c29f32014-06-20 12:00:00 -0700475 connFile := os.NewFile(uintptr(socks[1]), "our end")
David Benjamin1d5c83e2014-07-22 19:20:02 -0400476 conn, err = net.FileConn(connFile)
477 if err != nil {
478 panic(err)
479 }
Adam Langley95c29f32014-06-20 12:00:00 -0700480 connFile.Close()
481 if err != nil {
482 panic(err)
483 }
David Benjamin1d5c83e2014-07-22 19:20:02 -0400484 return shimEnd, conn
485}
486
David Benjamin884fdf12014-08-02 15:28:23 -0400487func runTest(test *testCase, buildDir string) error {
David Benjamin1d5c83e2014-07-22 19:20:02 -0400488 shimEnd, conn := openSocketPair()
489 shimEndResume, connResume := openSocketPair()
Adam Langley95c29f32014-06-20 12:00:00 -0700490
David Benjamin884fdf12014-08-02 15:28:23 -0400491 shim_path := path.Join(buildDir, "ssl/test/bssl_shim")
David Benjamin025b3d32014-07-01 19:53:04 -0400492 flags := []string{}
493 if test.testType == clientTest {
494 flags = append(flags, "client")
Adam Langley95c29f32014-06-20 12:00:00 -0700495 } else {
David Benjamin025b3d32014-07-01 19:53:04 -0400496 flags = append(flags, "server")
David Benjamin1d5c83e2014-07-22 19:20:02 -0400497 }
Adam Langley95c29f32014-06-20 12:00:00 -0700498
David Benjamin1d5c83e2014-07-22 19:20:02 -0400499 if test.resumeSession {
500 flags = append(flags, "resume")
501 } else {
502 flags = append(flags, "normal")
503 }
504
505 if test.testType == serverTest {
David Benjamin025b3d32014-07-01 19:53:04 -0400506 flags = append(flags, "-key-file")
507 if test.keyFile == "" {
508 flags = append(flags, rsaKeyFile)
509 } else {
510 flags = append(flags, test.keyFile)
511 }
512
513 flags = append(flags, "-cert-file")
514 if test.certFile == "" {
515 flags = append(flags, rsaCertificateFile)
516 } else {
517 flags = append(flags, test.certFile)
518 }
519 }
520 flags = append(flags, test.flags...)
521
522 var shim *exec.Cmd
523 if *useValgrind {
524 shim = valgrindOf(false, shim_path, flags...)
525 } else {
526 shim = exec.Command(shim_path, flags...)
527 }
528 // shim = gdbOf(shim_path, flags...)
David Benjamin1d5c83e2014-07-22 19:20:02 -0400529 shim.ExtraFiles = []*os.File{shimEnd, shimEndResume}
David Benjamin025b3d32014-07-01 19:53:04 -0400530 shim.Stdin = os.Stdin
531 var stdoutBuf, stderrBuf bytes.Buffer
532 shim.Stdout = &stdoutBuf
533 shim.Stderr = &stderrBuf
534
535 if err := shim.Start(); err != nil {
Adam Langley95c29f32014-06-20 12:00:00 -0700536 panic(err)
537 }
David Benjamin025b3d32014-07-01 19:53:04 -0400538 shimEnd.Close()
David Benjamin1d5c83e2014-07-22 19:20:02 -0400539 shimEndResume.Close()
Adam Langley95c29f32014-06-20 12:00:00 -0700540
541 config := test.config
David Benjamin1d5c83e2014-07-22 19:20:02 -0400542 config.ClientSessionCache = NewLRUClientSessionCache(1)
David Benjamin025b3d32014-07-01 19:53:04 -0400543 if test.testType == clientTest {
544 if len(config.Certificates) == 0 {
545 config.Certificates = []Certificate{getRSACertificate()}
546 }
David Benjamin025b3d32014-07-01 19:53:04 -0400547 }
Adam Langley95c29f32014-06-20 12:00:00 -0700548
David Benjamin7e2e6cf2014-08-07 17:44:24 -0400549 err := doExchange(test, &config, conn, test.messageLen)
Adam Langley95c29f32014-06-20 12:00:00 -0700550 conn.Close()
David Benjamin1d5c83e2014-07-22 19:20:02 -0400551 if err == nil && test.resumeSession {
David Benjamin7e2e6cf2014-08-07 17:44:24 -0400552 err = doExchange(test, &config, connResume, test.messageLen)
David Benjamin1d5c83e2014-07-22 19:20:02 -0400553 connResume.Close()
554 }
555
David Benjamin025b3d32014-07-01 19:53:04 -0400556 childErr := shim.Wait()
Adam Langley95c29f32014-06-20 12:00:00 -0700557
558 stdout := string(stdoutBuf.Bytes())
559 stderr := string(stderrBuf.Bytes())
560 failed := err != nil || childErr != nil
561 correctFailure := len(test.expectedError) == 0 || strings.Contains(stdout, test.expectedError)
Adam Langleyac61fa32014-06-23 12:03:11 -0700562 localError := "none"
563 if err != nil {
564 localError = err.Error()
565 }
566 if len(test.expectedLocalError) != 0 {
567 correctFailure = correctFailure && strings.Contains(localError, test.expectedLocalError)
568 }
Adam Langley95c29f32014-06-20 12:00:00 -0700569
570 if failed != test.shouldFail || failed && !correctFailure {
Adam Langley95c29f32014-06-20 12:00:00 -0700571 childError := "none"
Adam Langley95c29f32014-06-20 12:00:00 -0700572 if childErr != nil {
573 childError = childErr.Error()
574 }
575
576 var msg string
577 switch {
578 case failed && !test.shouldFail:
579 msg = "unexpected failure"
580 case !failed && test.shouldFail:
581 msg = "unexpected success"
582 case failed && !correctFailure:
Adam Langleyac61fa32014-06-23 12:03:11 -0700583 msg = "bad error (wanted '" + test.expectedError + "' / '" + test.expectedLocalError + "')"
Adam Langley95c29f32014-06-20 12:00:00 -0700584 default:
585 panic("internal error")
586 }
587
588 return fmt.Errorf("%s: local error '%s', child error '%s', stdout:\n%s\nstderr:\n%s", msg, localError, childError, string(stdoutBuf.Bytes()), stderr)
589 }
590
591 if !*useValgrind && len(stderr) > 0 {
592 println(stderr)
593 }
594
595 return nil
596}
597
598var tlsVersions = []struct {
599 name string
600 version uint16
David Benjamin7e2e6cf2014-08-07 17:44:24 -0400601 flag string
Adam Langley95c29f32014-06-20 12:00:00 -0700602}{
David Benjamin7e2e6cf2014-08-07 17:44:24 -0400603 {"SSL3", VersionSSL30, "-no-ssl3"},
604 {"TLS1", VersionTLS10, "-no-tls1"},
605 {"TLS11", VersionTLS11, "-no-tls11"},
606 {"TLS12", VersionTLS12, "-no-tls12"},
Adam Langley95c29f32014-06-20 12:00:00 -0700607}
608
609var testCipherSuites = []struct {
610 name string
611 id uint16
612}{
613 {"3DES-SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
David Benjaminf4e5c4e2014-08-02 17:35:45 -0400614 {"AES128-GCM", TLS_RSA_WITH_AES_128_GCM_SHA256},
Adam Langley95c29f32014-06-20 12:00:00 -0700615 {"AES128-SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
David Benjaminf4e5c4e2014-08-02 17:35:45 -0400616 {"AES256-GCM", TLS_RSA_WITH_AES_256_GCM_SHA384},
Adam Langley95c29f32014-06-20 12:00:00 -0700617 {"AES256-SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
David Benjaminf4e5c4e2014-08-02 17:35:45 -0400618 {"DHE-RSA-3DES-SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
619 {"DHE-RSA-AES128-GCM", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
620 {"DHE-RSA-AES128-SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
621 {"DHE-RSA-AES256-GCM", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
622 {"DHE-RSA-AES256-SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
Adam Langley95c29f32014-06-20 12:00:00 -0700623 {"ECDHE-ECDSA-AES128-GCM", TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
624 {"ECDHE-ECDSA-AES128-SHA", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA},
625 {"ECDHE-ECDSA-AES256-SHA", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA},
626 {"ECDHE-ECDSA-RC4-SHA", TLS_ECDHE_ECDSA_WITH_RC4_128_SHA},
627 {"ECDHE-RSA-3DES-SHA", TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA},
628 {"ECDHE-RSA-AES128-GCM", TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
Adam Langley95c29f32014-06-20 12:00:00 -0700629 {"ECDHE-RSA-AES128-SHA", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
David Benjaminf4e5c4e2014-08-02 17:35:45 -0400630 {"ECDHE-RSA-AES256-GCM", TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384},
Adam Langley95c29f32014-06-20 12:00:00 -0700631 {"ECDHE-RSA-AES256-SHA", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA},
632 {"ECDHE-RSA-RC4-SHA", TLS_ECDHE_RSA_WITH_RC4_128_SHA},
Adam Langley95c29f32014-06-20 12:00:00 -0700633 {"RC4-MD5", TLS_RSA_WITH_RC4_128_MD5},
David Benjaminf4e5c4e2014-08-02 17:35:45 -0400634 {"RC4-SHA", TLS_RSA_WITH_RC4_128_SHA},
Adam Langley95c29f32014-06-20 12:00:00 -0700635}
636
637func addCipherSuiteTests() {
638 for _, suite := range testCipherSuites {
639 var cert Certificate
David Benjamin025b3d32014-07-01 19:53:04 -0400640 var certFile string
641 var keyFile string
Adam Langley95c29f32014-06-20 12:00:00 -0700642 if strings.Contains(suite.name, "ECDSA") {
643 cert = getECDSACertificate()
David Benjamin025b3d32014-07-01 19:53:04 -0400644 certFile = ecdsaCertificateFile
645 keyFile = ecdsaKeyFile
Adam Langley95c29f32014-06-20 12:00:00 -0700646 } else {
647 cert = getRSACertificate()
David Benjamin025b3d32014-07-01 19:53:04 -0400648 certFile = rsaCertificateFile
649 keyFile = rsaKeyFile
Adam Langley95c29f32014-06-20 12:00:00 -0700650 }
651
652 for _, ver := range tlsVersions {
653 if ver.version != VersionTLS12 && strings.HasSuffix(suite.name, "-GCM") {
654 continue
655 }
656
David Benjamin1d5c83e2014-07-22 19:20:02 -0400657 // Go's TLS implementation only implements session
658 // resumption with tickets, so SSLv3 cannot resume
659 // sessions.
660 resumeSession := ver.version != VersionSSL30
661
David Benjamin025b3d32014-07-01 19:53:04 -0400662 testCases = append(testCases, testCase{
663 testType: clientTest,
664 name: ver.name + "-" + suite.name + "-client",
Adam Langley95c29f32014-06-20 12:00:00 -0700665 config: Config{
666 MinVersion: ver.version,
667 MaxVersion: ver.version,
668 CipherSuites: []uint16{suite.id},
669 Certificates: []Certificate{cert},
670 },
David Benjamin1d5c83e2014-07-22 19:20:02 -0400671 resumeSession: resumeSession,
Adam Langley95c29f32014-06-20 12:00:00 -0700672 })
David Benjamin025b3d32014-07-01 19:53:04 -0400673
674 // Go's TLS implementation implements SSLv3 as a server,
675 // but not as a client.
676 //
677 // TODO(davidben): Implement SSLv3 as a client too to
678 // exercise that code.
679 if ver.version != VersionSSL30 {
680 testCases = append(testCases, testCase{
681 testType: serverTest,
682 name: ver.name + "-" + suite.name + "-server",
683 config: Config{
684 MinVersion: ver.version,
685 MaxVersion: ver.version,
686 CipherSuites: []uint16{suite.id},
687 Certificates: []Certificate{cert},
688 },
David Benjamin1d5c83e2014-07-22 19:20:02 -0400689 certFile: certFile,
690 keyFile: keyFile,
691 resumeSession: resumeSession,
David Benjamin025b3d32014-07-01 19:53:04 -0400692 })
693 }
Adam Langley95c29f32014-06-20 12:00:00 -0700694 }
695 }
696}
697
698func addBadECDSASignatureTests() {
699 for badR := BadValue(1); badR < NumBadValues; badR++ {
700 for badS := BadValue(1); badS < NumBadValues; badS++ {
David Benjamin025b3d32014-07-01 19:53:04 -0400701 testCases = append(testCases, testCase{
Adam Langley95c29f32014-06-20 12:00:00 -0700702 name: fmt.Sprintf("BadECDSA-%d-%d", badR, badS),
703 config: Config{
704 CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
705 Certificates: []Certificate{getECDSACertificate()},
706 Bugs: ProtocolBugs{
707 BadECDSAR: badR,
708 BadECDSAS: badS,
709 },
710 },
711 shouldFail: true,
712 expectedError: "SIGNATURE",
713 })
714 }
715 }
716}
717
Adam Langley80842bd2014-06-20 12:00:00 -0700718func addCBCPaddingTests() {
David Benjamin025b3d32014-07-01 19:53:04 -0400719 testCases = append(testCases, testCase{
Adam Langley80842bd2014-06-20 12:00:00 -0700720 name: "MaxCBCPadding",
721 config: Config{
722 CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
723 Bugs: ProtocolBugs{
724 MaxPadding: true,
725 },
726 },
727 messageLen: 12, // 20 bytes of SHA-1 + 12 == 0 % block size
728 })
David Benjamin025b3d32014-07-01 19:53:04 -0400729 testCases = append(testCases, testCase{
Adam Langley80842bd2014-06-20 12:00:00 -0700730 name: "BadCBCPadding",
731 config: Config{
732 CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
733 Bugs: ProtocolBugs{
734 PaddingFirstByteBad: true,
735 },
736 },
737 shouldFail: true,
738 expectedError: "DECRYPTION_FAILED_OR_BAD_RECORD_MAC",
739 })
740 // OpenSSL previously had an issue where the first byte of padding in
741 // 255 bytes of padding wasn't checked.
David Benjamin025b3d32014-07-01 19:53:04 -0400742 testCases = append(testCases, testCase{
Adam Langley80842bd2014-06-20 12:00:00 -0700743 name: "BadCBCPadding255",
744 config: Config{
745 CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
746 Bugs: ProtocolBugs{
747 MaxPadding: true,
748 PaddingFirstByteBadIf255: true,
749 },
750 },
751 messageLen: 12, // 20 bytes of SHA-1 + 12 == 0 % block size
752 shouldFail: true,
753 expectedError: "DECRYPTION_FAILED_OR_BAD_RECORD_MAC",
754 })
755}
756
Kenny Root7fdeaf12014-08-05 15:23:37 -0700757func addCBCSplittingTests() {
758 testCases = append(testCases, testCase{
759 name: "CBCRecordSplitting",
760 config: Config{
761 MaxVersion: VersionTLS10,
762 MinVersion: VersionTLS10,
763 CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
764 },
765 messageLen: -1, // read until EOF
766 flags: []string{
767 "-async",
768 "-write-different-record-sizes",
769 "-cbc-record-splitting",
770 },
David Benjamina8e3e0e2014-08-06 22:11:10 -0400771 })
772 testCases = append(testCases, testCase{
Kenny Root7fdeaf12014-08-05 15:23:37 -0700773 name: "CBCRecordSplittingPartialWrite",
774 config: Config{
775 MaxVersion: VersionTLS10,
776 MinVersion: VersionTLS10,
777 CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
778 },
779 messageLen: -1, // read until EOF
780 flags: []string{
781 "-async",
782 "-write-different-record-sizes",
783 "-cbc-record-splitting",
784 "-partial-write",
785 },
786 })
787}
788
David Benjamin636293b2014-07-08 17:59:18 -0400789func addClientAuthTests() {
David Benjamin407a10c2014-07-16 12:58:59 -0400790 // Add a dummy cert pool to stress certificate authority parsing.
791 // TODO(davidben): Add tests that those values parse out correctly.
792 certPool := x509.NewCertPool()
793 cert, err := x509.ParseCertificate(rsaCertificate.Certificate[0])
794 if err != nil {
795 panic(err)
796 }
797 certPool.AddCert(cert)
798
David Benjamin636293b2014-07-08 17:59:18 -0400799 for _, ver := range tlsVersions {
800 if ver.version == VersionSSL30 {
801 // TODO(davidben): The Go implementation does not
802 // correctly compute CertificateVerify hashes for SSLv3.
803 continue
804 }
805
806 var cipherSuites []uint16
807 if ver.version >= VersionTLS12 {
808 // Pick a SHA-256 cipher suite. The Go implementation
809 // does not correctly handle client auth with a SHA-384
810 // cipher suite.
811 cipherSuites = []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}
812 }
813
814 testCases = append(testCases, testCase{
815 testType: clientTest,
David Benjamin67666e72014-07-12 15:47:52 -0400816 name: ver.name + "-Client-ClientAuth-RSA",
David Benjamin636293b2014-07-08 17:59:18 -0400817 config: Config{
818 MinVersion: ver.version,
819 MaxVersion: ver.version,
820 CipherSuites: cipherSuites,
821 ClientAuth: RequireAnyClientCert,
David Benjamin407a10c2014-07-16 12:58:59 -0400822 ClientCAs: certPool,
David Benjamin636293b2014-07-08 17:59:18 -0400823 },
824 flags: []string{
825 "-cert-file", rsaCertificateFile,
826 "-key-file", rsaKeyFile,
827 },
828 })
829 testCases = append(testCases, testCase{
830 testType: clientTest,
David Benjamin67666e72014-07-12 15:47:52 -0400831 name: ver.name + "-Client-ClientAuth-ECDSA",
David Benjamin636293b2014-07-08 17:59:18 -0400832 config: Config{
833 MinVersion: ver.version,
834 MaxVersion: ver.version,
835 CipherSuites: cipherSuites,
836 ClientAuth: RequireAnyClientCert,
David Benjamin407a10c2014-07-16 12:58:59 -0400837 ClientCAs: certPool,
David Benjamin636293b2014-07-08 17:59:18 -0400838 },
839 flags: []string{
840 "-cert-file", ecdsaCertificateFile,
841 "-key-file", ecdsaKeyFile,
842 },
843 })
David Benjamin67666e72014-07-12 15:47:52 -0400844 testCases = append(testCases, testCase{
845 testType: serverTest,
846 name: ver.name + "-Server-ClientAuth-RSA",
847 config: Config{
848 Certificates: []Certificate{rsaCertificate},
849 },
850 flags: []string{"-require-any-client-certificate"},
851 })
852 testCases = append(testCases, testCase{
853 testType: serverTest,
854 name: ver.name + "-Server-ClientAuth-ECDSA",
855 config: Config{
856 Certificates: []Certificate{ecdsaCertificate},
857 },
858 flags: []string{"-require-any-client-certificate"},
859 })
David Benjamin636293b2014-07-08 17:59:18 -0400860 }
861}
862
David Benjamin43ec06f2014-08-05 02:28:57 -0400863// Adds tests that try to cover the range of the handshake state machine, under
864// various conditions. Some of these are redundant with other tests, but they
865// only cover the synchronous case.
866func addStateMachineCoverageTests(async bool, splitHandshake bool) {
867 var suffix string
868 var flags []string
869 var maxHandshakeRecordLength int
870 if async {
871 suffix = "-Async"
872 flags = append(flags, "-async")
873 } else {
874 suffix = "-Sync"
875 }
876 if splitHandshake {
877 suffix += "-SplitHandshakeRecords"
David Benjamin98214542014-08-07 18:02:39 -0400878 maxHandshakeRecordLength = 1
David Benjamin43ec06f2014-08-05 02:28:57 -0400879 }
880
881 // Basic handshake, with resumption. Client and server.
882 testCases = append(testCases, testCase{
883 name: "Basic-Client" + suffix,
884 config: Config{
885 Bugs: ProtocolBugs{
886 MaxHandshakeRecordLength: maxHandshakeRecordLength,
887 },
888 },
889 flags: flags,
890 })
891 testCases = append(testCases, testCase{
892 testType: serverTest,
893 name: "Basic-Server" + suffix,
894 config: Config{
895 Bugs: ProtocolBugs{
896 MaxHandshakeRecordLength: maxHandshakeRecordLength,
897 },
898 },
899 flags: flags,
900 })
901
902 // No session ticket support; server doesn't send NewSessionTicket.
903 testCases = append(testCases, testCase{
904 name: "SessionTicketsDisabled-Client" + suffix,
905 config: Config{
906 SessionTicketsDisabled: true,
907 Bugs: ProtocolBugs{
908 MaxHandshakeRecordLength: maxHandshakeRecordLength,
909 },
910 },
911 flags: flags,
912 })
913 testCases = append(testCases, testCase{
914 testType: serverTest,
915 name: "SessionTicketsDisabled-Server" + suffix,
916 config: Config{
917 SessionTicketsDisabled: true,
918 Bugs: ProtocolBugs{
919 MaxHandshakeRecordLength: maxHandshakeRecordLength,
920 },
921 },
922 flags: flags,
923 })
924
925 // NPN on client and server; results in post-handshake message.
926 testCases = append(testCases, testCase{
927 name: "NPN-Client" + suffix,
928 config: Config{
929 CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
930 NextProtos: []string{"foo"},
931 Bugs: ProtocolBugs{
932 MaxHandshakeRecordLength: maxHandshakeRecordLength,
933 },
934 },
935 flags: append(flags, "-select-next-proto", "foo"),
936 })
937 testCases = append(testCases, testCase{
938 testType: serverTest,
939 name: "NPN-Server" + suffix,
940 config: Config{
941 NextProtos: []string{"bar"},
942 Bugs: ProtocolBugs{
943 MaxHandshakeRecordLength: maxHandshakeRecordLength,
944 },
945 },
946 flags: append(flags,
947 "-advertise-npn", "\x03foo\x03bar\x03baz",
948 "-expect-next-proto", "bar"),
949 })
950
951 // Client does False Start and negotiates NPN.
952 testCases = append(testCases, testCase{
953 name: "FalseStart" + suffix,
954 config: Config{
955 CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
956 NextProtos: []string{"foo"},
957 Bugs: ProtocolBugs{
958 MaxHandshakeRecordLength: maxHandshakeRecordLength,
959 },
960 },
961 flags: append(flags,
962 "-false-start",
963 "-select-next-proto", "foo"),
964 resumeSession: true,
965 })
966
967 // TLS client auth.
968 testCases = append(testCases, testCase{
969 testType: clientTest,
970 name: "ClientAuth-Client" + suffix,
971 config: Config{
972 CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
973 ClientAuth: RequireAnyClientCert,
974 Bugs: ProtocolBugs{
975 MaxHandshakeRecordLength: maxHandshakeRecordLength,
976 },
977 },
978 flags: append(flags,
979 "-cert-file", rsaCertificateFile,
980 "-key-file", rsaKeyFile),
981 })
982 testCases = append(testCases, testCase{
983 testType: serverTest,
984 name: "ClientAuth-Server" + suffix,
985 config: Config{
986 Certificates: []Certificate{rsaCertificate},
987 },
988 flags: append(flags, "-require-any-client-certificate"),
989 })
990}
991
David Benjamin7e2e6cf2014-08-07 17:44:24 -0400992func addVersionNegotiationTests() {
993 for i, shimVers := range tlsVersions {
994 // Assemble flags to disable all newer versions on the shim.
995 var flags []string
996 for _, vers := range tlsVersions[i+1:] {
997 flags = append(flags, vers.flag)
998 }
999
1000 for _, runnerVers := range tlsVersions {
1001 expectedVersion := shimVers.version
1002 if runnerVers.version < shimVers.version {
1003 expectedVersion = runnerVers.version
1004 }
1005 suffix := shimVers.name + "-" + runnerVers.name
1006
1007 testCases = append(testCases, testCase{
1008 testType: clientTest,
1009 name: "VersionNegotiation-Client-" + suffix,
1010 config: Config{
1011 MaxVersion: runnerVers.version,
1012 },
1013 flags: flags,
1014 expectedVersion: expectedVersion,
1015 })
1016
1017 // TODO(davidben): Implement SSLv3 as a client in the runner.
1018 if expectedVersion > VersionSSL30 {
1019 testCases = append(testCases, testCase{
1020 testType: serverTest,
1021 name: "VersionNegotiation-Server-" + suffix,
1022 config: Config{
1023 MaxVersion: runnerVers.version,
1024 },
1025 flags: flags,
1026 expectedVersion: expectedVersion,
1027 })
1028 }
1029 }
1030 }
1031}
1032
David Benjamin884fdf12014-08-02 15:28:23 -04001033func worker(statusChan chan statusMsg, c chan *testCase, buildDir string, wg *sync.WaitGroup) {
Adam Langley95c29f32014-06-20 12:00:00 -07001034 defer wg.Done()
1035
1036 for test := range c {
1037 statusChan <- statusMsg{test: test, started: true}
David Benjamin884fdf12014-08-02 15:28:23 -04001038 err := runTest(test, buildDir)
Adam Langley95c29f32014-06-20 12:00:00 -07001039 statusChan <- statusMsg{test: test, err: err}
1040 }
1041}
1042
1043type statusMsg struct {
1044 test *testCase
1045 started bool
1046 err error
1047}
1048
1049func statusPrinter(doneChan chan struct{}, statusChan chan statusMsg, total int) {
1050 var started, done, failed, lineLen int
1051 defer close(doneChan)
1052
1053 for msg := range statusChan {
1054 if msg.started {
1055 started++
1056 } else {
1057 done++
1058 }
1059
1060 fmt.Printf("\x1b[%dD\x1b[K", lineLen)
1061
1062 if msg.err != nil {
1063 fmt.Printf("FAILED (%s)\n%s\n", msg.test.name, msg.err)
1064 failed++
1065 }
1066 line := fmt.Sprintf("%d/%d/%d/%d", failed, done, started, total)
1067 lineLen = len(line)
1068 os.Stdout.WriteString(line)
1069 }
1070}
1071
1072func main() {
1073 var flagTest *string = flag.String("test", "", "The name of a test to run, or empty to run all tests")
David Benjamin2bc8e6f2014-08-02 15:22:37 -04001074 var flagNumWorkers *int = flag.Int("num-workers", runtime.NumCPU(), "The number of workers to run in parallel.")
David Benjamin884fdf12014-08-02 15:28:23 -04001075 var flagBuildDir *string = flag.String("build-dir", "../../../build", "The build directory to run the shim from.")
Adam Langley95c29f32014-06-20 12:00:00 -07001076
1077 flag.Parse()
1078
1079 addCipherSuiteTests()
1080 addBadECDSASignatureTests()
Adam Langley80842bd2014-06-20 12:00:00 -07001081 addCBCPaddingTests()
Kenny Root7fdeaf12014-08-05 15:23:37 -07001082 addCBCSplittingTests()
David Benjamin636293b2014-07-08 17:59:18 -04001083 addClientAuthTests()
David Benjamin7e2e6cf2014-08-07 17:44:24 -04001084 addVersionNegotiationTests()
David Benjamin43ec06f2014-08-05 02:28:57 -04001085 for _, async := range []bool{false, true} {
1086 for _, splitHandshake := range []bool{false, true} {
1087 addStateMachineCoverageTests(async, splitHandshake)
1088 }
1089 }
Adam Langley95c29f32014-06-20 12:00:00 -07001090
1091 var wg sync.WaitGroup
1092
David Benjamin2bc8e6f2014-08-02 15:22:37 -04001093 numWorkers := *flagNumWorkers
Adam Langley95c29f32014-06-20 12:00:00 -07001094
1095 statusChan := make(chan statusMsg, numWorkers)
1096 testChan := make(chan *testCase, numWorkers)
1097 doneChan := make(chan struct{})
1098
David Benjamin025b3d32014-07-01 19:53:04 -04001099 go statusPrinter(doneChan, statusChan, len(testCases))
Adam Langley95c29f32014-06-20 12:00:00 -07001100
1101 for i := 0; i < numWorkers; i++ {
1102 wg.Add(1)
David Benjamin884fdf12014-08-02 15:28:23 -04001103 go worker(statusChan, testChan, *flagBuildDir, &wg)
Adam Langley95c29f32014-06-20 12:00:00 -07001104 }
1105
David Benjamin025b3d32014-07-01 19:53:04 -04001106 for i := range testCases {
1107 if len(*flagTest) == 0 || *flagTest == testCases[i].name {
1108 testChan <- &testCases[i]
Adam Langley95c29f32014-06-20 12:00:00 -07001109 }
1110 }
1111
1112 close(testChan)
1113 wg.Wait()
1114 close(statusChan)
1115 <-doneChan
1116
1117 fmt.Printf("\n")
1118}