boringssl/ssl/test/runner/runner.go
Adam Langley 69a01608f3 Add malloc failure tests.
This commit fixes a number of crashes caused by malloc failures. They
were found using the -malloc-test=0 option to runner.go which runs tests
many times, causing a different allocation call to fail in each case.

(This test only works on Linux and only looks for crashes caused by
allocation failures, not memory leaks or other errors.)

This is not the complete set of crashes! More can be found by collecting
core dumps from running with -malloc-test=0.

Change-Id: Ia61d19f51e373bccb7bc604642c51e043a74bd83
Reviewed-on: https://boringssl-review.googlesource.com/2320
Reviewed-by: Adam Langley <agl@google.com>
2014-11-19 01:24:46 +00:00

2275 lines
59 KiB
Go

package main
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"flag"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"os/exec"
"path"
"runtime"
"strconv"
"strings"
"sync"
"syscall"
)
var (
useValgrind = flag.Bool("valgrind", false, "If true, run code under valgrind")
useGDB = flag.Bool("gdb", false, "If true, run BoringSSL code under gdb")
flagDebug *bool = flag.Bool("debug", false, "Hexdump the contents of the connection")
mallocTest *int64 = flag.Int64("malloc-test", -1, "If non-negative, run each test with each malloc in turn failing from the given number onwards.")
mallocTestDebug *bool = flag.Bool("malloc-test-debug", false, "If true, ask bssl_shim to abort rather than fail a malloc. This can be used with a specific value for --malloc-test to identity the malloc failing that is causing problems.")
)
const (
rsaCertificateFile = "cert.pem"
ecdsaCertificateFile = "ecdsa_cert.pem"
)
const (
rsaKeyFile = "key.pem"
ecdsaKeyFile = "ecdsa_key.pem"
channelIDKeyFile = "channel_id_key.pem"
)
var rsaCertificate, ecdsaCertificate Certificate
var channelIDKey *ecdsa.PrivateKey
var channelIDBytes []byte
func initCertificates() {
var err error
rsaCertificate, err = LoadX509KeyPair(rsaCertificateFile, rsaKeyFile)
if err != nil {
panic(err)
}
ecdsaCertificate, err = LoadX509KeyPair(ecdsaCertificateFile, ecdsaKeyFile)
if err != nil {
panic(err)
}
channelIDPEMBlock, err := ioutil.ReadFile(channelIDKeyFile)
if err != nil {
panic(err)
}
channelIDDERBlock, _ := pem.Decode(channelIDPEMBlock)
if channelIDDERBlock.Type != "EC PRIVATE KEY" {
panic("bad key type")
}
channelIDKey, err = x509.ParseECPrivateKey(channelIDDERBlock.Bytes)
if err != nil {
panic(err)
}
if channelIDKey.Curve != elliptic.P256() {
panic("bad curve")
}
channelIDBytes = make([]byte, 64)
writeIntPadded(channelIDBytes[:32], channelIDKey.X)
writeIntPadded(channelIDBytes[32:], channelIDKey.Y)
}
var certificateOnce sync.Once
func getRSACertificate() Certificate {
certificateOnce.Do(initCertificates)
return rsaCertificate
}
func getECDSACertificate() Certificate {
certificateOnce.Do(initCertificates)
return ecdsaCertificate
}
type testType int
const (
clientTest testType = iota
serverTest
)
type protocol int
const (
tls protocol = iota
dtls
)
const (
alpn = 1
npn = 2
)
type testCase struct {
testType testType
protocol protocol
name string
config Config
shouldFail bool
expectedError string
// expectedLocalError, if not empty, contains a substring that must be
// found in the local error.
expectedLocalError string
// expectedVersion, if non-zero, specifies the TLS version that must be
// negotiated.
expectedVersion uint16
// expectedResumeVersion, if non-zero, specifies the TLS version that
// must be negotiated on resumption. If zero, expectedVersion is used.
expectedResumeVersion uint16
// expectChannelID controls whether the connection should have
// negotiated a Channel ID with channelIDKey.
expectChannelID bool
// expectedNextProto controls whether the connection should
// negotiate a next protocol via NPN or ALPN.
expectedNextProto string
// expectedNextProtoType, if non-zero, is the expected next
// protocol negotiation mechanism.
expectedNextProtoType int
// expectedSRTPProtectionProfile is the DTLS-SRTP profile that
// should be negotiated. If zero, none should be negotiated.
expectedSRTPProtectionProfile uint16
// messageLen is the length, in bytes, of the test message that will be
// sent.
messageLen int
// certFile is the path to the certificate to use for the server.
certFile string
// keyFile is the path to the private key to use for the server.
keyFile string
// resumeSession controls whether a second connection should be tested
// which attempts to resume the first session.
resumeSession bool
// resumeConfig, if not nil, points to a Config to be used on
// resumption. SessionTicketKey and ClientSessionCache are copied from
// the initial connection's config. If nil, the initial connection's
// config is used.
resumeConfig *Config
// sendPrefix sends a prefix on the socket before actually performing a
// handshake.
sendPrefix string
// shimWritesFirst controls whether the shim sends an initial "hello"
// message before doing a roundtrip with the runner.
shimWritesFirst bool
// renegotiate indicates the the connection should be renegotiated
// during the exchange.
renegotiate bool
// renegotiateCiphers is a list of ciphersuite ids that will be
// switched in just before renegotiation.
renegotiateCiphers []uint16
// replayWrites, if true, configures the underlying transport
// to replay every write it makes in DTLS tests.
replayWrites bool
// flags, if not empty, contains a list of command-line flags that will
// be passed to the shim program.
flags []string
}
var testCases = []testCase{
{
name: "BadRSASignature",
config: Config{
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
Bugs: ProtocolBugs{
InvalidSKXSignature: true,
},
},
shouldFail: true,
expectedError: ":BAD_SIGNATURE:",
},
{
name: "BadECDSASignature",
config: Config{
CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
Bugs: ProtocolBugs{
InvalidSKXSignature: true,
},
Certificates: []Certificate{getECDSACertificate()},
},
shouldFail: true,
expectedError: ":BAD_SIGNATURE:",
},
{
name: "BadECDSACurve",
config: Config{
CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
Bugs: ProtocolBugs{
InvalidSKXCurve: true,
},
Certificates: []Certificate{getECDSACertificate()},
},
shouldFail: true,
expectedError: ":WRONG_CURVE:",
},
{
testType: serverTest,
name: "BadRSAVersion",
config: Config{
CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA},
Bugs: ProtocolBugs{
RsaClientKeyExchangeVersion: VersionTLS11,
},
},
shouldFail: true,
expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
},
{
name: "NoFallbackSCSV",
config: Config{
Bugs: ProtocolBugs{
FailIfNotFallbackSCSV: true,
},
},
shouldFail: true,
expectedLocalError: "no fallback SCSV found",
},
{
name: "SendFallbackSCSV",
config: Config{
Bugs: ProtocolBugs{
FailIfNotFallbackSCSV: true,
},
},
flags: []string{"-fallback-scsv"},
},
{
name: "ClientCertificateTypes",
config: Config{
ClientAuth: RequestClientCert,
ClientCertificateTypes: []byte{
CertTypeDSSSign,
CertTypeRSASign,
CertTypeECDSASign,
},
},
flags: []string{
"-expect-certificate-types",
base64.StdEncoding.EncodeToString([]byte{
CertTypeDSSSign,
CertTypeRSASign,
CertTypeECDSASign,
}),
},
},
{
name: "NoClientCertificate",
config: Config{
ClientAuth: RequireAnyClientCert,
},
shouldFail: true,
expectedLocalError: "client didn't provide a certificate",
},
{
name: "UnauthenticatedECDH",
config: Config{
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
Bugs: ProtocolBugs{
UnauthenticatedECDH: true,
},
},
shouldFail: true,
expectedError: ":UNEXPECTED_MESSAGE:",
},
{
name: "SkipServerKeyExchange",
config: Config{
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
Bugs: ProtocolBugs{
SkipServerKeyExchange: true,
},
},
shouldFail: true,
expectedError: ":UNEXPECTED_MESSAGE:",
},
{
name: "SkipChangeCipherSpec-Client",
config: Config{
Bugs: ProtocolBugs{
SkipChangeCipherSpec: true,
},
},
shouldFail: true,
expectedError: ":HANDSHAKE_RECORD_BEFORE_CCS:",
},
{
testType: serverTest,
name: "SkipChangeCipherSpec-Server",
config: Config{
Bugs: ProtocolBugs{
SkipChangeCipherSpec: true,
},
},
shouldFail: true,
expectedError: ":HANDSHAKE_RECORD_BEFORE_CCS:",
},
{
testType: serverTest,
name: "SkipChangeCipherSpec-Server-NPN",
config: Config{
NextProtos: []string{"bar"},
Bugs: ProtocolBugs{
SkipChangeCipherSpec: true,
},
},
flags: []string{
"-advertise-npn", "\x03foo\x03bar\x03baz",
},
shouldFail: true,
expectedError: ":HANDSHAKE_RECORD_BEFORE_CCS:",
},
{
name: "FragmentAcrossChangeCipherSpec-Client",
config: Config{
Bugs: ProtocolBugs{
FragmentAcrossChangeCipherSpec: true,
},
},
shouldFail: true,
expectedError: ":HANDSHAKE_RECORD_BEFORE_CCS:",
},
{
testType: serverTest,
name: "FragmentAcrossChangeCipherSpec-Server",
config: Config{
Bugs: ProtocolBugs{
FragmentAcrossChangeCipherSpec: true,
},
},
shouldFail: true,
expectedError: ":HANDSHAKE_RECORD_BEFORE_CCS:",
},
{
testType: serverTest,
name: "FragmentAcrossChangeCipherSpec-Server-NPN",
config: Config{
NextProtos: []string{"bar"},
Bugs: ProtocolBugs{
FragmentAcrossChangeCipherSpec: true,
},
},
flags: []string{
"-advertise-npn", "\x03foo\x03bar\x03baz",
},
shouldFail: true,
expectedError: ":HANDSHAKE_RECORD_BEFORE_CCS:",
},
{
testType: serverTest,
name: "FragmentAlert",
config: Config{
Bugs: ProtocolBugs{
FragmentAlert: true,
SendSpuriousAlert: true,
},
},
shouldFail: true,
expectedError: ":BAD_ALERT:",
},
{
testType: serverTest,
name: "EarlyChangeCipherSpec-server-1",
config: Config{
Bugs: ProtocolBugs{
EarlyChangeCipherSpec: 1,
},
},
shouldFail: true,
expectedError: ":CCS_RECEIVED_EARLY:",
},
{
testType: serverTest,
name: "EarlyChangeCipherSpec-server-2",
config: Config{
Bugs: ProtocolBugs{
EarlyChangeCipherSpec: 2,
},
},
shouldFail: true,
expectedError: ":CCS_RECEIVED_EARLY:",
},
{
name: "SkipNewSessionTicket",
config: Config{
Bugs: ProtocolBugs{
SkipNewSessionTicket: true,
},
},
shouldFail: true,
expectedError: ":CCS_RECEIVED_EARLY:",
},
{
testType: serverTest,
name: "FallbackSCSV",
config: Config{
MaxVersion: VersionTLS11,
Bugs: ProtocolBugs{
SendFallbackSCSV: true,
},
},
shouldFail: true,
expectedError: ":INAPPROPRIATE_FALLBACK:",
},
{
testType: serverTest,
name: "FallbackSCSV-VersionMatch",
config: Config{
Bugs: ProtocolBugs{
SendFallbackSCSV: true,
},
},
},
{
testType: serverTest,
name: "FragmentedClientVersion",
config: Config{
Bugs: ProtocolBugs{
MaxHandshakeRecordLength: 1,
FragmentClientVersion: true,
},
},
shouldFail: true,
expectedError: ":RECORD_TOO_SMALL:",
},
{
testType: serverTest,
name: "MinorVersionTolerance",
config: Config{
Bugs: ProtocolBugs{
SendClientVersion: 0x03ff,
},
},
expectedVersion: VersionTLS12,
},
{
testType: serverTest,
name: "MajorVersionTolerance",
config: Config{
Bugs: ProtocolBugs{
SendClientVersion: 0x0400,
},
},
expectedVersion: VersionTLS12,
},
{
testType: serverTest,
name: "VersionTooLow",
config: Config{
Bugs: ProtocolBugs{
SendClientVersion: 0x0200,
},
},
shouldFail: true,
expectedError: ":UNSUPPORTED_PROTOCOL:",
},
{
testType: serverTest,
name: "HttpGET",
sendPrefix: "GET / HTTP/1.0\n",
shouldFail: true,
expectedError: ":HTTP_REQUEST:",
},
{
testType: serverTest,
name: "HttpPOST",
sendPrefix: "POST / HTTP/1.0\n",
shouldFail: true,
expectedError: ":HTTP_REQUEST:",
},
{
testType: serverTest,
name: "HttpHEAD",
sendPrefix: "HEAD / HTTP/1.0\n",
shouldFail: true,
expectedError: ":HTTP_REQUEST:",
},
{
testType: serverTest,
name: "HttpPUT",
sendPrefix: "PUT / HTTP/1.0\n",
shouldFail: true,
expectedError: ":HTTP_REQUEST:",
},
{
testType: serverTest,
name: "HttpCONNECT",
sendPrefix: "CONNECT www.google.com:443 HTTP/1.0\n",
shouldFail: true,
expectedError: ":HTTPS_PROXY_REQUEST:",
},
{
name: "SkipCipherVersionCheck",
config: Config{
CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
MaxVersion: VersionTLS11,
Bugs: ProtocolBugs{
SkipCipherVersionCheck: true,
},
},
shouldFail: true,
expectedError: ":WRONG_CIPHER_RETURNED:",
},
{
name: "RSAServerKeyExchange",
config: Config{
CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
Bugs: ProtocolBugs{
RSAServerKeyExchange: true,
},
},
shouldFail: true,
expectedError: ":UNEXPECTED_MESSAGE:",
},
}
func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int, isResume bool) error {
if test.protocol == dtls {
conn = newPacketAdaptor(conn)
if test.replayWrites {
conn = newReplayAdaptor(conn)
}
}
if test.sendPrefix != "" {
if _, err := conn.Write([]byte(test.sendPrefix)); err != nil {
return err
}
}
var tlsConn *Conn
if test.testType == clientTest {
if test.protocol == dtls {
tlsConn = DTLSServer(conn, config)
} else {
tlsConn = Server(conn, config)
}
} else {
config.InsecureSkipVerify = true
if test.protocol == dtls {
tlsConn = DTLSClient(conn, config)
} else {
tlsConn = Client(conn, config)
}
}
if err := tlsConn.Handshake(); err != nil {
return err
}
// TODO(davidben): move all per-connection expectations into a dedicated
// expectations struct that can be specified separately for the two
// legs.
expectedVersion := test.expectedVersion
if isResume && test.expectedResumeVersion != 0 {
expectedVersion = test.expectedResumeVersion
}
if vers := tlsConn.ConnectionState().Version; expectedVersion != 0 && vers != expectedVersion {
return fmt.Errorf("got version %x, expected %x", vers, expectedVersion)
}
if test.expectChannelID {
channelID := tlsConn.ConnectionState().ChannelID
if channelID == nil {
return fmt.Errorf("no channel ID negotiated")
}
if channelID.Curve != channelIDKey.Curve ||
channelIDKey.X.Cmp(channelIDKey.X) != 0 ||
channelIDKey.Y.Cmp(channelIDKey.Y) != 0 {
return fmt.Errorf("incorrect channel ID")
}
}
if expected := test.expectedNextProto; expected != "" {
if actual := tlsConn.ConnectionState().NegotiatedProtocol; actual != expected {
return fmt.Errorf("next proto mismatch: got %s, wanted %s", actual, expected)
}
}
if test.expectedNextProtoType != 0 {
if (test.expectedNextProtoType == alpn) != tlsConn.ConnectionState().NegotiatedProtocolFromALPN {
return fmt.Errorf("next proto type mismatch")
}
}
if p := tlsConn.ConnectionState().SRTPProtectionProfile; p != test.expectedSRTPProtectionProfile {
return fmt.Errorf("SRTP profile mismatch: got %d, wanted %d", p, test.expectedSRTPProtectionProfile)
}
if test.shimWritesFirst {
var buf [5]byte
_, err := io.ReadFull(tlsConn, buf[:])
if err != nil {
return err
}
if string(buf[:]) != "hello" {
return fmt.Errorf("bad initial message")
}
}
if test.renegotiate {
if test.renegotiateCiphers != nil {
config.CipherSuites = test.renegotiateCiphers
}
if err := tlsConn.Renegotiate(); err != nil {
return err
}
} else if test.renegotiateCiphers != nil {
panic("renegotiateCiphers without renegotiate")
}
if messageLen < 0 {
if test.protocol == dtls {
return fmt.Errorf("messageLen < 0 not supported for DTLS tests")
}
// Read until EOF.
_, err := io.Copy(ioutil.Discard, tlsConn)
return err
}
if messageLen == 0 {
messageLen = 32
}
testMessage := make([]byte, messageLen)
for i := range testMessage {
testMessage[i] = 0x42
}
tlsConn.Write(testMessage)
buf := make([]byte, len(testMessage))
if test.protocol == dtls {
bufTmp := make([]byte, len(buf)+1)
n, err := tlsConn.Read(bufTmp)
if err != nil {
return err
}
if n != len(buf) {
return fmt.Errorf("bad reply; length mismatch (%d vs %d)", n, len(buf))
}
copy(buf, bufTmp)
} else {
_, err := io.ReadFull(tlsConn, buf)
if err != nil {
return err
}
}
for i, v := range buf {
if v != testMessage[i]^0xff {
return fmt.Errorf("bad reply contents at byte %d", i)
}
}
return nil
}
func valgrindOf(dbAttach bool, path string, args ...string) *exec.Cmd {
valgrindArgs := []string{"--error-exitcode=99", "--track-origins=yes", "--leak-check=full"}
if dbAttach {
valgrindArgs = append(valgrindArgs, "--db-attach=yes", "--db-command=xterm -e gdb -nw %f %p")
}
valgrindArgs = append(valgrindArgs, path)
valgrindArgs = append(valgrindArgs, args...)
return exec.Command("valgrind", valgrindArgs...)
}
func gdbOf(path string, args ...string) *exec.Cmd {
xtermArgs := []string{"-e", "gdb", "--args"}
xtermArgs = append(xtermArgs, path)
xtermArgs = append(xtermArgs, args...)
return exec.Command("xterm", xtermArgs...)
}
func openSocketPair() (shimEnd *os.File, conn net.Conn) {
socks, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
if err != nil {
panic(err)
}
syscall.CloseOnExec(socks[0])
syscall.CloseOnExec(socks[1])
shimEnd = os.NewFile(uintptr(socks[0]), "shim end")
connFile := os.NewFile(uintptr(socks[1]), "our end")
conn, err = net.FileConn(connFile)
if err != nil {
panic(err)
}
connFile.Close()
if err != nil {
panic(err)
}
return shimEnd, conn
}
type moreMallocsError struct{}
func (moreMallocsError) Error() string {
return "child process did not exhaust all allocation calls"
}
var errMoreMallocs = moreMallocsError{}
func runTest(test *testCase, buildDir string, mallocNumToFail int64) error {
if !test.shouldFail && (len(test.expectedError) > 0 || len(test.expectedLocalError) > 0) {
panic("Error expected without shouldFail in " + test.name)
}
shimEnd, conn := openSocketPair()
shimEndResume, connResume := openSocketPair()
shim_path := path.Join(buildDir, "ssl/test/bssl_shim")
var flags []string
if test.testType == serverTest {
flags = append(flags, "-server")
flags = append(flags, "-key-file")
if test.keyFile == "" {
flags = append(flags, rsaKeyFile)
} else {
flags = append(flags, test.keyFile)
}
flags = append(flags, "-cert-file")
if test.certFile == "" {
flags = append(flags, rsaCertificateFile)
} else {
flags = append(flags, test.certFile)
}
}
if test.protocol == dtls {
flags = append(flags, "-dtls")
}
if test.resumeSession {
flags = append(flags, "-resume")
}
if test.shimWritesFirst {
flags = append(flags, "-shim-writes-first")
}
flags = append(flags, test.flags...)
var shim *exec.Cmd
if *useValgrind {
shim = valgrindOf(false, shim_path, flags...)
} else if *useGDB {
shim = gdbOf(shim_path, flags...)
} else {
shim = exec.Command(shim_path, flags...)
}
shim.ExtraFiles = []*os.File{shimEnd, shimEndResume}
shim.Stdin = os.Stdin
var stdoutBuf, stderrBuf bytes.Buffer
shim.Stdout = &stdoutBuf
shim.Stderr = &stderrBuf
if mallocNumToFail >= 0 {
shim.Env = []string{"MALLOC_NUMBER_TO_FAIL=" + strconv.FormatInt(mallocNumToFail, 10)}
if *mallocTestDebug {
shim.Env = append(shim.Env, "MALLOC_ABORT_ON_FAIL=1")
}
shim.Env = append(shim.Env, "_MALLOC_CHECK=1")
}
if err := shim.Start(); err != nil {
panic(err)
}
shimEnd.Close()
shimEndResume.Close()
config := test.config
config.ClientSessionCache = NewLRUClientSessionCache(1)
if test.testType == clientTest {
if len(config.Certificates) == 0 {
config.Certificates = []Certificate{getRSACertificate()}
}
}
var connDebug *recordingConn
if *flagDebug {
connDebug = &recordingConn{Conn: conn}
conn = connDebug
}
err := doExchange(test, &config, conn, test.messageLen,
false /* not a resumption */)
if *flagDebug {
connDebug.WriteTo(os.Stdout)
}
conn.Close()
if err == nil && test.resumeSession {
var resumeConfig Config
if test.resumeConfig != nil {
resumeConfig = *test.resumeConfig
if len(resumeConfig.Certificates) == 0 {
resumeConfig.Certificates = []Certificate{getRSACertificate()}
}
resumeConfig.SessionTicketKey = config.SessionTicketKey
resumeConfig.ClientSessionCache = config.ClientSessionCache
} else {
resumeConfig = config
}
err = doExchange(test, &resumeConfig, connResume, test.messageLen,
true /* resumption */)
}
connResume.Close()
childErr := shim.Wait()
if exitError, ok := childErr.(*exec.ExitError); ok {
if exitError.Sys().(syscall.WaitStatus).ExitStatus() == 88 {
return errMoreMallocs
}
}
stdout := string(stdoutBuf.Bytes())
stderr := string(stderrBuf.Bytes())
failed := err != nil || childErr != nil
correctFailure := len(test.expectedError) == 0 || strings.Contains(stdout, test.expectedError)
localError := "none"
if err != nil {
localError = err.Error()
}
if len(test.expectedLocalError) != 0 {
correctFailure = correctFailure && strings.Contains(localError, test.expectedLocalError)
}
if failed != test.shouldFail || failed && !correctFailure {
childError := "none"
if childErr != nil {
childError = childErr.Error()
}
var msg string
switch {
case failed && !test.shouldFail:
msg = "unexpected failure"
case !failed && test.shouldFail:
msg = "unexpected success"
case failed && !correctFailure:
msg = "bad error (wanted '" + test.expectedError + "' / '" + test.expectedLocalError + "')"
default:
panic("internal error")
}
return fmt.Errorf("%s: local error '%s', child error '%s', stdout:\n%s\nstderr:\n%s", msg, localError, childError, string(stdoutBuf.Bytes()), stderr)
}
if !*useValgrind && len(stderr) > 0 {
println(stderr)
}
return nil
}
var tlsVersions = []struct {
name string
version uint16
flag string
}{
{"SSL3", VersionSSL30, "-no-ssl3"},
{"TLS1", VersionTLS10, "-no-tls1"},
{"TLS11", VersionTLS11, "-no-tls11"},
{"TLS12", VersionTLS12, "-no-tls12"},
}
var testCipherSuites = []struct {
name string
id uint16
}{
{"3DES-SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
{"AES128-GCM", TLS_RSA_WITH_AES_128_GCM_SHA256},
{"AES128-SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
{"AES128-SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
{"AES256-GCM", TLS_RSA_WITH_AES_256_GCM_SHA384},
{"AES256-SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
{"AES256-SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
{"DHE-RSA-AES128-GCM", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
{"DHE-RSA-AES128-SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
{"DHE-RSA-AES128-SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
{"DHE-RSA-AES256-GCM", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
{"DHE-RSA-AES256-SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
{"DHE-RSA-AES256-SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
{"ECDHE-ECDSA-AES128-GCM", TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
{"ECDHE-ECDSA-AES128-SHA", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA},
{"ECDHE-ECDSA-AES128-SHA256", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256},
{"ECDHE-ECDSA-AES256-GCM", TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384},
{"ECDHE-ECDSA-AES256-SHA", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA},
{"ECDHE-ECDSA-AES256-SHA384", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384},
{"ECDHE-ECDSA-RC4-SHA", TLS_ECDHE_ECDSA_WITH_RC4_128_SHA},
{"ECDHE-PSK-WITH-AES-128-GCM-SHA256", TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256},
{"ECDHE-RSA-AES128-GCM", TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
{"ECDHE-RSA-AES128-SHA", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
{"ECDHE-RSA-AES128-SHA256", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256},
{"ECDHE-RSA-AES256-GCM", TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384},
{"ECDHE-RSA-AES256-SHA", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA},
{"ECDHE-RSA-AES256-SHA384", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384},
{"ECDHE-RSA-RC4-SHA", TLS_ECDHE_RSA_WITH_RC4_128_SHA},
{"PSK-AES128-CBC-SHA", TLS_PSK_WITH_AES_128_CBC_SHA},
{"PSK-AES256-CBC-SHA", TLS_PSK_WITH_AES_256_CBC_SHA},
{"PSK-RC4-SHA", TLS_PSK_WITH_RC4_128_SHA},
{"RC4-MD5", TLS_RSA_WITH_RC4_128_MD5},
{"RC4-SHA", TLS_RSA_WITH_RC4_128_SHA},
}
func isTLS12Only(suiteName string) bool {
return strings.HasSuffix(suiteName, "-GCM") ||
strings.HasSuffix(suiteName, "-SHA256") ||
strings.HasSuffix(suiteName, "-SHA384")
}
func addCipherSuiteTests() {
for _, suite := range testCipherSuites {
const psk = "12345"
const pskIdentity = "luggage combo"
var cert Certificate
var certFile string
var keyFile string
if strings.Contains(suite.name, "ECDSA") {
cert = getECDSACertificate()
certFile = ecdsaCertificateFile
keyFile = ecdsaKeyFile
} else {
cert = getRSACertificate()
certFile = rsaCertificateFile
keyFile = rsaKeyFile
}
var flags []string
if strings.HasPrefix(suite.name, "PSK-") || strings.Contains(suite.name, "-PSK-") {
flags = append(flags,
"-psk", psk,
"-psk-identity", pskIdentity)
}
for _, ver := range tlsVersions {
if ver.version < VersionTLS12 && isTLS12Only(suite.name) {
continue
}
// Go's TLS implementation only implements session
// resumption with tickets, so SSLv3 cannot resume
// sessions.
resumeSession := ver.version != VersionSSL30
testCases = append(testCases, testCase{
testType: clientTest,
name: ver.name + "-" + suite.name + "-client",
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
CipherSuites: []uint16{suite.id},
Certificates: []Certificate{cert},
PreSharedKey: []byte(psk),
PreSharedKeyIdentity: pskIdentity,
},
flags: flags,
resumeSession: resumeSession,
})
testCases = append(testCases, testCase{
testType: serverTest,
name: ver.name + "-" + suite.name + "-server",
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
CipherSuites: []uint16{suite.id},
Certificates: []Certificate{cert},
PreSharedKey: []byte(psk),
PreSharedKeyIdentity: pskIdentity,
},
certFile: certFile,
keyFile: keyFile,
flags: flags,
resumeSession: resumeSession,
})
// TODO(davidben): Fix DTLS 1.2 support and test that.
if ver.version == VersionTLS10 && strings.Index(suite.name, "RC4") == -1 {
testCases = append(testCases, testCase{
testType: clientTest,
protocol: dtls,
name: "D" + ver.name + "-" + suite.name + "-client",
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
CipherSuites: []uint16{suite.id},
Certificates: []Certificate{cert},
PreSharedKey: []byte(psk),
PreSharedKeyIdentity: pskIdentity,
},
flags: flags,
resumeSession: resumeSession,
})
testCases = append(testCases, testCase{
testType: serverTest,
protocol: dtls,
name: "D" + ver.name + "-" + suite.name + "-server",
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
CipherSuites: []uint16{suite.id},
Certificates: []Certificate{cert},
PreSharedKey: []byte(psk),
PreSharedKeyIdentity: pskIdentity,
},
certFile: certFile,
keyFile: keyFile,
flags: flags,
resumeSession: resumeSession,
})
}
}
}
}
func addBadECDSASignatureTests() {
for badR := BadValue(1); badR < NumBadValues; badR++ {
for badS := BadValue(1); badS < NumBadValues; badS++ {
testCases = append(testCases, testCase{
name: fmt.Sprintf("BadECDSA-%d-%d", badR, badS),
config: Config{
CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
Certificates: []Certificate{getECDSACertificate()},
Bugs: ProtocolBugs{
BadECDSAR: badR,
BadECDSAS: badS,
},
},
shouldFail: true,
expectedError: "SIGNATURE",
})
}
}
}
func addCBCPaddingTests() {
testCases = append(testCases, testCase{
name: "MaxCBCPadding",
config: Config{
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
Bugs: ProtocolBugs{
MaxPadding: true,
},
},
messageLen: 12, // 20 bytes of SHA-1 + 12 == 0 % block size
})
testCases = append(testCases, testCase{
name: "BadCBCPadding",
config: Config{
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
Bugs: ProtocolBugs{
PaddingFirstByteBad: true,
},
},
shouldFail: true,
expectedError: "DECRYPTION_FAILED_OR_BAD_RECORD_MAC",
})
// OpenSSL previously had an issue where the first byte of padding in
// 255 bytes of padding wasn't checked.
testCases = append(testCases, testCase{
name: "BadCBCPadding255",
config: Config{
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
Bugs: ProtocolBugs{
MaxPadding: true,
PaddingFirstByteBadIf255: true,
},
},
messageLen: 12, // 20 bytes of SHA-1 + 12 == 0 % block size
shouldFail: true,
expectedError: "DECRYPTION_FAILED_OR_BAD_RECORD_MAC",
})
}
func addCBCSplittingTests() {
testCases = append(testCases, testCase{
name: "CBCRecordSplitting",
config: Config{
MaxVersion: VersionTLS10,
MinVersion: VersionTLS10,
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
},
messageLen: -1, // read until EOF
flags: []string{
"-async",
"-write-different-record-sizes",
"-cbc-record-splitting",
},
})
testCases = append(testCases, testCase{
name: "CBCRecordSplittingPartialWrite",
config: Config{
MaxVersion: VersionTLS10,
MinVersion: VersionTLS10,
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
},
messageLen: -1, // read until EOF
flags: []string{
"-async",
"-write-different-record-sizes",
"-cbc-record-splitting",
"-partial-write",
},
})
}
func addClientAuthTests() {
// Add a dummy cert pool to stress certificate authority parsing.
// TODO(davidben): Add tests that those values parse out correctly.
certPool := x509.NewCertPool()
cert, err := x509.ParseCertificate(rsaCertificate.Certificate[0])
if err != nil {
panic(err)
}
certPool.AddCert(cert)
for _, ver := range tlsVersions {
testCases = append(testCases, testCase{
testType: clientTest,
name: ver.name + "-Client-ClientAuth-RSA",
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
ClientAuth: RequireAnyClientCert,
ClientCAs: certPool,
},
flags: []string{
"-cert-file", rsaCertificateFile,
"-key-file", rsaKeyFile,
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: ver.name + "-Server-ClientAuth-RSA",
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Certificates: []Certificate{rsaCertificate},
},
flags: []string{"-require-any-client-certificate"},
})
if ver.version != VersionSSL30 {
testCases = append(testCases, testCase{
testType: serverTest,
name: ver.name + "-Server-ClientAuth-ECDSA",
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Certificates: []Certificate{ecdsaCertificate},
},
flags: []string{"-require-any-client-certificate"},
})
testCases = append(testCases, testCase{
testType: clientTest,
name: ver.name + "-Client-ClientAuth-ECDSA",
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
ClientAuth: RequireAnyClientCert,
ClientCAs: certPool,
},
flags: []string{
"-cert-file", ecdsaCertificateFile,
"-key-file", ecdsaKeyFile,
},
})
}
}
}
func addExtendedMasterSecretTests() {
const expectEMSFlag = "-expect-extended-master-secret"
for _, with := range []bool{false, true} {
prefix := "No"
var flags []string
if with {
prefix = ""
flags = []string{expectEMSFlag}
}
for _, isClient := range []bool{false, true} {
suffix := "-Server"
testType := serverTest
if isClient {
suffix = "-Client"
testType = clientTest
}
for _, ver := range tlsVersions {
test := testCase{
testType: testType,
name: prefix + "ExtendedMasterSecret-" + ver.name + suffix,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Bugs: ProtocolBugs{
NoExtendedMasterSecret: !with,
RequireExtendedMasterSecret: with,
},
},
flags: flags,
shouldFail: ver.version == VersionSSL30 && with,
}
if test.shouldFail {
test.expectedLocalError = "extended master secret required but not supported by peer"
}
testCases = append(testCases, test)
}
}
}
// When a session is resumed, it should still be aware that its master
// secret was generated via EMS and thus it's safe to use tls-unique.
testCases = append(testCases, testCase{
name: "ExtendedMasterSecret-Resume",
config: Config{
Bugs: ProtocolBugs{
RequireExtendedMasterSecret: true,
},
},
flags: []string{expectEMSFlag},
resumeSession: true,
})
}
// Adds tests that try to cover the range of the handshake state machine, under
// various conditions. Some of these are redundant with other tests, but they
// only cover the synchronous case.
func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol) {
var suffix string
var flags []string
var maxHandshakeRecordLength int
if protocol == dtls {
suffix = "-DTLS"
}
if async {
suffix += "-Async"
flags = append(flags, "-async")
} else {
suffix += "-Sync"
}
if splitHandshake {
suffix += "-SplitHandshakeRecords"
maxHandshakeRecordLength = 1
}
// Basic handshake, with resumption. Client and server.
testCases = append(testCases, testCase{
protocol: protocol,
name: "Basic-Client" + suffix,
config: Config{
Bugs: ProtocolBugs{
MaxHandshakeRecordLength: maxHandshakeRecordLength,
},
},
flags: flags,
resumeSession: true,
})
testCases = append(testCases, testCase{
protocol: protocol,
name: "Basic-Client-RenewTicket" + suffix,
config: Config{
Bugs: ProtocolBugs{
MaxHandshakeRecordLength: maxHandshakeRecordLength,
RenewTicketOnResume: true,
},
},
flags: flags,
resumeSession: true,
})
testCases = append(testCases, testCase{
protocol: protocol,
testType: serverTest,
name: "Basic-Server" + suffix,
config: Config{
Bugs: ProtocolBugs{
MaxHandshakeRecordLength: maxHandshakeRecordLength,
},
},
flags: flags,
resumeSession: true,
})
// TLS client auth.
testCases = append(testCases, testCase{
protocol: protocol,
testType: clientTest,
name: "ClientAuth-Client" + suffix,
config: Config{
ClientAuth: RequireAnyClientCert,
Bugs: ProtocolBugs{
MaxHandshakeRecordLength: maxHandshakeRecordLength,
},
},
flags: append(flags,
"-cert-file", rsaCertificateFile,
"-key-file", rsaKeyFile),
})
testCases = append(testCases, testCase{
protocol: protocol,
testType: serverTest,
name: "ClientAuth-Server" + suffix,
config: Config{
Certificates: []Certificate{rsaCertificate},
},
flags: append(flags, "-require-any-client-certificate"),
})
// No session ticket support; server doesn't send NewSessionTicket.
testCases = append(testCases, testCase{
protocol: protocol,
name: "SessionTicketsDisabled-Client" + suffix,
config: Config{
SessionTicketsDisabled: true,
Bugs: ProtocolBugs{
MaxHandshakeRecordLength: maxHandshakeRecordLength,
},
},
flags: flags,
})
testCases = append(testCases, testCase{
protocol: protocol,
testType: serverTest,
name: "SessionTicketsDisabled-Server" + suffix,
config: Config{
SessionTicketsDisabled: true,
Bugs: ProtocolBugs{
MaxHandshakeRecordLength: maxHandshakeRecordLength,
},
},
flags: flags,
})
// Skip ServerKeyExchange in PSK key exchange if there's no
// identity hint.
testCases = append(testCases, testCase{
protocol: protocol,
name: "EmptyPSKHint-Client" + suffix,
config: Config{
CipherSuites: []uint16{TLS_PSK_WITH_AES_128_CBC_SHA},
PreSharedKey: []byte("secret"),
Bugs: ProtocolBugs{
MaxHandshakeRecordLength: maxHandshakeRecordLength,
},
},
flags: append(flags, "-psk", "secret"),
})
testCases = append(testCases, testCase{
protocol: protocol,
testType: serverTest,
name: "EmptyPSKHint-Server" + suffix,
config: Config{
CipherSuites: []uint16{TLS_PSK_WITH_AES_128_CBC_SHA},
PreSharedKey: []byte("secret"),
Bugs: ProtocolBugs{
MaxHandshakeRecordLength: maxHandshakeRecordLength,
},
},
flags: append(flags, "-psk", "secret"),
})
if protocol == tls {
// NPN on client and server; results in post-handshake message.
testCases = append(testCases, testCase{
protocol: protocol,
name: "NPN-Client" + suffix,
config: Config{
NextProtos: []string{"foo"},
Bugs: ProtocolBugs{
MaxHandshakeRecordLength: maxHandshakeRecordLength,
},
},
flags: append(flags, "-select-next-proto", "foo"),
expectedNextProto: "foo",
expectedNextProtoType: npn,
})
testCases = append(testCases, testCase{
protocol: protocol,
testType: serverTest,
name: "NPN-Server" + suffix,
config: Config{
NextProtos: []string{"bar"},
Bugs: ProtocolBugs{
MaxHandshakeRecordLength: maxHandshakeRecordLength,
},
},
flags: append(flags,
"-advertise-npn", "\x03foo\x03bar\x03baz",
"-expect-next-proto", "bar"),
expectedNextProto: "bar",
expectedNextProtoType: npn,
})
// Client does False Start and negotiates NPN.
testCases = append(testCases, testCase{
protocol: protocol,
name: "FalseStart" + suffix,
config: Config{
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
NextProtos: []string{"foo"},
Bugs: ProtocolBugs{
ExpectFalseStart: true,
MaxHandshakeRecordLength: maxHandshakeRecordLength,
},
},
flags: append(flags,
"-false-start",
"-select-next-proto", "foo"),
shimWritesFirst: true,
resumeSession: true,
})
// Client does False Start and negotiates ALPN.
testCases = append(testCases, testCase{
protocol: protocol,
name: "FalseStart-ALPN" + suffix,
config: Config{
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
NextProtos: []string{"foo"},
Bugs: ProtocolBugs{
ExpectFalseStart: true,
MaxHandshakeRecordLength: maxHandshakeRecordLength,
},
},
flags: append(flags,
"-false-start",
"-advertise-alpn", "\x03foo"),
shimWritesFirst: true,
resumeSession: true,
})
// False Start without session tickets.
testCases = append(testCases, testCase{
name: "FalseStart-SessionTicketsDisabled",
config: Config{
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
NextProtos: []string{"foo"},
SessionTicketsDisabled: true,
Bugs: ProtocolBugs{
ExpectFalseStart: true,
MaxHandshakeRecordLength: maxHandshakeRecordLength,
},
},
flags: append(flags,
"-false-start",
"-select-next-proto", "foo",
),
shimWritesFirst: true,
})
// Server parses a V2ClientHello.
testCases = append(testCases, testCase{
protocol: protocol,
testType: serverTest,
name: "SendV2ClientHello" + suffix,
config: Config{
// Choose a cipher suite that does not involve
// elliptic curves, so no extensions are
// involved.
CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA},
Bugs: ProtocolBugs{
MaxHandshakeRecordLength: maxHandshakeRecordLength,
SendV2ClientHello: true,
},
},
flags: flags,
})
// Client sends a Channel ID.
testCases = append(testCases, testCase{
protocol: protocol,
name: "ChannelID-Client" + suffix,
config: Config{
RequestChannelID: true,
Bugs: ProtocolBugs{
MaxHandshakeRecordLength: maxHandshakeRecordLength,
},
},
flags: append(flags,
"-send-channel-id", channelIDKeyFile,
),
resumeSession: true,
expectChannelID: true,
})
// Server accepts a Channel ID.
testCases = append(testCases, testCase{
protocol: protocol,
testType: serverTest,
name: "ChannelID-Server" + suffix,
config: Config{
ChannelID: channelIDKey,
Bugs: ProtocolBugs{
MaxHandshakeRecordLength: maxHandshakeRecordLength,
},
},
flags: append(flags,
"-expect-channel-id",
base64.StdEncoding.EncodeToString(channelIDBytes),
),
resumeSession: true,
expectChannelID: true,
})
} else {
testCases = append(testCases, testCase{
protocol: protocol,
name: "SkipHelloVerifyRequest" + suffix,
config: Config{
Bugs: ProtocolBugs{
MaxHandshakeRecordLength: maxHandshakeRecordLength,
SkipHelloVerifyRequest: true,
},
},
flags: flags,
})
testCases = append(testCases, testCase{
testType: serverTest,
protocol: protocol,
name: "CookieExchange" + suffix,
config: Config{
Bugs: ProtocolBugs{
MaxHandshakeRecordLength: maxHandshakeRecordLength,
},
},
flags: append(flags, "-cookie-exchange"),
})
}
}
func addVersionNegotiationTests() {
for i, shimVers := range tlsVersions {
// Assemble flags to disable all newer versions on the shim.
var flags []string
for _, vers := range tlsVersions[i+1:] {
flags = append(flags, vers.flag)
}
for _, runnerVers := range tlsVersions {
expectedVersion := shimVers.version
if runnerVers.version < shimVers.version {
expectedVersion = runnerVers.version
}
suffix := shimVers.name + "-" + runnerVers.name
testCases = append(testCases, testCase{
testType: clientTest,
name: "VersionNegotiation-Client-" + suffix,
config: Config{
MaxVersion: runnerVers.version,
},
flags: flags,
expectedVersion: expectedVersion,
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "VersionNegotiation-Server-" + suffix,
config: Config{
MaxVersion: runnerVers.version,
},
flags: flags,
expectedVersion: expectedVersion,
})
}
}
}
func addD5BugTests() {
testCases = append(testCases, testCase{
testType: serverTest,
name: "D5Bug-NoQuirk-Reject",
config: Config{
CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
Bugs: ProtocolBugs{
SSL3RSAKeyExchange: true,
},
},
shouldFail: true,
expectedError: ":TLS_RSA_ENCRYPTED_VALUE_LENGTH_IS_WRONG:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "D5Bug-Quirk-Normal",
config: Config{
CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
},
flags: []string{"-tls-d5-bug"},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "D5Bug-Quirk-Bug",
config: Config{
CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
Bugs: ProtocolBugs{
SSL3RSAKeyExchange: true,
},
},
flags: []string{"-tls-d5-bug"},
})
}
func addExtensionTests() {
testCases = append(testCases, testCase{
testType: clientTest,
name: "DuplicateExtensionClient",
config: Config{
Bugs: ProtocolBugs{
DuplicateExtension: true,
},
},
shouldFail: true,
expectedLocalError: "remote error: error decoding message",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "DuplicateExtensionServer",
config: Config{
Bugs: ProtocolBugs{
DuplicateExtension: true,
},
},
shouldFail: true,
expectedLocalError: "remote error: error decoding message",
})
testCases = append(testCases, testCase{
testType: clientTest,
name: "ServerNameExtensionClient",
config: Config{
Bugs: ProtocolBugs{
ExpectServerName: "example.com",
},
},
flags: []string{"-host-name", "example.com"},
})
testCases = append(testCases, testCase{
testType: clientTest,
name: "ServerNameExtensionClient",
config: Config{
Bugs: ProtocolBugs{
ExpectServerName: "mismatch.com",
},
},
flags: []string{"-host-name", "example.com"},
shouldFail: true,
expectedLocalError: "tls: unexpected server name",
})
testCases = append(testCases, testCase{
testType: clientTest,
name: "ServerNameExtensionClient",
config: Config{
Bugs: ProtocolBugs{
ExpectServerName: "missing.com",
},
},
shouldFail: true,
expectedLocalError: "tls: unexpected server name",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "ServerNameExtensionServer",
config: Config{
ServerName: "example.com",
},
flags: []string{"-expect-server-name", "example.com"},
resumeSession: true,
})
testCases = append(testCases, testCase{
testType: clientTest,
name: "ALPNClient",
config: Config{
NextProtos: []string{"foo"},
},
flags: []string{
"-advertise-alpn", "\x03foo\x03bar\x03baz",
"-expect-alpn", "foo",
},
expectedNextProto: "foo",
expectedNextProtoType: alpn,
resumeSession: true,
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "ALPNServer",
config: Config{
NextProtos: []string{"foo", "bar", "baz"},
},
flags: []string{
"-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
"-select-alpn", "foo",
},
expectedNextProto: "foo",
expectedNextProtoType: alpn,
resumeSession: true,
})
// Test that the server prefers ALPN over NPN.
testCases = append(testCases, testCase{
testType: serverTest,
name: "ALPNServer-Preferred",
config: Config{
NextProtos: []string{"foo", "bar", "baz"},
},
flags: []string{
"-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
"-select-alpn", "foo",
"-advertise-npn", "\x03foo\x03bar\x03baz",
},
expectedNextProto: "foo",
expectedNextProtoType: alpn,
resumeSession: true,
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "ALPNServer-Preferred-Swapped",
config: Config{
NextProtos: []string{"foo", "bar", "baz"},
Bugs: ProtocolBugs{
SwapNPNAndALPN: true,
},
},
flags: []string{
"-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
"-select-alpn", "foo",
"-advertise-npn", "\x03foo\x03bar\x03baz",
},
expectedNextProto: "foo",
expectedNextProtoType: alpn,
resumeSession: true,
})
// Resume with a corrupt ticket.
testCases = append(testCases, testCase{
testType: serverTest,
name: "CorruptTicket",
config: Config{
Bugs: ProtocolBugs{
CorruptTicket: true,
},
},
resumeSession: true,
flags: []string{"-expect-session-miss"},
})
// Resume with an oversized session id.
testCases = append(testCases, testCase{
testType: serverTest,
name: "OversizedSessionId",
config: Config{
Bugs: ProtocolBugs{
OversizedSessionId: true,
},
},
resumeSession: true,
shouldFail: true,
expectedError: ":DECODE_ERROR:",
})
// Basic DTLS-SRTP tests. Include fake profiles to ensure they
// are ignored.
testCases = append(testCases, testCase{
protocol: dtls,
name: "SRTP-Client",
config: Config{
SRTPProtectionProfiles: []uint16{40, SRTP_AES128_CM_HMAC_SHA1_80, 42},
},
flags: []string{
"-srtp-profiles",
"SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32",
},
expectedSRTPProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_80,
})
testCases = append(testCases, testCase{
protocol: dtls,
testType: serverTest,
name: "SRTP-Server",
config: Config{
SRTPProtectionProfiles: []uint16{40, SRTP_AES128_CM_HMAC_SHA1_80, 42},
},
flags: []string{
"-srtp-profiles",
"SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32",
},
expectedSRTPProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_80,
})
// Test that the MKI is ignored.
testCases = append(testCases, testCase{
protocol: dtls,
testType: serverTest,
name: "SRTP-Server-IgnoreMKI",
config: Config{
SRTPProtectionProfiles: []uint16{SRTP_AES128_CM_HMAC_SHA1_80},
Bugs: ProtocolBugs{
SRTPMasterKeyIdentifer: "bogus",
},
},
flags: []string{
"-srtp-profiles",
"SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32",
},
expectedSRTPProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_80,
})
// Test that SRTP isn't negotiated on the server if there were
// no matching profiles.
testCases = append(testCases, testCase{
protocol: dtls,
testType: serverTest,
name: "SRTP-Server-NoMatch",
config: Config{
SRTPProtectionProfiles: []uint16{100, 101, 102},
},
flags: []string{
"-srtp-profiles",
"SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32",
},
expectedSRTPProtectionProfile: 0,
})
// Test that the server returning an invalid SRTP profile is
// flagged as an error by the client.
testCases = append(testCases, testCase{
protocol: dtls,
name: "SRTP-Client-NoMatch",
config: Config{
Bugs: ProtocolBugs{
SendSRTPProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_32,
},
},
flags: []string{
"-srtp-profiles",
"SRTP_AES128_CM_SHA1_80",
},
shouldFail: true,
expectedError: ":BAD_SRTP_PROTECTION_PROFILE_LIST:",
})
}
func addResumptionVersionTests() {
// TODO(davidben): Once DTLS 1.2 is working, test that as well.
for _, sessionVers := range tlsVersions {
// TODO(davidben): SSLv3 is omitted here because runner does not
// support resumption with session IDs.
if sessionVers.version == VersionSSL30 {
continue
}
for _, resumeVers := range tlsVersions {
if resumeVers.version == VersionSSL30 {
continue
}
suffix := "-" + sessionVers.name + "-" + resumeVers.name
testCases = append(testCases, testCase{
name: "Resume-Client" + suffix,
resumeSession: true,
config: Config{
MaxVersion: sessionVers.version,
CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA},
Bugs: ProtocolBugs{
AllowSessionVersionMismatch: true,
},
},
expectedVersion: sessionVers.version,
resumeConfig: &Config{
MaxVersion: resumeVers.version,
CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA},
Bugs: ProtocolBugs{
AllowSessionVersionMismatch: true,
},
},
expectedResumeVersion: resumeVers.version,
})
testCases = append(testCases, testCase{
name: "Resume-Client-NoResume" + suffix,
flags: []string{"-expect-session-miss"},
resumeSession: true,
config: Config{
MaxVersion: sessionVers.version,
CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA},
},
expectedVersion: sessionVers.version,
resumeConfig: &Config{
MaxVersion: resumeVers.version,
CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA},
SessionTicketsDisabled: true,
},
expectedResumeVersion: resumeVers.version,
})
var flags []string
if sessionVers.version != resumeVers.version {
flags = append(flags, "-expect-session-miss")
}
testCases = append(testCases, testCase{
testType: serverTest,
name: "Resume-Server" + suffix,
flags: flags,
resumeSession: true,
config: Config{
MaxVersion: sessionVers.version,
CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA},
},
expectedVersion: sessionVers.version,
resumeConfig: &Config{
MaxVersion: resumeVers.version,
CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA},
},
expectedResumeVersion: resumeVers.version,
})
}
}
}
func addRenegotiationTests() {
testCases = append(testCases, testCase{
testType: serverTest,
name: "Renegotiate-Server",
flags: []string{"-renegotiate"},
shimWritesFirst: true,
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "Renegotiate-Server-EmptyExt",
config: Config{
Bugs: ProtocolBugs{
EmptyRenegotiationInfo: true,
},
},
flags: []string{"-renegotiate"},
shimWritesFirst: true,
shouldFail: true,
expectedError: ":RENEGOTIATION_MISMATCH:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "Renegotiate-Server-BadExt",
config: Config{
Bugs: ProtocolBugs{
BadRenegotiationInfo: true,
},
},
flags: []string{"-renegotiate"},
shimWritesFirst: true,
shouldFail: true,
expectedError: ":RENEGOTIATION_MISMATCH:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "Renegotiate-Server-ClientInitiated",
renegotiate: true,
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "Renegotiate-Server-ClientInitiated-NoExt",
renegotiate: true,
config: Config{
Bugs: ProtocolBugs{
NoRenegotiationInfo: true,
},
},
shouldFail: true,
expectedError: ":UNSAFE_LEGACY_RENEGOTIATION_DISABLED:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "Renegotiate-Server-ClientInitiated-NoExt-Allowed",
renegotiate: true,
config: Config{
Bugs: ProtocolBugs{
NoRenegotiationInfo: true,
},
},
flags: []string{"-allow-unsafe-legacy-renegotiation"},
})
// TODO(agl): test the renegotiation info SCSV.
testCases = append(testCases, testCase{
name: "Renegotiate-Client",
renegotiate: true,
})
testCases = append(testCases, testCase{
name: "Renegotiate-Client-EmptyExt",
renegotiate: true,
config: Config{
Bugs: ProtocolBugs{
EmptyRenegotiationInfo: true,
},
},
shouldFail: true,
expectedError: ":RENEGOTIATION_MISMATCH:",
})
testCases = append(testCases, testCase{
name: "Renegotiate-Client-BadExt",
renegotiate: true,
config: Config{
Bugs: ProtocolBugs{
BadRenegotiationInfo: true,
},
},
shouldFail: true,
expectedError: ":RENEGOTIATION_MISMATCH:",
})
testCases = append(testCases, testCase{
name: "Renegotiate-Client-SwitchCiphers",
renegotiate: true,
config: Config{
CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA},
},
renegotiateCiphers: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
})
testCases = append(testCases, testCase{
name: "Renegotiate-Client-SwitchCiphers2",
renegotiate: true,
config: Config{
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
},
renegotiateCiphers: []uint16{TLS_RSA_WITH_RC4_128_SHA},
})
}
func addDTLSReplayTests() {
// Test that sequence number replays are detected.
testCases = append(testCases, testCase{
protocol: dtls,
name: "DTLS-Replay",
replayWrites: true,
})
// Test the outgoing sequence number skipping by values larger
// than the retransmit window.
testCases = append(testCases, testCase{
protocol: dtls,
name: "DTLS-Replay-LargeGaps",
config: Config{
Bugs: ProtocolBugs{
SequenceNumberIncrement: 127,
},
},
replayWrites: true,
})
}
var testHashes = []struct {
name string
id uint8
}{
{"SHA1", hashSHA1},
{"SHA224", hashSHA224},
{"SHA256", hashSHA256},
{"SHA384", hashSHA384},
{"SHA512", hashSHA512},
}
func addSigningHashTests() {
// Make sure each hash works. Include some fake hashes in the list and
// ensure they're ignored.
for _, hash := range testHashes {
testCases = append(testCases, testCase{
name: "SigningHash-ClientAuth-" + hash.name,
config: Config{
ClientAuth: RequireAnyClientCert,
SignatureAndHashes: []signatureAndHash{
{signatureRSA, 42},
{signatureRSA, hash.id},
{signatureRSA, 255},
},
},
flags: []string{
"-cert-file", rsaCertificateFile,
"-key-file", rsaKeyFile,
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "SigningHash-ServerKeyExchange-Sign-" + hash.name,
config: Config{
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
SignatureAndHashes: []signatureAndHash{
{signatureRSA, 42},
{signatureRSA, hash.id},
{signatureRSA, 255},
},
},
})
}
// Test that hash resolution takes the signature type into account.
testCases = append(testCases, testCase{
name: "SigningHash-ClientAuth-SignatureType",
config: Config{
ClientAuth: RequireAnyClientCert,
SignatureAndHashes: []signatureAndHash{
{signatureECDSA, hashSHA512},
{signatureRSA, hashSHA384},
{signatureECDSA, hashSHA1},
},
},
flags: []string{
"-cert-file", rsaCertificateFile,
"-key-file", rsaKeyFile,
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "SigningHash-ServerKeyExchange-SignatureType",
config: Config{
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
SignatureAndHashes: []signatureAndHash{
{signatureECDSA, hashSHA512},
{signatureRSA, hashSHA384},
{signatureECDSA, hashSHA1},
},
},
})
// Test that, if the list is missing, the peer falls back to SHA-1.
testCases = append(testCases, testCase{
name: "SigningHash-ClientAuth-Fallback",
config: Config{
ClientAuth: RequireAnyClientCert,
SignatureAndHashes: []signatureAndHash{
{signatureRSA, hashSHA1},
},
Bugs: ProtocolBugs{
NoSignatureAndHashes: true,
},
},
flags: []string{
"-cert-file", rsaCertificateFile,
"-key-file", rsaKeyFile,
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "SigningHash-ServerKeyExchange-Fallback",
config: Config{
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
SignatureAndHashes: []signatureAndHash{
{signatureRSA, hashSHA1},
},
Bugs: ProtocolBugs{
NoSignatureAndHashes: true,
},
},
})
}
func worker(statusChan chan statusMsg, c chan *testCase, buildDir string, wg *sync.WaitGroup) {
defer wg.Done()
for test := range c {
var err error
if *mallocTest < 0 {
statusChan <- statusMsg{test: test, started: true}
err = runTest(test, buildDir, -1)
} else {
for mallocNumToFail := int64(*mallocTest); ; mallocNumToFail++ {
statusChan <- statusMsg{test: test, started: true}
if err = runTest(test, buildDir, mallocNumToFail); err != errMoreMallocs {
if err != nil {
fmt.Printf("\n\nmalloc test failed at %d: %s\n", mallocNumToFail, err)
}
break
}
}
}
statusChan <- statusMsg{test: test, err: err}
}
}
type statusMsg struct {
test *testCase
started bool
err error
}
func statusPrinter(doneChan chan struct{}, statusChan chan statusMsg, total int) {
var started, done, failed, lineLen int
defer close(doneChan)
for msg := range statusChan {
if msg.started {
started++
} else {
done++
}
fmt.Printf("\x1b[%dD\x1b[K", lineLen)
if msg.err != nil {
fmt.Printf("FAILED (%s)\n%s\n", msg.test.name, msg.err)
failed++
}
line := fmt.Sprintf("%d/%d/%d/%d", failed, done, started, total)
lineLen = len(line)
os.Stdout.WriteString(line)
}
}
func main() {
var flagTest *string = flag.String("test", "", "The name of a test to run, or empty to run all tests")
var flagNumWorkers *int = flag.Int("num-workers", runtime.NumCPU(), "The number of workers to run in parallel.")
var flagBuildDir *string = flag.String("build-dir", "../../../build", "The build directory to run the shim from.")
flag.Parse()
addCipherSuiteTests()
addBadECDSASignatureTests()
addCBCPaddingTests()
addCBCSplittingTests()
addClientAuthTests()
addVersionNegotiationTests()
addD5BugTests()
addExtensionTests()
addResumptionVersionTests()
addExtendedMasterSecretTests()
addRenegotiationTests()
addDTLSReplayTests()
addSigningHashTests()
for _, async := range []bool{false, true} {
for _, splitHandshake := range []bool{false, true} {
for _, protocol := range []protocol{tls, dtls} {
addStateMachineCoverageTests(async, splitHandshake, protocol)
}
}
}
var wg sync.WaitGroup
numWorkers := *flagNumWorkers
statusChan := make(chan statusMsg, numWorkers)
testChan := make(chan *testCase, numWorkers)
doneChan := make(chan struct{})
go statusPrinter(doneChan, statusChan, len(testCases))
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go worker(statusChan, testChan, *flagBuildDir, &wg)
}
for i := range testCases {
if len(*flagTest) == 0 || *flagTest == testCases[i].name {
testChan <- &testCases[i]
}
}
close(testChan)
wg.Wait()
close(statusChan)
<-doneChan
fmt.Printf("\n")
}