blob: 96b63cddf22a0704caeb90eb1daa7ccb0e19ad26 [file] [log] [blame]
Adam Langley4883b732010-12-16 17:10:50 -05001// Copyright 2010 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
5package tls
6
7import (
8 "bytes"
Adam Langley6f149492013-12-20 11:37:05 -05009 "crypto/ecdsa"
10 "crypto/rsa"
11 "crypto/x509"
12 "encoding/pem"
13 "fmt"
Adam Langley4883b732010-12-16 17:10:50 -050014 "io"
15 "net"
Adam Langley7247dca2012-04-11 12:55:57 -040016 "os"
Adam Langley6f149492013-12-20 11:37:05 -050017 "os/exec"
18 "path/filepath"
19 "strconv"
Adam Langley4883b732010-12-16 17:10:50 -050020 "testing"
Adam Langley6f149492013-12-20 11:37:05 -050021 "time"
Adam Langley4883b732010-12-16 17:10:50 -050022)
23
Adam Langley6f149492013-12-20 11:37:05 -050024// Note: see comment in handshake_test.go for details of how the reference
25// tests work.
Adam Langley4883b732010-12-16 17:10:50 -050026
Adam Langley6f149492013-12-20 11:37:05 -050027// blockingSource is an io.Reader that blocks a Read call until it's closed.
28type blockingSource chan bool
29
30func (b blockingSource) Read([]byte) (n int, err error) {
31 <-b
32 return 0, io.EOF
33}
34
35// clientTest represents a test of the TLS client handshake against a reference
36// implementation.
37type clientTest struct {
38 // name is a freeform string identifying the test and the file in which
39 // the expected results will be stored.
40 name string
41 // command, if not empty, contains a series of arguments for the
42 // command to run for the reference server.
43 command []string
44 // config, if not nil, contains a custom Config to use for this test.
45 config *Config
46 // cert, if not empty, contains a DER-encoded certificate for the
47 // reference server.
48 cert []byte
49 // key, if not nil, contains either a *rsa.PrivateKey or
50 // *ecdsa.PrivateKey which is the private key for the reference server.
51 key interface{}
Adam Langleyd0e255f2014-08-05 11:36:20 -070052 // validate, if not nil, is a function that will be called with the
53 // ConnectionState of the resulting connection. It returns a non-nil
54 // error if the ConnectionState is unacceptable.
55 validate func(ConnectionState) error
Adam Langley6f149492013-12-20 11:37:05 -050056}
57
58var defaultServerCommand = []string{"openssl", "s_server"}
59
60// connFromCommand starts the reference server process, connects to it and
61// returns a recordingConn for the connection. The stdin return value is a
62// blockingSource for the stdin of the child process. It must be closed before
63// Waiting for child.
64func (test *clientTest) connFromCommand() (conn *recordingConn, child *exec.Cmd, stdin blockingSource, err error) {
65 cert := testRSACertificate
66 if len(test.cert) > 0 {
67 cert = test.cert
68 }
69 certPath := tempFile(string(cert))
70 defer os.Remove(certPath)
71
72 var key interface{} = testRSAPrivateKey
73 if test.key != nil {
74 key = test.key
75 }
76 var pemType string
77 var derBytes []byte
78 switch key := key.(type) {
79 case *rsa.PrivateKey:
80 pemType = "RSA"
81 derBytes = x509.MarshalPKCS1PrivateKey(key)
82 case *ecdsa.PrivateKey:
83 pemType = "EC"
84 var err error
85 derBytes, err = x509.MarshalECPrivateKey(key)
Adam Langley4883b732010-12-16 17:10:50 -050086 if err != nil {
Adam Langley6f149492013-12-20 11:37:05 -050087 panic(err)
Adam Langley4883b732010-12-16 17:10:50 -050088 }
Adam Langley6f149492013-12-20 11:37:05 -050089 default:
90 panic("unknown key type")
Adam Langley4883b732010-12-16 17:10:50 -050091 }
92
Adam Langley6f149492013-12-20 11:37:05 -050093 var pemOut bytes.Buffer
94 pem.Encode(&pemOut, &pem.Block{Type: pemType + " PRIVATE KEY", Bytes: derBytes})
95
96 keyPath := tempFile(string(pemOut.Bytes()))
97 defer os.Remove(keyPath)
98
99 var command []string
100 if len(test.command) > 0 {
101 command = append(command, test.command...)
102 } else {
103 command = append(command, defaultServerCommand...)
104 }
105 command = append(command, "-cert", certPath, "-certform", "DER", "-key", keyPath)
106 // serverPort contains the port that OpenSSL will listen on. OpenSSL
107 // can't take "0" as an argument here so we have to pick a number and
108 // hope that it's not in use on the machine. Since this only occurs
109 // when -update is given and thus when there's a human watching the
110 // test, this isn't too bad.
111 const serverPort = 24323
112 command = append(command, "-accept", strconv.Itoa(serverPort))
113
114 cmd := exec.Command(command[0], command[1:]...)
115 stdin = blockingSource(make(chan bool))
116 cmd.Stdin = stdin
117 var out bytes.Buffer
118 cmd.Stdout = &out
119 cmd.Stderr = &out
120 if err := cmd.Start(); err != nil {
121 return nil, nil, nil, err
122 }
123
124 // OpenSSL does print an "ACCEPT" banner, but it does so *before*
125 // opening the listening socket, so we can't use that to wait until it
126 // has started listening. Thus we are forced to poll until we get a
127 // connection.
128 var tcpConn net.Conn
129 for i := uint(0); i < 5; i++ {
Adam Langley6f149492013-12-20 11:37:05 -0500130 tcpConn, err = net.DialTCP("tcp", nil, &net.TCPAddr{
131 IP: net.IPv4(127, 0, 0, 1),
132 Port: serverPort,
133 })
134 if err == nil {
135 break
136 }
137 time.Sleep((1 << i) * 5 * time.Millisecond)
138 }
Jacob H. Havenf1d669a2015-02-03 16:15:18 -0800139 if err != nil {
Adam Langley6f149492013-12-20 11:37:05 -0500140 close(stdin)
141 out.WriteTo(os.Stdout)
142 cmd.Process.Kill()
143 return nil, nil, nil, cmd.Wait()
Adam Langley4883b732010-12-16 17:10:50 -0500144 }
145
Adam Langley7247dca2012-04-11 12:55:57 -0400146 record := &recordingConn{
147 Conn: tcpConn,
148 }
149
Adam Langley6f149492013-12-20 11:37:05 -0500150 return record, cmd, stdin, nil
Adam Langley4883b732010-12-16 17:10:50 -0500151}
152
Adam Langley6f149492013-12-20 11:37:05 -0500153func (test *clientTest) dataPath() string {
154 return filepath.Join("testdata", "Client-"+test.name)
155}
Adam Langleya1dbfee2013-05-15 10:25:54 -0400156
Adam Langley6f149492013-12-20 11:37:05 -0500157func (test *clientTest) loadData() (flows [][]byte, err error) {
158 in, err := os.Open(test.dataPath())
159 if err != nil {
160 return nil, err
161 }
162 defer in.Close()
163 return parseTestData(in)
164}
Adam Langleya1dbfee2013-05-15 10:25:54 -0400165
Adam Langley6f149492013-12-20 11:37:05 -0500166func (test *clientTest) run(t *testing.T, write bool) {
167 var clientConn, serverConn net.Conn
168 var recordingConn *recordingConn
169 var childProcess *exec.Cmd
170 var stdin blockingSource
171
172 if write {
173 var err error
174 recordingConn, childProcess, stdin, err = test.connFromCommand()
Adam Langleya1dbfee2013-05-15 10:25:54 -0400175 if err != nil {
Adam Langley6f149492013-12-20 11:37:05 -0500176 t.Fatalf("Failed to start subcommand: %s", err)
Adam Langleya1dbfee2013-05-15 10:25:54 -0400177 }
Adam Langley6f149492013-12-20 11:37:05 -0500178 clientConn = recordingConn
179 } else {
180 clientConn, serverConn = net.Pipe()
181 }
182
183 config := test.config
184 if config == nil {
185 config = testConfig
186 }
187 client := Client(clientConn, config)
188
189 doneChan := make(chan bool)
190 go func() {
191 if _, err := client.Write([]byte("hello\n")); err != nil {
Jacob H. Havenf1d669a2015-02-03 16:15:18 -0800192 t.Errorf("Client.Write failed: %s", err)
Adam Langleya1dbfee2013-05-15 10:25:54 -0400193 }
Adam Langleyd0e255f2014-08-05 11:36:20 -0700194 if test.validate != nil {
195 if err := test.validate(client.ConnectionState()); err != nil {
196 t.Logf("validate callback returned error: %s", err)
197 }
198 }
Adam Langley6f149492013-12-20 11:37:05 -0500199 client.Close()
200 clientConn.Close()
201 doneChan <- true
Adam Langleya1dbfee2013-05-15 10:25:54 -0400202 }()
203
Adam Langley6f149492013-12-20 11:37:05 -0500204 if !write {
205 flows, err := test.loadData()
Adam Langleya1dbfee2013-05-15 10:25:54 -0400206 if err != nil {
Russ Cox2c14dbe2014-09-07 09:07:19 -0400207 t.Fatalf("%s: failed to load data from %s: %v", test.name, test.dataPath(), err)
Adam Langleya1dbfee2013-05-15 10:25:54 -0400208 }
Adam Langley6f149492013-12-20 11:37:05 -0500209 for i, b := range flows {
210 if i%2 == 1 {
211 serverConn.Write(b)
212 continue
213 }
214 bb := make([]byte, len(b))
215 _, err := io.ReadFull(serverConn, bb)
216 if err != nil {
217 t.Fatalf("%s #%d: %s", test.name, i, err)
218 }
219 if !bytes.Equal(b, bb) {
220 t.Fatalf("%s #%d: mismatch on read: got:%x want:%x", test.name, i, bb, b)
221 }
Adam Langleya1dbfee2013-05-15 10:25:54 -0400222 }
Adam Langley6f149492013-12-20 11:37:05 -0500223 serverConn.Close()
224 }
225
226 <-doneChan
227
228 if write {
229 path := test.dataPath()
230 out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
231 if err != nil {
232 t.Fatalf("Failed to create output file: %s", err)
233 }
234 defer out.Close()
235 recordingConn.Close()
236 close(stdin)
237 childProcess.Process.Kill()
238 childProcess.Wait()
239 if len(recordingConn.flows) < 3 {
240 childProcess.Stdout.(*bytes.Buffer).WriteTo(os.Stdout)
241 t.Fatalf("Client connection didn't work")
242 }
243 recordingConn.WriteTo(out)
244 fmt.Printf("Wrote %s\n", path)
Adam Langleya1dbfee2013-05-15 10:25:54 -0400245 }
246}
247
Adam Langley6f149492013-12-20 11:37:05 -0500248func runClientTestForVersion(t *testing.T, template *clientTest, prefix, option string) {
249 test := *template
250 test.name = prefix + test.name
251 if len(test.command) == 0 {
252 test.command = defaultClientCommand
253 }
254 test.command = append([]string(nil), test.command...)
255 test.command = append(test.command, option)
256 test.run(t, *update)
Adam Langley4883b732010-12-16 17:10:50 -0500257}
Adam Langley7247dca2012-04-11 12:55:57 -0400258
Adam Langley6f149492013-12-20 11:37:05 -0500259func runClientTestTLS10(t *testing.T, template *clientTest) {
260 runClientTestForVersion(t, template, "TLSv10-", "-tls1")
Adam Langley7247dca2012-04-11 12:55:57 -0400261}
Adam Langleya1dbfee2013-05-15 10:25:54 -0400262
Adam Langley6f149492013-12-20 11:37:05 -0500263func runClientTestTLS11(t *testing.T, template *clientTest) {
264 runClientTestForVersion(t, template, "TLSv11-", "-tls1_1")
Adam Langleya1dbfee2013-05-15 10:25:54 -0400265}
John Shahidca986a22013-05-29 11:21:32 -0400266
Adam Langley6f149492013-12-20 11:37:05 -0500267func runClientTestTLS12(t *testing.T, template *clientTest) {
268 runClientTestForVersion(t, template, "TLSv12-", "-tls1_2")
Adam Langley2112fed2013-06-04 20:02:22 -0400269}
270
Adam Langley6f149492013-12-20 11:37:05 -0500271func TestHandshakeClientRSARC4(t *testing.T) {
272 test := &clientTest{
273 name: "RSA-RC4",
274 command: []string{"openssl", "s_server", "-cipher", "RC4-SHA"},
275 }
276 runClientTestTLS10(t, test)
277 runClientTestTLS11(t, test)
278 runClientTestTLS12(t, test)
John Shahidca986a22013-05-29 11:21:32 -0400279}
280
Adam Langley6f149492013-12-20 11:37:05 -0500281func TestHandshakeClientECDHERSAAES(t *testing.T) {
282 test := &clientTest{
283 name: "ECDHE-RSA-AES",
284 command: []string{"openssl", "s_server", "-cipher", "ECDHE-RSA-AES128-SHA"},
285 }
286 runClientTestTLS10(t, test)
287 runClientTestTLS11(t, test)
288 runClientTestTLS12(t, test)
Adam Langley7e767792013-07-02 19:58:56 -0400289}
290
Adam Langley6f149492013-12-20 11:37:05 -0500291func TestHandshakeClientECDHEECDSAAES(t *testing.T) {
292 test := &clientTest{
293 name: "ECDHE-ECDSA-AES",
294 command: []string{"openssl", "s_server", "-cipher", "ECDHE-ECDSA-AES128-SHA"},
295 cert: testECDSACertificate,
296 key: testECDSAPrivateKey,
297 }
298 runClientTestTLS10(t, test)
299 runClientTestTLS11(t, test)
300 runClientTestTLS12(t, test)
Adam Langley6a1022a2013-09-16 16:39:42 -0400301}
302
Adam Langley6f149492013-12-20 11:37:05 -0500303func TestHandshakeClientECDHEECDSAAESGCM(t *testing.T) {
304 test := &clientTest{
305 name: "ECDHE-ECDSA-AES-GCM",
306 command: []string{"openssl", "s_server", "-cipher", "ECDHE-ECDSA-AES128-GCM-SHA256"},
307 cert: testECDSACertificate,
308 key: testECDSAPrivateKey,
309 }
310 runClientTestTLS12(t, test)
311}
Joel Sing7b7dac52013-07-17 12:33:16 -0400312
Jacob H. Havenf1d669a2015-02-03 16:15:18 -0800313func TestHandshakeClientAES256GCMSHA384(t *testing.T) {
314 test := &clientTest{
315 name: "ECDHE-ECDSA-AES256-GCM-SHA384",
316 command: []string{"openssl", "s_server", "-cipher", "ECDHE-ECDSA-AES256-GCM-SHA384"},
317 cert: testECDSACertificate,
318 key: testECDSAPrivateKey,
319 }
320 runClientTestTLS12(t, test)
321}
322
Adam Langley6f149492013-12-20 11:37:05 -0500323func TestHandshakeClientCertRSA(t *testing.T) {
324 config := *testConfig
325 cert, _ := X509KeyPair([]byte(clientCertificatePEM), []byte(clientKeyPEM))
326 config.Certificates = []Certificate{cert}
327
328 test := &clientTest{
329 name: "ClientCert-RSA-RSA",
330 command: []string{"openssl", "s_server", "-cipher", "RC4-SHA", "-verify", "1"},
331 config: &config,
332 }
333
334 runClientTestTLS10(t, test)
335 runClientTestTLS12(t, test)
336
337 test = &clientTest{
338 name: "ClientCert-RSA-ECDSA",
339 command: []string{"openssl", "s_server", "-cipher", "ECDHE-ECDSA-AES128-SHA", "-verify", "1"},
340 config: &config,
341 cert: testECDSACertificate,
342 key: testECDSAPrivateKey,
343 }
344
345 runClientTestTLS10(t, test)
346 runClientTestTLS12(t, test)
347}
348
349func TestHandshakeClientCertECDSA(t *testing.T) {
350 config := *testConfig
351 cert, _ := X509KeyPair([]byte(clientECDSACertificatePEM), []byte(clientECDSAKeyPEM))
352 config.Certificates = []Certificate{cert}
353
354 test := &clientTest{
355 name: "ClientCert-ECDSA-RSA",
356 command: []string{"openssl", "s_server", "-cipher", "RC4-SHA", "-verify", "1"},
357 config: &config,
358 }
359
360 runClientTestTLS10(t, test)
361 runClientTestTLS12(t, test)
362
363 test = &clientTest{
364 name: "ClientCert-ECDSA-ECDSA",
365 command: []string{"openssl", "s_server", "-cipher", "ECDHE-ECDSA-AES128-SHA", "-verify", "1"},
366 config: &config,
367 cert: testECDSACertificate,
368 key: testECDSAPrivateKey,
369 }
370
371 runClientTestTLS10(t, test)
372 runClientTestTLS12(t, test)
Joel Sing7b7dac52013-07-17 12:33:16 -0400373}
Gautham Thambidorai988ffc02014-01-22 18:24:03 -0500374
375func TestClientResumption(t *testing.T) {
376 serverConfig := &Config{
377 CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA, TLS_ECDHE_RSA_WITH_RC4_128_SHA},
378 Certificates: testConfig.Certificates,
379 }
380 clientConfig := &Config{
381 CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA},
382 InsecureSkipVerify: true,
383 ClientSessionCache: NewLRUClientSessionCache(32),
384 }
385
386 testResumeState := func(test string, didResume bool) {
387 hs, err := testHandshake(clientConfig, serverConfig)
388 if err != nil {
389 t.Fatalf("%s: handshake failed: %s", test, err)
390 }
391 if hs.DidResume != didResume {
392 t.Fatalf("%s resumed: %v, expected: %v", test, hs.DidResume, didResume)
393 }
394 }
395
396 testResumeState("Handshake", false)
397 testResumeState("Resume", true)
398
399 if _, err := io.ReadFull(serverConfig.rand(), serverConfig.SessionTicketKey[:]); err != nil {
400 t.Fatalf("Failed to invalidate SessionTicketKey")
401 }
402 testResumeState("InvalidSessionTicketKey", false)
403 testResumeState("ResumeAfterInvalidSessionTicketKey", true)
404
405 clientConfig.CipherSuites = []uint16{TLS_ECDHE_RSA_WITH_RC4_128_SHA}
406 testResumeState("DifferentCipherSuite", false)
407 testResumeState("DifferentCipherSuiteRecovers", true)
408
409 clientConfig.ClientSessionCache = nil
410 testResumeState("WithoutSessionCache", false)
411}
412
413func TestLRUClientSessionCache(t *testing.T) {
414 // Initialize cache of capacity 4.
415 cache := NewLRUClientSessionCache(4)
416 cs := make([]ClientSessionState, 6)
417 keys := []string{"0", "1", "2", "3", "4", "5", "6"}
418
419 // Add 4 entries to the cache and look them up.
420 for i := 0; i < 4; i++ {
421 cache.Put(keys[i], &cs[i])
422 }
423 for i := 0; i < 4; i++ {
424 if s, ok := cache.Get(keys[i]); !ok || s != &cs[i] {
425 t.Fatalf("session cache failed lookup for added key: %s", keys[i])
426 }
427 }
428
429 // Add 2 more entries to the cache. First 2 should be evicted.
430 for i := 4; i < 6; i++ {
431 cache.Put(keys[i], &cs[i])
432 }
433 for i := 0; i < 2; i++ {
434 if s, ok := cache.Get(keys[i]); ok || s != nil {
435 t.Fatalf("session cache should have evicted key: %s", keys[i])
436 }
437 }
438
439 // Touch entry 2. LRU should evict 3 next.
440 cache.Get(keys[2])
441 cache.Put(keys[0], &cs[0])
442 if s, ok := cache.Get(keys[3]); ok || s != nil {
443 t.Fatalf("session cache should have evicted key 3")
444 }
445
446 // Update entry 0 in place.
447 cache.Put(keys[0], &cs[3])
448 if s, ok := cache.Get(keys[0]); !ok || s != &cs[3] {
449 t.Fatalf("session cache failed update for key 0")
450 }
451
452 // Adding a nil entry is valid.
453 cache.Put(keys[0], nil)
454 if s, ok := cache.Get(keys[0]); !ok || s != nil {
455 t.Fatalf("failed to add nil entry to cache")
456 }
457}
Adam Langleyd0e255f2014-08-05 11:36:20 -0700458
459func TestHandshakeClientALPNMatch(t *testing.T) {
460 config := *testConfig
461 config.NextProtos = []string{"proto2", "proto1"}
462
463 test := &clientTest{
464 name: "ALPN",
465 // Note that this needs OpenSSL 1.0.2 because that is the first
466 // version that supports the -alpn flag.
467 command: []string{"openssl", "s_server", "-alpn", "proto1,proto2"},
468 config: &config,
469 validate: func(state ConnectionState) error {
470 // The server's preferences should override the client.
471 if state.NegotiatedProtocol != "proto1" {
472 return fmt.Errorf("Got protocol %q, wanted proto1", state.NegotiatedProtocol)
473 }
474 return nil
475 },
476 }
477 runClientTestTLS12(t, test)
478}
479
480func TestHandshakeClientALPNNoMatch(t *testing.T) {
481 config := *testConfig
482 config.NextProtos = []string{"proto3"}
483
484 test := &clientTest{
485 name: "ALPN-NoMatch",
486 // Note that this needs OpenSSL 1.0.2 because that is the first
487 // version that supports the -alpn flag.
488 command: []string{"openssl", "s_server", "-alpn", "proto1,proto2"},
489 config: &config,
490 validate: func(state ConnectionState) error {
491 // There's no overlap so OpenSSL will not select a protocol.
492 if state.NegotiatedProtocol != "" {
493 return fmt.Errorf("Got protocol %q, wanted ''", state.NegotiatedProtocol)
494 }
495 return nil
496 },
497 }
498 runClientTestTLS12(t, test)
499}