2010-12-16 22:10:50 +00:00
|
|
|
// Copyright 2010 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 tls
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2013-12-20 16:37:05 +00:00
|
|
|
"crypto/ecdsa"
|
|
|
|
"crypto/rsa"
|
|
|
|
"crypto/x509"
|
|
|
|
"encoding/pem"
|
|
|
|
"fmt"
|
2010-12-16 22:10:50 +00:00
|
|
|
"io"
|
|
|
|
"net"
|
2012-04-11 17:55:57 +01:00
|
|
|
"os"
|
2013-12-20 16:37:05 +00:00
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
"strconv"
|
2010-12-16 22:10:50 +00:00
|
|
|
"testing"
|
2013-12-20 16:37:05 +00:00
|
|
|
"time"
|
2010-12-16 22:10:50 +00:00
|
|
|
)
|
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
// Note: see comment in handshake_test.go for details of how the reference
|
|
|
|
// tests work.
|
2010-12-16 22:10:50 +00:00
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
// blockingSource is an io.Reader that blocks a Read call until it's closed.
|
|
|
|
type blockingSource chan bool
|
2010-12-16 22:10:50 +00:00
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
func (b blockingSource) Read([]byte) (n int, err error) {
|
|
|
|
<-b
|
|
|
|
return 0, io.EOF
|
2012-04-11 17:55:57 +01:00
|
|
|
}
|
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
// clientTest represents a test of the TLS client handshake against a reference
|
|
|
|
// implementation.
|
|
|
|
type clientTest struct {
|
|
|
|
// name is a freeform string identifying the test and the file in which
|
|
|
|
// the expected results will be stored.
|
|
|
|
name string
|
|
|
|
// command, if not empty, contains a series of arguments for the
|
|
|
|
// command to run for the reference server.
|
|
|
|
command []string
|
|
|
|
// config, if not nil, contains a custom Config to use for this test.
|
|
|
|
config *Config
|
|
|
|
// cert, if not empty, contains a DER-encoded certificate for the
|
|
|
|
// reference server.
|
|
|
|
cert []byte
|
|
|
|
// key, if not nil, contains either a *rsa.PrivateKey or
|
|
|
|
// *ecdsa.PrivateKey which is the private key for the reference server.
|
|
|
|
key interface{}
|
2013-07-17 17:33:16 +01:00
|
|
|
}
|
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
var defaultServerCommand = []string{"openssl", "s_server"}
|
2013-05-29 16:21:32 +01:00
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
// connFromCommand starts the reference server process, connects to it and
|
|
|
|
// returns a recordingConn for the connection. The stdin return value is a
|
|
|
|
// blockingSource for the stdin of the child process. It must be closed before
|
|
|
|
// Waiting for child.
|
|
|
|
func (test *clientTest) connFromCommand() (conn *recordingConn, child *exec.Cmd, stdin blockingSource, err error) {
|
|
|
|
cert := testRSACertificate
|
|
|
|
if len(test.cert) > 0 {
|
|
|
|
cert = test.cert
|
|
|
|
}
|
|
|
|
certPath := tempFile(string(cert))
|
|
|
|
defer os.Remove(certPath)
|
2013-06-05 01:02:22 +01:00
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
var key interface{} = testRSAPrivateKey
|
|
|
|
if test.key != nil {
|
|
|
|
key = test.key
|
|
|
|
}
|
|
|
|
var pemType string
|
|
|
|
var derBytes []byte
|
|
|
|
switch key := key.(type) {
|
|
|
|
case *rsa.PrivateKey:
|
|
|
|
pemType = "RSA"
|
|
|
|
derBytes = x509.MarshalPKCS1PrivateKey(key)
|
|
|
|
case *ecdsa.PrivateKey:
|
|
|
|
pemType = "EC"
|
|
|
|
var err error
|
|
|
|
derBytes, err = x509.MarshalECPrivateKey(key)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
panic("unknown key type")
|
|
|
|
}
|
2013-07-03 00:58:56 +01:00
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
var pemOut bytes.Buffer
|
|
|
|
pem.Encode(&pemOut, &pem.Block{Type: pemType + " PRIVATE KEY", Bytes: derBytes})
|
2013-09-16 21:39:42 +01:00
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
keyPath := tempFile(string(pemOut.Bytes()))
|
|
|
|
defer os.Remove(keyPath)
|
2010-12-16 22:10:50 +00:00
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
var command []string
|
|
|
|
if len(test.command) > 0 {
|
|
|
|
command = append(command, test.command...)
|
|
|
|
} else {
|
|
|
|
command = append(command, defaultServerCommand...)
|
|
|
|
}
|
|
|
|
command = append(command, "-cert", certPath, "-certform", "DER", "-key", keyPath)
|
|
|
|
// serverPort contains the port that OpenSSL will listen on. OpenSSL
|
|
|
|
// can't take "0" as an argument here so we have to pick a number and
|
|
|
|
// hope that it's not in use on the machine. Since this only occurs
|
|
|
|
// when -update is given and thus when there's a human watching the
|
|
|
|
// test, this isn't too bad.
|
|
|
|
const serverPort = 24323
|
|
|
|
command = append(command, "-accept", strconv.Itoa(serverPort))
|
|
|
|
|
|
|
|
cmd := exec.Command(command[0], command[1:]...)
|
|
|
|
stdin = blockingSource(make(chan bool))
|
|
|
|
cmd.Stdin = stdin
|
|
|
|
var out bytes.Buffer
|
|
|
|
cmd.Stdout = &out
|
|
|
|
cmd.Stderr = &out
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
return nil, nil, nil, err
|
2010-12-16 22:10:50 +00:00
|
|
|
}
|
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
// OpenSSL does print an "ACCEPT" banner, but it does so *before*
|
|
|
|
// opening the listening socket, so we can't use that to wait until it
|
|
|
|
// has started listening. Thus we are forced to poll until we get a
|
|
|
|
// connection.
|
|
|
|
var tcpConn net.Conn
|
|
|
|
for i := uint(0); i < 5; i++ {
|
|
|
|
var err error
|
|
|
|
tcpConn, err = net.DialTCP("tcp", nil, &net.TCPAddr{
|
|
|
|
IP: net.IPv4(127, 0, 0, 1),
|
|
|
|
Port: serverPort,
|
|
|
|
})
|
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
time.Sleep((1 << i) * 5 * time.Millisecond)
|
|
|
|
}
|
|
|
|
if tcpConn == nil {
|
|
|
|
close(stdin)
|
|
|
|
out.WriteTo(os.Stdout)
|
|
|
|
cmd.Process.Kill()
|
|
|
|
return nil, nil, nil, cmd.Wait()
|
2010-12-16 22:10:50 +00:00
|
|
|
}
|
|
|
|
|
2012-04-11 17:55:57 +01:00
|
|
|
record := &recordingConn{
|
|
|
|
Conn: tcpConn,
|
|
|
|
}
|
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
return record, cmd, stdin, nil
|
|
|
|
}
|
2012-04-11 17:55:57 +01:00
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
func (test *clientTest) dataPath() string {
|
|
|
|
return filepath.Join("testdata", "Client-"+test.name)
|
|
|
|
}
|
2012-04-11 17:55:57 +01:00
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
func (test *clientTest) loadData() (flows [][]byte, err error) {
|
|
|
|
in, err := os.Open(test.dataPath())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer in.Close()
|
|
|
|
return parseTestData(in)
|
2010-12-16 22:10:50 +00:00
|
|
|
}
|
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
func (test *clientTest) run(t *testing.T, write bool) {
|
|
|
|
var clientConn, serverConn net.Conn
|
|
|
|
var recordingConn *recordingConn
|
|
|
|
var childProcess *exec.Cmd
|
|
|
|
var stdin blockingSource
|
2013-05-15 15:25:54 +01:00
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
if write {
|
|
|
|
var err error
|
|
|
|
recordingConn, childProcess, stdin, err = test.connFromCommand()
|
2013-05-15 15:25:54 +01:00
|
|
|
if err != nil {
|
2013-12-20 16:37:05 +00:00
|
|
|
t.Fatalf("Failed to start subcommand: %s", err)
|
2013-05-15 15:25:54 +01:00
|
|
|
}
|
2013-12-20 16:37:05 +00:00
|
|
|
clientConn = recordingConn
|
|
|
|
} else {
|
|
|
|
clientConn, serverConn = net.Pipe()
|
|
|
|
}
|
|
|
|
|
|
|
|
config := test.config
|
|
|
|
if config == nil {
|
|
|
|
config = testConfig
|
|
|
|
}
|
|
|
|
client := Client(clientConn, config)
|
|
|
|
|
|
|
|
doneChan := make(chan bool)
|
|
|
|
go func() {
|
|
|
|
if _, err := client.Write([]byte("hello\n")); err != nil {
|
|
|
|
t.Logf("Client.Write failed: %s", err)
|
2013-05-15 15:25:54 +01:00
|
|
|
}
|
2013-12-20 16:37:05 +00:00
|
|
|
client.Close()
|
|
|
|
clientConn.Close()
|
|
|
|
doneChan <- true
|
2013-05-15 15:25:54 +01:00
|
|
|
}()
|
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
if !write {
|
|
|
|
flows, err := test.loadData()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("%s: failed to load data from %s", test.name, test.dataPath())
|
|
|
|
}
|
|
|
|
for i, b := range flows {
|
|
|
|
if i%2 == 1 {
|
|
|
|
serverConn.Write(b)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
bb := make([]byte, len(b))
|
|
|
|
_, err := io.ReadFull(serverConn, bb)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("%s #%d: %s", test.name, i, err)
|
|
|
|
}
|
|
|
|
if !bytes.Equal(b, bb) {
|
|
|
|
t.Fatalf("%s #%d: mismatch on read: got:%x want:%x", test.name, i, bb, b)
|
|
|
|
}
|
2013-05-15 15:25:54 +01:00
|
|
|
}
|
2013-12-20 16:37:05 +00:00
|
|
|
serverConn.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
<-doneChan
|
|
|
|
|
|
|
|
if write {
|
|
|
|
path := test.dataPath()
|
|
|
|
out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
2013-05-15 15:25:54 +01:00
|
|
|
if err != nil {
|
2013-12-20 16:37:05 +00:00
|
|
|
t.Fatalf("Failed to create output file: %s", err)
|
2013-05-15 15:25:54 +01:00
|
|
|
}
|
2013-12-20 16:37:05 +00:00
|
|
|
defer out.Close()
|
|
|
|
recordingConn.Close()
|
|
|
|
close(stdin)
|
|
|
|
childProcess.Process.Kill()
|
|
|
|
childProcess.Wait()
|
|
|
|
if len(recordingConn.flows) < 3 {
|
|
|
|
childProcess.Stdout.(*bytes.Buffer).WriteTo(os.Stdout)
|
|
|
|
t.Fatalf("Client connection didn't work")
|
2013-05-15 15:25:54 +01:00
|
|
|
}
|
2013-12-20 16:37:05 +00:00
|
|
|
recordingConn.WriteTo(out)
|
|
|
|
fmt.Printf("Wrote %s\n", path)
|
2013-05-15 15:25:54 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
func runClientTestForVersion(t *testing.T, template *clientTest, prefix, option string) {
|
|
|
|
test := *template
|
|
|
|
test.name = prefix + test.name
|
|
|
|
if len(test.command) == 0 {
|
|
|
|
test.command = defaultClientCommand
|
|
|
|
}
|
|
|
|
test.command = append([]string(nil), test.command...)
|
|
|
|
test.command = append(test.command, option)
|
|
|
|
test.run(t, *update)
|
|
|
|
}
|
2010-12-16 22:10:50 +00:00
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
func runClientTestTLS10(t *testing.T, template *clientTest) {
|
|
|
|
runClientTestForVersion(t, template, "TLSv10-", "-tls1")
|
|
|
|
}
|
2010-12-16 22:10:50 +00:00
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
func runClientTestTLS11(t *testing.T, template *clientTest) {
|
|
|
|
runClientTestForVersion(t, template, "TLSv11-", "-tls1_1")
|
2010-12-16 22:10:50 +00:00
|
|
|
}
|
2012-04-11 17:55:57 +01:00
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
func runClientTestTLS12(t *testing.T, template *clientTest) {
|
|
|
|
runClientTestForVersion(t, template, "TLSv12-", "-tls1_2")
|
2012-04-11 17:55:57 +01:00
|
|
|
}
|
2013-05-15 15:25:54 +01:00
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
func TestHandshakeClientRSARC4(t *testing.T) {
|
|
|
|
test := &clientTest{
|
|
|
|
name: "RSA-RC4",
|
|
|
|
command: []string{"openssl", "s_server", "-cipher", "RC4-SHA"},
|
|
|
|
}
|
|
|
|
runClientTestTLS10(t, test)
|
|
|
|
runClientTestTLS11(t, test)
|
|
|
|
runClientTestTLS12(t, test)
|
2013-05-15 15:25:54 +01:00
|
|
|
}
|
2013-05-29 16:21:32 +01:00
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
func TestHandshakeClientECDHERSAAES(t *testing.T) {
|
|
|
|
test := &clientTest{
|
|
|
|
name: "ECDHE-RSA-AES",
|
|
|
|
command: []string{"openssl", "s_server", "-cipher", "ECDHE-RSA-AES128-SHA"},
|
|
|
|
}
|
|
|
|
runClientTestTLS10(t, test)
|
|
|
|
runClientTestTLS11(t, test)
|
|
|
|
runClientTestTLS12(t, test)
|
2013-06-05 01:02:22 +01:00
|
|
|
}
|
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
func TestHandshakeClientECDHEECDSAAES(t *testing.T) {
|
|
|
|
test := &clientTest{
|
|
|
|
name: "ECDHE-ECDSA-AES",
|
|
|
|
command: []string{"openssl", "s_server", "-cipher", "ECDHE-ECDSA-AES128-SHA"},
|
|
|
|
cert: testECDSACertificate,
|
|
|
|
key: testECDSAPrivateKey,
|
|
|
|
}
|
|
|
|
runClientTestTLS10(t, test)
|
|
|
|
runClientTestTLS11(t, test)
|
|
|
|
runClientTestTLS12(t, test)
|
2013-05-29 16:21:32 +01:00
|
|
|
}
|
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
func TestHandshakeClientECDHEECDSAAESGCM(t *testing.T) {
|
|
|
|
test := &clientTest{
|
|
|
|
name: "ECDHE-ECDSA-AES-GCM",
|
|
|
|
command: []string{"openssl", "s_server", "-cipher", "ECDHE-ECDSA-AES128-GCM-SHA256"},
|
|
|
|
cert: testECDSACertificate,
|
|
|
|
key: testECDSAPrivateKey,
|
|
|
|
}
|
|
|
|
runClientTestTLS12(t, test)
|
2013-07-03 00:58:56 +01:00
|
|
|
}
|
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
func TestHandshakeClientCertRSA(t *testing.T) {
|
|
|
|
config := *testConfig
|
|
|
|
cert, _ := X509KeyPair([]byte(clientCertificatePEM), []byte(clientKeyPEM))
|
|
|
|
config.Certificates = []Certificate{cert}
|
|
|
|
|
|
|
|
test := &clientTest{
|
|
|
|
name: "ClientCert-RSA-RSA",
|
|
|
|
command: []string{"openssl", "s_server", "-cipher", "RC4-SHA", "-verify", "1"},
|
|
|
|
config: &config,
|
|
|
|
}
|
|
|
|
|
|
|
|
runClientTestTLS10(t, test)
|
|
|
|
runClientTestTLS12(t, test)
|
|
|
|
|
|
|
|
test = &clientTest{
|
|
|
|
name: "ClientCert-RSA-ECDSA",
|
|
|
|
command: []string{"openssl", "s_server", "-cipher", "ECDHE-ECDSA-AES128-SHA", "-verify", "1"},
|
|
|
|
config: &config,
|
|
|
|
cert: testECDSACertificate,
|
|
|
|
key: testECDSAPrivateKey,
|
|
|
|
}
|
|
|
|
|
|
|
|
runClientTestTLS10(t, test)
|
|
|
|
runClientTestTLS12(t, test)
|
2013-09-16 21:39:42 +01:00
|
|
|
}
|
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
func TestHandshakeClientCertECDSA(t *testing.T) {
|
|
|
|
config := *testConfig
|
|
|
|
cert, _ := X509KeyPair([]byte(clientECDSACertificatePEM), []byte(clientECDSAKeyPEM))
|
|
|
|
config.Certificates = []Certificate{cert}
|
|
|
|
|
|
|
|
test := &clientTest{
|
|
|
|
name: "ClientCert-ECDSA-RSA",
|
|
|
|
command: []string{"openssl", "s_server", "-cipher", "RC4-SHA", "-verify", "1"},
|
|
|
|
config: &config,
|
|
|
|
}
|
|
|
|
|
|
|
|
runClientTestTLS10(t, test)
|
|
|
|
runClientTestTLS12(t, test)
|
|
|
|
|
|
|
|
test = &clientTest{
|
|
|
|
name: "ClientCert-ECDSA-ECDSA",
|
|
|
|
command: []string{"openssl", "s_server", "-cipher", "ECDHE-ECDSA-AES128-SHA", "-verify", "1"},
|
|
|
|
config: &config,
|
|
|
|
cert: testECDSACertificate,
|
|
|
|
key: testECDSAPrivateKey,
|
|
|
|
}
|
2013-07-17 17:33:16 +01:00
|
|
|
|
2013-12-20 16:37:05 +00:00
|
|
|
runClientTestTLS10(t, test)
|
|
|
|
runClientTestTLS12(t, test)
|
2013-07-17 17:33:16 +01:00
|
|
|
}
|