diff options
Diffstat (limited to 'src/ssl/test')
24 files changed, 4540 insertions, 904 deletions
diff --git a/src/ssl/test/CMakeLists.txt b/src/ssl/test/CMakeLists.txt index 9992360..a0d7a5e 100644 --- a/src/ssl/test/CMakeLists.txt +++ b/src/ssl/test/CMakeLists.txt @@ -11,6 +11,3 @@ add_executable( ) target_link_libraries(bssl_shim ssl crypto) -if (NOT APPLE AND NOT WIN32) - target_link_libraries(bssl_shim dl) -endif() diff --git a/src/ssl/test/async_bio.cc b/src/ssl/test/async_bio.cc index c007ffa..0534845 100644 --- a/src/ssl/test/async_bio.cc +++ b/src/ssl/test/async_bio.cc @@ -22,23 +22,23 @@ namespace { -extern const BIO_METHOD async_bio_method; +extern const BIO_METHOD g_async_bio_method; -struct async_bio { +struct AsyncBio { bool datagram; size_t read_quota; size_t write_quota; }; -async_bio *get_data(BIO *bio) { - if (bio->method != &async_bio_method) { +AsyncBio *GetData(BIO *bio) { + if (bio->method != &g_async_bio_method) { return NULL; } - return (async_bio *)bio->ptr; + return (AsyncBio *)bio->ptr; } -static int async_write(BIO *bio, const char *in, int inl) { - async_bio *a = get_data(bio); +static int AsyncWrite(BIO *bio, const char *in, int inl) { + AsyncBio *a = GetData(bio); if (a == NULL || bio->next_bio == NULL) { return 0; } @@ -69,8 +69,8 @@ static int async_write(BIO *bio, const char *in, int inl) { return ret; } -static int async_read(BIO *bio, char *out, int outl) { - async_bio *a = get_data(bio); +static int AsyncRead(BIO *bio, char *out, int outl) { + AsyncBio *a = GetData(bio); if (a == NULL || bio->next_bio == NULL) { return 0; } @@ -95,7 +95,7 @@ static int async_read(BIO *bio, char *out, int outl) { return ret; } -static long async_ctrl(BIO *bio, int cmd, long num, void *ptr) { +static long AsyncCtrl(BIO *bio, int cmd, long num, void *ptr) { if (bio->next_bio == NULL) { return 0; } @@ -105,8 +105,8 @@ static long async_ctrl(BIO *bio, int cmd, long num, void *ptr) { return ret; } -static int async_new(BIO *bio) { - async_bio *a = (async_bio *)OPENSSL_malloc(sizeof(*a)); +static int AsyncNew(BIO *bio) { + AsyncBio *a = (AsyncBio *)OPENSSL_malloc(sizeof(*a)); if (a == NULL) { return 0; } @@ -116,7 +116,7 @@ static int async_new(BIO *bio) { return 1; } -static int async_free(BIO *bio) { +static int AsyncFree(BIO *bio) { if (bio == NULL) { return 0; } @@ -128,51 +128,51 @@ static int async_free(BIO *bio) { return 1; } -static long async_callback_ctrl(BIO *bio, int cmd, bio_info_cb fp) { +static long AsyncCallbackCtrl(BIO *bio, int cmd, bio_info_cb fp) { if (bio->next_bio == NULL) { return 0; } return BIO_callback_ctrl(bio->next_bio, cmd, fp); } -const BIO_METHOD async_bio_method = { +const BIO_METHOD g_async_bio_method = { BIO_TYPE_FILTER, "async bio", - async_write, - async_read, + AsyncWrite, + AsyncRead, NULL /* puts */, NULL /* gets */, - async_ctrl, - async_new, - async_free, - async_callback_ctrl, + AsyncCtrl, + AsyncNew, + AsyncFree, + AsyncCallbackCtrl, }; } // namespace -BIO *async_bio_create() { - return BIO_new(&async_bio_method); +ScopedBIO AsyncBioCreate() { + return ScopedBIO(BIO_new(&g_async_bio_method)); } -BIO *async_bio_create_datagram() { - BIO *ret = BIO_new(&async_bio_method); +ScopedBIO AsyncBioCreateDatagram() { + ScopedBIO ret(BIO_new(&g_async_bio_method)); if (!ret) { - return NULL; + return nullptr; } - get_data(ret)->datagram = true; + GetData(ret.get())->datagram = true; return ret; } -void async_bio_allow_read(BIO *bio, size_t count) { - async_bio *a = get_data(bio); +void AsyncBioAllowRead(BIO *bio, size_t count) { + AsyncBio *a = GetData(bio); if (a == NULL) { return; } a->read_quota += count; } -void async_bio_allow_write(BIO *bio, size_t count) { - async_bio *a = get_data(bio); +void AsyncBioAllowWrite(BIO *bio, size_t count) { + AsyncBio *a = GetData(bio); if (a == NULL) { return; } diff --git a/src/ssl/test/async_bio.h b/src/ssl/test/async_bio.h index 2904036..1ccdf9b 100644 --- a/src/ssl/test/async_bio.h +++ b/src/ssl/test/async_bio.h @@ -17,24 +17,26 @@ #include <openssl/bio.h> +#include "../../crypto/test/scoped_types.h" -// async_bio_create creates a filter BIO for testing asynchronous state + +// AsyncBioCreate creates a filter BIO for testing asynchronous state // machines which consume a stream socket. Reads and writes will fail // and return EAGAIN unless explicitly allowed. Each async BIO has a // read quota and a write quota. Initially both are zero. As each is // incremented, bytes are allowed to flow through the BIO. -BIO *async_bio_create(); +ScopedBIO AsyncBioCreate(); -// async_bio_create_datagram creates a filter BIO for testing for +// AsyncBioCreateDatagram creates a filter BIO for testing for // asynchronous state machines which consume datagram sockets. The read // and write quota count in packets rather than bytes. -BIO *async_bio_create_datagram(); +ScopedBIO AsyncBioCreateDatagram(); -// async_bio_allow_read increments |bio|'s read quota by |count|. -void async_bio_allow_read(BIO *bio, size_t count); +// AsyncBioAllowRead increments |bio|'s read quota by |count|. +void AsyncBioAllowRead(BIO *bio, size_t count); -// async_bio_allow_write increments |bio|'s write quota by |count|. -void async_bio_allow_write(BIO *bio, size_t count); +// AsyncBioAllowWrite increments |bio|'s write quota by |count|. +void AsyncBioAllowWrite(BIO *bio, size_t count); #endif // HEADER_ASYNC_BIO diff --git a/src/ssl/test/bssl_shim.cc b/src/ssl/test/bssl_shim.cc index 37891b9..1cf96f2 100644 --- a/src/ssl/test/bssl_shim.cc +++ b/src/ssl/test/bssl_shim.cc @@ -17,9 +17,19 @@ #if !defined(OPENSSL_WINDOWS) #include <arpa/inet.h> #include <netinet/in.h> +#include <netinet/tcp.h> #include <signal.h> #include <sys/socket.h> +#include <sys/types.h> #include <unistd.h> +#else +#include <io.h> +#pragma warning(push, 3) +#include <winsock2.h> +#include <ws2tcpip.h> +#pragma warning(pop) + +#pragma comment(lib, "Ws2_32.lib") #endif #include <string.h> @@ -28,126 +38,198 @@ #include <openssl/bio.h> #include <openssl/buf.h> #include <openssl/bytestring.h> +#include <openssl/err.h> #include <openssl/ssl.h> +#include <memory> +#include <vector> + +#include "../../crypto/test/scoped_types.h" #include "async_bio.h" #include "packeted_bio.h" +#include "scoped_types.h" #include "test_config.h" -static int usage(const char *program) { - fprintf(stderr, "Usage: %s [flags...]\n", - program); + +#if !defined(OPENSSL_WINDOWS) +static int closesocket(int sock) { + return close(sock); +} + +static void PrintSocketError(const char *func) { + perror(func); +} +#else +static void PrintSocketError(const char *func) { + fprintf(stderr, "%s: %d\n", func, WSAGetLastError()); +} +#endif + +static int Usage(const char *program) { + fprintf(stderr, "Usage: %s [flags...]\n", program); return 1; } -static int g_ex_data_index = 0; +struct TestState { + TestState() { + // MSVC cannot initialize these inline. + memset(&clock, 0, sizeof(clock)); + memset(&clock_delta, 0, sizeof(clock_delta)); + } + + // async_bio is async BIO which pauses reads and writes. + BIO *async_bio = nullptr; + // clock is the current time for the SSL connection. + timeval clock; + // clock_delta is how far the clock advanced in the most recent failed + // |BIO_read|. + timeval clock_delta; + ScopedEVP_PKEY channel_id; + bool cert_ready = false; + ScopedSSL_SESSION session; + ScopedSSL_SESSION pending_session; + bool early_callback_called = false; + bool handshake_done = false; +}; + +static void TestStateExFree(void *parent, void *ptr, CRYPTO_EX_DATA *ad, + int index, long argl, void *argp) { + delete ((TestState *)ptr); +} + +static int g_config_index = 0; +static int g_state_index = 0; static bool SetConfigPtr(SSL *ssl, const TestConfig *config) { - return SSL_set_ex_data(ssl, g_ex_data_index, (void *)config) == 1; + return SSL_set_ex_data(ssl, g_config_index, (void *)config) == 1; } -static const TestConfig *GetConfigPtr(SSL *ssl) { - return (const TestConfig *)SSL_get_ex_data(ssl, g_ex_data_index); +static const TestConfig *GetConfigPtr(const SSL *ssl) { + return (const TestConfig *)SSL_get_ex_data(ssl, g_config_index); } -static EVP_PKEY *LoadPrivateKey(const std::string &file) { - BIO *bio = BIO_new(BIO_s_file()); - if (bio == NULL) { - return NULL; +static bool SetTestState(SSL *ssl, std::unique_ptr<TestState> async) { + if (SSL_set_ex_data(ssl, g_state_index, (void *)async.get()) == 1) { + async.release(); + return true; } - if (!BIO_read_filename(bio, file.c_str())) { - BIO_free(bio); - return NULL; + return false; +} + +static TestState *GetTestState(const SSL *ssl) { + return (TestState *)SSL_get_ex_data(ssl, g_state_index); +} + +static ScopedEVP_PKEY LoadPrivateKey(const std::string &file) { + ScopedBIO bio(BIO_new(BIO_s_file())); + if (!bio || !BIO_read_filename(bio.get(), file.c_str())) { + return nullptr; } - EVP_PKEY *pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); - BIO_free(bio); + ScopedEVP_PKEY pkey(PEM_read_bio_PrivateKey(bio.get(), NULL, NULL, NULL)); return pkey; } -static int early_callback_called = 0; - -static int select_certificate_callback(const struct ssl_early_callback_ctx *ctx) { - early_callback_called = 1; +static bool InstallCertificate(SSL *ssl) { + const TestConfig *config = GetConfigPtr(ssl); + if (!config->key_file.empty() && + !SSL_use_PrivateKey_file(ssl, config->key_file.c_str(), + SSL_FILETYPE_PEM)) { + return false; + } + if (!config->cert_file.empty() && + !SSL_use_certificate_file(ssl, config->cert_file.c_str(), + SSL_FILETYPE_PEM)) { + return false; + } + return true; +} +static int SelectCertificateCallback(const struct ssl_early_callback_ctx *ctx) { const TestConfig *config = GetConfigPtr(ctx->ssl); + GetTestState(ctx->ssl)->early_callback_called = true; - if (config->expected_server_name.empty()) { - return 1; - } + if (!config->expected_server_name.empty()) { + const uint8_t *extension_data; + size_t extension_len; + CBS extension, server_name_list, host_name; + uint8_t name_type; + + if (!SSL_early_callback_ctx_extension_get(ctx, TLSEXT_TYPE_server_name, + &extension_data, + &extension_len)) { + fprintf(stderr, "Could not find server_name extension.\n"); + return -1; + } - const uint8_t *extension_data; - size_t extension_len; - CBS extension, server_name_list, host_name; - uint8_t name_type; + CBS_init(&extension, extension_data, extension_len); + if (!CBS_get_u16_length_prefixed(&extension, &server_name_list) || + CBS_len(&extension) != 0 || + !CBS_get_u8(&server_name_list, &name_type) || + name_type != TLSEXT_NAMETYPE_host_name || + !CBS_get_u16_length_prefixed(&server_name_list, &host_name) || + CBS_len(&server_name_list) != 0) { + fprintf(stderr, "Could not decode server_name extension.\n"); + return -1; + } - if (!SSL_early_callback_ctx_extension_get(ctx, TLSEXT_TYPE_server_name, - &extension_data, - &extension_len)) { - fprintf(stderr, "Could not find server_name extension.\n"); - return -1; + if (!CBS_mem_equal(&host_name, + (const uint8_t*)config->expected_server_name.data(), + config->expected_server_name.size())) { + fprintf(stderr, "Server name mismatch.\n"); + } } - CBS_init(&extension, extension_data, extension_len); - if (!CBS_get_u16_length_prefixed(&extension, &server_name_list) || - CBS_len(&extension) != 0 || - !CBS_get_u8(&server_name_list, &name_type) || - name_type != TLSEXT_NAMETYPE_host_name || - !CBS_get_u16_length_prefixed(&server_name_list, &host_name) || - CBS_len(&server_name_list) != 0) { - fprintf(stderr, "Could not decode server_name extension.\n"); + if (config->fail_early_callback) { return -1; } - if (!CBS_mem_equal(&host_name, - (const uint8_t*)config->expected_server_name.data(), - config->expected_server_name.size())) { - fprintf(stderr, "Server name mismatch.\n"); + // Install the certificate in the early callback. + if (config->use_early_callback) { + if (config->async) { + // Install the certificate asynchronously. + return 0; + } + if (!InstallCertificate(ctx->ssl)) { + return -1; + } } - return 1; } -static int skip_verify(int preverify_ok, X509_STORE_CTX *store_ctx) { +static int SkipVerify(int preverify_ok, X509_STORE_CTX *store_ctx) { return 1; } -static int next_protos_advertised_callback(SSL *ssl, - const uint8_t **out, - unsigned int *out_len, - void *arg) { +static int NextProtosAdvertisedCallback(SSL *ssl, const uint8_t **out, + unsigned int *out_len, void *arg) { const TestConfig *config = GetConfigPtr(ssl); - if (config->advertise_npn.empty()) + if (config->advertise_npn.empty()) { return SSL_TLSEXT_ERR_NOACK; + } *out = (const uint8_t*)config->advertise_npn.data(); *out_len = config->advertise_npn.size(); return SSL_TLSEXT_ERR_OK; } -static int next_proto_select_callback(SSL* ssl, - uint8_t** out, - uint8_t* outlen, - const uint8_t* in, - unsigned inlen, - void* arg) { +static int NextProtoSelectCallback(SSL* ssl, uint8_t** out, uint8_t* outlen, + const uint8_t* in, unsigned inlen, void* arg) { const TestConfig *config = GetConfigPtr(ssl); - if (config->select_next_proto.empty()) + if (config->select_next_proto.empty()) { return SSL_TLSEXT_ERR_NOACK; + } *out = (uint8_t*)config->select_next_proto.data(); *outlen = config->select_next_proto.size(); 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) { +static int AlpnSelectCallback(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()) + if (config->select_alpn.empty()) { return SSL_TLSEXT_ERR_NOACK; + } if (!config->expected_advertised_alpn.empty() && (config->expected_advertised_alpn.size() != inlen || @@ -162,34 +244,10 @@ static int alpn_select_callback(SSL* ssl, return SSL_TLSEXT_ERR_OK; } -static int cookie_generate_callback(SSL *ssl, uint8_t *cookie, size_t *cookie_len) { - if (*cookie_len < 32) { - fprintf(stderr, "Insufficient space for cookie\n"); - return 0; - } - *cookie_len = 32; - memset(cookie, 42, *cookie_len); - return 1; -} - -static int cookie_verify_callback(SSL *ssl, const uint8_t *cookie, size_t cookie_len) { - if (cookie_len != 32) { - fprintf(stderr, "Cookie length mismatch.\n"); - return 0; - } - for (size_t i = 0; i < cookie_len; i++) { - if (cookie[i] != 42) { - fprintf(stderr, "Cookie mismatch.\n"); - return 0; - } - } - return 1; -} - -static unsigned psk_client_callback(SSL *ssl, const char *hint, - char *out_identity, - unsigned max_identity_len, - uint8_t *out_psk, unsigned max_psk_len) { +static unsigned PskClientCallback(SSL *ssl, const char *hint, + char *out_identity, + unsigned max_identity_len, + uint8_t *out_psk, unsigned max_psk_len) { const TestConfig *config = GetConfigPtr(ssl); if (strcmp(hint ? hint : "", config->psk_identity.c_str()) != 0) { @@ -210,8 +268,8 @@ static unsigned psk_client_callback(SSL *ssl, const char *hint, return config->psk.size(); } -static unsigned psk_server_callback(SSL *ssl, const char *identity, - uint8_t *out_psk, unsigned max_psk_len) { +static unsigned PskServerCallback(SSL *ssl, const char *identity, + uint8_t *out_psk, unsigned max_psk_len) { const TestConfig *config = GetConfigPtr(ssl); if (strcmp(identity, config->psk_identity.c_str()) != 0) { @@ -228,13 +286,124 @@ static unsigned psk_server_callback(SSL *ssl, const char *identity, return config->psk.size(); } -static SSL_CTX *setup_ctx(const TestConfig *config) { - SSL_CTX *ssl_ctx = NULL; - DH *dh = NULL; +static void CurrentTimeCallback(const SSL *ssl, timeval *out_clock) { + *out_clock = GetTestState(ssl)->clock; +} - ssl_ctx = SSL_CTX_new(config->is_dtls ? DTLS_method() : TLS_method()); - if (ssl_ctx == NULL) { - goto err; +static void ChannelIdCallback(SSL *ssl, EVP_PKEY **out_pkey) { + *out_pkey = GetTestState(ssl)->channel_id.release(); +} + +static int CertCallback(SSL *ssl, void *arg) { + if (!GetTestState(ssl)->cert_ready) { + return -1; + } + if (!InstallCertificate(ssl)) { + return 0; + } + return 1; +} + +static SSL_SESSION *GetSessionCallback(SSL *ssl, uint8_t *data, int len, + int *copy) { + TestState *async_state = GetTestState(ssl); + if (async_state->session) { + *copy = 0; + return async_state->session.release(); + } else if (async_state->pending_session) { + return SSL_magic_pending_session_ptr(); + } else { + return NULL; + } +} + +static int DDoSCallback(const struct ssl_early_callback_ctx *early_context) { + const TestConfig *config = GetConfigPtr(early_context->ssl); + static int callback_num = 0; + + callback_num++; + if (config->fail_ddos_callback || + (config->fail_second_ddos_callback && callback_num == 2)) { + return 0; + } + return 1; +} + +static void InfoCallback(const SSL *ssl, int type, int val) { + if (type == SSL_CB_HANDSHAKE_DONE) { + if (GetConfigPtr(ssl)->handshake_never_done) { + fprintf(stderr, "handshake completed\n"); + // Abort before any expected error code is printed, to ensure the overall + // test fails. + abort(); + } + GetTestState(ssl)->handshake_done = true; + } +} + +// Connect returns a new socket connected to localhost on |port| or -1 on +// error. +static int Connect(uint16_t port) { + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == -1) { + PrintSocketError("socket"); + return -1; + } + int nodelay = 1; + if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, + reinterpret_cast<const char*>(&nodelay), sizeof(nodelay)) != 0) { + PrintSocketError("setsockopt"); + closesocket(sock); + return -1; + } + sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(port); + if (!inet_pton(AF_INET, "127.0.0.1", &sin.sin_addr)) { + PrintSocketError("inet_pton"); + closesocket(sock); + return -1; + } + if (connect(sock, reinterpret_cast<const sockaddr*>(&sin), + sizeof(sin)) != 0) { + PrintSocketError("connect"); + closesocket(sock); + return -1; + } + return sock; +} + +class SocketCloser { + public: + explicit SocketCloser(int sock) : sock_(sock) {} + ~SocketCloser() { + // Half-close and drain the socket before releasing it. This seems to be + // necessary for graceful shutdown on Windows. It will also avoid write + // failures in the test runner. +#if defined(OPENSSL_WINDOWS) + shutdown(sock_, SD_SEND); +#else + shutdown(sock_, SHUT_WR); +#endif + while (true) { + char buf[1024]; + if (recv(sock_, buf, sizeof(buf), 0) <= 0) { + break; + } + } + closesocket(sock_); + } + + private: + const int sock_; +}; + +static ScopedSSL_CTX SetupCtx(const TestConfig *config) { + ScopedSSL_CTX ssl_ctx(SSL_CTX_new( + config->is_dtls ? DTLS_method() : TLS_method())); + if (!ssl_ctx) { + return nullptr; } if (config->is_dtls) { @@ -242,376 +411,473 @@ static SSL_CTX *setup_ctx(const TestConfig *config) { // // TODO(davidben): this should not be necessary. DTLS code should only // expect a datagram BIO. - SSL_CTX_set_read_ahead(ssl_ctx, 1); + SSL_CTX_set_read_ahead(ssl_ctx.get(), 1); } - if (!SSL_CTX_set_ecdh_auto(ssl_ctx, 1)) { - goto err; + if (!SSL_CTX_set_cipher_list(ssl_ctx.get(), "ALL")) { + return nullptr; } - if (!SSL_CTX_set_cipher_list(ssl_ctx, "ALL")) { - goto err; + ScopedDH dh(DH_get_2048_256(NULL)); + if (!dh || !SSL_CTX_set_tmp_dh(ssl_ctx.get(), dh.get())) { + return nullptr; } - dh = DH_get_2048_256(NULL); - if (dh == NULL || - !SSL_CTX_set_tmp_dh(ssl_ctx, dh)) { - goto err; + if (config->async && config->is_server) { + // Disable the internal session cache. To test asynchronous session lookup, + // we use an external session cache. + SSL_CTX_set_session_cache_mode( + ssl_ctx.get(), SSL_SESS_CACHE_BOTH | SSL_SESS_CACHE_NO_INTERNAL); + SSL_CTX_sess_set_get_cb(ssl_ctx.get(), GetSessionCallback); + } else { + SSL_CTX_set_session_cache_mode(ssl_ctx.get(), SSL_SESS_CACHE_BOTH); } - SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_BOTH); - - ssl_ctx->select_certificate_cb = select_certificate_callback; + ssl_ctx->select_certificate_cb = SelectCertificateCallback; SSL_CTX_set_next_protos_advertised_cb( - ssl_ctx, next_protos_advertised_callback, NULL); + ssl_ctx.get(), NextProtosAdvertisedCallback, NULL); if (!config->select_next_proto.empty()) { - SSL_CTX_set_next_proto_select_cb(ssl_ctx, next_proto_select_callback, NULL); + SSL_CTX_set_next_proto_select_cb(ssl_ctx.get(), NextProtoSelectCallback, + NULL); } if (!config->select_alpn.empty()) { - SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_callback, NULL); + SSL_CTX_set_alpn_select_cb(ssl_ctx.get(), AlpnSelectCallback, 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; + SSL_CTX_set_channel_id_cb(ssl_ctx.get(), ChannelIdCallback); - DH_free(dh); - return ssl_ctx; + ssl_ctx->current_time_cb = CurrentTimeCallback; - err: - if (dh != NULL) { - DH_free(dh); - } - if (ssl_ctx != NULL) { - SSL_CTX_free(ssl_ctx); - } - return NULL; + SSL_CTX_set_info_callback(ssl_ctx.get(), InfoCallback); + + return ssl_ctx; } -static int retry_async(SSL *ssl, int ret, BIO *bio) { +// RetryAsync is called after a failed operation on |ssl| with return code +// |ret|. If the operation should be retried, it simulates one asynchronous +// event and returns true. Otherwise it returns false. +static bool RetryAsync(SSL *ssl, int ret) { // No error; don't retry. if (ret >= 0) { - return 0; + return false; + } + + TestState *test_state = GetTestState(ssl); + if (test_state->clock_delta.tv_usec != 0 || + test_state->clock_delta.tv_sec != 0) { + // Process the timeout and retry. + test_state->clock.tv_usec += test_state->clock_delta.tv_usec; + test_state->clock.tv_sec += test_state->clock.tv_usec / 1000000; + test_state->clock.tv_usec %= 1000000; + test_state->clock.tv_sec += test_state->clock_delta.tv_sec; + memset(&test_state->clock_delta, 0, sizeof(test_state->clock_delta)); + + if (DTLSv1_handle_timeout(ssl) < 0) { + fprintf(stderr, "Error retransmitting.\n"); + return false; + } + return true; } + // See if we needed to read or write more. If so, allow one byte through on // the appropriate end to maximally stress the state machine. - int err = SSL_get_error(ssl, ret); - if (err == SSL_ERROR_WANT_READ) { - async_bio_allow_read(bio, 1); - return 1; - } else if (err == SSL_ERROR_WANT_WRITE) { - async_bio_allow_write(bio, 1); - return 1; + switch (SSL_get_error(ssl, ret)) { + case SSL_ERROR_WANT_READ: + AsyncBioAllowRead(test_state->async_bio, 1); + return true; + case SSL_ERROR_WANT_WRITE: + AsyncBioAllowWrite(test_state->async_bio, 1); + return true; + case SSL_ERROR_WANT_CHANNEL_ID_LOOKUP: { + ScopedEVP_PKEY pkey = LoadPrivateKey(GetConfigPtr(ssl)->send_channel_id); + if (!pkey) { + return false; + } + test_state->channel_id = std::move(pkey); + return true; + } + case SSL_ERROR_WANT_X509_LOOKUP: + test_state->cert_ready = true; + return true; + case SSL_ERROR_PENDING_SESSION: + test_state->session = std::move(test_state->pending_session); + return true; + case SSL_ERROR_PENDING_CERTIFICATE: + // The handshake will resume without a second call to the early callback. + return InstallCertificate(ssl); + default: + return false; } - return 0; } -static int do_exchange(SSL_SESSION **out_session, - SSL_CTX *ssl_ctx, - const TestConfig *config, - bool is_resume, - int fd, - SSL_SESSION *session) { - early_callback_called = 0; +// DoRead reads from |ssl|, resolving any asynchronous operations. It returns +// the result value of the final |SSL_read| call. +static int DoRead(SSL *ssl, uint8_t *out, size_t max_out) { + const TestConfig *config = GetConfigPtr(ssl); + int ret; + do { + ret = SSL_read(ssl, out, max_out); + } while (config->async && RetryAsync(ssl, ret)); + return ret; +} - SSL *ssl = SSL_new(ssl_ctx); - if (ssl == NULL) { - BIO_print_errors_fp(stdout); - return 1; - } +// WriteAll writes |in_len| bytes from |in| to |ssl|, resolving any asynchronous +// operations. It returns the result of the final |SSL_write| call. +static int WriteAll(SSL *ssl, const uint8_t *in, size_t in_len) { + const TestConfig *config = GetConfigPtr(ssl); + int ret; + do { + ret = SSL_write(ssl, in, in_len); + if (ret > 0) { + in += ret; + in_len -= ret; + } + } while ((config->async && RetryAsync(ssl, ret)) || (ret > 0 && in_len > 0)); + return ret; +} - if (!SetConfigPtr(ssl, config)) { - BIO_print_errors_fp(stdout); - return 1; +// DoExchange runs a test SSL exchange against the peer. On success, it returns +// true and sets |*out_session| to the negotiated SSL session. If the test is a +// resumption attempt, |is_resume| is true and |session| is the session from the +// previous exchange. +static bool DoExchange(ScopedSSL_SESSION *out_session, SSL_CTX *ssl_ctx, + const TestConfig *config, bool is_resume, + SSL_SESSION *session) { + ScopedSSL ssl(SSL_new(ssl_ctx)); + if (!ssl) { + return false; } - if (config->fallback_scsv) { - if (!SSL_enable_fallback_scsv(ssl)) { - BIO_print_errors_fp(stdout); - return 1; - } + if (!SetConfigPtr(ssl.get(), config) || + !SetTestState(ssl.get(), std::unique_ptr<TestState>(new TestState))) { + return false; } - if (!config->key_file.empty()) { - if (!SSL_use_PrivateKey_file(ssl, config->key_file.c_str(), - SSL_FILETYPE_PEM)) { - BIO_print_errors_fp(stdout); - return 1; - } + + if (config->fallback_scsv && + !SSL_set_mode(ssl.get(), SSL_MODE_SEND_FALLBACK_SCSV)) { + return false; } - if (!config->cert_file.empty()) { - if (!SSL_use_certificate_file(ssl, config->cert_file.c_str(), - SSL_FILETYPE_PEM)) { - BIO_print_errors_fp(stdout); - return 1; + if (!config->use_early_callback) { + if (config->async) { + // TODO(davidben): Also test |s->ctx->client_cert_cb| on the client. + SSL_set_cert_cb(ssl.get(), CertCallback, NULL); + } else if (!InstallCertificate(ssl.get())) { + return false; } } if (config->require_any_client_certificate) { - SSL_set_verify(ssl, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, - skip_verify); + SSL_set_verify(ssl.get(), SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + SkipVerify); } if (config->false_start) { - SSL_set_mode(ssl, SSL_MODE_HANDSHAKE_CUTTHROUGH); + SSL_set_mode(ssl.get(), SSL_MODE_ENABLE_FALSE_START); } if (config->cbc_record_splitting) { - SSL_set_mode(ssl, SSL_MODE_CBC_RECORD_SPLITTING); + SSL_set_mode(ssl.get(), SSL_MODE_CBC_RECORD_SPLITTING); } if (config->partial_write) { - SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE); + SSL_set_mode(ssl.get(), SSL_MODE_ENABLE_PARTIAL_WRITE); } if (config->no_tls12) { - SSL_set_options(ssl, SSL_OP_NO_TLSv1_2); + SSL_set_options(ssl.get(), SSL_OP_NO_TLSv1_2); } if (config->no_tls11) { - SSL_set_options(ssl, SSL_OP_NO_TLSv1_1); + SSL_set_options(ssl.get(), SSL_OP_NO_TLSv1_1); } if (config->no_tls1) { - SSL_set_options(ssl, SSL_OP_NO_TLSv1); + SSL_set_options(ssl.get(), SSL_OP_NO_TLSv1); } if (config->no_ssl3) { - SSL_set_options(ssl, SSL_OP_NO_SSLv3); - } - if (config->cookie_exchange) { - SSL_set_options(ssl, SSL_OP_COOKIE_EXCHANGE); + SSL_set_options(ssl.get(), SSL_OP_NO_SSLv3); } if (config->tls_d5_bug) { - SSL_set_options(ssl, SSL_OP_TLS_D5_BUG); + SSL_set_options(ssl.get(), SSL_OP_TLS_D5_BUG); } if (config->allow_unsafe_legacy_renegotiation) { - SSL_set_options(ssl, SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION); + SSL_set_options(ssl.get(), SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION); } if (!config->expected_channel_id.empty()) { - SSL_enable_tls_channel_id(ssl); + SSL_enable_tls_channel_id(ssl.get()); } 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; + SSL_enable_tls_channel_id(ssl.get()); + if (!config->async) { + // The async case will be supplied by |ChannelIdCallback|. + ScopedEVP_PKEY pkey = LoadPrivateKey(config->send_channel_id); + if (!pkey || !SSL_set1_tls_channel_id(ssl.get(), pkey.get())) { + return false; + } } - EVP_PKEY_free(pkey); } - if (!config->host_name.empty()) { - SSL_set_tlsext_host_name(ssl, config->host_name.c_str()); + if (!config->host_name.empty() && + !SSL_set_tlsext_host_name(ssl.get(), config->host_name.c_str())) { + return false; } - if (!config->advertise_alpn.empty()) { - SSL_set_alpn_protos(ssl, (const uint8_t *)config->advertise_alpn.data(), - config->advertise_alpn.size()); + if (!config->advertise_alpn.empty() && + SSL_set_alpn_protos(ssl.get(), + (const uint8_t *)config->advertise_alpn.data(), + config->advertise_alpn.size()) != 0) { + return false; } if (!config->psk.empty()) { - SSL_set_psk_client_callback(ssl, psk_client_callback); - SSL_set_psk_server_callback(ssl, psk_server_callback); + SSL_set_psk_client_callback(ssl.get(), PskClientCallback); + SSL_set_psk_server_callback(ssl.get(), PskServerCallback); } if (!config->psk_identity.empty() && - !SSL_use_psk_identity_hint(ssl, config->psk_identity.c_str())) { - BIO_print_errors_fp(stdout); - return 1; + !SSL_use_psk_identity_hint(ssl.get(), config->psk_identity.c_str())) { + return false; } if (!config->srtp_profiles.empty() && - !SSL_set_srtp_profiles(ssl, config->srtp_profiles.c_str())) { - BIO_print_errors_fp(stdout); - return 1; + !SSL_set_srtp_profiles(ssl.get(), config->srtp_profiles.c_str())) { + return false; } if (config->enable_ocsp_stapling && - !SSL_enable_ocsp_stapling(ssl)) { - BIO_print_errors_fp(stdout); - return 1; + !SSL_enable_ocsp_stapling(ssl.get())) { + return false; } if (config->enable_signed_cert_timestamps && - !SSL_enable_signed_cert_timestamps(ssl)) { - BIO_print_errors_fp(stdout); - return 1; + !SSL_enable_signed_cert_timestamps(ssl.get())) { + return false; } - SSL_enable_fastradio_padding(ssl, config->fastradio_padding); + SSL_enable_fastradio_padding(ssl.get(), config->fastradio_padding); if (config->min_version != 0) { - SSL_set_min_version(ssl, (uint16_t)config->min_version); + SSL_set_min_version(ssl.get(), (uint16_t)config->min_version); } if (config->max_version != 0) { - SSL_set_max_version(ssl, (uint16_t)config->max_version); + SSL_set_max_version(ssl.get(), (uint16_t)config->max_version); } if (config->mtu != 0) { - SSL_set_options(ssl, SSL_OP_NO_QUERY_MTU); - SSL_set_mtu(ssl, config->mtu); + SSL_set_options(ssl.get(), SSL_OP_NO_QUERY_MTU); + SSL_set_mtu(ssl.get(), config->mtu); + } + if (config->install_ddos_callback) { + SSL_CTX_set_dos_protection_cb(ssl_ctx, DDoSCallback); + } + if (!config->cipher.empty() && + !SSL_set_cipher_list(ssl.get(), config->cipher.c_str())) { + return false; + } + if (config->reject_peer_renegotiations) { + SSL_set_reject_peer_renegotiations(ssl.get(), 1); } - BIO *bio = BIO_new_fd(fd, 1 /* take ownership */); - if (bio == NULL) { - BIO_print_errors_fp(stdout); - return 1; + int sock = Connect(config->port); + if (sock == -1) { + return false; + } + SocketCloser closer(sock); + + ScopedBIO bio(BIO_new_socket(sock, BIO_NOCLOSE)); + if (!bio) { + return false; } if (config->is_dtls) { - BIO *packeted = packeted_bio_create(); - BIO_push(packeted, bio); - bio = packeted; + ScopedBIO packeted = + PacketedBioCreate(&GetTestState(ssl.get())->clock_delta); + BIO_push(packeted.get(), bio.release()); + bio = std::move(packeted); } if (config->async) { - BIO *async = - config->is_dtls ? async_bio_create_datagram() : async_bio_create(); - BIO_push(async, bio); - bio = async; + ScopedBIO async_scoped = + config->is_dtls ? AsyncBioCreateDatagram() : AsyncBioCreate(); + BIO_push(async_scoped.get(), bio.release()); + GetTestState(ssl.get())->async_bio = async_scoped.get(); + bio = std::move(async_scoped); } - SSL_set_bio(ssl, bio, bio); + SSL_set_bio(ssl.get(), bio.get(), bio.get()); + bio.release(); // SSL_set_bio takes ownership. if (session != NULL) { - if (SSL_set_session(ssl, session) != 1) { - fprintf(stderr, "failed to set session\n"); - return 2; + if (!config->is_server) { + if (SSL_set_session(ssl.get(), session) != 1) { + return false; + } + } else if (config->async) { + // The internal session cache is disabled, so install the session + // manually. + GetTestState(ssl.get())->pending_session.reset( + SSL_SESSION_up_ref(session)); } } int ret; - do { + if (config->implicit_handshake) { if (config->is_server) { - ret = SSL_accept(ssl); + SSL_set_accept_state(ssl.get()); } else { - ret = SSL_connect(ssl); + SSL_set_connect_state(ssl.get()); + } + } else { + do { + if (config->is_server) { + ret = SSL_accept(ssl.get()); + } else { + ret = SSL_connect(ssl.get()); + } + } while (config->async && RetryAsync(ssl.get(), ret)); + if (ret != 1) { + return false; } - } while (config->async && retry_async(ssl, ret, bio)); - if (ret != 1) { - SSL_free(ssl); - BIO_print_errors_fp(stdout); - return 2; - } - 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; - } + if (is_resume && + (!!SSL_session_reused(ssl.get()) == config->expect_session_miss)) { + fprintf(stderr, "session was%s reused\n", + SSL_session_reused(ssl.get()) ? "" : " not"); + return false; + } - if (!config->expected_server_name.empty()) { - const char *server_name = - SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); - if (server_name != config->expected_server_name) { - fprintf(stderr, "servername mismatch (got %s; want %s)\n", - server_name, config->expected_server_name.c_str()); - return 2; + bool expect_handshake_done = is_resume || !config->false_start; + if (expect_handshake_done != GetTestState(ssl.get())->handshake_done) { + fprintf(stderr, "handshake was%s completed\n", + GetTestState(ssl.get())->handshake_done ? "" : " not"); + return false; } - if (!early_callback_called) { + if (config->is_server && !GetTestState(ssl.get())->early_callback_called) { fprintf(stderr, "early callback not called\n"); - return 2; + return false; } - } - if (!config->expected_certificate_types.empty()) { - uint8_t *certificate_types; - int num_certificate_types = - SSL_get0_certificate_types(ssl, &certificate_types); - if (num_certificate_types != - (int)config->expected_certificate_types.size() || - memcmp(certificate_types, - config->expected_certificate_types.data(), - num_certificate_types) != 0) { - fprintf(stderr, "certificate types mismatch\n"); - return 2; + if (!config->expected_server_name.empty()) { + const char *server_name = + SSL_get_servername(ssl.get(), TLSEXT_NAMETYPE_host_name); + if (server_name != config->expected_server_name) { + fprintf(stderr, "servername mismatch (got %s; want %s)\n", + server_name, config->expected_server_name.c_str()); + return false; + } } - } - if (!config->expected_next_proto.empty()) { - const uint8_t *next_proto; - unsigned next_proto_len; - SSL_get0_next_proto_negotiated(ssl, &next_proto, &next_proto_len); - if (next_proto_len != config->expected_next_proto.size() || - memcmp(next_proto, config->expected_next_proto.data(), - next_proto_len) != 0) { - fprintf(stderr, "negotiated next proto mismatch\n"); - return 2; + if (!config->expected_certificate_types.empty()) { + uint8_t *certificate_types; + int num_certificate_types = + SSL_get0_certificate_types(ssl.get(), &certificate_types); + if (num_certificate_types != + (int)config->expected_certificate_types.size() || + memcmp(certificate_types, + config->expected_certificate_types.data(), + num_certificate_types) != 0) { + fprintf(stderr, "certificate types mismatch\n"); + return false; + } } - } - 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_next_proto.empty()) { + const uint8_t *next_proto; + unsigned next_proto_len; + SSL_get0_next_proto_negotiated(ssl.get(), &next_proto, &next_proto_len); + if (next_proto_len != config->expected_next_proto.size() || + memcmp(next_proto, config->expected_next_proto.data(), + next_proto_len) != 0) { + fprintf(stderr, "negotiated next proto mismatch\n"); + return false; + } } - } - 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_alpn.empty()) { + const uint8_t *alpn_proto; + unsigned alpn_proto_len; + SSL_get0_alpn_selected(ssl.get(), &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 false; + } } - 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->expected_channel_id.empty()) { + uint8_t channel_id[64]; + if (!SSL_get_tls_channel_id(ssl.get(), channel_id, sizeof(channel_id))) { + fprintf(stderr, "no channel id negotiated\n"); + return false; + } + if (config->expected_channel_id.size() != 64 || + memcmp(config->expected_channel_id.data(), + channel_id, 64) != 0) { + fprintf(stderr, "channel id mismatch\n"); + return false; + } } - } - if (config->expect_extended_master_secret) { - if (!ssl->session->extended_master_secret) { - fprintf(stderr, "No EMS for session when expected"); - return 2; + if (config->expect_extended_master_secret) { + if (!ssl->session->extended_master_secret) { + fprintf(stderr, "No EMS for session when expected"); + return false; + } } - } - if (!config->expected_ocsp_response.empty()) { - const uint8_t *data; - size_t len; - SSL_get0_ocsp_response(ssl, &data, &len); - if (config->expected_ocsp_response.size() != len || - memcmp(config->expected_ocsp_response.data(), data, len) != 0) { - fprintf(stderr, "OCSP response mismatch\n"); - return 2; + if (!config->expected_ocsp_response.empty()) { + const uint8_t *data; + size_t len; + SSL_get0_ocsp_response(ssl.get(), &data, &len); + if (config->expected_ocsp_response.size() != len || + memcmp(config->expected_ocsp_response.data(), data, len) != 0) { + fprintf(stderr, "OCSP response mismatch\n"); + return false; + } } - } - if (!config->expected_signed_cert_timestamps.empty()) { - const uint8_t *data; - size_t len; - SSL_get0_signed_cert_timestamp_list(ssl, &data, &len); - if (config->expected_signed_cert_timestamps.size() != len || - memcmp(config->expected_signed_cert_timestamps.data(), - data, len) != 0) { - fprintf(stderr, "SCT list mismatch\n"); - return 2; + if (!config->expected_signed_cert_timestamps.empty()) { + const uint8_t *data; + size_t len; + SSL_get0_signed_cert_timestamp_list(ssl.get(), &data, &len); + if (config->expected_signed_cert_timestamps.size() != len || + memcmp(config->expected_signed_cert_timestamps.data(), + data, len) != 0) { + fprintf(stderr, "SCT list mismatch\n"); + return false; + } } } if (config->renegotiate) { if (config->async) { - fprintf(stderr, "--renegotiate is not supported with --async.\n"); - return 2; + fprintf(stderr, "-renegotiate is not supported with -async.\n"); + return false; + } + if (config->implicit_handshake) { + fprintf(stderr, "-renegotiate is not supported with -implicit-handshake.\n"); + return false; } - SSL_renegotiate(ssl); + SSL_renegotiate(ssl.get()); - ret = SSL_do_handshake(ssl); + ret = SSL_do_handshake(ssl.get()); if (ret != 1) { - SSL_free(ssl); - BIO_print_errors_fp(stdout); - return 2; + return false; } - SSL_set_state(ssl, SSL_ST_ACCEPT); - ret = SSL_do_handshake(ssl); + SSL_set_state(ssl.get(), SSL_ST_ACCEPT); + ret = SSL_do_handshake(ssl.get()); if (ret != 1) { - SSL_free(ssl); - BIO_print_errors_fp(stdout); - return 2; + return false; + } + } + + if (config->export_keying_material > 0) { + std::vector<uint8_t> result( + static_cast<size_t>(config->export_keying_material)); + if (!SSL_export_keying_material( + ssl.get(), result.data(), result.size(), + config->export_label.data(), config->export_label.size(), + reinterpret_cast<const uint8_t*>(config->export_context.data()), + config->export_context.size(), config->use_export_context)) { + fprintf(stderr, "failed to export keying material\n"); + return false; + } + if (WriteAll(ssl.get(), result.data(), result.size()) < 0) { + return false; } } if (config->write_different_record_sizes) { if (config->is_dtls) { fprintf(stderr, "write_different_record_sizes not supported for DTLS\n"); - return 6; + return false; } // This mode writes a number of different record sizes in an attempt to // trip up the CBC record splitting code. @@ -621,138 +887,123 @@ static int do_exchange(SSL_SESSION **out_session, 0, 1, 255, 256, 257, 16383, 16384, 16385, 32767, 32768, 32769}; for (size_t i = 0; i < sizeof(kRecordSizes) / sizeof(kRecordSizes[0]); i++) { - int w; const size_t len = kRecordSizes[i]; - size_t off = 0; - if (len > sizeof(buf)) { fprintf(stderr, "Bad kRecordSizes value.\n"); - return 5; + return false; } - - do { - w = SSL_write(ssl, buf + off, len - off); - if (w > 0) { - off += (size_t) w; - } - } while ((config->async && retry_async(ssl, w, bio)) || - (w > 0 && off < len)); - - if (w < 0 || off != len) { - SSL_free(ssl); - BIO_print_errors_fp(stdout); - return 4; + if (WriteAll(ssl.get(), buf, len) < 0) { + return false; } } } else { if (config->shim_writes_first) { - int w; - do { - w = SSL_write(ssl, "hello", 5); - } while (config->async && retry_async(ssl, w, bio)); + if (WriteAll(ssl.get(), reinterpret_cast<const uint8_t *>("hello"), + 5) < 0) { + return false; + } } for (;;) { uint8_t buf[512]; - int n; - do { - n = SSL_read(ssl, buf, sizeof(buf)); - } while (config->async && retry_async(ssl, n, bio)); - int err = SSL_get_error(ssl, n); + int n = DoRead(ssl.get(), buf, sizeof(buf)); + int err = SSL_get_error(ssl.get(), n); if (err == SSL_ERROR_ZERO_RETURN || (n == 0 && err == SSL_ERROR_SYSCALL)) { if (n != 0) { fprintf(stderr, "Invalid SSL_get_error output\n"); - return 3; + return false; } - /* Accept shutdowns with or without close_notify. - * TODO(davidben): Write tests which distinguish these two cases. */ + // Accept shutdowns with or without close_notify. + // TODO(davidben): Write tests which distinguish these two cases. break; } else if (err != SSL_ERROR_NONE) { if (n > 0) { fprintf(stderr, "Invalid SSL_get_error output\n"); - return 3; + return false; } - SSL_free(ssl); - BIO_print_errors_fp(stdout); - return 3; + return false; } - /* Successfully read data. */ + // Successfully read data. if (n <= 0) { fprintf(stderr, "Invalid SSL_get_error output\n"); - return 3; + return false; } + + // After a successful read, with or without False Start, the handshake + // must be complete. + if (!GetTestState(ssl.get())->handshake_done) { + fprintf(stderr, "handshake was not completed after SSL_read\n"); + return false; + } + for (int i = 0; i < n; i++) { buf[i] ^= 0xff; } - int w; - do { - w = SSL_write(ssl, buf, n); - } while (config->async && retry_async(ssl, w, bio)); - if (w != n) { - SSL_free(ssl); - BIO_print_errors_fp(stdout); - return 4; + if (WriteAll(ssl.get(), buf, n) < 0) { + return false; } } } if (out_session) { - *out_session = SSL_get1_session(ssl); + out_session->reset(SSL_get1_session(ssl.get())); } - SSL_shutdown(ssl); - SSL_free(ssl); - return 0; + SSL_shutdown(ssl.get()); + return true; } int main(int argc, char **argv) { -#if !defined(OPENSSL_WINDOWS) +#if defined(OPENSSL_WINDOWS) + /* Initialize Winsock. */ + WORD wsa_version = MAKEWORD(2, 2); + WSADATA wsa_data; + int wsa_err = WSAStartup(wsa_version, &wsa_data); + if (wsa_err != 0) { + fprintf(stderr, "WSAStartup failed: %d\n", wsa_err); + return 1; + } + if (wsa_data.wVersion != wsa_version) { + fprintf(stderr, "Didn't get expected version: %x\n", wsa_data.wVersion); + return 1; + } +#else signal(SIGPIPE, SIG_IGN); #endif if (!SSL_library_init()) { return 1; } - g_ex_data_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); - if (g_ex_data_index < 0) { + g_config_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); + g_state_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, TestStateExFree); + if (g_config_index < 0 || g_state_index < 0) { return 1; } TestConfig config; if (!ParseConfig(argc - 1, argv + 1, &config)) { - return usage(argv[0]); + return Usage(argv[0]); } - SSL_CTX *ssl_ctx = setup_ctx(&config); - if (ssl_ctx == NULL) { - BIO_print_errors_fp(stdout); + ScopedSSL_CTX ssl_ctx = SetupCtx(&config); + if (!ssl_ctx) { + ERR_print_errors_fp(stderr); return 1; } - SSL_SESSION *session = NULL; - int ret = do_exchange(&session, - ssl_ctx, &config, - false /* is_resume */, - 3 /* fd */, NULL /* session */); - if (ret != 0) { - goto out; + ScopedSSL_SESSION session; + if (!DoExchange(&session, ssl_ctx.get(), &config, false /* is_resume */, + NULL /* session */)) { + ERR_print_errors_fp(stderr); + return 1; } - if (config.resume) { - ret = do_exchange(NULL, - ssl_ctx, &config, - true /* is_resume */, - 4 /* fd */, - config.is_server ? NULL : session); - if (ret != 0) { - goto out; - } + if (config.resume && + !DoExchange(NULL, ssl_ctx.get(), &config, true /* is_resume */, + session.get())) { + ERR_print_errors_fp(stderr); + return 1; } - ret = 0; - -out: - SSL_SESSION_free(session); - SSL_CTX_free(ssl_ctx); - return ret; + return 0; } diff --git a/src/ssl/test/malloc.cc b/src/ssl/test/malloc.cc index 6cc0b33..2ec5582 100644 --- a/src/ssl/test/malloc.cc +++ b/src/ssl/test/malloc.cc @@ -14,15 +14,24 @@ #include <openssl/base.h> +#if defined(__has_feature) +#if __has_feature(address_sanitizer) +#define OPENSSL_ASAN +#endif +#endif + // This file isn't built on ARM or Aarch64 because we link statically in those -// builds and trying to override malloc in a static link doesn't work. -#if defined(__linux__) && !defined(OPENSSL_ARM) && !defined(OPENSSL_AARCH64) +// builds and trying to override malloc in a static link doesn't work. It's also +// disabled on ASan builds as this interferes with ASan's malloc interceptor. +// +// TODO(davidben): See if this and ASan's interceptors can be made to coexist. +#if defined(__linux__) && !defined(OPENSSL_ARM) && \ + !defined(OPENSSL_AARCH64) && !defined(OPENSSL_ASAN) #include <stdint.h> +#include <stdio.h> #include <stdlib.h> #include <unistd.h> -#include <unistd.h> -#include <stdio.h> #include <new> @@ -127,4 +136,4 @@ void *realloc(void *ptr, size_t size) { } // extern "C" -#endif /* defined(linux) && !ARM && !AARCH64 */ +#endif /* defined(linux) && !ARM && !AARCH64 && !ASAN */ diff --git a/src/ssl/test/packeted_bio.cc b/src/ssl/test/packeted_bio.cc index 93b2164..e831082 100644 --- a/src/ssl/test/packeted_bio.cc +++ b/src/ssl/test/packeted_bio.cc @@ -15,7 +15,8 @@ #include "packeted_bio.h" #include <assert.h> -#include <errno.h> +#include <limits.h> +#include <stdio.h> #include <string.h> #include <openssl/mem.h> @@ -23,58 +24,135 @@ namespace { -extern const BIO_METHOD packeted_bio_method; +extern const BIO_METHOD g_packeted_bio_method; + +const uint8_t kOpcodePacket = 'P'; +const uint8_t kOpcodeTimeout = 'T'; +const uint8_t kOpcodeTimeoutAck = 't'; + +// ReadAll reads |len| bytes from |bio| into |out|. It returns 1 on success and +// 0 or -1 on error. +static int ReadAll(BIO *bio, uint8_t *out, size_t len) { + while (len > 0) { + int chunk_len = INT_MAX; + if (len <= INT_MAX) { + chunk_len = (int)len; + } + int ret = BIO_read(bio, out, chunk_len); + if (ret <= 0) { + return ret; + } + out += ret; + len -= ret; + } + return 1; +} -static int packeted_write(BIO *bio, const char *in, int inl) { +static int PacketedWrite(BIO *bio, const char *in, int inl) { if (bio->next_bio == NULL) { return 0; } BIO_clear_retry_flags(bio); - // Write the length prefix. - uint8_t len_bytes[4]; - len_bytes[0] = (inl >> 24) & 0xff; - len_bytes[1] = (inl >> 16) & 0xff; - len_bytes[2] = (inl >> 8) & 0xff; - len_bytes[3] = inl & 0xff; - int ret = BIO_write(bio->next_bio, len_bytes, sizeof(len_bytes)); + // Write the header. + uint8_t header[5]; + header[0] = kOpcodePacket; + header[1] = (inl >> 24) & 0xff; + header[2] = (inl >> 16) & 0xff; + header[3] = (inl >> 8) & 0xff; + header[4] = inl & 0xff; + int ret = BIO_write(bio->next_bio, header, sizeof(header)); if (ret <= 0) { BIO_copy_next_retry(bio); return ret; } - // Write the buffer. BIOs for which this operation fails are not supported. + // Write the buffer. ret = BIO_write(bio->next_bio, in, inl); + if (ret < 0 || (inl > 0 && ret == 0)) { + BIO_copy_next_retry(bio); + return ret; + } assert(ret == inl); return ret; } -static int packeted_read(BIO *bio, char *out, int outl) { +static int PacketedRead(BIO *bio, char *out, int outl) { if (bio->next_bio == NULL) { return 0; } BIO_clear_retry_flags(bio); + // Read the opcode. + uint8_t opcode; + int ret = ReadAll(bio->next_bio, &opcode, sizeof(opcode)); + if (ret <= 0) { + BIO_copy_next_retry(bio); + return ret; + } + + if (opcode == kOpcodeTimeout) { + // Process the timeout. + uint8_t buf[8]; + ret = ReadAll(bio->next_bio, buf, sizeof(buf)); + if (ret <= 0) { + BIO_copy_next_retry(bio); + return ret; + } + uint64_t timeout = (static_cast<uint64_t>(buf[0]) << 56) | + (static_cast<uint64_t>(buf[1]) << 48) | + (static_cast<uint64_t>(buf[2]) << 40) | + (static_cast<uint64_t>(buf[3]) << 32) | + (static_cast<uint64_t>(buf[4]) << 24) | + (static_cast<uint64_t>(buf[5]) << 16) | + (static_cast<uint64_t>(buf[6]) << 8) | + static_cast<uint64_t>(buf[7]); + timeout /= 1000; // Convert nanoseconds to microseconds. + timeval *out_timeout = reinterpret_cast<timeval *>(bio->ptr); + assert(out_timeout->tv_usec == 0); + assert(out_timeout->tv_sec == 0); + out_timeout->tv_usec = timeout % 1000000; + out_timeout->tv_sec = timeout / 1000000; + + // Send an ACK to the peer. + ret = BIO_write(bio->next_bio, &kOpcodeTimeoutAck, 1); + if (ret <= 0) { + return ret; + } + assert(ret == 1); + + // Signal to the caller to retry the read, after processing the + // new clock. + BIO_set_retry_read(bio); + return -1; + } + + if (opcode != kOpcodePacket) { + fprintf(stderr, "Unknown opcode, %u\n", opcode); + return -1; + } + // Read the length prefix. uint8_t len_bytes[4]; - int ret = BIO_read(bio->next_bio, &len_bytes, sizeof(len_bytes)); + ret = ReadAll(bio->next_bio, len_bytes, sizeof(len_bytes)); if (ret <= 0) { BIO_copy_next_retry(bio); return ret; } - // BIOs for which a partial length comes back are not supported. - assert(ret == 4); uint32_t len = (len_bytes[0] << 24) | (len_bytes[1] << 16) | (len_bytes[2] << 8) | len_bytes[3]; - char *buf = (char *)OPENSSL_malloc(len); + uint8_t *buf = (uint8_t *)OPENSSL_malloc(len); if (buf == NULL) { return -1; } - ret = BIO_read(bio->next_bio, buf, len); - assert(ret == (int)len); + ret = ReadAll(bio->next_bio, buf, len); + if (ret <= 0) { + fprintf(stderr, "Packeted BIO was truncated\n"); + return -1; + } if (outl > (int)len) { outl = len; @@ -84,7 +162,7 @@ static int packeted_read(BIO *bio, char *out, int outl) { return outl; } -static long packeted_ctrl(BIO *bio, int cmd, long num, void *ptr) { +static long PacketedCtrl(BIO *bio, int cmd, long num, void *ptr) { if (bio->next_bio == NULL) { return 0; } @@ -94,12 +172,12 @@ static long packeted_ctrl(BIO *bio, int cmd, long num, void *ptr) { return ret; } -static int packeted_new(BIO *bio) { +static int PacketedNew(BIO *bio) { bio->init = 1; return 1; } -static int packeted_free(BIO *bio) { +static int PacketedFree(BIO *bio) { if (bio == NULL) { return 0; } @@ -108,28 +186,33 @@ static int packeted_free(BIO *bio) { return 1; } -static long packeted_callback_ctrl(BIO *bio, int cmd, bio_info_cb fp) { +static long PacketedCallbackCtrl(BIO *bio, int cmd, bio_info_cb fp) { if (bio->next_bio == NULL) { return 0; } return BIO_callback_ctrl(bio->next_bio, cmd, fp); } -const BIO_METHOD packeted_bio_method = { +const BIO_METHOD g_packeted_bio_method = { BIO_TYPE_FILTER, "packeted bio", - packeted_write, - packeted_read, + PacketedWrite, + PacketedRead, NULL /* puts */, NULL /* gets */, - packeted_ctrl, - packeted_new, - packeted_free, - packeted_callback_ctrl, + PacketedCtrl, + PacketedNew, + PacketedFree, + PacketedCallbackCtrl, }; } // namespace -BIO *packeted_bio_create() { - return BIO_new(&packeted_bio_method); +ScopedBIO PacketedBioCreate(timeval *out_timeout) { + ScopedBIO bio(BIO_new(&g_packeted_bio_method)); + if (!bio) { + return nullptr; + } + bio->ptr = out_timeout; + return bio; } diff --git a/src/ssl/test/packeted_bio.h b/src/ssl/test/packeted_bio.h index 384bd64..30697a5 100644 --- a/src/ssl/test/packeted_bio.h +++ b/src/ssl/test/packeted_bio.h @@ -15,18 +15,30 @@ #ifndef HEADER_PACKETED_BIO #define HEADER_PACKETED_BIO +#include <openssl/base.h> #include <openssl/bio.h> +#include "../../crypto/test/scoped_types.h" -// packeted_bio_create creates a filter BIO for testing protocols which expect -// datagram BIOs. It implements a reliable datagram socket and reads and writes -// packets by prefixing each packet with a big-endian 32-bit length. It must be -// layered over a reliable blocking stream BIO. +#if defined(OPENSSL_WINDOWS) +#pragma warning(push, 3) +#include <winsock2.h> +#pragma warning(pop) +#else +#include <sys/types.h> +#endif + + +// PacketedBioCreate creates a filter BIO which implements a reliable in-order +// blocking datagram socket. The resulting BIO, on |BIO_read|, may simulate a +// timeout which sets |*out_timeout| to the timeout and fails the read. +// |*out_timeout| must be zero on entry to |BIO_read|; it is an error to not +// apply the timeout before the next |BIO_read|. // -// Note: packeted_bio_create exists because a SOCK_DGRAM socketpair on OS X is -// does not block the caller, unlike on Linux. Writes simply fail with -// ENOBUFS. POSIX also does not guarantee that such sockets are reliable. -BIO *packeted_bio_create(); +// Note: The read timeout simulation is intended to be used with the async BIO +// wrapper. It doesn't simulate BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT, used in DTLS's +// blocking mode. +ScopedBIO PacketedBioCreate(timeval *out_timeout); #endif // HEADER_PACKETED_BIO diff --git a/src/ssl/test/runner/chacha20_poly1305.go b/src/ssl/test/runner/chacha20_poly1305.go new file mode 100644 index 0000000..42911d4 --- /dev/null +++ b/src/ssl/test/runner/chacha20_poly1305.go @@ -0,0 +1,159 @@ +package main + +import ( + "crypto/cipher" + "crypto/subtle" + "encoding/binary" + "errors" +) + +// See draft-agl-tls-chacha20poly1305-04 and +// draft-irtf-cfrg-chacha20-poly1305-10. Where the two differ, the +// draft-agl-tls-chacha20poly1305-04 variant is implemented. + +func leftRotate(a uint32, n uint) uint32 { + return (a << n) | (a >> (32 - n)) +} + +func chaChaQuarterRound(state *[16]uint32, a, b, c, d int) { + state[a] += state[b] + state[d] = leftRotate(state[d]^state[a], 16) + + state[c] += state[d] + state[b] = leftRotate(state[b]^state[c], 12) + + state[a] += state[b] + state[d] = leftRotate(state[d]^state[a], 8) + + state[c] += state[d] + state[b] = leftRotate(state[b]^state[c], 7) +} + +func chaCha20Block(state *[16]uint32, out []byte) { + var workingState [16]uint32 + copy(workingState[:], state[:]) + for i := 0; i < 10; i++ { + chaChaQuarterRound(&workingState, 0, 4, 8, 12) + chaChaQuarterRound(&workingState, 1, 5, 9, 13) + chaChaQuarterRound(&workingState, 2, 6, 10, 14) + chaChaQuarterRound(&workingState, 3, 7, 11, 15) + chaChaQuarterRound(&workingState, 0, 5, 10, 15) + chaChaQuarterRound(&workingState, 1, 6, 11, 12) + chaChaQuarterRound(&workingState, 2, 7, 8, 13) + chaChaQuarterRound(&workingState, 3, 4, 9, 14) + } + for i := 0; i < 16; i++ { + binary.LittleEndian.PutUint32(out[i*4:i*4+4], workingState[i]+state[i]) + } +} + +// sliceForAppend takes a slice and a requested number of bytes. It returns a +// slice with the contents of the given slice followed by that many bytes and a +// second slice that aliases into it and contains only the extra bytes. If the +// original slice has sufficient capacity then no allocation is performed. +func sliceForAppend(in []byte, n int) (head, tail []byte) { + if total := len(in) + n; cap(in) >= total { + head = in[:total] + } else { + head = make([]byte, total) + copy(head, in) + } + tail = head[len(in):] + return +} + +type chaCha20Poly1305 struct { + key [32]byte +} + +func newChaCha20Poly1305(key []byte) (cipher.AEAD, error) { + if len(key) != 32 { + return nil, errors.New("bad key length") + } + aead := new(chaCha20Poly1305) + copy(aead.key[:], key) + return aead, nil +} + +func (c *chaCha20Poly1305) NonceSize() int { return 8 } +func (c *chaCha20Poly1305) Overhead() int { return 16 } + +func (c *chaCha20Poly1305) chaCha20(out, in, nonce []byte, counter uint64) { + var state [16]uint32 + state[0] = 0x61707865 + state[1] = 0x3320646e + state[2] = 0x79622d32 + state[3] = 0x6b206574 + for i := 0; i < 8; i++ { + state[4+i] = binary.LittleEndian.Uint32(c.key[i*4 : i*4+4]) + } + state[14] = binary.LittleEndian.Uint32(nonce[0:4]) + state[15] = binary.LittleEndian.Uint32(nonce[4:8]) + + for i := 0; i < len(in); i += 64 { + state[12] = uint32(counter & 0xffffffff) + state[13] = uint32(counter >> 32) + + var tmp [64]byte + chaCha20Block(&state, tmp[:]) + count := 64 + if len(in)-i < count { + count = len(in) - i + } + for j := 0; j < count; j++ { + out[i+j] = in[i+j] ^ tmp[j] + } + + counter++ + } +} + +func (c *chaCha20Poly1305) poly1305(tag *[16]byte, nonce, ciphertext, additionalData []byte) { + input := make([]byte, 0, len(additionalData)+8+len(ciphertext)+8) + input = append(input, additionalData...) + input, out := sliceForAppend(input, 8) + binary.LittleEndian.PutUint64(out, uint64(len(additionalData))) + input = append(input, ciphertext...) + input, out = sliceForAppend(input, 8) + binary.LittleEndian.PutUint64(out, uint64(len(ciphertext))) + + var poly1305Key [32]byte + c.chaCha20(poly1305Key[:], poly1305Key[:], nonce, 0) + + poly1305Sum(tag, input, &poly1305Key) +} + +func (c *chaCha20Poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte { + if len(nonce) != 8 { + panic("Bad nonce length") + } + + ret, out := sliceForAppend(dst, len(plaintext)+16) + c.chaCha20(out[:len(plaintext)], plaintext, nonce, 1) + + var tag [16]byte + c.poly1305(&tag, nonce, out[:len(plaintext)], additionalData) + copy(out[len(plaintext):], tag[:]) + + return ret +} + +func (c *chaCha20Poly1305) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { + if len(nonce) != 8 { + panic("Bad nonce length") + } + if len(ciphertext) < 16 { + return nil, errors.New("chacha20: message authentication failed") + } + plaintextLen := len(ciphertext) - 16 + + var tag [16]byte + c.poly1305(&tag, nonce, ciphertext[:plaintextLen], additionalData) + if subtle.ConstantTimeCompare(tag[:], ciphertext[plaintextLen:]) != 1 { + return nil, errors.New("chacha20: message authentication failed") + } + + ret, out := sliceForAppend(dst, plaintextLen) + c.chaCha20(out, ciphertext[:plaintextLen], nonce, 1) + return ret, nil +} diff --git a/src/ssl/test/runner/chacha20_poly1305_test.go b/src/ssl/test/runner/chacha20_poly1305_test.go new file mode 100644 index 0000000..726f482 --- /dev/null +++ b/src/ssl/test/runner/chacha20_poly1305_test.go @@ -0,0 +1,99 @@ +package main + +import ( + "bytes" + "encoding/hex" + "testing" +) + +// See draft-irtf-cfrg-chacha20-poly1305-10, section 2.1.1. +func TestChaChaQuarterRound(t *testing.T) { + state := [16]uint32{0x11111111, 0x01020304, 0x9b8d6f43, 0x01234567} + chaChaQuarterRound(&state, 0, 1, 2, 3) + + a, b, c, d := state[0], state[1], state[2], state[3] + if a != 0xea2a92f4 || b != 0xcb1cf8ce || c != 0x4581472e || d != 0x5881c4bb { + t.Errorf("Incorrect results: %x", state) + } +} + +// See draft-irtf-cfrg-chacha20-poly1305-10, section 2.2.1. +func TestChaChaQuarterRoundState(t *testing.T) { + state := [16]uint32{ + 0x879531e0, 0xc5ecf37d, 0x516461b1, 0xc9a62f8a, + 0x44c20ef3, 0x3390af7f, 0xd9fc690b, 0x2a5f714c, + 0x53372767, 0xb00a5631, 0x974c541a, 0x359e9963, + 0x5c971061, 0x3d631689, 0x2098d9d6, 0x91dbd320, + } + chaChaQuarterRound(&state, 2, 7, 8, 13) + + expected := [16]uint32{ + 0x879531e0, 0xc5ecf37d, 0xbdb886dc, 0xc9a62f8a, + 0x44c20ef3, 0x3390af7f, 0xd9fc690b, 0xcfacafd2, + 0xe46bea80, 0xb00a5631, 0x974c541a, 0x359e9963, + 0x5c971061, 0xccc07c79, 0x2098d9d6, 0x91dbd320, + } + for i := range state { + if state[i] != expected[i] { + t.Errorf("Mismatch at %d: %x vs %x", i, state, expected) + } + } +} + +// See draft-irtf-cfrg-chacha20-poly1305-10, section 2.3.2. +func TestChaCha20Block(t *testing.T) { + state := [16]uint32{ + 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, + 0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c, + 0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c, + 0x00000001, 0x09000000, 0x4a000000, 0x00000000, + } + out := make([]byte, 64) + chaCha20Block(&state, out) + + expected := []byte{ + 0x10, 0xf1, 0xe7, 0xe4, 0xd1, 0x3b, 0x59, 0x15, + 0x50, 0x0f, 0xdd, 0x1f, 0xa3, 0x20, 0x71, 0xc4, + 0xc7, 0xd1, 0xf4, 0xc7, 0x33, 0xc0, 0x68, 0x03, + 0x04, 0x22, 0xaa, 0x9a, 0xc3, 0xd4, 0x6c, 0x4e, + 0xd2, 0x82, 0x64, 0x46, 0x07, 0x9f, 0xaa, 0x09, + 0x14, 0xc2, 0xd7, 0x05, 0xd9, 0x8b, 0x02, 0xa2, + 0xb5, 0x12, 0x9c, 0xd1, 0xde, 0x16, 0x4e, 0xb9, + 0xcb, 0xd0, 0x83, 0xe8, 0xa2, 0x50, 0x3c, 0x4e, + } + if !bytes.Equal(out, expected) { + t.Errorf("Got %x, wanted %x", out, expected) + } +} + +// See draft-agl-tls-chacha20poly1305-04, section 7. +func TestChaCha20Poly1305(t *testing.T) { + key, _ := hex.DecodeString("4290bcb154173531f314af57f3be3b5006da371ece272afa1b5dbdd1100a1007") + input, _ := hex.DecodeString("86d09974840bded2a5ca") + nonce, _ := hex.DecodeString("cd7cf67be39c794a") + ad, _ := hex.DecodeString("87e229d4500845a079c0") + output, _ := hex.DecodeString("e3e446f7ede9a19b62a4677dabf4e3d24b876bb284753896e1d6") + + aead, err := newChaCha20Poly1305(key) + if err != nil { + t.Fatal(err) + } + + out, err := aead.Open(nil, nonce, output, ad) + if err != nil { + t.Errorf("Open failed: %s", err) + } else if !bytes.Equal(out, input) { + t.Errorf("Open gave %x, wanted %x", out, input) + } + + out = aead.Seal(nil, nonce, input, ad) + if !bytes.Equal(out, output) { + t.Errorf("Open gave %x, wanted %x", out, output) + } + + out[0]++ + _, err = aead.Open(nil, nonce, out, ad) + if err == nil { + t.Errorf("Open on malformed data unexpectedly succeeded") + } +} diff --git a/src/ssl/test/runner/cipher_suites.go b/src/ssl/test/runner/cipher_suites.go index 89e75c8..162c0c0 100644 --- a/src/ssl/test/runner/cipher_suites.go +++ b/src/ssl/test/runner/cipher_suites.go @@ -62,6 +62,11 @@ const ( suitePSK ) +type tlsAead struct { + cipher.AEAD + explicitNonce bool +} + // A cipherSuite is a specific combination of key agreement, cipher and MAC // function. All cipher suites currently assume RSA key agreement. type cipherSuite struct { @@ -75,12 +80,14 @@ type cipherSuite struct { flags int cipher func(key, iv []byte, isRead bool) interface{} mac func(version uint16, macKey []byte) macFunction - aead func(key, fixedNonce []byte) cipher.AEAD + aead func(key, fixedNonce []byte) *tlsAead } var cipherSuites = []*cipherSuite{ // Ciphersuite order is chosen so that ECDHE comes before plain RSA // and RC4 comes before AES (because of the Lucky13 attack). + {TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 32, 0, 0, ecdheECDSAKA, suiteECDHE | suiteECDSA | suiteTLS12, nil, nil, aeadCHACHA20POLY1305}, + {TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 32, 0, 0, ecdheRSAKA, suiteECDHE | suiteTLS12, nil, nil, aeadCHACHA20POLY1305}, {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}, @@ -95,6 +102,7 @@ var cipherSuites = []*cipherSuite{ {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_CHACHA20_POLY1305_SHA256, 32, 0, 0, dheRSAKA, suiteTLS12, nil, nil, aeadCHACHA20POLY1305}, {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}, @@ -216,7 +224,7 @@ func (f *fixedNonceAEAD) Open(out, nonce, plaintext, additionalData []byte) ([]b return f.aead.Open(out, f.openNonce, plaintext, additionalData) } -func aeadAESGCM(key, fixedNonce []byte) cipher.AEAD { +func aeadAESGCM(key, fixedNonce []byte) *tlsAead { aes, err := aes.NewCipher(key) if err != nil { panic(err) @@ -230,7 +238,15 @@ func aeadAESGCM(key, fixedNonce []byte) cipher.AEAD { copy(nonce1, fixedNonce) copy(nonce2, fixedNonce) - return &fixedNonceAEAD{nonce1, nonce2, aead} + return &tlsAead{&fixedNonceAEAD{nonce1, nonce2, aead}, true} +} + +func aeadCHACHA20POLY1305(key, fixedNonce []byte) *tlsAead { + aead, err := newChaCha20Poly1305(key) + if err != nil { + panic(err) + } + return &tlsAead{aead, false} } // ssl30MAC implements the SSLv3 MAC function, as defined in @@ -289,7 +305,7 @@ func (s tls10MAC) MAC(digestBuf, seq, header, length, data []byte) []byte { } func rsaKA(version uint16) keyAgreement { - return &rsaKeyAgreement{} + return &rsaKeyAgreement{version: version} } func ecdheECDSAKA(version uint16) keyAgreement { @@ -391,5 +407,8 @@ const ( // Additional cipher suite IDs, not IANA-assigned. const ( - TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256 uint16 = 0xcafe + TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256 uint16 = 0xcafe + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xcc13 + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xcc14 + TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xcc15 ) diff --git a/src/ssl/test/runner/common.go b/src/ssl/test/runner/common.go index 7aaf9a2..4ac7250 100644 --- a/src/ssl/test/runner/common.go +++ b/src/ssl/test/runner/common.go @@ -97,6 +97,7 @@ const ( type CurveID uint16 const ( + CurveP224 CurveID = 21 CurveP256 CurveID = 23 CurveP384 CurveID = 24 CurveP521 CurveID = 25 @@ -429,15 +430,32 @@ type ProtocolBugs struct { // ServerKeyExchange. UnauthenticatedECDH bool + // SkipHelloVerifyRequest causes a DTLS server to skip the + // HelloVerifyRequest message. + SkipHelloVerifyRequest bool + + // SkipCertificateStatus, if true, causes the server to skip the + // CertificateStatus message. This is legal because CertificateStatus is + // optional, even with a status_request in ServerHello. + SkipCertificateStatus bool + // SkipServerKeyExchange causes the server to skip sending // ServerKeyExchange messages. SkipServerKeyExchange bool + // SkipNewSessionTicket causes the server to skip sending the + // NewSessionTicket message despite promising to in ServerHello. + SkipNewSessionTicket bool + // SkipChangeCipherSpec causes the implementation to skip // sending the ChangeCipherSpec message (and adjusting cipher // state accordingly for the Finished message). SkipChangeCipherSpec bool + // SkipFinished causes the implementation to skip sending the Finished + // message. + SkipFinished bool + // EarlyChangeCipherSpec causes the client to send an early // ChangeCipherSpec message before the ClientKeyExchange. A value of // zero disables this behavior. One and two configure variants for 0.9.8 @@ -449,10 +467,6 @@ type ProtocolBugs struct { // messages. FragmentAcrossChangeCipherSpec bool - // SkipNewSessionTicket causes the server to skip sending the - // NewSessionTicket message despite promising to in ServerHello. - SkipNewSessionTicket bool - // SendV2ClientHello causes the client to send a V2ClientHello // instead of a normal ClientHello. SendV2ClientHello bool @@ -475,8 +489,9 @@ type ProtocolBugs struct { // two records. FragmentAlert bool - // SendSpuriousAlert will cause an spurious, unwanted alert to be sent. - SendSpuriousAlert bool + // SendSpuriousAlert, if non-zero, will cause an spurious, unwanted + // alert to be sent. + SendSpuriousAlert alert // RsaClientKeyExchangeVersion, if non-zero, causes the client to send a // ClientKeyExchange with the specified version rather than the @@ -491,16 +506,19 @@ type ProtocolBugs struct { // TLS version in the ClientHello than the maximum supported version. SendClientVersion uint16 - // 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 + // AlertBeforeFalseStartTest, if non-zero, causes the server to, on full + // handshakes, send an alert just before reading the application data + // record to test False Start. This can be used in a negative False + // Start test to determine whether the peer processed the alert (and + // closed the connection) before or after sending app data. + AlertBeforeFalseStartTest alert + // SSL3RSAKeyExchange causes the client to always send an RSA // ClientKeyExchange message without the two-byte length // prefix, as if it were SSL3. @@ -557,9 +575,10 @@ type ProtocolBugs struct { // retransmit at the record layer. SequenceNumberIncrement uint64 - // RSAServerKeyExchange, if true, causes the server to send a - // ServerKeyExchange message in the plain RSA key exchange. - RSAServerKeyExchange bool + // RSAEphemeralKey, if true, causes the server to send a + // ServerKeyExchange message containing an ephemeral key (as in + // RSA_EXPORT) in the plain RSA key exchange. + RSAEphemeralKey bool // SRTPMasterKeyIdentifer, if not empty, is the SRTP MKI value that the // client offers when negotiating SRTP. MKI support is still missing so @@ -578,6 +597,10 @@ type ProtocolBugs struct { // still be enforced. NoSignatureAndHashes bool + // NoSupportedCurves, if true, causes the client to omit the + // supported_curves extension. + NoSupportedCurves bool + // RequireSameRenegoClientVersion, if true, causes the server // to require that all ClientHellos match in offered version // across a renego. @@ -603,6 +626,87 @@ type ProtocolBugs struct { // AppDataAfterChangeCipherSpec, if not null, causes application data to // be sent immediately after ChangeCipherSpec. AppDataAfterChangeCipherSpec []byte + + // AlertAfterChangeCipherSpec, if non-zero, causes an alert to be sent + // immediately after ChangeCipherSpec. + AlertAfterChangeCipherSpec alert + + // TimeoutSchedule is the schedule of packet drops and simulated + // timeouts for before each handshake leg from the peer. + TimeoutSchedule []time.Duration + + // PacketAdaptor is the packetAdaptor to use to simulate timeouts. + PacketAdaptor *packetAdaptor + + // ReorderHandshakeFragments, if true, causes handshake fragments in + // DTLS to overlap and be sent in the wrong order. It also causes + // pre-CCS flights to be sent twice. (Post-CCS flights consist of + // Finished and will trigger a spurious retransmit.) + ReorderHandshakeFragments bool + + // MixCompleteMessageWithFragments, if true, causes handshake + // messages in DTLS to redundantly both fragment the message + // and include a copy of the full one. + MixCompleteMessageWithFragments bool + + // SendInvalidRecordType, if true, causes a record with an invalid + // content type to be sent immediately following the handshake. + SendInvalidRecordType bool + + // WrongCertificateMessageType, if true, causes Certificate message to + // be sent with the wrong message type. + WrongCertificateMessageType bool + + // FragmentMessageTypeMismatch, if true, causes all non-initial + // handshake fragments in DTLS to have the wrong message type. + FragmentMessageTypeMismatch bool + + // FragmentMessageLengthMismatch, if true, causes all non-initial + // handshake fragments in DTLS to have the wrong message length. + FragmentMessageLengthMismatch bool + + // SplitFragmentHeader, if true, causes the handshake fragments in DTLS + // to be split across two records. + SplitFragmentHeader bool + + // SplitFragmentBody, if true, causes the handshake bodies in DTLS to be + // split across two records. + // + // TODO(davidben): There's one final split to test: when the header and + // body are split across two records. But those are (incorrectly) + // accepted right now. + SplitFragmentBody bool + + // SendEmptyFragments, if true, causes handshakes to include empty + // fragments in DTLS. + SendEmptyFragments bool + + // NeverResumeOnRenego, if true, causes renegotiations to always be full + // handshakes. + NeverResumeOnRenego bool + + // NoSignatureAlgorithmsOnRenego, if true, causes renegotiations to omit + // the signature_algorithms extension. + NoSignatureAlgorithmsOnRenego bool + + // IgnorePeerCipherPreferences, if true, causes the peer's cipher + // preferences to be ignored. + IgnorePeerCipherPreferences bool + + // IgnorePeerSignatureAlgorithmPreferences, if true, causes the peer's + // signature algorithm preferences to be ignored. + IgnorePeerSignatureAlgorithmPreferences bool + + // IgnorePeerCurvePreferences, if true, causes the peer's curve + // preferences to be ignored. + IgnorePeerCurvePreferences bool + + // SendWarningAlerts, if non-zero, causes every record to be prefaced by + // a warning alert. + SendWarningAlerts alert + + // BadFinished, if true, causes the Finished hash to be broken. + BadFinished bool } func (c *Config) serverInit() { diff --git a/src/ssl/test/runner/conn.go b/src/ssl/test/runner/conn.go index d4a6817..fd198ca 100644 --- a/src/ssl/test/runner/conn.go +++ b/src/ssl/test/runner/conn.go @@ -37,14 +37,16 @@ type Conn struct { handshakeComplete bool didResume bool // whether this connection was a session resumption extendedMasterSecret bool // whether this session used an extended master secret - cipherSuite uint16 + cipherSuite *cipherSuite ocspResponse []byte // stapled OCSP response peerCertificates []*x509.Certificate // verifiedChains contains the certificate chains that we built, as // opposed to the ones presented by the server. verifiedChains [][]*x509.Certificate // serverName contains the server name indicated by the client, if any. - serverName string + serverName string + clientRandom, serverRandom [32]byte + masterSecret [48]byte clientProtocol string clientProtocolFallback bool @@ -69,8 +71,9 @@ type Conn struct { // DTLS state sendHandshakeSeq uint16 recvHandshakeSeq uint16 - handMsg []byte // pending assembled handshake message - handMsgLen int // handshake message length, not including the header + handMsg []byte // pending assembled handshake message + handMsgLen int // handshake message length, not including the header + pendingFragments [][]byte // pending outgoing handshake fragments. tmp [16]byte } @@ -131,6 +134,7 @@ type halfConn struct { nextCipher interface{} // next encryption state nextMac macFunction // next MAC algorithm + nextSeq [6]byte // next epoch's starting sequence number in DTLS // used to save allocating a new buffer for each MAC. inDigestBuf, outDigestBuf []byte @@ -200,10 +204,20 @@ func (hc *halfConn) incSeq(isOutgoing bool) { } } -// incEpoch resets the sequence number. In DTLS, it increments the -// epoch half of the sequence number. +// incNextSeq increments the starting sequence number for the next epoch. +func (hc *halfConn) incNextSeq() { + for i := len(hc.nextSeq) - 1; i >= 0; i-- { + hc.nextSeq[i]++ + if hc.nextSeq[i] != 0 { + return + } + } + panic("TLS: sequence number wraparound") +} + +// incEpoch resets the sequence number. In DTLS, it also increments the epoch +// half of the sequence number. func (hc *halfConn) incEpoch() { - limit := 0 if hc.isDTLS { for i := 1; i >= 0; i-- { hc.seq[i]++ @@ -214,11 +228,14 @@ func (hc *halfConn) incEpoch() { panic("TLS: epoch number wraparound") } } - limit = 2 - } - seq := hc.seq[limit:] - for i := range seq { - seq[i] = 0 + copy(hc.seq[2:], hc.nextSeq[:]) + for i := range hc.nextSeq { + hc.nextSeq[i] = 0 + } + } else { + for i := range hc.seq { + hc.seq[i] = 0 + } } } @@ -321,13 +338,16 @@ func (hc *halfConn) decrypt(b *block) (ok bool, prefixLen int, alertValue alert) switch c := hc.cipher.(type) { case cipher.Stream: c.XORKeyStream(payload, payload) - case cipher.AEAD: - explicitIVLen = 8 - if len(payload) < explicitIVLen { - return false, 0, alertBadRecordMAC + case *tlsAead: + nonce := seq + if c.explicitNonce { + explicitIVLen = 8 + if len(payload) < explicitIVLen { + return false, 0, alertBadRecordMAC + } + nonce = payload[:8] + payload = payload[8:] } - nonce := payload[:8] - payload = payload[8:] var additionalData [13]byte copy(additionalData[:], seq) @@ -451,10 +471,13 @@ func (hc *halfConn) encrypt(b *block, explicitIVLen int) (bool, alert) { switch c := hc.cipher.(type) { case cipher.Stream: c.XORKeyStream(payload, payload) - case cipher.AEAD: + case *tlsAead: payloadLen := len(b.data) - recordHeaderLen - explicitIVLen b.resize(len(b.data) + c.Overhead()) - nonce := b.data[recordHeaderLen : recordHeaderLen+explicitIVLen] + nonce := hc.seq[:] + if c.explicitNonce { + nonce = b.data[recordHeaderLen : recordHeaderLen+explicitIVLen] + } payload := b.data[recordHeaderLen+explicitIVLen:] payload = payload[:payloadLen] @@ -756,11 +779,8 @@ Again: if typ != want { // A client might need to process a HelloRequest from // the server, thus receiving a handshake message when - // application data is expected is ok. Moreover, a DTLS - // peer who sends Finished second may retransmit the - // final leg. BoringSSL retrainsmits on an internal - // timer, so this may also occur in test code. - if !c.isClient && !c.isDTLS { + // application data is expected is ok. + if !c.isClient { return c.in.setErrorLocked(c.sendAlert(alertNoRenegotiation)) } } @@ -817,6 +837,13 @@ func (c *Conn) writeV2Record(data []byte) (n int, err error) { // to the connection and updates the record layer state. // c.out.Mutex <= L. func (c *Conn) writeRecord(typ recordType, data []byte) (n int, err error) { + if typ != recordTypeAlert && c.config.Bugs.SendWarningAlerts != 0 { + alert := make([]byte, 2) + alert[0] = alertLevelWarning + alert[1] = byte(c.config.Bugs.SendWarningAlerts) + c.writeRecord(recordTypeAlert, alert) + } + if c.isDTLS { return c.dtlsWriteRecord(typ, data) } @@ -851,7 +878,7 @@ func (c *Conn) writeRecord(typ recordType, data []byte) (n int, err error) { } } if explicitIVLen == 0 { - if _, ok := c.out.cipher.(cipher.AEAD); ok { + if aead, ok := c.out.cipher.(*tlsAead); ok && aead.explicitNonce { explicitIVLen = 8 // The AES-GCM construction in TLS has an // explicit nonce so that the nonce can be @@ -1003,6 +1030,67 @@ func (c *Conn) readHandshake() (interface{}, error) { return m, nil } +// skipPacket processes all the DTLS records in packet. It updates +// sequence number expectations but otherwise ignores them. +func (c *Conn) skipPacket(packet []byte) error { + for len(packet) > 0 { + // Dropped packets are completely ignored save to update + // expected sequence numbers for this and the next epoch. (We + // don't assert on the contents of the packets both for + // simplicity and because a previous test with one shorter + // timeout schedule would have done so.) + epoch := packet[3:5] + seq := packet[5:11] + length := uint16(packet[11])<<8 | uint16(packet[12]) + if bytes.Equal(c.in.seq[:2], epoch) { + if !bytes.Equal(c.in.seq[2:], seq) { + return errors.New("tls: sequence mismatch") + } + c.in.incSeq(false) + } else { + if !bytes.Equal(c.in.nextSeq[:], seq) { + return errors.New("tls: sequence mismatch") + } + c.in.incNextSeq() + } + packet = packet[13+length:] + } + return nil +} + +// simulatePacketLoss simulates the loss of a handshake leg from the +// peer based on the schedule in c.config.Bugs. If resendFunc is +// non-nil, it is called after each simulated timeout to retransmit +// handshake messages from the local end. This is used in cases where +// the peer retransmits on a stale Finished rather than a timeout. +func (c *Conn) simulatePacketLoss(resendFunc func()) error { + if len(c.config.Bugs.TimeoutSchedule) == 0 { + return nil + } + if !c.isDTLS { + return errors.New("tls: TimeoutSchedule may only be set in DTLS") + } + if c.config.Bugs.PacketAdaptor == nil { + return errors.New("tls: TimeoutSchedule set without PacketAdapter") + } + for _, timeout := range c.config.Bugs.TimeoutSchedule { + // Simulate a timeout. + packets, err := c.config.Bugs.PacketAdaptor.SendReadTimeout(timeout) + if err != nil { + return err + } + for _, packet := range packets { + if err := c.skipPacket(packet); err != nil { + return err + } + } + if resendFunc != nil { + resendFunc() + } + } + return nil +} + // Write writes data to the connection. func (c *Conn) Write(b []byte) (int, error) { if err := c.Handshake(); err != nil { @@ -1020,8 +1108,8 @@ func (c *Conn) Write(b []byte) (int, error) { return 0, alertInternalError } - if c.config.Bugs.SendSpuriousAlert { - c.sendAlertLocked(alertRecordOverflow) + if c.config.Bugs.SendSpuriousAlert != 0 { + c.sendAlertLocked(c.config.Bugs.SendSpuriousAlert) } // SSL 3.0 and TLS 1.0 are susceptible to a chosen-plaintext @@ -1096,9 +1184,9 @@ func (c *Conn) Read(b []byte) (n int, err error) { // Soft error, like EAGAIN return 0, err } - if c.hand.Len() > 0 && !c.isDTLS { + if c.hand.Len() > 0 { // We received handshake bytes, indicating the - // start of a renegotiation or a DTLS retransmit. + // start of a renegotiation. if err := c.handleRenegotiation(); err != nil { return 0, err } @@ -1177,6 +1265,9 @@ func (c *Conn) Handshake() error { } else { c.handshakeErr = c.serverHandshake() } + if c.handshakeErr == nil && c.config.Bugs.SendInvalidRecordType { + c.writeRecord(recordType(42), []byte("invalid record")) + } return c.handshakeErr } @@ -1193,7 +1284,7 @@ func (c *Conn) ConnectionState() ConnectionState { state.DidResume = c.didResume state.NegotiatedProtocolIsMutual = !c.clientProtocolFallback state.NegotiatedProtocolFromALPN = c.usedALPN - state.CipherSuite = c.cipherSuite + state.CipherSuite = c.cipherSuite.id state.PeerCertificates = c.peerCertificates state.VerifiedChains = c.verifiedChains state.ServerName = c.serverName @@ -1227,3 +1318,28 @@ func (c *Conn) VerifyHostname(host string) error { } return c.peerCertificates[0].VerifyHostname(host) } + +// ExportKeyingMaterial exports keying material from the current connection +// state, as per RFC 5705. +func (c *Conn) ExportKeyingMaterial(length int, label, context []byte, useContext bool) ([]byte, error) { + c.handshakeMutex.Lock() + defer c.handshakeMutex.Unlock() + if !c.handshakeComplete { + return nil, errors.New("tls: handshake has not yet been performed") + } + + seedLen := len(c.clientRandom) + len(c.serverRandom) + if useContext { + seedLen += 2 + len(context) + } + seed := make([]byte, 0, seedLen) + seed = append(seed, c.clientRandom[:]...) + seed = append(seed, c.serverRandom[:]...) + if useContext { + seed = append(seed, byte(len(context)>>8), byte(len(context))) + seed = append(seed, context...) + } + result := make([]byte, length) + prfForVersion(c.vers, c.cipherSuite)(result, c.masterSecret[:], label, seed) + return result, nil +} diff --git a/src/ssl/test/runner/dtls.go b/src/ssl/test/runner/dtls.go index a395980..85c4247 100644 --- a/src/ssl/test/runner/dtls.go +++ b/src/ssl/test/runner/dtls.go @@ -16,10 +16,10 @@ package main import ( "bytes" - "crypto/cipher" "errors" "fmt" "io" + "math/rand" "net" ) @@ -38,7 +38,6 @@ func wireToVersion(vers uint16, isDTLS bool) uint16 { } func (c *Conn) dtlsDoReadRecord(want recordType) (recordType, *block, error) { -Again: recordHeaderLen := dtlsRecordHeaderLen if c.rawInput == nil { @@ -82,13 +81,6 @@ Again: } } seq := b.data[3:11] - if !bytes.Equal(seq[:2], c.in.seq[:2]) { - // If the epoch didn't match, silently drop the record. - // BoringSSL retransmits on an internal timer, so it may flakily - // revisit the previous epoch if retransmiting ChangeCipherSpec - // and Finished. - goto Again - } // For test purposes, we assume a reliable channel. Require // that the explicit sequence number matches the incrementing // one we maintain. A real implementation would maintain a @@ -113,127 +105,196 @@ Again: return typ, b, nil } +func (c *Conn) makeFragment(header, data []byte, fragOffset, fragLen int) []byte { + fragment := make([]byte, 0, 12+fragLen) + fragment = append(fragment, header...) + fragment = append(fragment, byte(c.sendHandshakeSeq>>8), byte(c.sendHandshakeSeq)) + fragment = append(fragment, byte(fragOffset>>16), byte(fragOffset>>8), byte(fragOffset)) + fragment = append(fragment, byte(fragLen>>16), byte(fragLen>>8), byte(fragLen)) + fragment = append(fragment, data[fragOffset:fragOffset+fragLen]...) + return fragment +} + func (c *Conn) dtlsWriteRecord(typ recordType, data []byte) (n int, err error) { - recordHeaderLen := dtlsRecordHeaderLen + if typ != recordTypeHandshake { + // Only handshake messages are fragmented. + return c.dtlsWriteRawRecord(typ, data) + } + maxLen := c.config.Bugs.MaxHandshakeRecordLength if maxLen <= 0 { maxLen = 1024 } - b := c.out.newBlock() + // Handshake messages have to be modified to include fragment + // offset and length and with the header replicated. Save the + // TLS header here. + // + // TODO(davidben): This assumes that data contains exactly one + // handshake message. This is incompatible with + // FragmentAcrossChangeCipherSpec. (Which is unfortunate + // because OpenSSL's DTLS implementation will probably accept + // such fragmentation and could do with a fix + tests.) + header := data[:4] + data = data[4:] - var header []byte - if typ == recordTypeHandshake { - // Handshake messages have to be modified to include - // fragment offset and length and with the header - // replicated. Save the header here. - // - // TODO(davidben): This assumes that data contains - // exactly one handshake message. This is incompatible - // with FragmentAcrossChangeCipherSpec. (Which is - // unfortunate because OpenSSL's DTLS implementation - // will probably accept such fragmentation and could - // do with a fix + tests.) - if len(data) < 4 { - // This should not happen. - panic(data) - } - header = data[:4] - data = data[4:] + isFinished := header[0] == typeFinished + + if c.config.Bugs.SendEmptyFragments { + fragment := c.makeFragment(header, data, 0, 0) + c.pendingFragments = append(c.pendingFragments, fragment) } firstRun := true - for firstRun || len(data) > 0 { + fragOffset := 0 + for firstRun || fragOffset < len(data) { firstRun = false - m := len(data) - var fragment []byte - // Handshake messages get fragmented. Other records we - // pass-through as is. DTLS should be a packet - // interface. - if typ == recordTypeHandshake { - if m > maxLen { - m = maxLen - } + fragLen := len(data) - fragOffset + if fragLen > maxLen { + fragLen = maxLen + } - // Standard handshake header. - fragment = make([]byte, 0, 12+m) - fragment = append(fragment, header...) - // message_seq - fragment = append(fragment, byte(c.sendHandshakeSeq>>8), byte(c.sendHandshakeSeq)) - // fragment_offset - fragment = append(fragment, byte(n>>16), byte(n>>8), byte(n)) - // fragment_length - fragment = append(fragment, byte(m>>16), byte(m>>8), byte(m)) - fragment = append(fragment, data[:m]...) - } else { - fragment = data[:m] + fragment := c.makeFragment(header, data, fragOffset, fragLen) + if c.config.Bugs.FragmentMessageTypeMismatch && fragOffset > 0 { + fragment[0]++ + } + if c.config.Bugs.FragmentMessageLengthMismatch && fragOffset > 0 { + fragment[3]++ } - // Send the fragment. - explicitIVLen := 0 - explicitIVIsSeq := false + // Buffer the fragment for later. They will be sent (and + // reordered) on flush. + c.pendingFragments = append(c.pendingFragments, fragment) + if c.config.Bugs.ReorderHandshakeFragments { + // Don't duplicate Finished to avoid the peer + // interpreting it as a retransmit request. + if !isFinished { + c.pendingFragments = append(c.pendingFragments, fragment) + } - if cbc, ok := c.out.cipher.(cbcMode); ok { - // Block cipher modes have an explicit IV. - explicitIVLen = cbc.BlockSize() - } else if _, ok := c.out.cipher.(cipher.AEAD); ok { - explicitIVLen = 8 - // The AES-GCM construction in TLS has an - // explicit nonce so that the nonce can be - // random. However, the nonce is only 8 bytes - // which is too small for a secure, random - // nonce. Therefore we use the sequence number - // as the nonce. - explicitIVIsSeq = true - } else if c.out.cipher != nil { - panic("Unknown cipher") + if fragLen > (maxLen+1)/2 { + // Overlap each fragment by half. + fragLen = (maxLen + 1) / 2 + } } - b.resize(recordHeaderLen + explicitIVLen + len(fragment)) - b.data[0] = byte(typ) - vers := c.vers - if vers == 0 { - // Some TLS servers fail if the record version is - // greater than TLS 1.0 for the initial ClientHello. - vers = VersionTLS10 + fragOffset += fragLen + n += fragLen + } + if !isFinished && c.config.Bugs.MixCompleteMessageWithFragments { + fragment := c.makeFragment(header, data, 0, len(data)) + c.pendingFragments = append(c.pendingFragments, fragment) + } + + // Increment the handshake sequence number for the next + // handshake message. + c.sendHandshakeSeq++ + return +} + +func (c *Conn) dtlsFlushHandshake() error { + if !c.isDTLS { + return nil + } + + var fragments [][]byte + fragments, c.pendingFragments = c.pendingFragments, fragments + + if c.config.Bugs.ReorderHandshakeFragments { + perm := rand.New(rand.NewSource(0)).Perm(len(fragments)) + tmp := make([][]byte, len(fragments)) + for i := range tmp { + tmp[i] = fragments[perm[i]] } - vers = versionToWire(vers, c.isDTLS) - b.data[1] = byte(vers >> 8) - b.data[2] = byte(vers) - // DTLS records include an explicit sequence number. - copy(b.data[3:11], c.out.seq[0:]) - b.data[11] = byte(len(fragment) >> 8) - b.data[12] = byte(len(fragment)) - if explicitIVLen > 0 { - explicitIV := b.data[recordHeaderLen : recordHeaderLen+explicitIVLen] - if explicitIVIsSeq { - copy(explicitIV, c.out.seq[:]) - } else { - if _, err = io.ReadFull(c.config.rand(), explicitIV); err != nil { - break - } + fragments = tmp + } + + // Send them all. + for _, fragment := range fragments { + if c.config.Bugs.SplitFragmentHeader { + if _, err := c.dtlsWriteRawRecord(recordTypeHandshake, fragment[:2]); err != nil { + return err } + fragment = fragment[2:] + } else if c.config.Bugs.SplitFragmentBody && len(fragment) > 12 { + if _, err := c.dtlsWriteRawRecord(recordTypeHandshake, fragment[:13]); err != nil { + return err + } + fragment = fragment[13:] } - copy(b.data[recordHeaderLen+explicitIVLen:], fragment) - c.out.encrypt(b, explicitIVLen) // TODO(davidben): A real DTLS implementation needs to - // retransmit handshake messages. For testing - // purposes, we don't actually care. - _, err = c.conn.Write(b.data) - if err != nil { - break + // retransmit handshake messages. For testing purposes, we don't + // actually care. + if _, err := c.dtlsWriteRawRecord(recordTypeHandshake, fragment); err != nil { + return err } - n += m - data = data[m:] } - c.out.freeBlock(b) + return nil +} - // Increment the handshake sequence number for the next - // handshake message. - if typ == recordTypeHandshake { - c.sendHandshakeSeq++ +func (c *Conn) dtlsWriteRawRecord(typ recordType, data []byte) (n int, err error) { + recordHeaderLen := dtlsRecordHeaderLen + maxLen := c.config.Bugs.MaxHandshakeRecordLength + if maxLen <= 0 { + maxLen = 1024 } + b := c.out.newBlock() + + explicitIVLen := 0 + explicitIVIsSeq := false + + if cbc, ok := c.out.cipher.(cbcMode); ok { + // Block cipher modes have an explicit IV. + explicitIVLen = cbc.BlockSize() + } else if aead, ok := c.out.cipher.(*tlsAead); ok { + if aead.explicitNonce { + explicitIVLen = 8 + // The AES-GCM construction in TLS has an explicit nonce so that + // the nonce can be random. However, the nonce is only 8 bytes + // which is too small for a secure, random nonce. Therefore we + // use the sequence number as the nonce. + explicitIVIsSeq = true + } + } else if c.out.cipher != nil { + panic("Unknown cipher") + } + b.resize(recordHeaderLen + explicitIVLen + len(data)) + b.data[0] = byte(typ) + vers := c.vers + if vers == 0 { + // Some TLS servers fail if the record version is greater than + // TLS 1.0 for the initial ClientHello. + vers = VersionTLS10 + } + vers = versionToWire(vers, c.isDTLS) + b.data[1] = byte(vers >> 8) + b.data[2] = byte(vers) + // DTLS records include an explicit sequence number. + copy(b.data[3:11], c.out.seq[0:]) + b.data[11] = byte(len(data) >> 8) + b.data[12] = byte(len(data)) + if explicitIVLen > 0 { + explicitIV := b.data[recordHeaderLen : recordHeaderLen+explicitIVLen] + if explicitIVIsSeq { + copy(explicitIV, c.out.seq[:]) + } else { + if _, err = io.ReadFull(c.config.rand(), explicitIV); err != nil { + return + } + } + } + copy(b.data[recordHeaderLen+explicitIVLen:], data) + c.out.encrypt(b, explicitIVLen) + + _, err = c.conn.Write(b.data) + if err != nil { + return + } + n = len(data) + + c.out.freeBlock(b) + if typ == recordTypeChangeCipherSpec { err = c.out.changeCipherSpec(c.config) if err != nil { @@ -250,9 +311,9 @@ func (c *Conn) dtlsWriteRecord(typ recordType, data []byte) (n int, err error) { func (c *Conn) dtlsDoReadHandshake() ([]byte, error) { // Assemble a full handshake message. For test purposes, this - // implementation assumes fragments arrive in order, but tolerates - // retransmits. It may need to be cleverer if we ever test BoringSSL's - // retransmit behavior. + // implementation assumes fragments arrive in order. It may + // need to be cleverer if we ever test BoringSSL's retransmit + // behavior. for len(c.handMsg) < 4+c.handMsgLen { // Get a new handshake record if the previous has been // exhausted. @@ -281,16 +342,9 @@ func (c *Conn) dtlsDoReadHandshake() ([]byte, error) { } fragment := c.hand.Next(fragLen) - if fragSeq < c.recvHandshakeSeq { - // BoringSSL retransmits based on an internal timer, so - // it may flakily retransmit part of a handshake - // message. Ignore those fragments. - // - // TODO(davidben): Revise this if BoringSSL's retransmit - // logic is made more deterministic. - continue - } else if fragSeq > c.recvHandshakeSeq { - return nil, errors.New("dtls: handshake messages sent out of order") + // Check it's a fragment for the right message. + if fragSeq != c.recvHandshakeSeq { + return nil, errors.New("dtls: bad handshake sequence number") } // Check that the length is consistent. diff --git a/src/ssl/test/runner/handshake_client.go b/src/ssl/test/runner/handshake_client.go index f297fc1..0dac05d 100644 --- a/src/ssl/test/runner/handshake_client.go +++ b/src/ssl/test/runner/handshake_client.go @@ -6,7 +6,6 @@ package main import ( "bytes" - "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rsa" @@ -22,13 +21,14 @@ import ( ) type clientHandshakeState struct { - c *Conn - serverHello *serverHelloMsg - hello *clientHelloMsg - suite *cipherSuite - finishedHash finishedHash - masterSecret []byte - session *ClientSessionState + c *Conn + serverHello *serverHelloMsg + hello *clientHelloMsg + suite *cipherSuite + finishedHash finishedHash + masterSecret []byte + session *ClientSessionState + finishedBytes []byte } func (c *Conn) clientHandshake() error { @@ -83,6 +83,10 @@ func (c *Conn) clientHandshake() error { hello.extendedMasterSecret = false } + if c.config.Bugs.NoSupportedCurves { + hello.supportedCurves = nil + } + if len(c.clientVerify) > 0 && !c.config.Bugs.EmptyRenegotiationInfo { if c.config.Bugs.BadRenegotiationInfo { hello.secureRenegotiation = append(hello.secureRenegotiation, c.clientVerify...) @@ -129,13 +133,16 @@ NextCipherSuite: return errors.New("tls: short read from Rand: " + err.Error()) } - if hello.vers >= VersionTLS12 && !c.config.Bugs.NoSignatureAndHashes { + if hello.vers >= VersionTLS12 && !c.config.Bugs.NoSignatureAndHashes && (c.cipherSuite == nil || !c.config.Bugs.NoSignatureAlgorithmsOnRenego) { hello.signatureAndHashes = c.config.signatureAndHashesForClient() } var session *ClientSessionState var cacheKey string sessionCache := c.config.ClientSessionCache + if c.config.Bugs.NeverResumeOnRenego && c.cipherSuite != nil { + sessionCache = nil + } if sessionCache != nil { hello.ticketSupported = !c.config.SessionTicketsDisabled @@ -213,7 +220,11 @@ NextCipherSuite: helloBytes = hello.marshal() c.writeRecord(recordTypeHandshake, helloBytes) } + c.dtlsFlushHandshake() + if err := c.simulatePacketLoss(nil); err != nil { + return err + } msg, err := c.readHandshake() if err != nil { return err @@ -233,7 +244,11 @@ NextCipherSuite: hello.cookie = helloVerifyRequest.cookie helloBytes = hello.marshal() c.writeRecord(recordTypeHandshake, helloBytes) + c.dtlsFlushHandshake() + if err := c.simulatePacketLoss(nil); err != nil { + return err + } msg, err = c.readHandshake() if err != nil { return err @@ -317,6 +332,15 @@ NextCipherSuite: if err := hs.sendFinished(isResume); err != nil { return err } + // Most retransmits are triggered by a timeout, but the final + // leg of the handshake is retransmited upon re-receiving a + // Finished. + if err := c.simulatePacketLoss(func() { + c.writeRecord(recordTypeHandshake, hs.finishedBytes) + c.dtlsFlushHandshake() + }); err != nil { + return err + } if err := hs.readSessionTicket(); err != nil { return err } @@ -331,7 +355,10 @@ NextCipherSuite: c.didResume = isResume c.handshakeComplete = true - c.cipherSuite = suite.id + c.cipherSuite = suite + copy(c.clientRandom[:], hs.hello.random) + copy(c.serverRandom[:], hs.serverHello.random) + copy(c.masterSecret[:], hs.masterSecret) return nil } @@ -559,33 +586,39 @@ func (hs *clientHandshakeState) doFullHandshake() error { hasSignatureAndHash: c.vers >= VersionTLS12, } + // Determine the hash to sign. + var signatureType uint8 + switch c.config.Certificates[0].PrivateKey.(type) { + case *ecdsa.PrivateKey: + signatureType = signatureECDSA + case *rsa.PrivateKey: + signatureType = signatureRSA + default: + c.sendAlert(alertInternalError) + return errors.New("unknown private key type") + } + if c.config.Bugs.IgnorePeerSignatureAlgorithmPreferences { + certReq.signatureAndHashes = c.config.signatureAndHashesForClient() + } + certVerify.signatureAndHash, err = hs.finishedHash.selectClientCertSignatureAlgorithm(certReq.signatureAndHashes, c.config.signatureAndHashesForClient(), signatureType) + if err != nil { + c.sendAlert(alertInternalError) + return err + } + digest, hashFunc, err := hs.finishedHash.hashForClientCertificate(certVerify.signatureAndHash, hs.masterSecret) + if err != nil { + c.sendAlert(alertInternalError) + return err + } + switch key := c.config.Certificates[0].PrivateKey.(type) { case *ecdsa.PrivateKey: - certVerify.signatureAndHash, err = hs.finishedHash.selectClientCertSignatureAlgorithm(certReq.signatureAndHashes, signatureECDSA) - if err != nil { - break - } - var digest []byte - digest, _, err = hs.finishedHash.hashForClientCertificate(certVerify.signatureAndHash, hs.masterSecret) - if err != nil { - break - } var r, s *big.Int r, s, err = ecdsa.Sign(c.config.rand(), key, digest) if err == nil { signed, err = asn1.Marshal(ecdsaSignature{r, s}) } case *rsa.PrivateKey: - certVerify.signatureAndHash, err = hs.finishedHash.selectClientCertSignatureAlgorithm(certReq.signatureAndHashes, signatureRSA) - if err != nil { - break - } - var digest []byte - var hashFunc crypto.Hash - digest, hashFunc, err = hs.finishedHash.hashForClientCertificate(certVerify.signatureAndHash, hs.masterSecret) - if err != nil { - break - } signed, err = rsa.SignPKCS1v15(c.config.rand(), key, hashFunc, digest) default: err = errors.New("unknown private key type") @@ -599,6 +632,7 @@ func (hs *clientHandshakeState) doFullHandshake() error { hs.writeClientHash(certVerify.marshal()) c.writeRecord(recordTypeHandshake, certVerify.marshal()) } + c.dtlsFlushHandshake() hs.finishedHash.discardHandshakeBuffer() @@ -825,15 +859,19 @@ func (hs *clientHandshakeState) sendFinished(isResume bool) error { } else { finished.verifyData = hs.finishedHash.clientSum(hs.masterSecret) } + if c.config.Bugs.BadFinished { + finished.verifyData[0]++ + } c.clientVerify = append(c.clientVerify[:0], finished.verifyData...) - finishedBytes := finished.marshal() - hs.writeHash(finishedBytes, seqno) - postCCSBytes = append(postCCSBytes, finishedBytes...) + hs.finishedBytes = finished.marshal() + hs.writeHash(hs.finishedBytes, seqno) + postCCSBytes = append(postCCSBytes, hs.finishedBytes...) if c.config.Bugs.FragmentAcrossChangeCipherSpec { c.writeRecord(recordTypeHandshake, postCCSBytes[:5]) postCCSBytes = postCCSBytes[5:] } + c.dtlsFlushHandshake() if !c.config.Bugs.SkipChangeCipherSpec && c.config.Bugs.EarlyChangeCipherSpec == 0 { @@ -843,8 +881,15 @@ func (hs *clientHandshakeState) sendFinished(isResume bool) error { if c.config.Bugs.AppDataAfterChangeCipherSpec != nil { c.writeRecord(recordTypeApplicationData, c.config.Bugs.AppDataAfterChangeCipherSpec) } + if c.config.Bugs.AlertAfterChangeCipherSpec != 0 { + c.sendAlert(c.config.Bugs.AlertAfterChangeCipherSpec) + return errors.New("tls: simulating post-CCS alert") + } - c.writeRecord(recordTypeHandshake, postCCSBytes) + if !c.config.Bugs.SkipFinished { + c.writeRecord(recordTypeHandshake, postCCSBytes) + c.dtlsFlushHandshake() + } return nil } diff --git a/src/ssl/test/runner/handshake_server.go b/src/ssl/test/runner/handshake_server.go index 1234a57..59ed9df 100644 --- a/src/ssl/test/runner/handshake_server.go +++ b/src/ssl/test/runner/handshake_server.go @@ -33,6 +33,7 @@ type serverHandshakeState struct { masterSecret []byte certsFromClient [][]byte cert *Certificate + finishedBytes []byte } // serverHandshake performs a TLS handshake as a server. @@ -71,6 +72,15 @@ func (c *Conn) serverHandshake() error { if err := hs.sendFinished(); err != nil { return err } + // Most retransmits are triggered by a timeout, but the final + // leg of the handshake is retransmited upon re-receiving a + // Finished. + if err := c.simulatePacketLoss(func() { + c.writeRecord(recordTypeHandshake, hs.finishedBytes) + c.dtlsFlushHandshake() + }); err != nil { + return err + } if err := hs.readFinished(isResume); err != nil { return err } @@ -87,9 +97,12 @@ func (c *Conn) serverHandshake() error { if err := hs.readFinished(isResume); err != nil { return err } + if c.config.Bugs.AlertBeforeFalseStartTest != 0 { + c.sendAlert(c.config.Bugs.AlertBeforeFalseStartTest) + } if c.config.Bugs.ExpectFalseStart { if err := c.readRecord(recordTypeApplicationData); err != nil { - return err + return fmt.Errorf("tls: peer did not false start: %s", err) } } if err := hs.sendSessionTicket(); err != nil { @@ -100,6 +113,9 @@ func (c *Conn) serverHandshake() error { } } c.handshakeComplete = true + copy(c.clientRandom[:], hs.clientHello.random) + copy(c.serverRandom[:], hs.hello.random) + copy(c.masterSecret[:], hs.masterSecret) return nil } @@ -110,6 +126,9 @@ func (hs *serverHandshakeState) readClientHello() (isResume bool, err error) { config := hs.c.config c := hs.c + if err := c.simulatePacketLoss(nil); err != nil { + return false, err + } msg, err := c.readHandshake() if err != nil { return false, err @@ -136,7 +155,11 @@ func (hs *serverHandshakeState) readClientHello() (isResume bool, err error) { return false, errors.New("dtls: short read from Rand: " + err.Error()) } c.writeRecord(recordTypeHandshake, helloVerifyRequest.marshal()) + c.dtlsFlushHandshake() + if err := c.simulatePacketLoss(nil); err != nil { + return false, err + } msg, err := c.readHandshake() if err != nil { return false, err @@ -176,6 +199,9 @@ func (hs *serverHandshakeState) readClientHello() (isResume bool, err error) { if c.clientVersion < VersionTLS12 && len(hs.clientHello.signatureAndHashes) > 0 { return false, fmt.Errorf("tls: client included signature_algorithms before TLS 1.2") } + if config.Bugs.IgnorePeerSignatureAlgorithmPreferences { + hs.clientHello.signatureAndHashes = config.signatureAndHashesForServer() + } c.vers, ok = config.mutualVersion(hs.clientHello.vers) if !ok { @@ -189,6 +215,9 @@ func (hs *serverHandshakeState) readClientHello() (isResume bool, err error) { supportedCurve := false preferredCurves := config.curvePreferences() + if config.Bugs.IgnorePeerCurvePreferences { + hs.clientHello.supportedCurves = preferredCurves + } Curves: for _, curve := range hs.clientHello.supportedCurves { for _, supported := range preferredCurves { @@ -323,6 +352,9 @@ Curves: return false, errors.New("tls: fallback SCSV found when not expected") } + if config.Bugs.IgnorePeerCipherPreferences { + hs.clientHello.cipherSuites = c.config.cipherSuites() + } var preferenceList, supportedList []uint16 if c.config.PreferServerCipherSuites { preferenceList = c.config.cipherSuites() @@ -350,6 +382,10 @@ Curves: func (hs *serverHandshakeState) checkForResumption() bool { c := hs.c + if c.config.Bugs.NeverResumeOnRenego && c.cipherSuite != nil { + return false + } + if len(hs.clientHello.sessionTicket) > 0 { if c.config.SessionTicketsDisabled { return false @@ -410,6 +446,9 @@ func (hs *serverHandshakeState) doResumeHandshake() error { c := hs.c hs.hello.cipherSuite = hs.suite.id + if c.config.Bugs.SendCipherSuite != 0 { + hs.hello.cipherSuite = c.config.Bugs.SendCipherSuite + } // We echo the client's session ID in the ServerHello to let it know // that we're doing a resumption. hs.hello.sessionId = hs.clientHello.sessionId @@ -473,12 +512,16 @@ func (hs *serverHandshakeState) doFullHandshake() error { certMsg := new(certificateMsg) certMsg.certificates = hs.cert.Certificate if !config.Bugs.UnauthenticatedECDH { - hs.writeServerHash(certMsg.marshal()) - c.writeRecord(recordTypeHandshake, certMsg.marshal()) + certMsgBytes := certMsg.marshal() + if config.Bugs.WrongCertificateMessageType { + certMsgBytes[0] += 42 + } + hs.writeServerHash(certMsgBytes) + c.writeRecord(recordTypeHandshake, certMsgBytes) } } - if hs.hello.ocspStapling { + if hs.hello.ocspStapling && !c.config.Bugs.SkipCertificateStatus { certStatus := new(certificateStatusMsg) certStatus.statusType = statusTypeOCSP certStatus.response = hs.cert.OCSPStaple @@ -530,9 +573,13 @@ func (hs *serverHandshakeState) doFullHandshake() error { helloDone := new(serverHelloDoneMsg) hs.writeServerHash(helloDone.marshal()) c.writeRecord(recordTypeHandshake, helloDone.marshal()) + c.dtlsFlushHandshake() var pub crypto.PublicKey // public key for client auth, if any + if err := c.simulatePacketLoss(nil); err != nil { + return err + } msg, err := c.readHandshake() if err != nil { return err @@ -811,14 +858,19 @@ func (hs *serverHandshakeState) sendFinished() error { finished := new(finishedMsg) finished.verifyData = hs.finishedHash.serverSum(hs.masterSecret) + if c.config.Bugs.BadFinished { + finished.verifyData[0]++ + } c.serverVerify = append(c.serverVerify[:0], finished.verifyData...) - postCCSBytes := finished.marshal() - hs.writeServerHash(postCCSBytes) + hs.finishedBytes = finished.marshal() + hs.writeServerHash(hs.finishedBytes) + postCCSBytes := hs.finishedBytes if c.config.Bugs.FragmentAcrossChangeCipherSpec { c.writeRecord(recordTypeHandshake, postCCSBytes[:5]) postCCSBytes = postCCSBytes[5:] } + c.dtlsFlushHandshake() if !c.config.Bugs.SkipChangeCipherSpec { c.writeRecord(recordTypeChangeCipherSpec, []byte{1}) @@ -827,10 +879,17 @@ func (hs *serverHandshakeState) sendFinished() error { if c.config.Bugs.AppDataAfterChangeCipherSpec != nil { c.writeRecord(recordTypeApplicationData, c.config.Bugs.AppDataAfterChangeCipherSpec) } + if c.config.Bugs.AlertAfterChangeCipherSpec != 0 { + c.sendAlert(c.config.Bugs.AlertAfterChangeCipherSpec) + return errors.New("tls: simulating post-CCS alert") + } - c.writeRecord(recordTypeHandshake, postCCSBytes) + if !c.config.Bugs.SkipFinished { + c.writeRecord(recordTypeHandshake, postCCSBytes) + c.dtlsFlushHandshake() + } - c.cipherSuite = hs.suite.id + c.cipherSuite = hs.suite return nil } diff --git a/src/ssl/test/runner/key_agreement.go b/src/ssl/test/runner/key_agreement.go index 116dfd8..5e44b54 100644 --- a/src/ssl/test/runner/key_agreement.go +++ b/src/ssl/test/runner/key_agreement.go @@ -25,19 +25,73 @@ var errServerKeyExchange = errors.New("tls: invalid ServerKeyExchange message") // rsaKeyAgreement implements the standard TLS key agreement where the client // encrypts the pre-master secret to the server's public key. type rsaKeyAgreement struct { + version uint16 clientVersion uint16 + exportKey *rsa.PrivateKey } func (ka *rsaKeyAgreement) generateServerKeyExchange(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) { // Save the client version for comparison later. ka.clientVersion = versionToWire(clientHello.vers, clientHello.isDTLS) - if config.Bugs.RSAServerKeyExchange { - // Send an empty ServerKeyExchange message. - return &serverKeyExchangeMsg{}, nil + if !config.Bugs.RSAEphemeralKey { + return nil, nil } - return nil, nil + // Generate an ephemeral RSA key to use instead of the real + // one, as in RSA_EXPORT. + key, err := rsa.GenerateKey(config.rand(), 512) + if err != nil { + return nil, err + } + ka.exportKey = key + + modulus := key.N.Bytes() + exponent := big.NewInt(int64(key.E)).Bytes() + serverRSAParams := make([]byte, 0, 2+len(modulus)+2+len(exponent)) + serverRSAParams = append(serverRSAParams, byte(len(modulus)>>8), byte(len(modulus))) + serverRSAParams = append(serverRSAParams, modulus...) + serverRSAParams = append(serverRSAParams, byte(len(exponent)>>8), byte(len(exponent))) + serverRSAParams = append(serverRSAParams, exponent...) + + var tls12HashId uint8 + if ka.version >= VersionTLS12 { + if tls12HashId, err = pickTLS12HashForSignature(signatureRSA, clientHello.signatureAndHashes, config.signatureAndHashesForServer()); err != nil { + return nil, err + } + } + + digest, hashFunc, err := hashForServerKeyExchange(signatureRSA, tls12HashId, ka.version, clientHello.random, hello.random, serverRSAParams) + if err != nil { + return nil, err + } + privKey, ok := cert.PrivateKey.(*rsa.PrivateKey) + if !ok { + return nil, errors.New("RSA ephemeral key requires an RSA server private key") + } + sig, err := rsa.SignPKCS1v15(config.rand(), privKey, hashFunc, digest) + if err != nil { + return nil, errors.New("failed to sign RSA parameters: " + err.Error()) + } + + skx := new(serverKeyExchangeMsg) + sigAndHashLen := 0 + if ka.version >= VersionTLS12 { + sigAndHashLen = 2 + } + skx.key = make([]byte, len(serverRSAParams)+sigAndHashLen+2+len(sig)) + copy(skx.key, serverRSAParams) + k := skx.key[len(serverRSAParams):] + if ka.version >= VersionTLS12 { + k[0] = tls12HashId + k[1] = signatureRSA + k = k[2:] + } + k[0] = byte(len(sig) >> 8) + k[1] = byte(len(sig)) + copy(k[2:], sig) + + return skx, nil } func (ka *rsaKeyAgreement) processClientKeyExchange(config *Config, cert *Certificate, ckx *clientKeyExchangeMsg, version uint16) ([]byte, error) { @@ -60,7 +114,11 @@ func (ka *rsaKeyAgreement) processClientKeyExchange(config *Config, cert *Certif ciphertext = ckx.ciphertext[2:] } - err = rsa.DecryptPKCS1v15SessionKey(config.rand(), cert.PrivateKey.(*rsa.PrivateKey), ciphertext, preMasterSecret) + key := cert.PrivateKey.(*rsa.PrivateKey) + if ka.exportKey != nil { + key = ka.exportKey + } + err = rsa.DecryptPKCS1v15SessionKey(config.rand(), key, ciphertext, preMasterSecret) if err != nil { return nil, err } @@ -154,20 +212,19 @@ func hashForServerKeyExchange(sigType, hashFunc uint8, version uint16, slices .. // pickTLS12HashForSignature returns a TLS 1.2 hash identifier for signing a // ServerKeyExchange given the signature type being used and the client's // advertized list of supported signature and hash combinations. -func pickTLS12HashForSignature(sigType uint8, clientSignatureAndHashes []signatureAndHash) (uint8, error) { - if len(clientSignatureAndHashes) == 0 { +func pickTLS12HashForSignature(sigType uint8, clientList, serverList []signatureAndHash) (uint8, error) { + if len(clientList) == 0 { // If the client didn't specify any signature_algorithms // extension then we can assume that it supports SHA1. See // http://tools.ietf.org/html/rfc5246#section-7.4.1.4.1 return hashSHA1, nil } - for _, sigAndHash := range clientSignatureAndHashes { + for _, sigAndHash := range clientList { if sigAndHash.signature != sigType { continue } - switch sigAndHash.hash { - case hashSHA1, hashSHA256: + if isSupportedSignatureAndHash(sigAndHash, serverList) { return sigAndHash.hash, nil } } @@ -177,6 +234,8 @@ func pickTLS12HashForSignature(sigType uint8, clientSignatureAndHashes []signatu func curveForCurveID(id CurveID) (elliptic.Curve, bool) { switch id { + case CurveP224: + return elliptic.P224(), true case CurveP256: return elliptic.P256(), true case CurveP384: @@ -221,7 +280,7 @@ func (ka *signedKeyAgreement) signParameters(config *Config, cert *Certificate, var tls12HashId uint8 var err error if ka.version >= VersionTLS12 { - if tls12HashId, err = pickTLS12HashForSignature(ka.sigType, clientHello.signatureAndHashes); err != nil { + if tls12HashId, err = pickTLS12HashForSignature(ka.sigType, clientHello.signatureAndHashes, config.signatureAndHashesForServer()); err != nil { return nil, err } } diff --git a/src/ssl/test/runner/packet_adapter.go b/src/ssl/test/runner/packet_adapter.go index 671b413..bbcd388 100644 --- a/src/ssl/test/runner/packet_adapter.go +++ b/src/ssl/test/runner/packet_adapter.go @@ -6,50 +6,115 @@ package main import ( "encoding/binary" - "errors" + "fmt" + "io" "net" + "time" ) +// opcodePacket signals a packet, encoded with a 32-bit length prefix, followed +// by the payload. +const opcodePacket = byte('P') + +// opcodeTimeout signals a read timeout, encoded by a 64-bit number of +// nanoseconds. On receipt, the peer should reply with +// opcodeTimeoutAck. opcodeTimeout may only be sent by the Go side. +const opcodeTimeout = byte('T') + +// opcodeTimeoutAck acknowledges a read timeout. This opcode has no payload and +// may only be sent by the C side. Timeout ACKs act as a synchronization point +// at the timeout, to bracket one flight of messages from C. +const opcodeTimeoutAck = byte('t') + type packetAdaptor struct { net.Conn } -// newPacketAdaptor wraps a reliable streaming net.Conn into a -// reliable packet-based net.Conn. Every packet is encoded with a -// 32-bit length prefix as a framing layer. -func newPacketAdaptor(conn net.Conn) net.Conn { +// newPacketAdaptor wraps a reliable streaming net.Conn into a reliable +// packet-based net.Conn. The stream contains packets and control commands, +// distinguished by a one byte opcode. +func newPacketAdaptor(conn net.Conn) *packetAdaptor { return &packetAdaptor{conn} } -func (p *packetAdaptor) Read(b []byte) (int, error) { +func (p *packetAdaptor) readOpcode() (byte, error) { + out := make([]byte, 1) + if _, err := io.ReadFull(p.Conn, out); err != nil { + return 0, err + } + return out[0], nil +} + +func (p *packetAdaptor) readPacketBody() ([]byte, error) { var length uint32 if err := binary.Read(p.Conn, binary.BigEndian, &length); err != nil { - return 0, err + return nil, err } out := make([]byte, length) - n, err := p.Conn.Read(out) + if _, err := io.ReadFull(p.Conn, out); err != nil { + return nil, err + } + return out, nil +} + +func (p *packetAdaptor) Read(b []byte) (int, error) { + opcode, err := p.readOpcode() if err != nil { return 0, err } - if n != int(length) { - return 0, errors.New("internal error: length mismatch!") + if opcode != opcodePacket { + return 0, fmt.Errorf("unexpected opcode '%d'", opcode) + } + out, err := p.readPacketBody() + if err != nil { + return 0, err } return copy(b, out), nil } func (p *packetAdaptor) Write(b []byte) (int, error) { - length := uint32(len(b)) - if err := binary.Write(p.Conn, binary.BigEndian, length); err != nil { + payload := make([]byte, 1+4+len(b)) + payload[0] = opcodePacket + binary.BigEndian.PutUint32(payload[1:5], uint32(len(b))) + copy(payload[5:], b) + if _, err := p.Conn.Write(payload); err != nil { return 0, err } - n, err := p.Conn.Write(b) - if err != nil { - return 0, err + return len(b), nil +} + +// SendReadTimeout instructs the peer to simulate a read timeout. It then waits +// for acknowledgement of the timeout, buffering any packets received since +// then. The packets are then returned. +func (p *packetAdaptor) SendReadTimeout(d time.Duration) ([][]byte, error) { + payload := make([]byte, 1+8) + payload[0] = opcodeTimeout + binary.BigEndian.PutUint64(payload[1:], uint64(d.Nanoseconds())) + if _, err := p.Conn.Write(payload); err != nil { + return nil, err } - if n != len(b) { - return 0, errors.New("internal error: length mismatch!") + + var packets [][]byte + for { + opcode, err := p.readOpcode() + if err != nil { + return nil, err + } + switch opcode { + case opcodeTimeoutAck: + // Done! Return the packets buffered and continue. + return packets, nil + case opcodePacket: + // Buffer the packet for the caller to process. + packet, err := p.readPacketBody() + if err != nil { + return nil, err + } + packets = append(packets, packet) + default: + return nil, fmt.Errorf("unexpected opcode '%d'", opcode) + } } - return len(b), nil } type replayAdaptor struct { diff --git a/src/ssl/test/runner/poly1305.go b/src/ssl/test/runner/poly1305.go new file mode 100644 index 0000000..51a1009 --- /dev/null +++ b/src/ssl/test/runner/poly1305.go @@ -0,0 +1,1540 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// Based on original, public domain implementation from NaCl by D. J. +// Bernstein. + +import ( + "crypto/subtle" + "math" +) + +const ( + alpham80 = 0.00000000558793544769287109375 + alpham48 = 24.0 + alpham16 = 103079215104.0 + alpha0 = 6755399441055744.0 + alpha18 = 1770887431076116955136.0 + alpha32 = 29014219670751100192948224.0 + alpha50 = 7605903601369376408980219232256.0 + alpha64 = 124615124604835863084731911901282304.0 + alpha82 = 32667107224410092492483962313449748299776.0 + alpha96 = 535217884764734955396857238543560676143529984.0 + alpha112 = 35076039295941670036888435985190792471742381031424.0 + alpha130 = 9194973245195333150150082162901855101712434733101613056.0 + scale = 0.0000000000000000000000000000000000000036734198463196484624023016788195177431833298649127735047148490821200539357960224151611328125 + offset0 = 6755408030990331.0 + offset1 = 29014256564239239022116864.0 + offset2 = 124615283061160854719918951570079744.0 + offset3 = 535219245894202480694386063513315216128475136.0 +) + +// poly1305Verify returns true if mac is a valid authenticator for m with the +// given key. +func poly1305Verify(mac *[16]byte, m []byte, key *[32]byte) bool { + var tmp [16]byte + poly1305Sum(&tmp, m, key) + return subtle.ConstantTimeCompare(tmp[:], mac[:]) == 1 +} + +// poly1305Sum generates an authenticator for m using a one-time key and puts +// the 16-byte result into out. Authenticating two different messages with the +// same key allows an attacker to forge messages at will. +func poly1305Sum(out *[16]byte, m []byte, key *[32]byte) { + r := key + s := key[16:] + var ( + y7 float64 + y6 float64 + y1 float64 + y0 float64 + y5 float64 + y4 float64 + x7 float64 + x6 float64 + x1 float64 + x0 float64 + y3 float64 + y2 float64 + x5 float64 + r3lowx0 float64 + x4 float64 + r0lowx6 float64 + x3 float64 + r3highx0 float64 + x2 float64 + r0highx6 float64 + r0lowx0 float64 + sr1lowx6 float64 + r0highx0 float64 + sr1highx6 float64 + sr3low float64 + r1lowx0 float64 + sr2lowx6 float64 + r1highx0 float64 + sr2highx6 float64 + r2lowx0 float64 + sr3lowx6 float64 + r2highx0 float64 + sr3highx6 float64 + r1highx4 float64 + r1lowx4 float64 + r0highx4 float64 + r0lowx4 float64 + sr3highx4 float64 + sr3lowx4 float64 + sr2highx4 float64 + sr2lowx4 float64 + r0lowx2 float64 + r0highx2 float64 + r1lowx2 float64 + r1highx2 float64 + r2lowx2 float64 + r2highx2 float64 + sr3lowx2 float64 + sr3highx2 float64 + z0 float64 + z1 float64 + z2 float64 + z3 float64 + m0 int64 + m1 int64 + m2 int64 + m3 int64 + m00 uint32 + m01 uint32 + m02 uint32 + m03 uint32 + m10 uint32 + m11 uint32 + m12 uint32 + m13 uint32 + m20 uint32 + m21 uint32 + m22 uint32 + m23 uint32 + m30 uint32 + m31 uint32 + m32 uint32 + m33 uint64 + lbelow2 int32 + lbelow3 int32 + lbelow4 int32 + lbelow5 int32 + lbelow6 int32 + lbelow7 int32 + lbelow8 int32 + lbelow9 int32 + lbelow10 int32 + lbelow11 int32 + lbelow12 int32 + lbelow13 int32 + lbelow14 int32 + lbelow15 int32 + s00 uint32 + s01 uint32 + s02 uint32 + s03 uint32 + s10 uint32 + s11 uint32 + s12 uint32 + s13 uint32 + s20 uint32 + s21 uint32 + s22 uint32 + s23 uint32 + s30 uint32 + s31 uint32 + s32 uint32 + s33 uint32 + bits32 uint64 + f uint64 + f0 uint64 + f1 uint64 + f2 uint64 + f3 uint64 + f4 uint64 + g uint64 + g0 uint64 + g1 uint64 + g2 uint64 + g3 uint64 + g4 uint64 + ) + + var p int32 + + l := int32(len(m)) + + r00 := uint32(r[0]) + + r01 := uint32(r[1]) + + r02 := uint32(r[2]) + r0 := int64(2151) + + r03 := uint32(r[3]) + r03 &= 15 + r0 <<= 51 + + r10 := uint32(r[4]) + r10 &= 252 + r01 <<= 8 + r0 += int64(r00) + + r11 := uint32(r[5]) + r02 <<= 16 + r0 += int64(r01) + + r12 := uint32(r[6]) + r03 <<= 24 + r0 += int64(r02) + + r13 := uint32(r[7]) + r13 &= 15 + r1 := int64(2215) + r0 += int64(r03) + + d0 := r0 + r1 <<= 51 + r2 := int64(2279) + + r20 := uint32(r[8]) + r20 &= 252 + r11 <<= 8 + r1 += int64(r10) + + r21 := uint32(r[9]) + r12 <<= 16 + r1 += int64(r11) + + r22 := uint32(r[10]) + r13 <<= 24 + r1 += int64(r12) + + r23 := uint32(r[11]) + r23 &= 15 + r2 <<= 51 + r1 += int64(r13) + + d1 := r1 + r21 <<= 8 + r2 += int64(r20) + + r30 := uint32(r[12]) + r30 &= 252 + r22 <<= 16 + r2 += int64(r21) + + r31 := uint32(r[13]) + r23 <<= 24 + r2 += int64(r22) + + r32 := uint32(r[14]) + r2 += int64(r23) + r3 := int64(2343) + + d2 := r2 + r3 <<= 51 + + r33 := uint32(r[15]) + r33 &= 15 + r31 <<= 8 + r3 += int64(r30) + + r32 <<= 16 + r3 += int64(r31) + + r33 <<= 24 + r3 += int64(r32) + + r3 += int64(r33) + h0 := alpha32 - alpha32 + + d3 := r3 + h1 := alpha32 - alpha32 + + h2 := alpha32 - alpha32 + + h3 := alpha32 - alpha32 + + h4 := alpha32 - alpha32 + + r0low := math.Float64frombits(uint64(d0)) + h5 := alpha32 - alpha32 + + r1low := math.Float64frombits(uint64(d1)) + h6 := alpha32 - alpha32 + + r2low := math.Float64frombits(uint64(d2)) + h7 := alpha32 - alpha32 + + r0low -= alpha0 + + r1low -= alpha32 + + r2low -= alpha64 + + r0high := r0low + alpha18 + + r3low := math.Float64frombits(uint64(d3)) + + r1high := r1low + alpha50 + sr1low := scale * r1low + + r2high := r2low + alpha82 + sr2low := scale * r2low + + r0high -= alpha18 + r0high_stack := r0high + + r3low -= alpha96 + + r1high -= alpha50 + r1high_stack := r1high + + sr1high := sr1low + alpham80 + + r0low -= r0high + + r2high -= alpha82 + sr3low = scale * r3low + + sr2high := sr2low + alpham48 + + r1low -= r1high + r1low_stack := r1low + + sr1high -= alpham80 + sr1high_stack := sr1high + + r2low -= r2high + r2low_stack := r2low + + sr2high -= alpham48 + sr2high_stack := sr2high + + r3high := r3low + alpha112 + r0low_stack := r0low + + sr1low -= sr1high + sr1low_stack := sr1low + + sr3high := sr3low + alpham16 + r2high_stack := r2high + + sr2low -= sr2high + sr2low_stack := sr2low + + r3high -= alpha112 + r3high_stack := r3high + + sr3high -= alpham16 + sr3high_stack := sr3high + + r3low -= r3high + r3low_stack := r3low + + sr3low -= sr3high + sr3low_stack := sr3low + + if l < 16 { + goto addatmost15bytes + } + + m00 = uint32(m[p+0]) + m0 = 2151 + + m0 <<= 51 + m1 = 2215 + m01 = uint32(m[p+1]) + + m1 <<= 51 + m2 = 2279 + m02 = uint32(m[p+2]) + + m2 <<= 51 + m3 = 2343 + m03 = uint32(m[p+3]) + + m10 = uint32(m[p+4]) + m01 <<= 8 + m0 += int64(m00) + + m11 = uint32(m[p+5]) + m02 <<= 16 + m0 += int64(m01) + + m12 = uint32(m[p+6]) + m03 <<= 24 + m0 += int64(m02) + + m13 = uint32(m[p+7]) + m3 <<= 51 + m0 += int64(m03) + + m20 = uint32(m[p+8]) + m11 <<= 8 + m1 += int64(m10) + + m21 = uint32(m[p+9]) + m12 <<= 16 + m1 += int64(m11) + + m22 = uint32(m[p+10]) + m13 <<= 24 + m1 += int64(m12) + + m23 = uint32(m[p+11]) + m1 += int64(m13) + + m30 = uint32(m[p+12]) + m21 <<= 8 + m2 += int64(m20) + + m31 = uint32(m[p+13]) + m22 <<= 16 + m2 += int64(m21) + + m32 = uint32(m[p+14]) + m23 <<= 24 + m2 += int64(m22) + + m33 = uint64(m[p+15]) + m2 += int64(m23) + + d0 = m0 + m31 <<= 8 + m3 += int64(m30) + + d1 = m1 + m32 <<= 16 + m3 += int64(m31) + + d2 = m2 + m33 += 256 + + m33 <<= 24 + m3 += int64(m32) + + m3 += int64(m33) + d3 = m3 + + p += 16 + l -= 16 + + z0 = math.Float64frombits(uint64(d0)) + + z1 = math.Float64frombits(uint64(d1)) + + z2 = math.Float64frombits(uint64(d2)) + + z3 = math.Float64frombits(uint64(d3)) + + z0 -= alpha0 + + z1 -= alpha32 + + z2 -= alpha64 + + z3 -= alpha96 + + h0 += z0 + + h1 += z1 + + h3 += z2 + + h5 += z3 + + if l < 16 { + goto multiplyaddatmost15bytes + } + +multiplyaddatleast16bytes: + + m2 = 2279 + m20 = uint32(m[p+8]) + y7 = h7 + alpha130 + + m2 <<= 51 + m3 = 2343 + m21 = uint32(m[p+9]) + y6 = h6 + alpha130 + + m3 <<= 51 + m0 = 2151 + m22 = uint32(m[p+10]) + y1 = h1 + alpha32 + + m0 <<= 51 + m1 = 2215 + m23 = uint32(m[p+11]) + y0 = h0 + alpha32 + + m1 <<= 51 + m30 = uint32(m[p+12]) + y7 -= alpha130 + + m21 <<= 8 + m2 += int64(m20) + m31 = uint32(m[p+13]) + y6 -= alpha130 + + m22 <<= 16 + m2 += int64(m21) + m32 = uint32(m[p+14]) + y1 -= alpha32 + + m23 <<= 24 + m2 += int64(m22) + m33 = uint64(m[p+15]) + y0 -= alpha32 + + m2 += int64(m23) + m00 = uint32(m[p+0]) + y5 = h5 + alpha96 + + m31 <<= 8 + m3 += int64(m30) + m01 = uint32(m[p+1]) + y4 = h4 + alpha96 + + m32 <<= 16 + m02 = uint32(m[p+2]) + x7 = h7 - y7 + y7 *= scale + + m33 += 256 + m03 = uint32(m[p+3]) + x6 = h6 - y6 + y6 *= scale + + m33 <<= 24 + m3 += int64(m31) + m10 = uint32(m[p+4]) + x1 = h1 - y1 + + m01 <<= 8 + m3 += int64(m32) + m11 = uint32(m[p+5]) + x0 = h0 - y0 + + m3 += int64(m33) + m0 += int64(m00) + m12 = uint32(m[p+6]) + y5 -= alpha96 + + m02 <<= 16 + m0 += int64(m01) + m13 = uint32(m[p+7]) + y4 -= alpha96 + + m03 <<= 24 + m0 += int64(m02) + d2 = m2 + x1 += y7 + + m0 += int64(m03) + d3 = m3 + x0 += y6 + + m11 <<= 8 + m1 += int64(m10) + d0 = m0 + x7 += y5 + + m12 <<= 16 + m1 += int64(m11) + x6 += y4 + + m13 <<= 24 + m1 += int64(m12) + y3 = h3 + alpha64 + + m1 += int64(m13) + d1 = m1 + y2 = h2 + alpha64 + + x0 += x1 + + x6 += x7 + + y3 -= alpha64 + r3low = r3low_stack + + y2 -= alpha64 + r0low = r0low_stack + + x5 = h5 - y5 + r3lowx0 = r3low * x0 + r3high = r3high_stack + + x4 = h4 - y4 + r0lowx6 = r0low * x6 + r0high = r0high_stack + + x3 = h3 - y3 + r3highx0 = r3high * x0 + sr1low = sr1low_stack + + x2 = h2 - y2 + r0highx6 = r0high * x6 + sr1high = sr1high_stack + + x5 += y3 + r0lowx0 = r0low * x0 + r1low = r1low_stack + + h6 = r3lowx0 + r0lowx6 + sr1lowx6 = sr1low * x6 + r1high = r1high_stack + + x4 += y2 + r0highx0 = r0high * x0 + sr2low = sr2low_stack + + h7 = r3highx0 + r0highx6 + sr1highx6 = sr1high * x6 + sr2high = sr2high_stack + + x3 += y1 + r1lowx0 = r1low * x0 + r2low = r2low_stack + + h0 = r0lowx0 + sr1lowx6 + sr2lowx6 = sr2low * x6 + r2high = r2high_stack + + x2 += y0 + r1highx0 = r1high * x0 + sr3low = sr3low_stack + + h1 = r0highx0 + sr1highx6 + sr2highx6 = sr2high * x6 + sr3high = sr3high_stack + + x4 += x5 + r2lowx0 = r2low * x0 + z2 = math.Float64frombits(uint64(d2)) + + h2 = r1lowx0 + sr2lowx6 + sr3lowx6 = sr3low * x6 + + x2 += x3 + r2highx0 = r2high * x0 + z3 = math.Float64frombits(uint64(d3)) + + h3 = r1highx0 + sr2highx6 + sr3highx6 = sr3high * x6 + + r1highx4 = r1high * x4 + z2 -= alpha64 + + h4 = r2lowx0 + sr3lowx6 + r1lowx4 = r1low * x4 + + r0highx4 = r0high * x4 + z3 -= alpha96 + + h5 = r2highx0 + sr3highx6 + r0lowx4 = r0low * x4 + + h7 += r1highx4 + sr3highx4 = sr3high * x4 + + h6 += r1lowx4 + sr3lowx4 = sr3low * x4 + + h5 += r0highx4 + sr2highx4 = sr2high * x4 + + h4 += r0lowx4 + sr2lowx4 = sr2low * x4 + + h3 += sr3highx4 + r0lowx2 = r0low * x2 + + h2 += sr3lowx4 + r0highx2 = r0high * x2 + + h1 += sr2highx4 + r1lowx2 = r1low * x2 + + h0 += sr2lowx4 + r1highx2 = r1high * x2 + + h2 += r0lowx2 + r2lowx2 = r2low * x2 + + h3 += r0highx2 + r2highx2 = r2high * x2 + + h4 += r1lowx2 + sr3lowx2 = sr3low * x2 + + h5 += r1highx2 + sr3highx2 = sr3high * x2 + + p += 16 + l -= 16 + h6 += r2lowx2 + + h7 += r2highx2 + + z1 = math.Float64frombits(uint64(d1)) + h0 += sr3lowx2 + + z0 = math.Float64frombits(uint64(d0)) + h1 += sr3highx2 + + z1 -= alpha32 + + z0 -= alpha0 + + h5 += z3 + + h3 += z2 + + h1 += z1 + + h0 += z0 + + if l >= 16 { + goto multiplyaddatleast16bytes + } + +multiplyaddatmost15bytes: + + y7 = h7 + alpha130 + + y6 = h6 + alpha130 + + y1 = h1 + alpha32 + + y0 = h0 + alpha32 + + y7 -= alpha130 + + y6 -= alpha130 + + y1 -= alpha32 + + y0 -= alpha32 + + y5 = h5 + alpha96 + + y4 = h4 + alpha96 + + x7 = h7 - y7 + y7 *= scale + + x6 = h6 - y6 + y6 *= scale + + x1 = h1 - y1 + + x0 = h0 - y0 + + y5 -= alpha96 + + y4 -= alpha96 + + x1 += y7 + + x0 += y6 + + x7 += y5 + + x6 += y4 + + y3 = h3 + alpha64 + + y2 = h2 + alpha64 + + x0 += x1 + + x6 += x7 + + y3 -= alpha64 + r3low = r3low_stack + + y2 -= alpha64 + r0low = r0low_stack + + x5 = h5 - y5 + r3lowx0 = r3low * x0 + r3high = r3high_stack + + x4 = h4 - y4 + r0lowx6 = r0low * x6 + r0high = r0high_stack + + x3 = h3 - y3 + r3highx0 = r3high * x0 + sr1low = sr1low_stack + + x2 = h2 - y2 + r0highx6 = r0high * x6 + sr1high = sr1high_stack + + x5 += y3 + r0lowx0 = r0low * x0 + r1low = r1low_stack + + h6 = r3lowx0 + r0lowx6 + sr1lowx6 = sr1low * x6 + r1high = r1high_stack + + x4 += y2 + r0highx0 = r0high * x0 + sr2low = sr2low_stack + + h7 = r3highx0 + r0highx6 + sr1highx6 = sr1high * x6 + sr2high = sr2high_stack + + x3 += y1 + r1lowx0 = r1low * x0 + r2low = r2low_stack + + h0 = r0lowx0 + sr1lowx6 + sr2lowx6 = sr2low * x6 + r2high = r2high_stack + + x2 += y0 + r1highx0 = r1high * x0 + sr3low = sr3low_stack + + h1 = r0highx0 + sr1highx6 + sr2highx6 = sr2high * x6 + sr3high = sr3high_stack + + x4 += x5 + r2lowx0 = r2low * x0 + + h2 = r1lowx0 + sr2lowx6 + sr3lowx6 = sr3low * x6 + + x2 += x3 + r2highx0 = r2high * x0 + + h3 = r1highx0 + sr2highx6 + sr3highx6 = sr3high * x6 + + r1highx4 = r1high * x4 + + h4 = r2lowx0 + sr3lowx6 + r1lowx4 = r1low * x4 + + r0highx4 = r0high * x4 + + h5 = r2highx0 + sr3highx6 + r0lowx4 = r0low * x4 + + h7 += r1highx4 + sr3highx4 = sr3high * x4 + + h6 += r1lowx4 + sr3lowx4 = sr3low * x4 + + h5 += r0highx4 + sr2highx4 = sr2high * x4 + + h4 += r0lowx4 + sr2lowx4 = sr2low * x4 + + h3 += sr3highx4 + r0lowx2 = r0low * x2 + + h2 += sr3lowx4 + r0highx2 = r0high * x2 + + h1 += sr2highx4 + r1lowx2 = r1low * x2 + + h0 += sr2lowx4 + r1highx2 = r1high * x2 + + h2 += r0lowx2 + r2lowx2 = r2low * x2 + + h3 += r0highx2 + r2highx2 = r2high * x2 + + h4 += r1lowx2 + sr3lowx2 = sr3low * x2 + + h5 += r1highx2 + sr3highx2 = sr3high * x2 + + h6 += r2lowx2 + + h7 += r2highx2 + + h0 += sr3lowx2 + + h1 += sr3highx2 + +addatmost15bytes: + + if l == 0 { + goto nomorebytes + } + + lbelow2 = l - 2 + + lbelow3 = l - 3 + + lbelow2 >>= 31 + lbelow4 = l - 4 + + m00 = uint32(m[p+0]) + lbelow3 >>= 31 + p += lbelow2 + + m01 = uint32(m[p+1]) + lbelow4 >>= 31 + p += lbelow3 + + m02 = uint32(m[p+2]) + p += lbelow4 + m0 = 2151 + + m03 = uint32(m[p+3]) + m0 <<= 51 + m1 = 2215 + + m0 += int64(m00) + m01 &^= uint32(lbelow2) + + m02 &^= uint32(lbelow3) + m01 -= uint32(lbelow2) + + m01 <<= 8 + m03 &^= uint32(lbelow4) + + m0 += int64(m01) + lbelow2 -= lbelow3 + + m02 += uint32(lbelow2) + lbelow3 -= lbelow4 + + m02 <<= 16 + m03 += uint32(lbelow3) + + m03 <<= 24 + m0 += int64(m02) + + m0 += int64(m03) + lbelow5 = l - 5 + + lbelow6 = l - 6 + lbelow7 = l - 7 + + lbelow5 >>= 31 + lbelow8 = l - 8 + + lbelow6 >>= 31 + p += lbelow5 + + m10 = uint32(m[p+4]) + lbelow7 >>= 31 + p += lbelow6 + + m11 = uint32(m[p+5]) + lbelow8 >>= 31 + p += lbelow7 + + m12 = uint32(m[p+6]) + m1 <<= 51 + p += lbelow8 + + m13 = uint32(m[p+7]) + m10 &^= uint32(lbelow5) + lbelow4 -= lbelow5 + + m10 += uint32(lbelow4) + lbelow5 -= lbelow6 + + m11 &^= uint32(lbelow6) + m11 += uint32(lbelow5) + + m11 <<= 8 + m1 += int64(m10) + + m1 += int64(m11) + m12 &^= uint32(lbelow7) + + lbelow6 -= lbelow7 + m13 &^= uint32(lbelow8) + + m12 += uint32(lbelow6) + lbelow7 -= lbelow8 + + m12 <<= 16 + m13 += uint32(lbelow7) + + m13 <<= 24 + m1 += int64(m12) + + m1 += int64(m13) + m2 = 2279 + + lbelow9 = l - 9 + m3 = 2343 + + lbelow10 = l - 10 + lbelow11 = l - 11 + + lbelow9 >>= 31 + lbelow12 = l - 12 + + lbelow10 >>= 31 + p += lbelow9 + + m20 = uint32(m[p+8]) + lbelow11 >>= 31 + p += lbelow10 + + m21 = uint32(m[p+9]) + lbelow12 >>= 31 + p += lbelow11 + + m22 = uint32(m[p+10]) + m2 <<= 51 + p += lbelow12 + + m23 = uint32(m[p+11]) + m20 &^= uint32(lbelow9) + lbelow8 -= lbelow9 + + m20 += uint32(lbelow8) + lbelow9 -= lbelow10 + + m21 &^= uint32(lbelow10) + m21 += uint32(lbelow9) + + m21 <<= 8 + m2 += int64(m20) + + m2 += int64(m21) + m22 &^= uint32(lbelow11) + + lbelow10 -= lbelow11 + m23 &^= uint32(lbelow12) + + m22 += uint32(lbelow10) + lbelow11 -= lbelow12 + + m22 <<= 16 + m23 += uint32(lbelow11) + + m23 <<= 24 + m2 += int64(m22) + + m3 <<= 51 + lbelow13 = l - 13 + + lbelow13 >>= 31 + lbelow14 = l - 14 + + lbelow14 >>= 31 + p += lbelow13 + lbelow15 = l - 15 + + m30 = uint32(m[p+12]) + lbelow15 >>= 31 + p += lbelow14 + + m31 = uint32(m[p+13]) + p += lbelow15 + m2 += int64(m23) + + m32 = uint32(m[p+14]) + m30 &^= uint32(lbelow13) + lbelow12 -= lbelow13 + + m30 += uint32(lbelow12) + lbelow13 -= lbelow14 + + m3 += int64(m30) + m31 &^= uint32(lbelow14) + + m31 += uint32(lbelow13) + m32 &^= uint32(lbelow15) + + m31 <<= 8 + lbelow14 -= lbelow15 + + m3 += int64(m31) + m32 += uint32(lbelow14) + d0 = m0 + + m32 <<= 16 + m33 = uint64(lbelow15 + 1) + d1 = m1 + + m33 <<= 24 + m3 += int64(m32) + d2 = m2 + + m3 += int64(m33) + d3 = m3 + + z3 = math.Float64frombits(uint64(d3)) + + z2 = math.Float64frombits(uint64(d2)) + + z1 = math.Float64frombits(uint64(d1)) + + z0 = math.Float64frombits(uint64(d0)) + + z3 -= alpha96 + + z2 -= alpha64 + + z1 -= alpha32 + + z0 -= alpha0 + + h5 += z3 + + h3 += z2 + + h1 += z1 + + h0 += z0 + + y7 = h7 + alpha130 + + y6 = h6 + alpha130 + + y1 = h1 + alpha32 + + y0 = h0 + alpha32 + + y7 -= alpha130 + + y6 -= alpha130 + + y1 -= alpha32 + + y0 -= alpha32 + + y5 = h5 + alpha96 + + y4 = h4 + alpha96 + + x7 = h7 - y7 + y7 *= scale + + x6 = h6 - y6 + y6 *= scale + + x1 = h1 - y1 + + x0 = h0 - y0 + + y5 -= alpha96 + + y4 -= alpha96 + + x1 += y7 + + x0 += y6 + + x7 += y5 + + x6 += y4 + + y3 = h3 + alpha64 + + y2 = h2 + alpha64 + + x0 += x1 + + x6 += x7 + + y3 -= alpha64 + r3low = r3low_stack + + y2 -= alpha64 + r0low = r0low_stack + + x5 = h5 - y5 + r3lowx0 = r3low * x0 + r3high = r3high_stack + + x4 = h4 - y4 + r0lowx6 = r0low * x6 + r0high = r0high_stack + + x3 = h3 - y3 + r3highx0 = r3high * x0 + sr1low = sr1low_stack + + x2 = h2 - y2 + r0highx6 = r0high * x6 + sr1high = sr1high_stack + + x5 += y3 + r0lowx0 = r0low * x0 + r1low = r1low_stack + + h6 = r3lowx0 + r0lowx6 + sr1lowx6 = sr1low * x6 + r1high = r1high_stack + + x4 += y2 + r0highx0 = r0high * x0 + sr2low = sr2low_stack + + h7 = r3highx0 + r0highx6 + sr1highx6 = sr1high * x6 + sr2high = sr2high_stack + + x3 += y1 + r1lowx0 = r1low * x0 + r2low = r2low_stack + + h0 = r0lowx0 + sr1lowx6 + sr2lowx6 = sr2low * x6 + r2high = r2high_stack + + x2 += y0 + r1highx0 = r1high * x0 + sr3low = sr3low_stack + + h1 = r0highx0 + sr1highx6 + sr2highx6 = sr2high * x6 + sr3high = sr3high_stack + + x4 += x5 + r2lowx0 = r2low * x0 + + h2 = r1lowx0 + sr2lowx6 + sr3lowx6 = sr3low * x6 + + x2 += x3 + r2highx0 = r2high * x0 + + h3 = r1highx0 + sr2highx6 + sr3highx6 = sr3high * x6 + + r1highx4 = r1high * x4 + + h4 = r2lowx0 + sr3lowx6 + r1lowx4 = r1low * x4 + + r0highx4 = r0high * x4 + + h5 = r2highx0 + sr3highx6 + r0lowx4 = r0low * x4 + + h7 += r1highx4 + sr3highx4 = sr3high * x4 + + h6 += r1lowx4 + sr3lowx4 = sr3low * x4 + + h5 += r0highx4 + sr2highx4 = sr2high * x4 + + h4 += r0lowx4 + sr2lowx4 = sr2low * x4 + + h3 += sr3highx4 + r0lowx2 = r0low * x2 + + h2 += sr3lowx4 + r0highx2 = r0high * x2 + + h1 += sr2highx4 + r1lowx2 = r1low * x2 + + h0 += sr2lowx4 + r1highx2 = r1high * x2 + + h2 += r0lowx2 + r2lowx2 = r2low * x2 + + h3 += r0highx2 + r2highx2 = r2high * x2 + + h4 += r1lowx2 + sr3lowx2 = sr3low * x2 + + h5 += r1highx2 + sr3highx2 = sr3high * x2 + + h6 += r2lowx2 + + h7 += r2highx2 + + h0 += sr3lowx2 + + h1 += sr3highx2 + +nomorebytes: + + y7 = h7 + alpha130 + + y0 = h0 + alpha32 + + y1 = h1 + alpha32 + + y2 = h2 + alpha64 + + y7 -= alpha130 + + y3 = h3 + alpha64 + + y4 = h4 + alpha96 + + y5 = h5 + alpha96 + + x7 = h7 - y7 + y7 *= scale + + y0 -= alpha32 + + y1 -= alpha32 + + y2 -= alpha64 + + h6 += x7 + + y3 -= alpha64 + + y4 -= alpha96 + + y5 -= alpha96 + + y6 = h6 + alpha130 + + x0 = h0 - y0 + + x1 = h1 - y1 + + x2 = h2 - y2 + + y6 -= alpha130 + + x0 += y7 + + x3 = h3 - y3 + + x4 = h4 - y4 + + x5 = h5 - y5 + + x6 = h6 - y6 + + y6 *= scale + + x2 += y0 + + x3 += y1 + + x4 += y2 + + x0 += y6 + + x5 += y3 + + x6 += y4 + + x2 += x3 + + x0 += x1 + + x4 += x5 + + x6 += y5 + + x2 += offset1 + d1 = int64(math.Float64bits(x2)) + + x0 += offset0 + d0 = int64(math.Float64bits(x0)) + + x4 += offset2 + d2 = int64(math.Float64bits(x4)) + + x6 += offset3 + d3 = int64(math.Float64bits(x6)) + + f0 = uint64(d0) + + f1 = uint64(d1) + bits32 = math.MaxUint64 + + f2 = uint64(d2) + bits32 >>= 32 + + f3 = uint64(d3) + f = f0 >> 32 + + f0 &= bits32 + f &= 255 + + f1 += f + g0 = f0 + 5 + + g = g0 >> 32 + g0 &= bits32 + + f = f1 >> 32 + f1 &= bits32 + + f &= 255 + g1 = f1 + g + + g = g1 >> 32 + f2 += f + + f = f2 >> 32 + g1 &= bits32 + + f2 &= bits32 + f &= 255 + + f3 += f + g2 = f2 + g + + g = g2 >> 32 + g2 &= bits32 + + f4 = f3 >> 32 + f3 &= bits32 + + f4 &= 255 + g3 = f3 + g + + g = g3 >> 32 + g3 &= bits32 + + g4 = f4 + g + + g4 = g4 - 4 + s00 = uint32(s[0]) + + f = uint64(int64(g4) >> 63) + s01 = uint32(s[1]) + + f0 &= f + g0 &^= f + s02 = uint32(s[2]) + + f1 &= f + f0 |= g0 + s03 = uint32(s[3]) + + g1 &^= f + f2 &= f + s10 = uint32(s[4]) + + f3 &= f + g2 &^= f + s11 = uint32(s[5]) + + g3 &^= f + f1 |= g1 + s12 = uint32(s[6]) + + f2 |= g2 + f3 |= g3 + s13 = uint32(s[7]) + + s01 <<= 8 + f0 += uint64(s00) + s20 = uint32(s[8]) + + s02 <<= 16 + f0 += uint64(s01) + s21 = uint32(s[9]) + + s03 <<= 24 + f0 += uint64(s02) + s22 = uint32(s[10]) + + s11 <<= 8 + f1 += uint64(s10) + s23 = uint32(s[11]) + + s12 <<= 16 + f1 += uint64(s11) + s30 = uint32(s[12]) + + s13 <<= 24 + f1 += uint64(s12) + s31 = uint32(s[13]) + + f0 += uint64(s03) + f1 += uint64(s13) + s32 = uint32(s[14]) + + s21 <<= 8 + f2 += uint64(s20) + s33 = uint32(s[15]) + + s22 <<= 16 + f2 += uint64(s21) + + s23 <<= 24 + f2 += uint64(s22) + + s31 <<= 8 + f3 += uint64(s30) + + s32 <<= 16 + f3 += uint64(s31) + + s33 <<= 24 + f3 += uint64(s32) + + f2 += uint64(s23) + f3 += uint64(s33) + + out[0] = byte(f0) + f0 >>= 8 + out[1] = byte(f0) + f0 >>= 8 + out[2] = byte(f0) + f0 >>= 8 + out[3] = byte(f0) + f0 >>= 8 + f1 += f0 + + out[4] = byte(f1) + f1 >>= 8 + out[5] = byte(f1) + f1 >>= 8 + out[6] = byte(f1) + f1 >>= 8 + out[7] = byte(f1) + f1 >>= 8 + f2 += f1 + + out[8] = byte(f2) + f2 >>= 8 + out[9] = byte(f2) + f2 >>= 8 + out[10] = byte(f2) + f2 >>= 8 + out[11] = byte(f2) + f2 >>= 8 + f3 += f2 + + out[12] = byte(f3) + f3 >>= 8 + out[13] = byte(f3) + f3 >>= 8 + out[14] = byte(f3) + f3 >>= 8 + out[15] = byte(f3) +} diff --git a/src/ssl/test/runner/prf.go b/src/ssl/test/runner/prf.go index 75a8933..d445e76 100644 --- a/src/ssl/test/runner/prf.go +++ b/src/ssl/test/runner/prf.go @@ -323,14 +323,14 @@ func (h finishedHash) serverSum(masterSecret []byte) []byte { // selectClientCertSignatureAlgorithm returns a signatureAndHash to sign a // client's CertificateVerify with, or an error if none can be found. -func (h finishedHash) selectClientCertSignatureAlgorithm(serverList []signatureAndHash, sigType uint8) (signatureAndHash, error) { +func (h finishedHash) selectClientCertSignatureAlgorithm(serverList, clientList []signatureAndHash, sigType uint8) (signatureAndHash, error) { if h.version < VersionTLS12 { // Nothing to negotiate before TLS 1.2. return signatureAndHash{signature: sigType}, nil } for _, v := range serverList { - if v.signature == sigType && v.hash == hashSHA256 { + if v.signature == sigType && isSupportedSignatureAndHash(v, clientList) { return v, nil } } diff --git a/src/ssl/test/runner/runner.go b/src/ssl/test/runner/runner.go index aaa2a4d..ec2fede 100644 --- a/src/ssl/test/runner/runner.go +++ b/src/ssl/test/runner/runner.go @@ -20,14 +20,17 @@ import ( "strings" "sync" "syscall" + "time" ) 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.") + useValgrind = flag.Bool("valgrind", false, "If true, run code under valgrind") + useGDB = flag.Bool("gdb", false, "If true, run BoringSSL code under gdb") + flagDebug = flag.Bool("debug", false, "Hexdump the contents of the connection") + mallocTest = flag.Int64("malloc-test", -1, "If non-negative, run each test with each malloc in turn failing from the given number onwards.") + mallocTestDebug = 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.") + jsonOutput = flag.String("json-output", "", "The file to output JSON results to.") + pipe = flag.Bool("pipe", false, "If true, print status output suitable for piping into another program.") ) const ( @@ -132,6 +135,9 @@ type testCase struct { // expectedResumeVersion, if non-zero, specifies the TLS version that // must be negotiated on resumption. If zero, expectedVersion is used. expectedResumeVersion uint16 + // expectedCipher, if non-zero, specifies the TLS cipher suite that + // should be negotiated. + expectedCipher uint16 // expectChannelID controls whether the connection should have // negotiated a Channel ID with channelIDKey. expectChannelID bool @@ -181,6 +187,12 @@ type testCase struct { // damageFirstWrite, if true, configures the underlying transport to // damage the final byte of the first application data write. damageFirstWrite bool + // exportKeyingMaterial, if non-zero, configures the test to exchange + // keying material and verify they match. + exportKeyingMaterial int + exportLabel string + exportContext string + useExportContext bool // flags, if not empty, contains a list of command-line flags that will // be passed to the shim program. flags []string @@ -292,6 +304,18 @@ var testCases = []testCase{ expectedError: ":UNEXPECTED_MESSAGE:", }, { + name: "SkipCertificateStatus", + config: Config{ + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + Bugs: ProtocolBugs{ + SkipCertificateStatus: true, + }, + }, + flags: []string{ + "-enable-ocsp-stapling", + }, + }, + { name: "SkipServerKeyExchange", config: Config{ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, @@ -376,11 +400,47 @@ var testCases = []testCase{ }, { testType: serverTest, + name: "Alert", + config: Config{ + Bugs: ProtocolBugs{ + SendSpuriousAlert: alertRecordOverflow, + }, + }, + shouldFail: true, + expectedError: ":TLSV1_ALERT_RECORD_OVERFLOW:", + }, + { + protocol: dtls, + testType: serverTest, + name: "Alert-DTLS", + config: Config{ + Bugs: ProtocolBugs{ + SendSpuriousAlert: alertRecordOverflow, + }, + }, + shouldFail: true, + expectedError: ":TLSV1_ALERT_RECORD_OVERFLOW:", + }, + { + testType: serverTest, name: "FragmentAlert", config: Config{ Bugs: ProtocolBugs{ FragmentAlert: true, - SendSpuriousAlert: true, + SendSpuriousAlert: alertRecordOverflow, + }, + }, + shouldFail: true, + expectedError: ":BAD_ALERT:", + }, + { + protocol: dtls, + testType: serverTest, + name: "FragmentAlert-DTLS", + config: Config{ + Bugs: ProtocolBugs{ + FragmentAlert: true, + SendSpuriousAlert: alertRecordOverflow, }, }, shouldFail: true, @@ -536,11 +596,11 @@ var testCases = []testCase{ expectedError: ":WRONG_CIPHER_RETURNED:", }, { - name: "RSAServerKeyExchange", + name: "RSAEphemeralKey", config: Config{ CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA}, Bugs: ProtocolBugs{ - RSAServerKeyExchange: true, + RSAEphemeralKey: true, }, }, shouldFail: true, @@ -650,6 +710,380 @@ var testCases = []testCase{ AppDataAfterChangeCipherSpec: []byte("TEST MESSAGE"), }, }, + // BoringSSL's DTLS implementation will drop the out-of-order + // application data. + }, + { + name: "AlertAfterChangeCipherSpec", + config: Config{ + Bugs: ProtocolBugs{ + AlertAfterChangeCipherSpec: alertRecordOverflow, + }, + }, + shouldFail: true, + expectedError: ":TLSV1_ALERT_RECORD_OVERFLOW:", + }, + { + protocol: dtls, + name: "AlertAfterChangeCipherSpec-DTLS", + config: Config{ + Bugs: ProtocolBugs{ + AlertAfterChangeCipherSpec: alertRecordOverflow, + }, + }, + shouldFail: true, + expectedError: ":TLSV1_ALERT_RECORD_OVERFLOW:", + }, + { + protocol: dtls, + name: "ReorderHandshakeFragments-Small-DTLS", + config: Config{ + Bugs: ProtocolBugs{ + ReorderHandshakeFragments: true, + // Small enough that every handshake message is + // fragmented. + MaxHandshakeRecordLength: 2, + }, + }, + }, + { + protocol: dtls, + name: "ReorderHandshakeFragments-Large-DTLS", + config: Config{ + Bugs: ProtocolBugs{ + ReorderHandshakeFragments: true, + // Large enough that no handshake message is + // fragmented. + MaxHandshakeRecordLength: 2048, + }, + }, + }, + { + protocol: dtls, + name: "MixCompleteMessageWithFragments-DTLS", + config: Config{ + Bugs: ProtocolBugs{ + ReorderHandshakeFragments: true, + MixCompleteMessageWithFragments: true, + MaxHandshakeRecordLength: 2, + }, + }, + }, + { + name: "SendInvalidRecordType", + config: Config{ + Bugs: ProtocolBugs{ + SendInvalidRecordType: true, + }, + }, + shouldFail: true, + expectedError: ":UNEXPECTED_RECORD:", + }, + { + protocol: dtls, + name: "SendInvalidRecordType-DTLS", + config: Config{ + Bugs: ProtocolBugs{ + SendInvalidRecordType: true, + }, + }, + shouldFail: true, + expectedError: ":UNEXPECTED_RECORD:", + }, + { + name: "FalseStart-SkipServerSecondLeg", + config: Config{ + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + NextProtos: []string{"foo"}, + Bugs: ProtocolBugs{ + SkipNewSessionTicket: true, + SkipChangeCipherSpec: true, + SkipFinished: true, + ExpectFalseStart: true, + }, + }, + flags: []string{ + "-false-start", + "-handshake-never-done", + "-advertise-alpn", "\x03foo", + }, + shimWritesFirst: true, + shouldFail: true, + expectedError: ":UNEXPECTED_RECORD:", + }, + { + name: "FalseStart-SkipServerSecondLeg-Implicit", + config: Config{ + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + NextProtos: []string{"foo"}, + Bugs: ProtocolBugs{ + SkipNewSessionTicket: true, + SkipChangeCipherSpec: true, + SkipFinished: true, + }, + }, + flags: []string{ + "-implicit-handshake", + "-false-start", + "-handshake-never-done", + "-advertise-alpn", "\x03foo", + }, + shouldFail: true, + expectedError: ":UNEXPECTED_RECORD:", + }, + { + testType: serverTest, + name: "FailEarlyCallback", + flags: []string{"-fail-early-callback"}, + shouldFail: true, + expectedError: ":CONNECTION_REJECTED:", + expectedLocalError: "remote error: access denied", + }, + { + name: "WrongMessageType", + config: Config{ + Bugs: ProtocolBugs{ + WrongCertificateMessageType: true, + }, + }, + shouldFail: true, + expectedError: ":UNEXPECTED_MESSAGE:", + expectedLocalError: "remote error: unexpected message", + }, + { + protocol: dtls, + name: "WrongMessageType-DTLS", + config: Config{ + Bugs: ProtocolBugs{ + WrongCertificateMessageType: true, + }, + }, + shouldFail: true, + expectedError: ":UNEXPECTED_MESSAGE:", + expectedLocalError: "remote error: unexpected message", + }, + { + protocol: dtls, + name: "FragmentMessageTypeMismatch-DTLS", + config: Config{ + Bugs: ProtocolBugs{ + MaxHandshakeRecordLength: 2, + FragmentMessageTypeMismatch: true, + }, + }, + shouldFail: true, + expectedError: ":FRAGMENT_MISMATCH:", + }, + { + protocol: dtls, + name: "FragmentMessageLengthMismatch-DTLS", + config: Config{ + Bugs: ProtocolBugs{ + MaxHandshakeRecordLength: 2, + FragmentMessageLengthMismatch: true, + }, + }, + shouldFail: true, + expectedError: ":FRAGMENT_MISMATCH:", + }, + { + protocol: dtls, + name: "SplitFragmentHeader-DTLS", + config: Config{ + Bugs: ProtocolBugs{ + SplitFragmentHeader: true, + }, + }, + shouldFail: true, + expectedError: ":UNEXPECTED_MESSAGE:", + }, + { + protocol: dtls, + name: "SplitFragmentBody-DTLS", + config: Config{ + Bugs: ProtocolBugs{ + SplitFragmentBody: true, + }, + }, + shouldFail: true, + expectedError: ":UNEXPECTED_MESSAGE:", + }, + { + protocol: dtls, + name: "SendEmptyFragments-DTLS", + config: Config{ + Bugs: ProtocolBugs{ + SendEmptyFragments: true, + }, + }, + }, + { + name: "UnsupportedCipherSuite", + config: Config{ + CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA}, + Bugs: ProtocolBugs{ + IgnorePeerCipherPreferences: true, + }, + }, + flags: []string{"-cipher", "DEFAULT:!RC4"}, + shouldFail: true, + expectedError: ":WRONG_CIPHER_RETURNED:", + }, + { + name: "UnsupportedCurve", + config: Config{ + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + // BoringSSL implements P-224 but doesn't enable it by + // default. + CurvePreferences: []CurveID{CurveP224}, + Bugs: ProtocolBugs{ + IgnorePeerCurvePreferences: true, + }, + }, + shouldFail: true, + expectedError: ":WRONG_CURVE:", + }, + { + name: "SendWarningAlerts", + config: Config{ + Bugs: ProtocolBugs{ + SendWarningAlerts: alertAccessDenied, + }, + }, + }, + { + protocol: dtls, + name: "SendWarningAlerts-DTLS", + config: Config{ + Bugs: ProtocolBugs{ + SendWarningAlerts: alertAccessDenied, + }, + }, + }, + { + name: "BadFinished", + config: Config{ + Bugs: ProtocolBugs{ + BadFinished: true, + }, + }, + shouldFail: true, + expectedError: ":DIGEST_CHECK_FAILED:", + }, + { + name: "FalseStart-BadFinished", + config: Config{ + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + NextProtos: []string{"foo"}, + Bugs: ProtocolBugs{ + BadFinished: true, + ExpectFalseStart: true, + }, + }, + flags: []string{ + "-false-start", + "-handshake-never-done", + "-advertise-alpn", "\x03foo", + }, + shimWritesFirst: true, + shouldFail: true, + expectedError: ":DIGEST_CHECK_FAILED:", + }, + { + name: "NoFalseStart-NoALPN", + config: Config{ + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + Bugs: ProtocolBugs{ + ExpectFalseStart: true, + AlertBeforeFalseStartTest: alertAccessDenied, + }, + }, + flags: []string{ + "-false-start", + }, + shimWritesFirst: true, + shouldFail: true, + expectedError: ":TLSV1_ALERT_ACCESS_DENIED:", + expectedLocalError: "tls: peer did not false start: EOF", + }, + { + name: "NoFalseStart-NoAEAD", + config: Config{ + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA}, + NextProtos: []string{"foo"}, + Bugs: ProtocolBugs{ + ExpectFalseStart: true, + AlertBeforeFalseStartTest: alertAccessDenied, + }, + }, + flags: []string{ + "-false-start", + "-advertise-alpn", "\x03foo", + }, + shimWritesFirst: true, + shouldFail: true, + expectedError: ":TLSV1_ALERT_ACCESS_DENIED:", + expectedLocalError: "tls: peer did not false start: EOF", + }, + { + name: "NoFalseStart-RSA", + config: Config{ + CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256}, + NextProtos: []string{"foo"}, + Bugs: ProtocolBugs{ + ExpectFalseStart: true, + AlertBeforeFalseStartTest: alertAccessDenied, + }, + }, + flags: []string{ + "-false-start", + "-advertise-alpn", "\x03foo", + }, + shimWritesFirst: true, + shouldFail: true, + expectedError: ":TLSV1_ALERT_ACCESS_DENIED:", + expectedLocalError: "tls: peer did not false start: EOF", + }, + { + name: "NoFalseStart-DHE_RSA", + config: Config{ + CipherSuites: []uint16{TLS_DHE_RSA_WITH_AES_128_GCM_SHA256}, + NextProtos: []string{"foo"}, + Bugs: ProtocolBugs{ + ExpectFalseStart: true, + AlertBeforeFalseStartTest: alertAccessDenied, + }, + }, + flags: []string{ + "-false-start", + "-advertise-alpn", "\x03foo", + }, + shimWritesFirst: true, + shouldFail: true, + expectedError: ":TLSV1_ALERT_ACCESS_DENIED:", + expectedLocalError: "tls: peer did not false start: EOF", + }, + { + testType: serverTest, + name: "NoSupportedCurves", + config: Config{ + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + Bugs: ProtocolBugs{ + NoSupportedCurves: true, + }, + }, + }, + { + testType: serverTest, + name: "NoCommonCurves", + config: Config{ + CipherSuites: []uint16{ + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + }, + CurvePreferences: []CurveID{CurveP224}, + }, + expectedCipher: TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, }, } @@ -665,7 +1099,8 @@ func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int, i } if test.protocol == dtls { - conn = newPacketAdaptor(conn) + config.Bugs.PacketAdaptor = newPacketAdaptor(conn) + conn = config.Bugs.PacketAdaptor if test.replayWrites { conn = newReplayAdaptor(conn) } @@ -713,6 +1148,10 @@ func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int, i return fmt.Errorf("got version %x, expected %x", vers, expectedVersion) } + if cipher := tlsConn.ConnectionState().CipherSuite; test.expectedCipher != 0 && cipher != test.expectedCipher { + return fmt.Errorf("got cipher %x, expected %x", cipher, test.expectedCipher) + } + if test.expectChannelID { channelID := tlsConn.ConnectionState().ChannelID if channelID == nil { @@ -741,6 +1180,20 @@ func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int, i return fmt.Errorf("SRTP profile mismatch: got %d, wanted %d", p, test.expectedSRTPProtectionProfile) } + if test.exportKeyingMaterial > 0 { + actual := make([]byte, test.exportKeyingMaterial) + if _, err := io.ReadFull(tlsConn, actual); err != nil { + return err + } + expected, err := tlsConn.ExportKeyingMaterial(test.exportKeyingMaterial, []byte(test.exportLabel), []byte(test.exportContext), test.useExportContext) + if err != nil { + return err + } + if !bytes.Equal(actual, expected) { + return fmt.Errorf("keying material mismatch") + } + } + if test.shimWritesFirst { var buf [5]byte _, err := io.ReadFull(tlsConn, buf[:]) @@ -778,21 +1231,14 @@ func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int, i return err } - var testMessage []byte - if config.Bugs.AppDataAfterChangeCipherSpec != nil { - // We've already sent a message. Expect the shim to echo it - // back. - testMessage = config.Bugs.AppDataAfterChangeCipherSpec - } else { - if messageLen == 0 { - messageLen = 32 - } - testMessage = make([]byte, messageLen) - for i := range testMessage { - testMessage[i] = 0x42 - } - tlsConn.Write(testMessage) + 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 { @@ -840,27 +1286,6 @@ func gdbOf(path string, args ...string) *exec.Cmd { 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 { @@ -869,16 +1294,45 @@ func (moreMallocsError) Error() string { var errMoreMallocs = moreMallocsError{} +// accept accepts a connection from listener, unless waitChan signals a process +// exit first. +func acceptOrWait(listener net.Listener, waitChan chan error) (net.Conn, error) { + type connOrError struct { + conn net.Conn + err error + } + connChan := make(chan connOrError, 1) + go func() { + conn, err := listener.Accept() + connChan <- connOrError{conn, err} + close(connChan) + }() + select { + case result := <-connChan: + return result.conn, result.err + case childErr := <-waitChan: + waitChan <- childErr + return nil, fmt.Errorf("child exited early: %s", childErr) + } +} + 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() + listener, err := net.ListenTCP("tcp4", &net.TCPAddr{IP: net.IP{127, 0, 0, 1}}) + if err != nil { + panic(err) + } + defer func() { + if listener != nil { + listener.Close() + } + }() shim_path := path.Join(buildDir, "ssl/test/bssl_shim") - var flags []string + flags := []string{"-port", strconv.Itoa(listener.Addr().(*net.TCPAddr).Port)} if test.testType == serverTest { flags = append(flags, "-server") @@ -909,6 +1363,15 @@ func runTest(test *testCase, buildDir string, mallocNumToFail int64) error { flags = append(flags, "-shim-writes-first") } + if test.exportKeyingMaterial > 0 { + flags = append(flags, "-export-keying-material", strconv.Itoa(test.exportKeyingMaterial)) + flags = append(flags, "-export-label", test.exportLabel) + flags = append(flags, "-export-context", test.exportContext) + if test.useExportContext { + flags = append(flags, "-use-export-context") + } + } + flags = append(flags, test.flags...) var shim *exec.Cmd @@ -919,13 +1382,13 @@ func runTest(test *testCase, buildDir string, mallocNumToFail int64) error { } 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)} + shim.Env = os.Environ() + shim.Env = append(shim.Env, "MALLOC_NUMBER_TO_FAIL="+strconv.FormatInt(mallocNumToFail, 10)) if *mallocTestDebug { shim.Env = append(shim.Env, "MALLOC_ABORT_ON_FAIL=1") } @@ -935,8 +1398,8 @@ func runTest(test *testCase, buildDir string, mallocNumToFail int64) error { if err := shim.Start(); err != nil { panic(err) } - shimEnd.Close() - shimEndResume.Close() + waitChan := make(chan error, 1) + go func() { waitChan <- shim.Wait() }() config := test.config config.ClientSessionCache = NewLRUClientSessionCache(1) @@ -945,16 +1408,27 @@ func runTest(test *testCase, buildDir string, mallocNumToFail int64) error { if len(config.Certificates) == 0 { config.Certificates = []Certificate{getRSACertificate()} } + } else { + // Supply a ServerName to ensure a constant session cache key, + // rather than falling back to net.Conn.RemoteAddr. + if len(config.ServerName) == 0 { + config.ServerName = "test" + } } - err := doExchange(test, &config, conn, test.messageLen, - false /* not a resumption */) - conn.Close() + conn, err := acceptOrWait(listener, waitChan) + if err == nil { + err = doExchange(test, &config, conn, test.messageLen, false /* not a resumption */) + conn.Close() + } if err == nil && test.resumeSession { var resumeConfig Config if test.resumeConfig != nil { resumeConfig = *test.resumeConfig + if len(resumeConfig.ServerName) == 0 { + resumeConfig.ServerName = config.ServerName + } if len(resumeConfig.Certificates) == 0 { resumeConfig.Certificates = []Certificate{getRSACertificate()} } @@ -966,12 +1440,20 @@ func runTest(test *testCase, buildDir string, mallocNumToFail int64) error { } else { resumeConfig = config } - err = doExchange(test, &resumeConfig, connResume, test.messageLen, - true /* resumption */) + var connResume net.Conn + connResume, err = acceptOrWait(listener, waitChan) + if err == nil { + err = doExchange(test, &resumeConfig, connResume, test.messageLen, true /* resumption */) + connResume.Close() + } } - connResume.Close() - childErr := shim.Wait() + // Close the listener now. This is to avoid hangs should the shim try to + // open more connections than expected. + listener.Close() + listener = nil + + childErr := <-waitChan if exitError, ok := childErr.(*exec.ExitError); ok { if exitError.Sys().(syscall.WaitStatus).ExitStatus() == 88 { return errMoreMallocs @@ -981,7 +1463,7 @@ func runTest(test *testCase, buildDir string, mallocNumToFail int64) error { stdout := string(stdoutBuf.Bytes()) stderr := string(stderrBuf.Bytes()) failed := err != nil || childErr != nil - correctFailure := len(test.expectedError) == 0 || strings.Contains(stdout, test.expectedError) + correctFailure := len(test.expectedError) == 0 || strings.Contains(stderr, test.expectedError) localError := "none" if err != nil { localError = err.Error() @@ -1008,10 +1490,10 @@ func runTest(test *testCase, buildDir string, mallocNumToFail int64) error { 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) + return fmt.Errorf("%s: local error '%s', child error '%s', stdout:\n%s\nstderr:\n%s", msg, localError, childError, stdout, stderr) } - if !*useValgrind && len(stderr) > 0 { + if !*useValgrind && !failed && len(stderr) > 0 { println(stderr) } @@ -1047,12 +1529,14 @@ var testCipherSuites = []struct { {"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}, + {"DHE-RSA-CHACHA20-POLY1305", TLS_DHE_RSA_WITH_CHACHA20_POLY1305_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-CHACHA20-POLY1305", TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256}, {"ECDHE-ECDSA-RC4-SHA", TLS_ECDHE_ECDSA_WITH_RC4_128_SHA}, {"ECDHE-PSK-AES128-GCM-SHA256", TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256}, {"ECDHE-RSA-AES128-GCM", TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, @@ -1061,6 +1545,7 @@ var testCipherSuites = []struct { {"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-CHACHA20-POLY1305", TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256}, {"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}, @@ -1076,7 +1561,8 @@ func hasComponent(suiteName, component string) bool { func isTLS12Only(suiteName string) bool { return hasComponent(suiteName, "GCM") || hasComponent(suiteName, "SHA256") || - hasComponent(suiteName, "SHA384") + hasComponent(suiteName, "SHA384") || + hasComponent(suiteName, "POLY1305") } func isDTLSCipher(suiteName string) bool { @@ -1454,6 +1940,17 @@ func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol) }) testCases = append(testCases, testCase{ protocol: protocol, + name: "Basic-Client-Implicit" + suffix, + config: Config{ + Bugs: ProtocolBugs{ + MaxHandshakeRecordLength: maxHandshakeRecordLength, + }, + }, + flags: append(flags, "-implicit-handshake"), + resumeSession: true, + }) + testCases = append(testCases, testCase{ + protocol: protocol, testType: serverTest, name: "Basic-Server" + suffix, config: Config{ @@ -1477,6 +1974,30 @@ func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol) flags: flags, resumeSession: true, }) + testCases = append(testCases, testCase{ + protocol: protocol, + testType: serverTest, + name: "Basic-Server-Implicit" + suffix, + config: Config{ + Bugs: ProtocolBugs{ + MaxHandshakeRecordLength: maxHandshakeRecordLength, + }, + }, + flags: append(flags, "-implicit-handshake"), + resumeSession: true, + }) + testCases = append(testCases, testCase{ + protocol: protocol, + testType: serverTest, + name: "Basic-Server-EarlyCallback" + suffix, + config: Config{ + Bugs: ProtocolBugs{ + MaxHandshakeRecordLength: maxHandshakeRecordLength, + }, + }, + flags: append(flags, "-use-early-callback"), + resumeSession: true, + }) // TLS client auth. testCases = append(testCases, testCase{ @@ -1588,6 +2109,8 @@ func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol) expectedNextProtoType: npn, }) + // TODO(davidben): Add tests for when False Start doesn't trigger. + // Client does False Start and negotiates NPN. testCases = append(testCases, testCase{ protocol: protocol, @@ -1626,9 +2149,27 @@ func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol) resumeSession: true, }) + // Client does False Start but doesn't explicitly call + // SSL_connect. + testCases = append(testCases, testCase{ + protocol: protocol, + name: "FalseStart-Implicit" + suffix, + config: Config{ + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + NextProtos: []string{"foo"}, + Bugs: ProtocolBugs{ + MaxHandshakeRecordLength: maxHandshakeRecordLength, + }, + }, + flags: append(flags, + "-implicit-handshake", + "-false-start", + "-advertise-alpn", "\x03foo"), + }) + // False Start without session tickets. testCases = append(testCases, testCase{ - name: "FalseStart-SessionTicketsDisabled", + name: "FalseStart-SessionTicketsDisabled" + suffix, config: Config{ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, NextProtos: []string{"foo"}, @@ -1710,17 +2251,36 @@ func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol) }, flags: flags, }) + } +} + +func addDDoSCallbackTests() { + // DDoS callback. + + for _, resume := range []bool{false, true} { + suffix := "Resume" + if resume { + suffix = "No" + suffix + } testCases = append(testCases, testCase{ - testType: serverTest, - protocol: protocol, - name: "CookieExchange" + suffix, - config: Config{ - Bugs: ProtocolBugs{ - MaxHandshakeRecordLength: maxHandshakeRecordLength, - }, - }, - flags: append(flags, "-cookie-exchange"), + testType: serverTest, + name: "Server-DDoS-OK-" + suffix, + flags: []string{"-install-ddos-callback"}, + resumeSession: resume, + }) + + failFlag := "-fail-ddos-callback" + if resume { + failFlag = "-fail-second-ddos-callback" + } + testCases = append(testCases, testCase{ + testType: serverTest, + name: "Server-DDoS-Reject-" + suffix, + flags: []string{"-install-ddos-callback", failFlag}, + resumeSession: resume, + shouldFail: true, + expectedError: ":CONNECTION_REJECTED:", }) } } @@ -1976,7 +2536,7 @@ func addExtensionTests() { }) testCases = append(testCases, testCase{ testType: clientTest, - name: "ServerNameExtensionClient", + name: "ServerNameExtensionClientMismatch", config: Config{ Bugs: ProtocolBugs{ ExpectServerName: "mismatch.com", @@ -1988,7 +2548,7 @@ func addExtensionTests() { }) testCases = append(testCases, testCase{ testType: clientTest, - name: "ServerNameExtensionClient", + name: "ServerNameExtensionClientMissing", config: Config{ Bugs: ProtocolBugs{ ExpectServerName: "missing.com", @@ -2201,27 +2761,40 @@ func addResumptionVersionTests() { suffix += "-DTLS" } - testCases = append(testCases, testCase{ - protocol: protocol, - name: "Resume-Client" + suffix, - resumeSession: true, - config: Config{ - MaxVersion: sessionVers.version, - CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA}, - Bugs: ProtocolBugs{ - AllowSessionVersionMismatch: true, + if sessionVers.version == resumeVers.version { + testCases = append(testCases, testCase{ + protocol: protocol, + name: "Resume-Client" + suffix, + resumeSession: true, + config: Config{ + MaxVersion: sessionVers.version, + CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA}, }, - }, - expectedVersion: sessionVers.version, - resumeConfig: &Config{ - MaxVersion: resumeVers.version, - CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA}, - Bugs: ProtocolBugs{ - AllowSessionVersionMismatch: true, + expectedVersion: sessionVers.version, + expectedResumeVersion: resumeVers.version, + }) + } else { + testCases = append(testCases, testCase{ + protocol: protocol, + name: "Resume-Client-Mismatch" + suffix, + resumeSession: true, + config: Config{ + MaxVersion: sessionVers.version, + CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA}, }, - }, - expectedResumeVersion: resumeVers.version, - }) + expectedVersion: sessionVers.version, + resumeConfig: &Config{ + MaxVersion: resumeVers.version, + CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA}, + Bugs: ProtocolBugs{ + AllowSessionVersionMismatch: true, + }, + }, + expectedResumeVersion: resumeVers.version, + shouldFail: true, + expectedError: ":OLD_SESSION_VERSION_NOT_RETURNED:", + }) + } testCases = append(testCases, testCase{ protocol: protocol, @@ -2265,6 +2838,22 @@ func addResumptionVersionTests() { } } } + + testCases = append(testCases, testCase{ + name: "Resume-Client-CipherMismatch", + resumeSession: true, + config: Config{ + CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256}, + }, + resumeConfig: &Config{ + CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256}, + Bugs: ProtocolBugs{ + SendCipherSuite: TLS_RSA_WITH_AES_128_CBC_SHA, + }, + }, + shouldFail: true, + expectedError: ":OLD_SESSION_CIPHER_NOT_RETURNED:", + }) } func addRenegotiationTests() { @@ -2276,6 +2865,17 @@ func addRenegotiationTests() { }) testCases = append(testCases, testCase{ testType: serverTest, + name: "Renegotiate-Server-Full", + config: Config{ + Bugs: ProtocolBugs{ + NeverResumeOnRenego: true, + }, + }, + flags: []string{"-renegotiate"}, + shimWritesFirst: true, + }) + testCases = append(testCases, testCase{ + testType: serverTest, name: "Renegotiate-Server-EmptyExt", config: Config{ Bugs: ProtocolBugs{ @@ -2328,12 +2928,43 @@ func addRenegotiationTests() { }, flags: []string{"-allow-unsafe-legacy-renegotiation"}, }) + testCases = append(testCases, testCase{ + testType: serverTest, + name: "Renegotiate-Server-ClientInitiated-Forbidden", + renegotiate: true, + flags: []string{"-reject-peer-renegotiations"}, + shouldFail: true, + expectedError: ":NO_RENEGOTIATION:", + expectedLocalError: "remote error: no renegotiation", + }) + // Regression test for CVE-2015-0291. + testCases = append(testCases, testCase{ + testType: serverTest, + name: "Renegotiate-Server-NoSignatureAlgorithms", + config: Config{ + Bugs: ProtocolBugs{ + NeverResumeOnRenego: true, + NoSignatureAlgorithmsOnRenego: true, + }, + }, + flags: []string{"-renegotiate"}, + shimWritesFirst: true, + }) // TODO(agl): test the renegotiation info SCSV. testCases = append(testCases, testCase{ name: "Renegotiate-Client", renegotiate: true, }) testCases = append(testCases, testCase{ + name: "Renegotiate-Client-Full", + config: Config{ + Bugs: ProtocolBugs{ + NeverResumeOnRenego: true, + }, + }, + renegotiate: true, + }) + testCases = append(testCases, testCase{ name: "Renegotiate-Client-EmptyExt", renegotiate: true, config: Config{ @@ -2372,6 +3003,14 @@ func addRenegotiationTests() { renegotiateCiphers: []uint16{TLS_RSA_WITH_RC4_128_SHA}, }) testCases = append(testCases, testCase{ + name: "Renegotiate-Client-Forbidden", + renegotiate: true, + flags: []string{"-reject-peer-renegotiations"}, + shouldFail: true, + expectedError: ":NO_RENEGOTIATION:", + expectedLocalError: "remote error: no renegotiation", + }) + testCases = append(testCases, testCase{ name: "Renegotiate-SameClientVersion", renegotiate: true, config: Config{ @@ -2418,7 +3057,7 @@ func addFastRadioPaddingTests() { }) testCases = append(testCases, testCase{ protocol: dtls, - name: "FastRadio-Padding", + name: "FastRadio-Padding-DTLS", config: Config{ Bugs: ProtocolBugs{ RequireFastradioPadding: true, @@ -2534,6 +3173,196 @@ func addSigningHashTests() { }, }, }) + + // Test that hash preferences are enforced. BoringSSL defaults to + // rejecting MD5 signatures. + testCases = append(testCases, testCase{ + testType: serverTest, + name: "SigningHash-ClientAuth-Enforced", + config: Config{ + Certificates: []Certificate{rsaCertificate}, + SignatureAndHashes: []signatureAndHash{ + {signatureRSA, hashMD5}, + // Advertise SHA-1 so the handshake will + // proceed, but the shim's preferences will be + // ignored in CertificateVerify generation, so + // MD5 will be chosen. + {signatureRSA, hashSHA1}, + }, + Bugs: ProtocolBugs{ + IgnorePeerSignatureAlgorithmPreferences: true, + }, + }, + flags: []string{"-require-any-client-certificate"}, + shouldFail: true, + expectedError: ":WRONG_SIGNATURE_TYPE:", + }) + + testCases = append(testCases, testCase{ + name: "SigningHash-ServerKeyExchange-Enforced", + config: Config{ + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + SignatureAndHashes: []signatureAndHash{ + {signatureRSA, hashMD5}, + }, + Bugs: ProtocolBugs{ + IgnorePeerSignatureAlgorithmPreferences: true, + }, + }, + shouldFail: true, + expectedError: ":WRONG_SIGNATURE_TYPE:", + }) +} + +// timeouts is the retransmit schedule for BoringSSL. It doubles and +// caps at 60 seconds. On the 13th timeout, it gives up. +var timeouts = []time.Duration{ + 1 * time.Second, + 2 * time.Second, + 4 * time.Second, + 8 * time.Second, + 16 * time.Second, + 32 * time.Second, + 60 * time.Second, + 60 * time.Second, + 60 * time.Second, + 60 * time.Second, + 60 * time.Second, + 60 * time.Second, + 60 * time.Second, +} + +func addDTLSRetransmitTests() { + // Test that this is indeed the timeout schedule. Stress all + // four patterns of handshake. + for i := 1; i < len(timeouts); i++ { + number := strconv.Itoa(i) + testCases = append(testCases, testCase{ + protocol: dtls, + name: "DTLS-Retransmit-Client-" + number, + config: Config{ + Bugs: ProtocolBugs{ + TimeoutSchedule: timeouts[:i], + }, + }, + resumeSession: true, + flags: []string{"-async"}, + }) + testCases = append(testCases, testCase{ + protocol: dtls, + testType: serverTest, + name: "DTLS-Retransmit-Server-" + number, + config: Config{ + Bugs: ProtocolBugs{ + TimeoutSchedule: timeouts[:i], + }, + }, + resumeSession: true, + flags: []string{"-async"}, + }) + } + + // Test that exceeding the timeout schedule hits a read + // timeout. + testCases = append(testCases, testCase{ + protocol: dtls, + name: "DTLS-Retransmit-Timeout", + config: Config{ + Bugs: ProtocolBugs{ + TimeoutSchedule: timeouts, + }, + }, + resumeSession: true, + flags: []string{"-async"}, + shouldFail: true, + expectedError: ":READ_TIMEOUT_EXPIRED:", + }) + + // Test that timeout handling has a fudge factor, due to API + // problems. + testCases = append(testCases, testCase{ + protocol: dtls, + name: "DTLS-Retransmit-Fudge", + config: Config{ + Bugs: ProtocolBugs{ + TimeoutSchedule: []time.Duration{ + timeouts[0] - 10*time.Millisecond, + }, + }, + }, + resumeSession: true, + flags: []string{"-async"}, + }) + + // Test that the final Finished retransmitting isn't + // duplicated if the peer badly fragments everything. + testCases = append(testCases, testCase{ + testType: serverTest, + protocol: dtls, + name: "DTLS-Retransmit-Fragmented", + config: Config{ + Bugs: ProtocolBugs{ + TimeoutSchedule: []time.Duration{timeouts[0]}, + MaxHandshakeRecordLength: 2, + }, + }, + flags: []string{"-async"}, + }) +} + +func addExportKeyingMaterialTests() { + for _, vers := range tlsVersions { + if vers.version == VersionSSL30 { + continue + } + testCases = append(testCases, testCase{ + name: "ExportKeyingMaterial-" + vers.name, + config: Config{ + MaxVersion: vers.version, + }, + exportKeyingMaterial: 1024, + exportLabel: "label", + exportContext: "context", + useExportContext: true, + }) + testCases = append(testCases, testCase{ + name: "ExportKeyingMaterial-NoContext-" + vers.name, + config: Config{ + MaxVersion: vers.version, + }, + exportKeyingMaterial: 1024, + }) + testCases = append(testCases, testCase{ + name: "ExportKeyingMaterial-EmptyContext-" + vers.name, + config: Config{ + MaxVersion: vers.version, + }, + exportKeyingMaterial: 1024, + useExportContext: true, + }) + testCases = append(testCases, testCase{ + name: "ExportKeyingMaterial-Small-" + vers.name, + config: Config{ + MaxVersion: vers.version, + }, + exportKeyingMaterial: 1, + exportLabel: "label", + exportContext: "context", + useExportContext: true, + }) + } + testCases = append(testCases, testCase{ + name: "ExportKeyingMaterial-SSL3", + config: Config{ + MaxVersion: VersionSSL30, + }, + exportKeyingMaterial: 1024, + exportLabel: "label", + exportContext: "context", + useExportContext: true, + shouldFail: true, + expectedError: "failed to export keying material", + }) } func worker(statusChan chan statusMsg, c chan *testCase, buildDir string, wg *sync.WaitGroup) { @@ -2566,27 +3395,47 @@ type statusMsg struct { err error } -func statusPrinter(doneChan chan struct{}, statusChan chan statusMsg, total int) { +func statusPrinter(doneChan chan *testOutput, statusChan chan statusMsg, total int) { var started, done, failed, lineLen int - defer close(doneChan) + testOutput := newTestOutput() for msg := range statusChan { + if !*pipe { + // Erase the previous status line. + var erase string + for i := 0; i < lineLen; i++ { + erase += "\b \b" + } + fmt.Print(erase) + } + 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++ + testOutput.addResult(msg.test.name, "FAIL") + } else { + if *pipe { + // Print each test instead of a status line. + fmt.Printf("PASSED (%s)\n", msg.test.name) + } + testOutput.addResult(msg.test.name, "PASS") + } + } - if msg.err != nil { - fmt.Printf("FAILED (%s)\n%s\n", msg.test.name, msg.err) - failed++ + if !*pipe { + // Print a new status line. + line := fmt.Sprintf("%d/%d/%d/%d", failed, done, started, total) + lineLen = len(line) + os.Stdout.WriteString(line) } - line := fmt.Sprintf("%d/%d/%d/%d", failed, done, started, total) - lineLen = len(line) - os.Stdout.WriteString(line) } + + doneChan <- testOutput } func main() { @@ -2601,6 +3450,7 @@ func main() { addCBCPaddingTests() addCBCSplittingTests() addClientAuthTests() + addDDoSCallbackTests() addVersionNegotiationTests() addMinimumVersionTests() addD5BugTests() @@ -2611,6 +3461,8 @@ func main() { addDTLSReplayTests() addSigningHashTests() addFastRadioPaddingTests() + addDTLSRetransmitTests() + addExportKeyingMaterialTests() for _, async := range []bool{false, true} { for _, splitHandshake := range []bool{false, true} { for _, protocol := range []protocol{tls, dtls} { @@ -2625,7 +3477,7 @@ func main() { statusChan := make(chan statusMsg, numWorkers) testChan := make(chan *testCase, numWorkers) - doneChan := make(chan struct{}) + doneChan := make(chan *testOutput) go statusPrinter(doneChan, statusChan, len(testCases)) @@ -2643,7 +3495,17 @@ func main() { close(testChan) wg.Wait() close(statusChan) - <-doneChan + testOutput := <-doneChan fmt.Printf("\n") + + if *jsonOutput != "" { + if err := testOutput.writeTo(*jsonOutput); err != nil { + fmt.Fprintf(os.Stderr, "Error: %s\n", err) + } + } + + if !testOutput.allPassed { + os.Exit(1) + } } diff --git a/src/ssl/test/runner/test_output.go b/src/ssl/test/runner/test_output.go new file mode 100644 index 0000000..bcb7a93 --- /dev/null +++ b/src/ssl/test/runner/test_output.go @@ -0,0 +1,79 @@ +/* Copyright (c) 2015, Google Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + +package main + +import ( + "encoding/json" + "os" + "time" +) + +// testOutput is a representation of Chromium's JSON test result format. See +// https://www.chromium.org/developers/the-json-test-results-format +type testOutput struct { + Version int `json:"version"` + Interrupted bool `json:"interrupted"` + PathDelimiter string `json:"path_delimiter"` + SecondsSinceEpoch float64 `json:"seconds_since_epoch"` + NumFailuresByType map[string]int `json:"num_failures_by_type"` + Tests map[string]testResult `json:"tests"` + allPassed bool +} + +type testResult struct { + Actual string `json:"actual"` + Expected string `json:"expected"` + IsUnexpected bool `json:"is_unexpected"` +} + +func newTestOutput() *testOutput { + return &testOutput{ + Version: 3, + PathDelimiter: ".", + SecondsSinceEpoch: float64(time.Now().UnixNano()) / float64(time.Second/time.Nanosecond), + NumFailuresByType: make(map[string]int), + Tests: make(map[string]testResult), + allPassed: true, + } +} + +func (t *testOutput) addResult(name, result string) { + if _, found := t.Tests[name]; found { + panic(name) + } + t.Tests[name] = testResult{ + Actual: result, + Expected: "PASS", + IsUnexpected: result != "PASS", + } + t.NumFailuresByType[result]++ + if result != "PASS" { + t.allPassed = false + } +} + +func (t *testOutput) writeTo(name string) error { + file, err := os.Create(name) + if err != nil { + return err + } + defer file.Close() + out, err := json.MarshalIndent(t, "", " ") + if err != nil { + return err + } + _, err = file.Write(out) + return err +} diff --git a/src/ssl/test/scoped_types.h b/src/ssl/test/scoped_types.h new file mode 100644 index 0000000..7e92cee --- /dev/null +++ b/src/ssl/test/scoped_types.h @@ -0,0 +1,28 @@ +/* Copyright (c) 2015, Google Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + +#ifndef OPENSSL_HEADER_SSL_TEST_SCOPED_TYPES_H +#define OPENSSL_HEADER_SSL_TEST_SCOPED_TYPES_H + +#include <openssl/ssl.h> + +#include "../../crypto/test/scoped_types.h" + + +using ScopedSSL = ScopedOpenSSLType<SSL, SSL_free>; +using ScopedSSL_CTX = ScopedOpenSSLType<SSL_CTX, SSL_CTX_free>; +using ScopedSSL_SESSION = ScopedOpenSSLType<SSL_SESSION, SSL_SESSION_free>; + + +#endif // OPENSSL_HEADER_SSL_TEST_SCOPED_TYPES_H diff --git a/src/ssl/test/test_config.cc b/src/ssl/test/test_config.cc index c032d96..25906f7 100644 --- a/src/ssl/test/test_config.cc +++ b/src/ssl/test/test_config.cc @@ -60,7 +60,6 @@ const Flag<bool> kBoolFlags[] = { { "-no-tls11", &TestConfig::no_tls11 }, { "-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 }, @@ -73,6 +72,15 @@ const Flag<bool> kBoolFlags[] = { { "-enable-signed-cert-timestamps", &TestConfig::enable_signed_cert_timestamps }, { "-fastradio-padding", &TestConfig::fastradio_padding }, + { "-implicit-handshake", &TestConfig::implicit_handshake }, + { "-use-early-callback", &TestConfig::use_early_callback }, + { "-fail-early-callback", &TestConfig::fail_early_callback }, + { "-install-ddos-callback", &TestConfig::install_ddos_callback }, + { "-fail-ddos-callback", &TestConfig::fail_ddos_callback }, + { "-fail-second-ddos-callback", &TestConfig::fail_second_ddos_callback }, + { "-handshake-never-done", &TestConfig::handshake_never_done }, + { "-use-export-context", &TestConfig::use_export_context }, + { "-reject-peer-renegotiations", &TestConfig::reject_peer_renegotiations }, }; const Flag<std::string> kStringFlags[] = { @@ -91,6 +99,9 @@ const Flag<std::string> kStringFlags[] = { { "-psk", &TestConfig::psk }, { "-psk-identity", &TestConfig::psk_identity }, { "-srtp-profiles", &TestConfig::srtp_profiles }, + { "-cipher", &TestConfig::cipher }, + { "-export-label", &TestConfig::export_label }, + { "-export-context", &TestConfig::export_context }, }; const Flag<std::string> kBase64Flags[] = { @@ -102,43 +113,15 @@ const Flag<std::string> kBase64Flags[] = { }; const Flag<int> kIntFlags[] = { + { "-port", &TestConfig::port }, { "-min-version", &TestConfig::min_version }, { "-max-version", &TestConfig::max_version }, { "-mtu", &TestConfig::mtu }, + { "-export-keying-material", &TestConfig::export_keying_material }, }; } // namespace -TestConfig::TestConfig() - : is_server(false), - is_dtls(false), - resume(false), - fallback_scsv(false), - require_any_client_certificate(false), - false_start(false), - async(false), - write_different_record_sizes(false), - cbc_record_splitting(false), - partial_write(false), - no_tls12(false), - no_tls11(false), - no_tls1(false), - no_ssl3(false), - cookie_exchange(false), - shim_writes_first(false), - tls_d5_bug(false), - expect_session_miss(false), - expect_extended_master_secret(false), - renegotiate(false), - allow_unsafe_legacy_renegotiation(false), - enable_ocsp_stapling(false), - enable_signed_cert_timestamps(false), - fastradio_padding(false), - min_version(0), - max_version(0), - mtu(0) { -} - bool ParseConfig(int argc, char **argv, TestConfig *out_config) { for (int i = 0; i < argc; i++) { bool *bool_field = FindField(out_config, kBoolFlags, argv[i]); diff --git a/src/ssl/test/test_config.h b/src/ssl/test/test_config.h index ba54227..f107a0f 100644 --- a/src/ssl/test/test_config.h +++ b/src/ssl/test/test_config.h @@ -19,54 +19,65 @@ struct TestConfig { - TestConfig(); - - bool is_server; - bool is_dtls; - bool resume; - bool fallback_scsv; + int port = 0; + bool is_server = false; + bool is_dtls = false; + bool resume = false; + bool fallback_scsv = false; std::string key_file; std::string cert_file; std::string expected_server_name; std::string expected_certificate_types; - bool require_any_client_certificate; + bool require_any_client_certificate = false; std::string advertise_npn; std::string expected_next_proto; - bool false_start; + bool false_start = false; std::string select_next_proto; - bool async; - bool write_different_record_sizes; - bool cbc_record_splitting; - bool partial_write; - bool no_tls12; - bool no_tls11; - bool no_tls1; - bool no_ssl3; - bool cookie_exchange; + bool async = false; + bool write_different_record_sizes = false; + bool cbc_record_splitting = false; + bool partial_write = false; + bool no_tls12 = false; + bool no_tls11 = false; + bool no_tls1 = false; + bool no_ssl3 = false; std::string expected_channel_id; std::string send_channel_id; - bool shim_writes_first; - bool tls_d5_bug; + bool shim_writes_first = false; + bool tls_d5_bug = false; 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 expect_extended_master_secret; + bool expect_session_miss = false; + bool expect_extended_master_secret = false; std::string psk; std::string psk_identity; - bool renegotiate; - bool allow_unsafe_legacy_renegotiation; + bool renegotiate = false; + bool allow_unsafe_legacy_renegotiation = false; std::string srtp_profiles; - bool enable_ocsp_stapling; + bool enable_ocsp_stapling = false; std::string expected_ocsp_response; - bool enable_signed_cert_timestamps; + bool enable_signed_cert_timestamps = false; std::string expected_signed_cert_timestamps; - bool fastradio_padding; - int min_version; - int max_version; - int mtu; + bool fastradio_padding = false; + int min_version = 0; + int max_version = 0; + int mtu = 0; + bool implicit_handshake = false; + bool use_early_callback = false; + bool fail_early_callback = false; + bool install_ddos_callback = false; + bool fail_ddos_callback = false; + bool fail_second_ddos_callback = false; + std::string cipher; + bool handshake_never_done = false; + int export_keying_material = 0; + std::string export_label; + std::string export_context; + bool use_export_context = false; + bool reject_peer_renegotiations = false; }; bool ParseConfig(int argc, char **argv, TestConfig *out_config); |