summaryrefslogtreecommitdiff
path: root/ssl/test
diff options
context:
space:
mode:
Diffstat (limited to 'ssl/test')
-rw-r--r--ssl/test/bssl_shim.cc114
-rw-r--r--ssl/test/runner/channel_id_key.pem5
-rw-r--r--ssl/test/runner/cipher_suites.go46
-rw-r--r--ssl/test/runner/common.go84
-rw-r--r--ssl/test/runner/conn.go10
-rw-r--r--ssl/test/runner/handshake_client.go116
-rw-r--r--ssl/test/runner/handshake_messages.go195
-rw-r--r--ssl/test/runner/handshake_server.go119
-rw-r--r--ssl/test/runner/key_agreement.go2
-rw-r--r--ssl/test/runner/prf.go66
-rw-r--r--ssl/test/runner/runner.go587
-rw-r--r--ssl/test/runner/ticket.go33
-rw-r--r--ssl/test/test_config.cc54
-rw-r--r--ssl/test/test_config.h10
14 files changed, 1246 insertions, 195 deletions
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index c976e7c..5ee7c65 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -48,6 +48,20 @@ static const TestConfig *GetConfigPtr(SSL *ssl) {
return (const TestConfig *)SSL_get_ex_data(ssl, g_ex_data_index);
}
+static EVP_PKEY *LoadPrivateKey(const std::string &file) {
+ BIO *bio = BIO_new(BIO_s_file());
+ if (bio == NULL) {
+ return NULL;
+ }
+ if (!BIO_read_filename(bio, file.c_str())) {
+ BIO_free(bio);
+ return NULL;
+ }
+ EVP_PKEY *pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
+ BIO_free(bio);
+ return pkey;
+}
+
static int early_callback_called = 0;
static int select_certificate_callback(const struct ssl_early_callback_ctx *ctx) {
@@ -125,6 +139,29 @@ static int next_proto_select_callback(SSL* ssl,
return SSL_TLSEXT_ERR_OK;
}
+static int alpn_select_callback(SSL* ssl,
+ const uint8_t** out,
+ uint8_t* outlen,
+ const uint8_t* in,
+ unsigned inlen,
+ void* arg) {
+ const TestConfig *config = GetConfigPtr(ssl);
+ if (config->select_alpn.empty())
+ return SSL_TLSEXT_ERR_NOACK;
+
+ if (!config->expected_advertised_alpn.empty() &&
+ (config->expected_advertised_alpn.size() != inlen ||
+ memcmp(config->expected_advertised_alpn.data(),
+ in, inlen) != 0)) {
+ fprintf(stderr, "bad ALPN select callback inputs\n");
+ exit(1);
+ }
+
+ *out = (const uint8_t*)config->select_alpn.data();
+ *outlen = config->select_alpn.size();
+ return SSL_TLSEXT_ERR_OK;
+}
+
static int cookie_generate_callback(SSL *ssl, uint8_t *cookie, size_t *cookie_len) {
*cookie_len = 32;
memset(cookie, 42, *cookie_len);
@@ -199,12 +236,19 @@ static SSL_CTX *setup_ctx(const TestConfig *config) {
SSL_CTX_set_next_protos_advertised_cb(
ssl_ctx, next_protos_advertised_callback, NULL);
- SSL_CTX_set_next_proto_select_cb(
- ssl_ctx, next_proto_select_callback, NULL);
+ if (!config->select_next_proto.empty()) {
+ SSL_CTX_set_next_proto_select_cb(ssl_ctx, next_proto_select_callback, NULL);
+ }
+
+ if (!config->select_alpn.empty()) {
+ SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_callback, NULL);
+ }
SSL_CTX_set_cookie_generate_cb(ssl_ctx, cookie_generate_callback);
SSL_CTX_set_cookie_verify_cb(ssl_ctx, cookie_verify_callback);
+ ssl_ctx->tlsext_channel_id_enabled_new = 1;
+
DH_free(dh);
return ssl_ctx;
@@ -300,6 +344,33 @@ static int do_exchange(SSL_SESSION **out_session,
if (config->cookie_exchange) {
SSL_set_options(ssl, SSL_OP_COOKIE_EXCHANGE);
}
+ if (config->tls_d5_bug) {
+ SSL_set_options(ssl, SSL_OP_TLS_D5_BUG);
+ }
+ if (!config->expected_channel_id.empty()) {
+ SSL_enable_tls_channel_id(ssl);
+ }
+ if (!config->send_channel_id.empty()) {
+ EVP_PKEY *pkey = LoadPrivateKey(config->send_channel_id);
+ if (pkey == NULL) {
+ BIO_print_errors_fp(stdout);
+ return 1;
+ }
+ SSL_enable_tls_channel_id(ssl);
+ if (!SSL_set1_tls_channel_id(ssl, pkey)) {
+ EVP_PKEY_free(pkey);
+ BIO_print_errors_fp(stdout);
+ return 1;
+ }
+ EVP_PKEY_free(pkey);
+ }
+ if (!config->host_name.empty()) {
+ SSL_set_tlsext_host_name(ssl, config->host_name.c_str());
+ }
+ if (!config->advertise_alpn.empty()) {
+ SSL_set_alpn_protos(ssl, (const uint8_t *)config->advertise_alpn.data(),
+ config->advertise_alpn.size());
+ }
BIO *bio = BIO_new_fd(fd, 1 /* take ownership */);
if (bio == NULL) {
@@ -340,8 +411,9 @@ static int do_exchange(SSL_SESSION **out_session,
return 2;
}
- if (is_resume && !SSL_session_reused(ssl)) {
- fprintf(stderr, "session was not reused\n");
+ if (is_resume && (SSL_session_reused(ssl) == config->expect_session_miss)) {
+ fprintf(stderr, "session was%s reused\n",
+ SSL_session_reused(ssl) ? "" : " not");
return 2;
}
@@ -363,7 +435,7 @@ static int do_exchange(SSL_SESSION **out_session,
if (!config->expected_certificate_types.empty()) {
uint8_t *certificate_types;
int num_certificate_types =
- SSL_get0_certificate_types(ssl, &certificate_types);
+ SSL_get0_certificate_types(ssl, &certificate_types);
if (num_certificate_types !=
(int)config->expected_certificate_types.size() ||
memcmp(certificate_types,
@@ -386,6 +458,32 @@ static int do_exchange(SSL_SESSION **out_session,
}
}
+ if (!config->expected_alpn.empty()) {
+ const uint8_t *alpn_proto;
+ unsigned alpn_proto_len;
+ SSL_get0_alpn_selected(ssl, &alpn_proto, &alpn_proto_len);
+ if (alpn_proto_len != config->expected_alpn.size() ||
+ memcmp(alpn_proto, config->expected_alpn.data(),
+ alpn_proto_len) != 0) {
+ fprintf(stderr, "negotiated alpn proto mismatch\n");
+ return 2;
+ }
+ }
+
+ if (!config->expected_channel_id.empty()) {
+ uint8_t channel_id[64];
+ if (!SSL_get_tls_channel_id(ssl, channel_id, sizeof(channel_id))) {
+ fprintf(stderr, "no channel id negotiated\n");
+ return 2;
+ }
+ if (config->expected_channel_id.size() != 64 ||
+ memcmp(config->expected_channel_id.data(),
+ channel_id, 64) != 0) {
+ fprintf(stderr, "channel id mismatch\n");
+ return 2;
+ }
+ }
+
if (config->write_different_record_sizes) {
if (config->is_dtls) {
fprintf(stderr, "write_different_record_sizes not supported for DTLS\n");
@@ -423,6 +521,12 @@ static int do_exchange(SSL_SESSION **out_session,
}
}
} else {
+ if (config->shim_writes_first) {
+ int w;
+ do {
+ w = SSL_write(ssl, "hello", 5);
+ } while (config->async && retry_async(ssl, w, bio));
+ }
for (;;) {
uint8_t buf[512];
int n;
diff --git a/ssl/test/runner/channel_id_key.pem b/ssl/test/runner/channel_id_key.pem
new file mode 100644
index 0000000..604752b
--- /dev/null
+++ b/ssl/test/runner/channel_id_key.pem
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIPwxu50c7LEhVNRYJFRWBUnoaz7JSos96T5hBp4rjyptoAoGCCqGSM49
+AwEHoUQDQgAEzFSVTE5guxJRQ0VbZ8dicPs5e/DT7xpW7Yc9hq0VOchv7cbXuI/T
+CwadDjGWX/oaz0ftFqrVmfkwZu+C58ioWg==
+-----END EC PRIVATE KEY-----
diff --git a/ssl/test/runner/cipher_suites.go b/ssl/test/runner/cipher_suites.go
index ed26f09..6cd0de9 100644
--- a/ssl/test/runner/cipher_suites.go
+++ b/ssl/test/runner/cipher_suites.go
@@ -12,6 +12,8 @@ import (
"crypto/md5"
"crypto/rc4"
"crypto/sha1"
+ "crypto/sha256"
+ "crypto/sha512"
"crypto/x509"
"hash"
)
@@ -79,20 +81,29 @@ var cipherSuites = []*cipherSuite{
{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, ecdheRSAKA, suiteECDHE | suiteTLS12, nil, nil, aeadAESGCM},
{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, ecdheECDSAKA, suiteECDHE | suiteECDSA | suiteTLS12, nil, nil, aeadAESGCM},
{TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 32, 0, 4, ecdheRSAKA, suiteECDHE | suiteTLS12 | suiteSHA384, nil, nil, aeadAESGCM},
+ {TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 32, 0, 4, ecdheECDSAKA, suiteECDHE | suiteECDSA | suiteTLS12 | suiteSHA384, nil, nil, aeadAESGCM},
{TLS_ECDHE_RSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheRSAKA, suiteECDHE | suiteNoDTLS, cipherRC4, macSHA1, nil},
{TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheECDSAKA, suiteECDHE | suiteECDSA | suiteNoDTLS, cipherRC4, macSHA1, nil},
+ {TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, 16, 32, 16, ecdheRSAKA, suiteECDHE | suiteTLS12, cipherAES, macSHA256, nil},
+ {TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, 16, 32, 16, ecdheECDSAKA, suiteECDHE | suiteECDSA | suiteTLS12, cipherAES, macSHA256, nil},
{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, ecdheRSAKA, suiteECDHE, cipherAES, macSHA1, nil},
{TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, 16, 20, 16, ecdheECDSAKA, suiteECDHE | suiteECDSA, cipherAES, macSHA1, nil},
+ {TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, 32, 48, 16, ecdheRSAKA, suiteECDHE | suiteTLS12 | suiteSHA384, cipherAES, macSHA384, nil},
+ {TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, 32, 48, 16, ecdheECDSAKA, suiteECDHE | suiteECDSA | suiteTLS12 | suiteSHA384, cipherAES, macSHA384, nil},
{TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, 32, 20, 16, ecdheRSAKA, suiteECDHE, cipherAES, macSHA1, nil},
{TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, 32, 20, 16, ecdheECDSAKA, suiteECDHE | suiteECDSA, cipherAES, macSHA1, nil},
{TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, dheRSAKA, suiteTLS12, nil, nil, aeadAESGCM},
{TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, 32, 0, 4, dheRSAKA, suiteTLS12 | suiteSHA384, nil, nil, aeadAESGCM},
+ {TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, 16, 32, 16, dheRSAKA, suiteTLS12, cipherAES, macSHA256, nil},
+ {TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, 32, 32, 16, dheRSAKA, suiteTLS12, cipherAES, macSHA256, nil},
{TLS_DHE_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, dheRSAKA, 0, cipherAES, macSHA1, nil},
{TLS_DHE_RSA_WITH_AES_256_CBC_SHA, 32, 20, 16, dheRSAKA, 0, cipherAES, macSHA1, nil},
{TLS_RSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, rsaKA, suiteTLS12, nil, nil, aeadAESGCM},
{TLS_RSA_WITH_AES_256_GCM_SHA384, 32, 0, 4, rsaKA, suiteTLS12 | suiteSHA384, nil, nil, aeadAESGCM},
{TLS_RSA_WITH_RC4_128_SHA, 16, 20, 0, rsaKA, suiteNoDTLS, cipherRC4, macSHA1, nil},
{TLS_RSA_WITH_RC4_128_MD5, 16, 16, 0, rsaKA, suiteNoDTLS, cipherRC4, macMD5, nil},
+ {TLS_RSA_WITH_AES_128_CBC_SHA256, 16, 32, 16, rsaKA, suiteTLS12, cipherAES, macSHA256, nil},
+ {TLS_RSA_WITH_AES_256_CBC_SHA256, 32, 32, 16, rsaKA, suiteTLS12, cipherAES, macSHA256, nil},
{TLS_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, rsaKA, 0, cipherAES, macSHA1, nil},
{TLS_RSA_WITH_AES_256_CBC_SHA, 32, 20, 16, rsaKA, 0, cipherAES, macSHA1, nil},
{TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, 24, 20, 8, ecdheRSAKA, suiteECDHE, cipher3DES, macSHA1, nil},
@@ -146,6 +157,30 @@ func macMD5(version uint16, key []byte) macFunction {
return tls10MAC{hmac.New(md5.New, key)}
}
+func macSHA256(version uint16, key []byte) macFunction {
+ if version == VersionSSL30 {
+ mac := ssl30MAC{
+ h: sha256.New(),
+ key: make([]byte, len(key)),
+ }
+ copy(mac.key, key)
+ return mac
+ }
+ return tls10MAC{hmac.New(sha256.New, key)}
+}
+
+func macSHA384(version uint16, key []byte) macFunction {
+ if version == VersionSSL30 {
+ mac := ssl30MAC{
+ h: sha512.New384(),
+ key: make([]byte, len(key)),
+ }
+ copy(mac.key, key)
+ return mac
+ }
+ return tls10MAC{hmac.New(sha512.New384, key)}
+}
+
type macFunction interface {
Size() int
MAC(digestBuf, seq, header, length, data []byte) []byte
@@ -304,6 +339,10 @@ const (
TLS_DHE_RSA_WITH_AES_128_CBC_SHA uint16 = 0x0033
TLS_RSA_WITH_AES_256_CBC_SHA uint16 = 0x0035
TLS_DHE_RSA_WITH_AES_256_CBC_SHA uint16 = 0x0039
+ TLS_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0x003c
+ TLS_RSA_WITH_AES_256_CBC_SHA256 uint16 = 0x003d
+ TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0x0067
+ TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 uint16 = 0x006b
TLS_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0x009c
TLS_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0x009d
TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0x009e
@@ -315,8 +354,13 @@ const (
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xc012
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA uint16 = 0xc013
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA uint16 = 0xc014
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0xc02f
+ TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 uint16 = 0xc023
+ TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 uint16 = 0xc024
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0xc027
+ TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 uint16 = 0xc028
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 uint16 = 0xc02b
+ TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 uint16 = 0xc02c
+ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0xc02f
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0xc030
fallbackSCSV uint16 = 0x5600
)
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index f14f4e9..cf244bc 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -7,6 +7,7 @@ package main
import (
"container/list"
"crypto"
+ "crypto/ecdsa"
"crypto/rand"
"crypto/x509"
"fmt"
@@ -47,19 +48,20 @@ const (
// TLS handshake message types.
const (
- typeClientHello uint8 = 1
- typeServerHello uint8 = 2
- typeHelloVerifyRequest uint8 = 3
- typeNewSessionTicket uint8 = 4
- typeCertificate uint8 = 11
- typeServerKeyExchange uint8 = 12
- typeCertificateRequest uint8 = 13
- typeServerHelloDone uint8 = 14
- typeCertificateVerify uint8 = 15
- typeClientKeyExchange uint8 = 16
- typeFinished uint8 = 20
- typeCertificateStatus uint8 = 22
- typeNextProtocol uint8 = 67 // Not IANA assigned
+ typeClientHello uint8 = 1
+ typeServerHello uint8 = 2
+ typeHelloVerifyRequest uint8 = 3
+ typeNewSessionTicket uint8 = 4
+ typeCertificate uint8 = 11
+ typeServerKeyExchange uint8 = 12
+ typeCertificateRequest uint8 = 13
+ typeServerHelloDone uint8 = 14
+ typeCertificateVerify uint8 = 15
+ typeClientKeyExchange uint8 = 16
+ typeFinished uint8 = 20
+ typeCertificateStatus uint8 = 22
+ typeNextProtocol uint8 = 67 // Not IANA assigned
+ typeEncryptedExtensions uint8 = 203 // Not IANA assigned
)
// TLS compression types.
@@ -74,9 +76,11 @@ const (
extensionSupportedCurves uint16 = 10
extensionSupportedPoints uint16 = 11
extensionSignatureAlgorithms uint16 = 13
+ extensionALPN uint16 = 16
extensionSessionTicket uint16 = 35
extensionNextProtoNeg uint16 = 13172 // not IANA assigned
extensionRenegotiationInfo uint16 = 0xff01
+ extensionChannelID uint16 = 30032 // not IANA assigned
)
// TLS signaling cipher suite values
@@ -135,24 +139,24 @@ const (
// signatureAndHash mirrors the TLS 1.2, SignatureAndHashAlgorithm struct. See
// RFC 5246, section A.4.1.
type signatureAndHash struct {
- hash, signature uint8
+ signature, hash uint8
}
// supportedSKXSignatureAlgorithms contains the signature and hash algorithms
// that the code advertises as supported in a TLS 1.2 ClientHello.
var supportedSKXSignatureAlgorithms = []signatureAndHash{
- {hashSHA256, signatureRSA},
- {hashSHA256, signatureECDSA},
- {hashSHA1, signatureRSA},
- {hashSHA1, signatureECDSA},
+ {signatureRSA, hashSHA256},
+ {signatureECDSA, hashSHA256},
+ {signatureRSA, hashSHA1},
+ {signatureECDSA, hashSHA1},
}
// supportedClientCertSignatureAlgorithms contains the signature and hash
// algorithms that the code advertises as supported in a TLS 1.2
// CertificateRequest.
var supportedClientCertSignatureAlgorithms = []signatureAndHash{
- {hashSHA256, signatureRSA},
- {hashSHA256, signatureECDSA},
+ {signatureRSA, hashSHA256},
+ {signatureECDSA, hashSHA256},
}
// ConnectionState records basic TLS details about the connection.
@@ -163,9 +167,11 @@ type ConnectionState struct {
CipherSuite uint16 // cipher suite in use (TLS_RSA_WITH_RC4_128_SHA, ...)
NegotiatedProtocol string // negotiated next protocol (from Config.NextProtos)
NegotiatedProtocolIsMutual bool // negotiated protocol was advertised by server
+ NegotiatedProtocolFromALPN bool // protocol negotiated with ALPN
ServerName string // server name requested by client, if any (server side only)
PeerCertificates []*x509.Certificate // certificate chain presented by remote peer
VerifiedChains [][]*x509.Certificate // verified chains built from PeerCertificates
+ ChannelID *ecdsa.PublicKey // the channel ID for this connection
}
// ClientAuthType declares the policy the server will follow for
@@ -187,6 +193,7 @@ type ClientSessionState struct {
vers uint16 // SSL/TLS version negotiated for the session
cipherSuite uint16 // Ciphersuite negotiated for the session
masterSecret []byte // MasterSecret generated by client on a full handshake
+ handshakeHash []byte // Handshake hash for Channel ID purposes.
serverCertificates []*x509.Certificate // Certificate chain presented by the server
}
@@ -307,6 +314,15 @@ type Config struct {
// be used.
CurvePreferences []CurveID
+ // ChannelID contains the ECDSA key for the client to use as
+ // its TLS Channel ID.
+ ChannelID *ecdsa.PrivateKey
+
+ // RequestChannelID controls whether the server requests a TLS
+ // Channel ID. If negotiated, the client's public key is
+ // returned in the ConnectionState.
+ RequestChannelID bool
+
// Bugs specifies optional misbehaviour to be used for testing other
// implementations.
Bugs ProtocolBugs
@@ -420,6 +436,34 @@ type ProtocolBugs struct {
// SkipHelloVerifyRequest causes a DTLS server to skip the
// HelloVerifyRequest message.
SkipHelloVerifyRequest bool
+
+ // ExpectFalseStart causes the server to, on full handshakes,
+ // expect the peer to False Start; the server Finished message
+ // isn't sent until we receive an application data record
+ // from the peer.
+ ExpectFalseStart bool
+
+ // SSL3RSAKeyExchange causes the client to always send an RSA
+ // ClientKeyExchange message without the two-byte length
+ // prefix, as if it were SSL3.
+ SSL3RSAKeyExchange bool
+
+ // SkipCipherVersionCheck causes the server to negotiate
+ // TLS 1.2 ciphers in earlier versions of TLS.
+ SkipCipherVersionCheck bool
+
+ // ExpectServerName, if not empty, is the hostname the client
+ // must specify in the server_name extension.
+ ExpectServerName string
+
+ // SwapNPNAndALPN switches the relative order between NPN and
+ // ALPN on the server. This is to test that server preference
+ // of ALPN works regardless of their relative order.
+ SwapNPNAndALPN bool
+
+ // AllowSessionVersionMismatch causes the server to resume sessions
+ // regardless of the version associated with the session.
+ AllowSessionVersionMismatch bool
}
func (c *Config) serverInit() {
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index 5371a64..9f0c328 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -9,6 +9,7 @@ package main
import (
"bytes"
"crypto/cipher"
+ "crypto/ecdsa"
"crypto/subtle"
"crypto/x509"
"errors"
@@ -46,6 +47,9 @@ type Conn struct {
clientProtocol string
clientProtocolFallback bool
+ usedALPN bool
+
+ channelID *ecdsa.PublicKey
// input/output
in, out halfConn // in.Mutex < out.Mutex
@@ -657,7 +661,7 @@ func (c *Conn) readRecord(want recordType) error {
return c.in.setErrorLocked(errors.New("tls: handshake or ChangeCipherSpec requested after handshake complete"))
}
case recordTypeApplicationData:
- if !c.handshakeComplete {
+ if !c.handshakeComplete && !c.config.Bugs.ExpectFalseStart {
c.sendAlert(alertInternalError)
return c.in.setErrorLocked(errors.New("tls: application data record requested before handshake complete"))
}
@@ -937,6 +941,8 @@ func (c *Conn) readHandshake() (interface{}, error) {
m = new(finishedMsg)
case typeHelloVerifyRequest:
m = new(helloVerifyRequestMsg)
+ case typeEncryptedExtensions:
+ m = new(encryptedExtensionsMsg)
default:
return nil, c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage))
}
@@ -1100,10 +1106,12 @@ func (c *Conn) ConnectionState() ConnectionState {
state.NegotiatedProtocol = c.clientProtocol
state.DidResume = c.didResume
state.NegotiatedProtocolIsMutual = !c.clientProtocolFallback
+ state.NegotiatedProtocolFromALPN = c.usedALPN
state.CipherSuite = c.cipherSuite
state.PeerCertificates = c.peerCertificates
state.VerifiedChains = c.verifiedChains
state.ServerName = c.serverName
+ state.ChannelID = c.channelID
}
return state
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index ecc2bed..d78e767 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -8,6 +8,7 @@ import (
"bytes"
"crypto"
"crypto/ecdsa"
+ "crypto/elliptic"
"crypto/rsa"
"crypto/subtle"
"crypto/x509"
@@ -42,6 +43,18 @@ func (c *Conn) clientHandshake() error {
c.sendHandshakeSeq = 0
c.recvHandshakeSeq = 0
+ nextProtosLength := 0
+ for _, proto := range c.config.NextProtos {
+ if l := len(proto); l == 0 || l > 255 {
+ return errors.New("tls: invalid NextProtos value")
+ } else {
+ nextProtosLength += 1 + l
+ }
+ }
+ if nextProtosLength > 0xffff {
+ return errors.New("tls: NextProtos values too large")
+ }
+
hello := &clientHelloMsg{
isDTLS: c.isDTLS,
vers: c.config.maxVersion(),
@@ -53,7 +66,10 @@ func (c *Conn) clientHandshake() error {
supportedPoints: []uint8{pointFormatUncompressed},
nextProtoNeg: len(c.config.NextProtos) > 0,
secureRenegotiation: true,
+ alpnProtocols: c.config.NextProtos,
duplicateExtension: c.config.Bugs.DuplicateExtension,
+ channelIDSupported: c.config.ChannelID != nil,
+ npnLast: c.config.Bugs.SwapNPNAndALPN,
}
if c.config.Bugs.SendClientVersion != 0 {
@@ -238,7 +254,7 @@ NextCipherSuite:
if err := hs.readFinished(); err != nil {
return err
}
- if err := hs.sendFinished(); err != nil {
+ if err := hs.sendFinished(isResume); err != nil {
return err
}
} else {
@@ -248,7 +264,7 @@ NextCipherSuite:
if err := hs.establishKeys(); err != nil {
return err
}
- if err := hs.sendFinished(); err != nil {
+ if err := hs.sendFinished(isResume); err != nil {
return err
}
if err := hs.readSessionTicket(); err != nil {
@@ -472,6 +488,8 @@ func (hs *clientHandshakeState) doFullHandshake() error {
c.writeRecord(recordTypeHandshake, ckx.marshal())
}
+ hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.hello.random, hs.serverHello.random)
+
if chainToSend != nil {
var signed []byte
certVerify := &certificateVerifyMsg{
@@ -485,7 +503,7 @@ func (hs *clientHandshakeState) doFullHandshake() error {
break
}
var digest []byte
- digest, _, err = hs.finishedHash.hashForClientCertificate(certVerify.signatureAndHash)
+ digest, _, err = hs.finishedHash.hashForClientCertificate(certVerify.signatureAndHash, hs.masterSecret)
if err != nil {
break
}
@@ -501,7 +519,7 @@ func (hs *clientHandshakeState) doFullHandshake() error {
}
var digest []byte
var hashFunc crypto.Hash
- digest, hashFunc, err = hs.finishedHash.hashForClientCertificate(certVerify.signatureAndHash)
+ digest, hashFunc, err = hs.finishedHash.hashForClientCertificate(certVerify.signatureAndHash, hs.masterSecret)
if err != nil {
break
}
@@ -519,7 +537,8 @@ func (hs *clientHandshakeState) doFullHandshake() error {
c.writeRecord(recordTypeHandshake, certVerify.marshal())
}
- hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.hello.random, hs.serverHello.random)
+ hs.finishedHash.discardHandshakeBuffer()
+
return nil
}
@@ -560,15 +579,42 @@ func (hs *clientHandshakeState) processServerHello() (bool, error) {
return false, errors.New("tls: server selected unsupported compression format")
}
- if !hs.hello.nextProtoNeg && hs.serverHello.nextProtoNeg {
+ clientDidNPN := hs.hello.nextProtoNeg
+ clientDidALPN := len(hs.hello.alpnProtocols) > 0
+ serverHasNPN := hs.serverHello.nextProtoNeg
+ serverHasALPN := len(hs.serverHello.alpnProtocol) > 0
+
+ if !clientDidNPN && serverHasNPN {
c.sendAlert(alertHandshakeFailure)
return false, errors.New("server advertised unrequested NPN extension")
}
+ if !clientDidALPN && serverHasALPN {
+ c.sendAlert(alertHandshakeFailure)
+ return false, errors.New("server advertised unrequested ALPN extension")
+ }
+
+ if serverHasNPN && serverHasALPN {
+ c.sendAlert(alertHandshakeFailure)
+ return false, errors.New("server advertised both NPN and ALPN extensions")
+ }
+
+ if serverHasALPN {
+ c.clientProtocol = hs.serverHello.alpnProtocol
+ c.clientProtocolFallback = false
+ c.usedALPN = true
+ }
+
+ if !hs.hello.channelIDSupported && hs.serverHello.channelIDRequested {
+ c.sendAlert(alertHandshakeFailure)
+ return false, errors.New("server advertised unrequested Channel ID extension")
+ }
+
if hs.serverResumedSession() {
// Restore masterSecret and peerCerts from previous state
hs.masterSecret = hs.session.masterSecret
c.peerCertificates = hs.session.serverCertificates
+ hs.finishedHash.discardHandshakeBuffer()
return true, nil
}
return false, nil
@@ -619,20 +665,22 @@ func (hs *clientHandshakeState) readSessionTicket() error {
c.sendAlert(alertUnexpectedMessage)
return unexpectedMessageError(sessionTicketMsg, msg)
}
- hs.writeServerHash(sessionTicketMsg.marshal())
hs.session = &ClientSessionState{
sessionTicket: sessionTicketMsg.ticket,
vers: c.vers,
cipherSuite: hs.suite.id,
masterSecret: hs.masterSecret,
+ handshakeHash: hs.finishedHash.server.Sum(nil),
serverCertificates: c.peerCertificates,
}
+ hs.writeServerHash(sessionTicketMsg.marshal())
+
return nil
}
-func (hs *clientHandshakeState) sendFinished() error {
+func (hs *clientHandshakeState) sendFinished(isResume bool) error {
c := hs.c
var postCCSBytes []byte
@@ -650,6 +698,34 @@ func (hs *clientHandshakeState) sendFinished() error {
postCCSBytes = append(postCCSBytes, nextProtoBytes...)
}
+ if hs.serverHello.channelIDRequested {
+ encryptedExtensions := new(encryptedExtensionsMsg)
+ if c.config.ChannelID.Curve != elliptic.P256() {
+ return fmt.Errorf("tls: Channel ID is not on P-256.")
+ }
+ var resumeHash []byte
+ if isResume {
+ resumeHash = hs.session.handshakeHash
+ }
+ r, s, err := ecdsa.Sign(c.config.rand(), c.config.ChannelID, hs.finishedHash.hashForChannelID(resumeHash))
+ if err != nil {
+ return err
+ }
+ channelID := make([]byte, 128)
+ writeIntPadded(channelID[0:32], c.config.ChannelID.X)
+ writeIntPadded(channelID[32:64], c.config.ChannelID.Y)
+ writeIntPadded(channelID[64:96], r)
+ writeIntPadded(channelID[96:128], s)
+ encryptedExtensions.channelID = channelID
+
+ c.channelID = &c.config.ChannelID.PublicKey
+
+ encryptedExtensionsBytes := encryptedExtensions.marshal()
+ hs.writeHash(encryptedExtensionsBytes, seqno)
+ seqno++
+ postCCSBytes = append(postCCSBytes, encryptedExtensionsBytes...)
+ }
+
finished := new(finishedMsg)
if c.config.Bugs.EarlyChangeCipherSpec == 2 {
finished.verifyData = hs.finishedHash.clientSum(nil)
@@ -709,18 +785,28 @@ func clientSessionCacheKey(serverAddr net.Addr, config *Config) string {
return serverAddr.String()
}
-// mutualProtocol finds the mutual Next Protocol Negotiation protocol given the
-// set of client and server supported protocols. The set of client supported
-// protocols must not be empty. It returns the resulting protocol and flag
+// mutualProtocol finds the mutual Next Protocol Negotiation or ALPN protocol
+// given list of possible protocols and a list of the preference order. The
+// first list must not be empty. It returns the resulting protocol and flag
// indicating if the fallback case was reached.
-func mutualProtocol(clientProtos, serverProtos []string) (string, bool) {
- for _, s := range serverProtos {
- for _, c := range clientProtos {
+func mutualProtocol(protos, preferenceProtos []string) (string, bool) {
+ for _, s := range preferenceProtos {
+ for _, c := range protos {
if s == c {
return s, false
}
}
}
- return clientProtos[0], true
+ return protos[0], true
+}
+
+// writeIntPadded writes x into b, padded up with leading zeros as
+// needed.
+func writeIntPadded(b []byte, x *big.Int) {
+ for i := range b {
+ b[i] = 0
+ }
+ xb := x.Bytes()
+ copy(b[len(b)-len(xb):], xb)
}
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go
index 7fe8bf5..136360d 100644
--- a/ssl/test/runner/handshake_messages.go
+++ b/ssl/test/runner/handshake_messages.go
@@ -24,7 +24,10 @@ type clientHelloMsg struct {
sessionTicket []uint8
signatureAndHashes []signatureAndHash
secureRenegotiation bool
+ alpnProtocols []string
duplicateExtension bool
+ channelIDSupported bool
+ npnLast bool
}
func (m *clientHelloMsg) equal(i interface{}) bool {
@@ -49,7 +52,11 @@ func (m *clientHelloMsg) equal(i interface{}) bool {
m.ticketSupported == m1.ticketSupported &&
bytes.Equal(m.sessionTicket, m1.sessionTicket) &&
eqSignatureAndHashes(m.signatureAndHashes, m1.signatureAndHashes) &&
- m.secureRenegotiation == m1.secureRenegotiation
+ m.secureRenegotiation == m1.secureRenegotiation &&
+ eqStrings(m.alpnProtocols, m1.alpnProtocols) &&
+ m.duplicateExtension == m1.duplicateExtension &&
+ m.channelIDSupported == m1.channelIDSupported &&
+ m.npnLast == m1.npnLast
}
func (m *clientHelloMsg) marshal() []byte {
@@ -97,6 +104,20 @@ func (m *clientHelloMsg) marshal() []byte {
if m.duplicateExtension {
numExtensions += 2
}
+ if m.channelIDSupported {
+ numExtensions++
+ }
+ if len(m.alpnProtocols) > 0 {
+ extensionsLength += 2
+ for _, s := range m.alpnProtocols {
+ if l := len(s); l == 0 || l > 255 {
+ panic("invalid ALPN protocol")
+ }
+ extensionsLength++
+ extensionsLength += len(s)
+ }
+ numExtensions++
+ }
if numExtensions > 0 {
extensionsLength += 4 * numExtensions
length += 2 + extensionsLength
@@ -141,7 +162,7 @@ func (m *clientHelloMsg) marshal() []byte {
z[1] = 0xff
z = z[4:]
}
- if m.nextProtoNeg {
+ if m.nextProtoNeg && !m.npnLast {
z[0] = byte(extensionNextProtoNeg >> 8)
z[1] = byte(extensionNextProtoNeg & 0xff)
// The length is always 0
@@ -260,6 +281,38 @@ func (m *clientHelloMsg) marshal() []byte {
z[3] = 1
z = z[5:]
}
+ if len(m.alpnProtocols) > 0 {
+ z[0] = byte(extensionALPN >> 8)
+ z[1] = byte(extensionALPN & 0xff)
+ lengths := z[2:]
+ z = z[6:]
+
+ stringsLength := 0
+ for _, s := range m.alpnProtocols {
+ l := len(s)
+ z[0] = byte(l)
+ copy(z[1:], s)
+ z = z[1+l:]
+ stringsLength += 1 + l
+ }
+
+ lengths[2] = byte(stringsLength >> 8)
+ lengths[3] = byte(stringsLength)
+ stringsLength += 2
+ lengths[0] = byte(stringsLength >> 8)
+ lengths[1] = byte(stringsLength)
+ }
+ if m.channelIDSupported {
+ z[0] = byte(extensionChannelID >> 8)
+ z[1] = byte(extensionChannelID & 0xff)
+ z = z[4:]
+ }
+ if m.nextProtoNeg && m.npnLast {
+ z[0] = byte(extensionNextProtoNeg >> 8)
+ z[1] = byte(extensionNextProtoNeg & 0xff)
+ // The length is always 0
+ z = z[4:]
+ }
if m.duplicateExtension {
// Add a duplicate bogus extension at the beginning and end.
z[0] = 0xff
@@ -331,6 +384,7 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool {
m.ticketSupported = false
m.sessionTicket = nil
m.signatureAndHashes = nil
+ m.alpnProtocols = nil
if len(data) == 0 {
// ClientHello is optionally followed by extension data
@@ -440,6 +494,29 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool {
return false
}
m.secureRenegotiation = true
+ case extensionALPN:
+ if length < 2 {
+ return false
+ }
+ l := int(data[0])<<8 | int(data[1])
+ if l != length-2 {
+ return false
+ }
+ d := data[2:length]
+ for len(d) != 0 {
+ stringLen := int(d[0])
+ d = d[1:]
+ if stringLen == 0 || stringLen > len(d) {
+ return false
+ }
+ m.alpnProtocols = append(m.alpnProtocols, string(d[:stringLen]))
+ d = d[stringLen:]
+ }
+ case extensionChannelID:
+ if length > 0 {
+ return false
+ }
+ m.channelIDSupported = true
}
data = data[length:]
}
@@ -460,7 +537,9 @@ type serverHelloMsg struct {
ocspStapling bool
ticketSupported bool
secureRenegotiation bool
+ alpnProtocol string
duplicateExtension bool
+ channelIDRequested bool
}
func (m *serverHelloMsg) equal(i interface{}) bool {
@@ -480,7 +559,10 @@ func (m *serverHelloMsg) equal(i interface{}) bool {
eqStrings(m.nextProtos, m1.nextProtos) &&
m.ocspStapling == m1.ocspStapling &&
m.ticketSupported == m1.ticketSupported &&
- m.secureRenegotiation == m1.secureRenegotiation
+ m.secureRenegotiation == m1.secureRenegotiation &&
+ m.alpnProtocol == m1.alpnProtocol &&
+ m.duplicateExtension == m1.duplicateExtension &&
+ m.channelIDRequested == m1.channelIDRequested
}
func (m *serverHelloMsg) marshal() []byte {
@@ -514,6 +596,17 @@ func (m *serverHelloMsg) marshal() []byte {
if m.duplicateExtension {
numExtensions += 2
}
+ if m.channelIDRequested {
+ numExtensions++
+ }
+ if alpnLen := len(m.alpnProtocol); alpnLen > 0 {
+ if alpnLen >= 256 {
+ panic("invalid ALPN protocol")
+ }
+ extensionsLength += 2 + 1 + alpnLen
+ numExtensions++
+ }
+
if numExtensions > 0 {
extensionsLength += 4 * numExtensions
length += 2 + extensionsLength
@@ -581,6 +674,25 @@ func (m *serverHelloMsg) marshal() []byte {
z[3] = 1
z = z[5:]
}
+ if alpnLen := len(m.alpnProtocol); alpnLen > 0 {
+ z[0] = byte(extensionALPN >> 8)
+ z[1] = byte(extensionALPN & 0xff)
+ l := 2 + 1 + alpnLen
+ z[2] = byte(l >> 8)
+ z[3] = byte(l)
+ l -= 2
+ z[4] = byte(l >> 8)
+ z[5] = byte(l)
+ l -= 1
+ z[6] = byte(l)
+ copy(z[7:], []byte(m.alpnProtocol))
+ z = z[7+alpnLen:]
+ }
+ if m.channelIDRequested {
+ z[0] = byte(extensionChannelID >> 8)
+ z[1] = byte(extensionChannelID & 0xff)
+ z = z[4:]
+ }
if m.duplicateExtension {
// Add a duplicate bogus extension at the beginning and end.
z[0] = 0xff
@@ -617,6 +729,7 @@ func (m *serverHelloMsg) unmarshal(data []byte) bool {
m.nextProtos = nil
m.ocspStapling = false
m.ticketSupported = false
+ m.alpnProtocol = ""
if len(data) == 0 {
// ServerHello is optionally followed by extension data
@@ -671,6 +784,27 @@ func (m *serverHelloMsg) unmarshal(data []byte) bool {
return false
}
m.secureRenegotiation = true
+ case extensionALPN:
+ d := data[:length]
+ if len(d) < 3 {
+ return false
+ }
+ l := int(d[0])<<8 | int(d[1])
+ if l != len(d)-2 {
+ return false
+ }
+ d = d[2:]
+ l = int(d[0])
+ if l != len(d)-1 {
+ return false
+ }
+ d = d[1:]
+ m.alpnProtocol = string(d)
+ case extensionChannelID:
+ if length > 0 {
+ return false
+ }
+ m.channelIDRequested = true
}
data = data[length:]
}
@@ -1407,7 +1541,8 @@ func (m *helloVerifyRequestMsg) equal(i interface{}) bool {
return false
}
- return m.vers == m1.vers &&
+ return bytes.Equal(m.raw, m1.raw) &&
+ m.vers == m1.vers &&
bytes.Equal(m.cookie, m1.cookie)
}
@@ -1447,6 +1582,58 @@ func (m *helloVerifyRequestMsg) unmarshal(data []byte) bool {
return true
}
+type encryptedExtensionsMsg struct {
+ raw []byte
+ channelID []byte
+}
+
+func (m *encryptedExtensionsMsg) equal(i interface{}) bool {
+ m1, ok := i.(*encryptedExtensionsMsg)
+ if !ok {
+ return false
+ }
+
+ return bytes.Equal(m.raw, m1.raw) &&
+ bytes.Equal(m.channelID, m1.channelID)
+}
+
+func (m *encryptedExtensionsMsg) marshal() []byte {
+ if m.raw != nil {
+ return m.raw
+ }
+
+ length := 2 + 2 + len(m.channelID)
+
+ x := make([]byte, 4+length)
+ x[0] = typeEncryptedExtensions
+ x[1] = uint8(length >> 16)
+ x[2] = uint8(length >> 8)
+ x[3] = uint8(length)
+ x[4] = uint8(extensionChannelID >> 8)
+ x[5] = uint8(extensionChannelID & 0xff)
+ x[6] = uint8(len(m.channelID) >> 8)
+ x[7] = uint8(len(m.channelID) & 0xff)
+ copy(x[8:], m.channelID)
+
+ return x
+}
+
+func (m *encryptedExtensionsMsg) unmarshal(data []byte) bool {
+ if len(data) != 4+2+2+128 {
+ return false
+ }
+ m.raw = data
+ if (uint16(data[4])<<8)|uint16(data[5]) != extensionChannelID {
+ return false
+ }
+ if int(data[6])<<8|int(data[7]) != 128 {
+ return false
+ }
+ m.channelID = data[4+2+2:]
+
+ return true
+}
+
func eqUint16s(x, y []uint16) bool {
if len(x) != len(y) {
return false
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index d5a660a..1eb3f11 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -8,6 +8,7 @@ import (
"bytes"
"crypto"
"crypto/ecdsa"
+ "crypto/elliptic"
"crypto/rsa"
"crypto/subtle"
"crypto/x509"
@@ -15,6 +16,7 @@ import (
"errors"
"fmt"
"io"
+ "math/big"
)
// serverHandshakeState contains details of a server handshake in progress.
@@ -69,7 +71,7 @@ func (c *Conn) serverHandshake() error {
if err := hs.sendFinished(); err != nil {
return err
}
- if err := hs.readFinished(); err != nil {
+ if err := hs.readFinished(isResume); err != nil {
return err
}
c.didResume = true
@@ -82,9 +84,14 @@ func (c *Conn) serverHandshake() error {
if err := hs.establishKeys(); err != nil {
return err
}
- if err := hs.readFinished(); err != nil {
+ if err := hs.readFinished(isResume); err != nil {
return err
}
+ if c.config.Bugs.ExpectFalseStart {
+ if err := c.readRecord(recordTypeApplicationData); err != nil {
+ return err
+ }
+ }
if err := hs.sendSessionTicket(); err != nil {
return err
}
@@ -213,13 +220,22 @@ Curves:
if len(hs.clientHello.serverName) > 0 {
c.serverName = hs.clientHello.serverName
}
- // Although sending an empty NPN extension is reasonable, Firefox has
- // had a bug around this. Best to send nothing at all if
- // config.NextProtos is empty. See
- // https://code.google.com/p/go/issues/detail?id=5445.
- if hs.clientHello.nextProtoNeg && len(config.NextProtos) > 0 {
- hs.hello.nextProtoNeg = true
- hs.hello.nextProtos = config.NextProtos
+
+ if len(hs.clientHello.alpnProtocols) > 0 {
+ if selectedProto, fallback := mutualProtocol(hs.clientHello.alpnProtocols, c.config.NextProtos); !fallback {
+ hs.hello.alpnProtocol = selectedProto
+ c.clientProtocol = selectedProto
+ c.usedALPN = true
+ }
+ } else {
+ // Although sending an empty NPN extension is reasonable, Firefox has
+ // had a bug around this. Best to send nothing at all if
+ // config.NextProtos is empty. See
+ // https://code.google.com/p/go/issues/detail?id=5445.
+ if hs.clientHello.nextProtoNeg && len(config.NextProtos) > 0 {
+ hs.hello.nextProtoNeg = true
+ hs.hello.nextProtos = config.NextProtos
+ }
}
if len(config.Certificates) == 0 {
@@ -230,6 +246,13 @@ Curves:
if len(hs.clientHello.serverName) > 0 {
hs.cert = config.getCertificateForName(hs.clientHello.serverName)
}
+ if expected := c.config.Bugs.ExpectServerName; expected != "" && expected != hs.clientHello.serverName {
+ return false, errors.New("tls: unexpected server name")
+ }
+
+ if hs.clientHello.channelIDSupported && config.RequestChannelID {
+ hs.hello.channelIDRequested = true
+ }
_, hs.ecdsaOk = hs.cert.PrivateKey.(*ecdsa.PrivateKey)
@@ -279,16 +302,22 @@ Curves:
func (hs *serverHandshakeState) checkForResumption() bool {
c := hs.c
- var ok bool
- if hs.sessionState, ok = c.decryptTicket(hs.clientHello.sessionTicket); !ok {
+ if c.config.SessionTicketsDisabled {
return false
}
- if hs.sessionState.vers > hs.clientHello.vers {
+ var ok bool
+ if hs.sessionState, ok = c.decryptTicket(hs.clientHello.sessionTicket); !ok {
return false
}
- if vers, ok := c.config.mutualVersion(hs.sessionState.vers); !ok || vers != hs.sessionState.vers {
- return false
+
+ if !c.config.Bugs.AllowSessionVersionMismatch {
+ if hs.sessionState.vers > hs.clientHello.vers {
+ return false
+ }
+ if vers, ok := c.config.mutualVersion(hs.sessionState.vers); !ok || vers != hs.sessionState.vers {
+ return false
+ }
}
cipherSuiteOk := false
@@ -331,6 +360,7 @@ func (hs *serverHandshakeState) doResumeHandshake() error {
hs.hello.ticketSupported = c.config.Bugs.RenewTicketOnResume
hs.finishedHash = newFinishedHash(c.vers, hs.suite)
+ hs.finishedHash.discardHandshakeBuffer()
hs.writeClientHash(hs.clientHello.marshal())
hs.writeServerHash(hs.hello.marshal())
@@ -467,6 +497,13 @@ func (hs *serverHandshakeState) doFullHandshake() error {
}
hs.writeClientHash(ckx.marshal())
+ preMasterSecret, err := keyAgreement.processClientKeyExchange(config, hs.cert, ckx, c.vers)
+ if err != nil {
+ c.sendAlert(alertHandshakeFailure)
+ return err
+ }
+ hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.clientHello.random, hs.hello.random)
+
// If we received a client cert in response to our certificate request message,
// the client will send us a certificateVerifyMsg immediately after the
// clientKeyExchangeMsg. This message is a digest of all preceding
@@ -515,7 +552,7 @@ func (hs *serverHandshakeState) doFullHandshake() error {
break
}
var digest []byte
- digest, _, err = hs.finishedHash.hashForClientCertificate(signatureAndHash)
+ digest, _, err = hs.finishedHash.hashForClientCertificate(signatureAndHash, hs.masterSecret)
if err != nil {
break
}
@@ -530,7 +567,7 @@ func (hs *serverHandshakeState) doFullHandshake() error {
}
var digest []byte
var hashFunc crypto.Hash
- digest, hashFunc, err = hs.finishedHash.hashForClientCertificate(signatureAndHash)
+ digest, hashFunc, err = hs.finishedHash.hashForClientCertificate(signatureAndHash, hs.masterSecret)
if err != nil {
break
}
@@ -544,12 +581,7 @@ func (hs *serverHandshakeState) doFullHandshake() error {
hs.writeClientHash(certVerify.marshal())
}
- preMasterSecret, err := keyAgreement.processClientKeyExchange(config, hs.cert, ckx, c.vers)
- if err != nil {
- c.sendAlert(alertHandshakeFailure)
- return err
- }
- hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.clientHello.random, hs.hello.random)
+ hs.finishedHash.discardHandshakeBuffer()
return nil
}
@@ -579,7 +611,7 @@ func (hs *serverHandshakeState) establishKeys() error {
return nil
}
-func (hs *serverHandshakeState) readFinished() error {
+func (hs *serverHandshakeState) readFinished(isResume bool) error {
c := hs.c
c.readRecord(recordTypeChangeCipherSpec)
@@ -601,6 +633,36 @@ func (hs *serverHandshakeState) readFinished() error {
c.clientProtocol = nextProto.proto
}
+ if hs.hello.channelIDRequested {
+ msg, err := c.readHandshake()
+ if err != nil {
+ return err
+ }
+ encryptedExtensions, ok := msg.(*encryptedExtensionsMsg)
+ if !ok {
+ c.sendAlert(alertUnexpectedMessage)
+ return unexpectedMessageError(encryptedExtensions, msg)
+ }
+ x := new(big.Int).SetBytes(encryptedExtensions.channelID[0:32])
+ y := new(big.Int).SetBytes(encryptedExtensions.channelID[32:64])
+ r := new(big.Int).SetBytes(encryptedExtensions.channelID[64:96])
+ s := new(big.Int).SetBytes(encryptedExtensions.channelID[96:128])
+ if !elliptic.P256().IsOnCurve(x, y) {
+ return errors.New("tls: invalid channel ID public key")
+ }
+ channelID := &ecdsa.PublicKey{elliptic.P256(), x, y}
+ var resumeHash []byte
+ if isResume {
+ resumeHash = hs.sessionState.handshakeHash
+ }
+ if !ecdsa.Verify(channelID, hs.finishedHash.hashForChannelID(resumeHash), r, s) {
+ return errors.New("tls: invalid channel ID signature")
+ }
+ c.channelID = channelID
+
+ hs.writeClientHash(encryptedExtensions.marshal())
+ }
+
msg, err := c.readHandshake()
if err != nil {
return err
@@ -632,10 +694,11 @@ func (hs *serverHandshakeState) sendSessionTicket() error {
var err error
state := sessionState{
- vers: c.vers,
- cipherSuite: hs.suite.id,
- masterSecret: hs.masterSecret,
- certificates: hs.certsFromClient,
+ vers: c.vers,
+ cipherSuite: hs.suite.id,
+ masterSecret: hs.masterSecret,
+ certificates: hs.certsFromClient,
+ handshakeHash: hs.finishedHash.server.Sum(nil),
}
m.ticket, err = c.encryptTicket(&state)
if err != nil {
@@ -787,7 +850,7 @@ func (c *Conn) tryCipherSuite(id uint16, supportedCipherSuites []uint16, version
if (candidate.flags&suiteECDSA != 0) != ecdsaOk {
continue
}
- if version < VersionTLS12 && candidate.flags&suiteTLS12 != 0 {
+ if !c.config.Bugs.SkipCipherVersionCheck && version < VersionTLS12 && candidate.flags&suiteTLS12 != 0 {
continue
}
if c.isDTLS && candidate.flags&suiteNoDTLS != 0 {
diff --git a/ssl/test/runner/key_agreement.go b/ssl/test/runner/key_agreement.go
index 2e2eff4..f8ba1f8 100644
--- a/ssl/test/runner/key_agreement.go
+++ b/ssl/test/runner/key_agreement.go
@@ -87,7 +87,7 @@ func (ka rsaKeyAgreement) generateClientKeyExchange(config *Config, clientHello
return nil, nil, err
}
ckx := new(clientKeyExchangeMsg)
- if clientHello.vers != VersionSSL30 {
+ if clientHello.vers != VersionSSL30 && !config.Bugs.SSL3RSAKeyExchange {
ckx.ciphertext = make([]byte, len(encrypted)+2)
ckx.ciphertext[0] = byte(len(encrypted) >> 8)
ckx.ciphertext[1] = byte(len(encrypted))
diff --git a/ssl/test/runner/prf.go b/ssl/test/runner/prf.go
index 991196f..6d0db97 100644
--- a/ssl/test/runner/prf.go
+++ b/ssl/test/runner/prf.go
@@ -120,6 +120,8 @@ var masterSecretLabel = []byte("master secret")
var keyExpansionLabel = []byte("key expansion")
var clientFinishedLabel = []byte("client finished")
var serverFinishedLabel = []byte("server finished")
+var channelIDLabel = []byte("TLS Channel ID signature\x00")
+var channelIDResumeLabel = []byte("Resumption\x00")
func prfForVersion(version uint16, suite *cipherSuite) func(result, secret, label, seed []byte) {
switch version {
@@ -180,9 +182,9 @@ func newFinishedHash(version uint16, cipherSuite *cipherSuite) finishedHash {
newHash = sha512.New384
}
- return finishedHash{newHash(), newHash(), nil, nil, version, prf12(newHash)}
+ return finishedHash{newHash(), newHash(), nil, nil, []byte{}, version, prf12(newHash)}
}
- return finishedHash{sha1.New(), sha1.New(), md5.New(), md5.New(), version, prf10}
+ return finishedHash{sha1.New(), sha1.New(), md5.New(), md5.New(), []byte{}, version, prf10}
}
// A finishedHash calculates the hash of a set of handshake messages suitable
@@ -195,11 +197,15 @@ type finishedHash struct {
clientMD5 hash.Hash
serverMD5 hash.Hash
+ // In TLS 1.2 (and SSL 3 for implementation convenience), a
+ // full buffer is required.
+ buffer []byte
+
version uint16
prf func(result, secret, label, seed []byte)
}
-func (h finishedHash) Write(msg []byte) (n int, err error) {
+func (h *finishedHash) Write(msg []byte) (n int, err error) {
h.client.Write(msg)
h.server.Write(msg)
@@ -207,14 +213,19 @@ func (h finishedHash) Write(msg []byte) (n int, err error) {
h.clientMD5.Write(msg)
h.serverMD5.Write(msg)
}
+
+ if h.buffer != nil {
+ h.buffer = append(h.buffer, msg...)
+ }
+
return len(msg), nil
}
// finishedSum30 calculates the contents of the verify_data member of a SSLv3
// Finished message given the MD5 and SHA1 hashes of a set of handshake
// messages.
-func finishedSum30(md5, sha1 hash.Hash, masterSecret []byte, magic [4]byte) []byte {
- md5.Write(magic[:])
+func finishedSum30(md5, sha1 hash.Hash, masterSecret []byte, magic []byte) []byte {
+ md5.Write(magic)
md5.Write(masterSecret)
md5.Write(ssl30Pad1[:])
md5Digest := md5.Sum(nil)
@@ -225,7 +236,7 @@ func finishedSum30(md5, sha1 hash.Hash, masterSecret []byte, magic [4]byte) []by
md5.Write(md5Digest)
md5Digest = md5.Sum(nil)
- sha1.Write(magic[:])
+ sha1.Write(magic)
sha1.Write(masterSecret)
sha1.Write(ssl30Pad1[:40])
sha1Digest := sha1.Sum(nil)
@@ -249,7 +260,7 @@ var ssl3ServerFinishedMagic = [4]byte{0x53, 0x52, 0x56, 0x52}
// Finished message.
func (h finishedHash) clientSum(masterSecret []byte) []byte {
if h.version == VersionSSL30 {
- return finishedSum30(h.clientMD5, h.client, masterSecret, ssl3ClientFinishedMagic)
+ return finishedSum30(h.clientMD5, h.client, masterSecret, ssl3ClientFinishedMagic[:])
}
out := make([]byte, finishedVerifyLength)
@@ -269,7 +280,7 @@ func (h finishedHash) clientSum(masterSecret []byte) []byte {
// Finished message.
func (h finishedHash) serverSum(masterSecret []byte) []byte {
if h.version == VersionSSL30 {
- return finishedSum30(h.serverMD5, h.server, masterSecret, ssl3ServerFinishedMagic)
+ return finishedSum30(h.serverMD5, h.server, masterSecret, ssl3ServerFinishedMagic[:])
}
out := make([]byte, finishedVerifyLength)
@@ -290,7 +301,7 @@ func (h finishedHash) serverSum(masterSecret []byte) []byte {
func (h finishedHash) selectClientCertSignatureAlgorithm(serverList []signatureAndHash, sigType uint8) (signatureAndHash, error) {
if h.version < VersionTLS12 {
// Nothing to negotiate before TLS 1.2.
- return signatureAndHash{sigType, 0}, nil
+ return signatureAndHash{signature: sigType}, nil
}
for _, v := range serverList {
@@ -303,13 +314,24 @@ func (h finishedHash) selectClientCertSignatureAlgorithm(serverList []signatureA
// hashForClientCertificate returns a digest, hash function, and TLS 1.2 hash
// id suitable for signing by a TLS client certificate.
-func (h finishedHash) hashForClientCertificate(signatureAndHash signatureAndHash) ([]byte, crypto.Hash, error) {
+func (h finishedHash) hashForClientCertificate(signatureAndHash signatureAndHash, masterSecret []byte) ([]byte, crypto.Hash, error) {
+ if h.version == VersionSSL30 {
+ if signatureAndHash.signature != signatureRSA {
+ return nil, 0, errors.New("tls: unsupported signature type for client certificate")
+ }
+
+ md5Hash := md5.New()
+ md5Hash.Write(h.buffer)
+ sha1Hash := sha1.New()
+ sha1Hash.Write(h.buffer)
+ return finishedSum30(md5Hash, sha1Hash, masterSecret, nil), crypto.MD5SHA1, nil
+ }
if h.version >= VersionTLS12 {
if signatureAndHash.hash != hashSHA256 {
return nil, 0, errors.New("tls: unsupported hash function for client certificate")
}
- digest := h.server.Sum(nil)
- return digest, crypto.SHA256, nil
+ digest := sha256.Sum256(h.buffer)
+ return digest[:], crypto.SHA256, nil
}
if signatureAndHash.signature == signatureECDSA {
digest := h.server.Sum(nil)
@@ -321,3 +343,23 @@ func (h finishedHash) hashForClientCertificate(signatureAndHash signatureAndHash
digest = h.server.Sum(digest)
return digest, crypto.MD5SHA1, nil
}
+
+// hashForChannelID returns the hash to be signed for TLS Channel
+// ID. If a resumption, resumeHash has the previous handshake
+// hash. Otherwise, it is nil.
+func (h finishedHash) hashForChannelID(resumeHash []byte) []byte {
+ hash := sha256.New()
+ hash.Write(channelIDLabel)
+ if resumeHash != nil {
+ hash.Write(channelIDResumeLabel)
+ hash.Write(resumeHash)
+ }
+ hash.Write(h.server.Sum(nil))
+ return hash.Sum(nil)
+}
+
+// discardHandshakeBuffer is called when there is no more need to
+// buffer the entirety of the handshake messages.
+func (h *finishedHash) discardHandshakeBuffer() {
+ h.buffer = nil
+}
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 4a5b762..323f43f 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -2,7 +2,11 @@ package main
import (
"bytes"
+ "crypto/ecdsa"
+ "crypto/elliptic"
"crypto/x509"
+ "encoding/base64"
+ "encoding/pem"
"flag"
"fmt"
"io"
@@ -25,11 +29,14 @@ const (
)
const (
- rsaKeyFile = "key.pem"
- ecdsaKeyFile = "ecdsa_key.pem"
+ 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
@@ -42,6 +49,26 @@ func initCertificates() {
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
@@ -70,6 +97,11 @@ const (
dtls
)
+const (
+ alpn = 1
+ npn = 2
+)
+
type testCase struct {
testType testType
protocol protocol
@@ -83,6 +115,18 @@ type testCase struct {
// 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
// messageLen is the length, in bytes, of the test message that will be
// sent.
messageLen int
@@ -91,11 +135,19 @@ type testCase struct {
// 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 resumes the first session.
+ // 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
// flags, if not empty, contains a list of command-line flags that will
// be passed to the shim program.
flags []string
@@ -160,7 +212,7 @@ var testCases = []testCase{
expectedLocalError: "no fallback SCSV found",
},
{
- name: "FallbackSCSV",
+ name: "SendFallbackSCSV",
config: Config{
Bugs: ProtocolBugs{
FailIfNotFallbackSCSV: true,
@@ -169,36 +221,6 @@ var testCases = []testCase{
flags: []string{"-fallback-scsv"},
},
{
- testType: serverTest,
- name: "ServerNameExtension",
- config: Config{
- ServerName: "example.com",
- },
- flags: []string{"-expect-server-name", "example.com"},
- },
- {
- testType: clientTest,
- name: "DuplicateExtensionClient",
- config: Config{
- Bugs: ProtocolBugs{
- DuplicateExtension: true,
- },
- },
- shouldFail: true,
- expectedLocalError: "remote error: error decoding message",
- },
- {
- testType: serverTest,
- name: "DuplicateExtensionServer",
- config: Config{
- Bugs: ProtocolBugs{
- DuplicateExtension: true,
- },
- },
- shouldFail: true,
- expectedLocalError: "remote error: error decoding message",
- },
- {
name: "ClientCertificateTypes",
config: Config{
ClientAuth: RequestClientCert,
@@ -208,11 +230,14 @@ var testCases = []testCase{
CertTypeECDSASign,
},
},
- flags: []string{"-expect-certificate-types", string([]byte{
- CertTypeDSSSign,
- CertTypeRSASign,
- CertTypeECDSASign,
- })},
+ flags: []string{
+ "-expect-certificate-types",
+ base64.StdEncoding.EncodeToString([]byte{
+ CertTypeDSSSign,
+ CertTypeRSASign,
+ CertTypeECDSASign,
+ }),
+ },
},
{
name: "NoClientCertificate",
@@ -447,9 +472,21 @@ var testCases = []testCase{
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:",
+ },
}
-func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int) error {
+func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int, isResume bool) error {
if test.protocol == dtls {
conn = newPacketAdaptor(conn)
}
@@ -480,8 +517,50 @@ func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int) e
return err
}
- if vers := tlsConn.ConnectionState().Version; test.expectedVersion != 0 && vers != test.expectedVersion {
- return fmt.Errorf("got version %x, expected %x", vers, test.expectedVersion)
+ // 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 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 messageLen < 0 {
@@ -601,6 +680,10 @@ func runTest(test *testCase, buildDir string) error {
flags = append(flags, "-resume")
}
+ if test.shimWritesFirst {
+ flags = append(flags, "-shim-writes-first")
+ }
+
flags = append(flags, test.flags...)
var shim *exec.Cmd
@@ -630,12 +713,25 @@ func runTest(test *testCase, buildDir string) error {
}
}
- err := doExchange(test, &config, conn, test.messageLen)
+ err := doExchange(test, &config, conn, test.messageLen,
+ false /* not a resumption */)
conn.Close()
if err == nil && test.resumeSession {
- err = doExchange(test, &config, connResume, test.messageLen)
- connResume.Close()
+ 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()
@@ -697,25 +793,40 @@ var testCipherSuites = []struct {
{"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-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},
{"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 {
var cert Certificate
@@ -732,7 +843,7 @@ func addCipherSuiteTests() {
}
for _, ver := range tlsVersions {
- if ver.version != VersionTLS12 && strings.HasSuffix(suite.name, "-GCM") {
+ if ver.version < VersionTLS12 && isTLS12Only(suite.name) {
continue
}
@@ -902,29 +1013,14 @@ func addClientAuthTests() {
certPool.AddCert(cert)
for _, ver := range tlsVersions {
- if ver.version == VersionSSL30 {
- // TODO(davidben): The Go implementation does not
- // correctly compute CertificateVerify hashes for SSLv3.
- continue
- }
-
- var cipherSuites []uint16
- if ver.version >= VersionTLS12 {
- // Pick a SHA-256 cipher suite. The Go implementation
- // does not correctly handle client auth with a SHA-384
- // cipher suite.
- cipherSuites = []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}
- }
-
testCases = append(testCases, testCase{
testType: clientTest,
name: ver.name + "-Client-ClientAuth-RSA",
config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- CipherSuites: cipherSuites,
- ClientAuth: RequireAnyClientCert,
- ClientCAs: certPool,
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ ClientAuth: RequireAnyClientCert,
+ ClientCAs: certPool,
},
flags: []string{
"-cert-file", rsaCertificateFile,
@@ -932,36 +1028,41 @@ func addClientAuthTests() {
},
})
testCases = append(testCases, testCase{
- testType: clientTest,
- name: ver.name + "-Client-ClientAuth-ECDSA",
- config: Config{
- MinVersion: ver.version,
- MaxVersion: ver.version,
- CipherSuites: cipherSuites,
- ClientAuth: RequireAnyClientCert,
- ClientCAs: certPool,
- },
- flags: []string{
- "-cert-file", ecdsaCertificateFile,
- "-key-file", ecdsaKeyFile,
- },
- })
- 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"},
})
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: ver.name + "-Server-ClientAuth-ECDSA",
- config: Config{
- Certificates: []Certificate{ecdsaCertificate},
- },
- 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,
+ },
+ })
+ }
}
}
@@ -1029,8 +1130,7 @@ func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol)
testType: clientTest,
name: "ClientAuth-Client" + suffix,
config: Config{
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
- ClientAuth: RequireAnyClientCert,
+ ClientAuth: RequireAnyClientCert,
Bugs: ProtocolBugs{
MaxHandshakeRecordLength: maxHandshakeRecordLength,
},
@@ -1080,13 +1180,14 @@ func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol)
protocol: protocol,
name: "NPN-Client" + suffix,
config: Config{
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- NextProtos: []string{"foo"},
+ NextProtos: []string{"foo"},
Bugs: ProtocolBugs{
MaxHandshakeRecordLength: maxHandshakeRecordLength,
},
},
- flags: append(flags, "-select-next-proto", "foo"),
+ flags: append(flags, "-select-next-proto", "foo"),
+ expectedNextProto: "foo",
+ expectedNextProtoType: npn,
})
testCases = append(testCases, testCase{
protocol: protocol,
@@ -1101,6 +1202,8 @@ func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol)
flags: append(flags,
"-advertise-npn", "\x03foo\x03bar\x03baz",
"-expect-next-proto", "bar"),
+ expectedNextProto: "bar",
+ expectedNextProtoType: npn,
})
// Client does False Start and negotiates NPN.
@@ -1111,13 +1214,34 @@ func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol)
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"),
- resumeSession: true,
+ 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.
@@ -1127,14 +1251,19 @@ func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol)
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
NextProtos: []string{"foo"},
SessionTicketsDisabled: true,
+ Bugs: ProtocolBugs{
+ ExpectFalseStart: true,
+ MaxHandshakeRecordLength: maxHandshakeRecordLength,
+ },
},
- flags: []string{
+ flags: append(flags,
"-false-start",
"-select-next-proto", "foo",
- },
+ ),
+ shimWritesFirst: true,
})
- // Client sends a V2ClientHello.
+ // Server parses a V2ClientHello.
testCases = append(testCases, testCase{
protocol: protocol,
testType: serverTest,
@@ -1151,6 +1280,42 @@ func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol)
},
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,
@@ -1216,6 +1381,231 @@ func addVersionNegotiationTests() {
}
}
+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,
+ })
+}
+
+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
+
+ // TODO(davidben): Write equivalent tests for the server
+ // and clean up the server's logic. This requires being
+ // able to give the shim a different set of SSL_OP_NO_*
+ // flags between the initial connection and the
+ // resume. Perhaps resumption should be tested by
+ // serializing the SSL_SESSION and starting a second
+ // shim.
+ 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,
+ })
+ }
+ }
+}
+
func worker(statusChan chan statusMsg, c chan *testCase, buildDir string, wg *sync.WaitGroup) {
defer wg.Done()
@@ -1268,6 +1658,9 @@ func main() {
addCBCSplittingTests()
addClientAuthTests()
addVersionNegotiationTests()
+ addD5BugTests()
+ addExtensionTests()
+ addResumptionVersionTests()
for _, async := range []bool{false, true} {
for _, splitHandshake := range []bool{false, true} {
for _, protocol := range []protocol{tls, dtls} {
diff --git a/ssl/test/runner/ticket.go b/ssl/test/runner/ticket.go
index 519543b..74791d6 100644
--- a/ssl/test/runner/ticket.go
+++ b/ssl/test/runner/ticket.go
@@ -18,10 +18,11 @@ import (
// sessionState contains the information that is serialized into a session
// ticket in order to later resume a connection.
type sessionState struct {
- vers uint16
- cipherSuite uint16
- masterSecret []byte
- certificates [][]byte
+ vers uint16
+ cipherSuite uint16
+ masterSecret []byte
+ handshakeHash []byte
+ certificates [][]byte
}
func (s *sessionState) equal(i interface{}) bool {
@@ -32,7 +33,8 @@ func (s *sessionState) equal(i interface{}) bool {
if s.vers != s1.vers ||
s.cipherSuite != s1.cipherSuite ||
- !bytes.Equal(s.masterSecret, s1.masterSecret) {
+ !bytes.Equal(s.masterSecret, s1.masterSecret) ||
+ !bytes.Equal(s.handshakeHash, s1.handshakeHash) {
return false
}
@@ -50,7 +52,7 @@ func (s *sessionState) equal(i interface{}) bool {
}
func (s *sessionState) marshal() []byte {
- length := 2 + 2 + 2 + len(s.masterSecret) + 2
+ length := 2 + 2 + 2 + len(s.masterSecret) + 2 + len(s.handshakeHash) + 2
for _, cert := range s.certificates {
length += 4 + len(cert)
}
@@ -67,6 +69,12 @@ func (s *sessionState) marshal() []byte {
copy(x, s.masterSecret)
x = x[len(s.masterSecret):]
+ x[0] = byte(len(s.handshakeHash) >> 8)
+ x[1] = byte(len(s.handshakeHash))
+ x = x[2:]
+ copy(x, s.handshakeHash)
+ x = x[len(s.handshakeHash):]
+
x[0] = byte(len(s.certificates) >> 8)
x[1] = byte(len(s.certificates))
x = x[2:]
@@ -103,6 +111,19 @@ func (s *sessionState) unmarshal(data []byte) bool {
return false
}
+ handshakeHashLen := int(data[0])<<8 | int(data[1])
+ data = data[2:]
+ if len(data) < handshakeHashLen {
+ return false
+ }
+
+ s.handshakeHash = data[:handshakeHashLen]
+ data = data[handshakeHashLen:]
+
+ if len(data) < 2 {
+ return false
+ }
+
numCerts := int(data[0])<<8 | int(data[1])
data = data[2:]
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index 9716227..70543cc 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -16,6 +16,9 @@
#include <string.h>
+#include <memory>
+
+#include <openssl/base64.h>
namespace {
@@ -50,25 +53,37 @@ const BoolFlag kBoolFlags[] = {
{ "-no-tls1", &TestConfig::no_tls1 },
{ "-no-ssl3", &TestConfig::no_ssl3 },
{ "-cookie-exchange", &TestConfig::cookie_exchange },
+ { "-shim-writes-first", &TestConfig::shim_writes_first },
+ { "-tls-d5-bug", &TestConfig::tls_d5_bug },
+ { "-expect-session-miss", &TestConfig::expect_session_miss },
};
const size_t kNumBoolFlags = sizeof(kBoolFlags) / sizeof(kBoolFlags[0]);
-// TODO(davidben): Some of these should be in a new kBase64Flags to allow NUL
-// bytes.
const StringFlag kStringFlags[] = {
{ "-key-file", &TestConfig::key_file },
{ "-cert-file", &TestConfig::cert_file },
{ "-expect-server-name", &TestConfig::expected_server_name },
- // Conveniently, 00 is not a certificate type.
- { "-expect-certificate-types", &TestConfig::expected_certificate_types },
{ "-advertise-npn", &TestConfig::advertise_npn },
{ "-expect-next-proto", &TestConfig::expected_next_proto },
{ "-select-next-proto", &TestConfig::select_next_proto },
+ { "-send-channel-id", &TestConfig::send_channel_id },
+ { "-host-name", &TestConfig::host_name },
+ { "-advertise-alpn", &TestConfig::advertise_alpn },
+ { "-expect-alpn", &TestConfig::expected_alpn },
+ { "-expect-advertised-alpn", &TestConfig::expected_advertised_alpn },
+ { "-select-alpn", &TestConfig::select_alpn },
};
const size_t kNumStringFlags = sizeof(kStringFlags) / sizeof(kStringFlags[0]);
+const StringFlag kBase64Flags[] = {
+ { "-expect-certificate-types", &TestConfig::expected_certificate_types },
+ { "-expect-channel-id", &TestConfig::expected_channel_id },
+};
+
+const size_t kNumBase64Flags = sizeof(kBase64Flags) / sizeof(kBase64Flags[0]);
+
} // namespace
TestConfig::TestConfig()
@@ -86,7 +101,10 @@ TestConfig::TestConfig()
no_tls11(false),
no_tls1(false),
no_ssl3(false),
- cookie_exchange(false) {
+ cookie_exchange(false),
+ shim_writes_first(false),
+ tls_d5_bug(false),
+ expect_session_miss(false) {
}
bool ParseConfig(int argc, char **argv, TestConfig *out_config) {
@@ -117,6 +135,32 @@ bool ParseConfig(int argc, char **argv, TestConfig *out_config) {
continue;
}
+ for (j = 0; j < kNumBase64Flags; j++) {
+ if (strcmp(argv[i], kBase64Flags[j].flag) == 0) {
+ break;
+ }
+ }
+ if (j < kNumBase64Flags) {
+ i++;
+ if (i >= argc) {
+ fprintf(stderr, "Missing parameter\n");
+ return false;
+ }
+ size_t len;
+ if (!EVP_DecodedLength(&len, strlen(argv[i]))) {
+ fprintf(stderr, "Invalid base64: %s\n", argv[i]);
+ }
+ std::unique_ptr<uint8_t[]> decoded(new uint8_t[len]);
+ if (!EVP_DecodeBase64(decoded.get(), &len, len,
+ reinterpret_cast<const uint8_t *>(argv[i]),
+ strlen(argv[i]))) {
+ fprintf(stderr, "Invalid base64: %s\n", argv[i]);
+ }
+ out_config->*(kBase64Flags[j].member) = std::string(
+ reinterpret_cast<const char *>(decoded.get()), len);
+ continue;
+ }
+
fprintf(stderr, "Unknown argument: %s\n", argv[i]);
return false;
}
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index 34d720e..acce504 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -43,6 +43,16 @@ struct TestConfig {
bool no_tls1;
bool no_ssl3;
bool cookie_exchange;
+ std::string expected_channel_id;
+ std::string send_channel_id;
+ bool shim_writes_first;
+ bool tls_d5_bug;
+ std::string host_name;
+ std::string advertise_alpn;
+ std::string expected_alpn;
+ std::string expected_advertised_alpn;
+ std::string select_alpn;
+ bool expect_session_miss;
};
bool ParseConfig(int argc, char **argv, TestConfig *out_config);