boringssl/ssl/test/runner/runner.go
David Benjamin 61f95277d4 Add tests for OCSP stapling and SCT lists.
We forgot to add those when we implemented the features. (Also relevant because
they will provide test coverage later for configuring features when using the
generic method tables rather than *_client_method.)

Change-Id: Ie08b27de893095e01a05a7084775676616459807
Reviewed-on: https://boringssl-review.googlesource.com/2410
Reviewed-by: Adam Langley <agl@google.com>
2014-12-02 19:26:01 +00:00

2320 lines
60 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
var testOCSPResponse = []byte{1, 2, 3, 4}
var testSCTList = []byte{5, 6, 7, 8}
func initCertificates() {
var err error
rsaCertificate, err = LoadX509KeyPair(rsaCertificateFile, rsaKeyFile)
if err != nil {
panic(err)
}
rsaCertificate.OCSPStaple = testOCSPResponse
rsaCertificate.SignedCertificateTimestampList = testSCTList
ecdsaCertificate, err = LoadX509KeyPair(ecdsaCertificateFile, ecdsaKeyFile)
if err != nil {
panic(err)
}
ecdsaCertificate.OCSPStaple = testOCSPResponse
ecdsaCertificate.SignedCertificateTimestampList = testSCTList
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. Unless newSessionsOnResume is set,
// SessionTicketKey, ServerSessionCache, and
// ClientSessionCache are copied from the initial connection's
// config. If nil, the initial connection's config is used.
resumeConfig *Config
// newSessionsOnResume, if true, will cause resumeConfig to
// use a different session resumption context.
newSessionsOnResume bool
// 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)
config.ServerSessionCache = NewLRUServerSessionCache(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()}
}
if !test.newSessionsOnResume {
resumeConfig.SessionTicketKey = config.SessionTicketKey
resumeConfig.ClientSessionCache = config.ClientSessionCache
resumeConfig.ServerSessionCache = config.ServerSessionCache
}
} 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
}
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: true,
})
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: true,
})
// 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: true,
})
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: true,
})
}
}
}
}
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,
// session ID and session ticket.
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,
name: "Basic-Client-NoTicket" + suffix,
config: Config{
SessionTicketsDisabled: true,
Bugs: ProtocolBugs{
MaxHandshakeRecordLength: maxHandshakeRecordLength,
},
},
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,
})
testCases = append(testCases, testCase{
protocol: protocol,
testType: serverTest,
name: "Basic-Server-NoTickets" + suffix,
config: Config{
SessionTicketsDisabled: true,
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:",
})
// Test OCSP stapling and SCT list.
testCases = append(testCases, testCase{
name: "OCSPStapling",
flags: []string{
"-enable-ocsp-stapling",
"-expect-ocsp-response",
base64.StdEncoding.EncodeToString(testOCSPResponse),
},
})
testCases = append(testCases, testCase{
name: "SignedCertificateTimestampList",
flags: []string{
"-enable-signed-cert-timestamps",
"-expect-signed-cert-timestamps",
base64.StdEncoding.EncodeToString(testSCTList),
},
})
}
func addResumptionVersionTests() {
// TODO(davidben): Once DTLS 1.2 is working, test that as well.
for _, sessionVers := range tlsVersions {
for _, resumeVers := range tlsVersions {
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},
},
newSessionsOnResume: 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")
}