2465 lines
94 KiB
C++
2465 lines
94 KiB
C++
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
// SPDX-License-Identifier: Apache-2.0 OR ISC
|
|
|
|
|
|
#include <openssl/hmac.h>
|
|
#include <openssl/rand.h>
|
|
#include <openssl/ssl.h>
|
|
#include <thread>
|
|
|
|
#include "internal.h"
|
|
#include "ssl_common_test.h"
|
|
|
|
#include "../crypto/test/test_util.h"
|
|
|
|
BSSL_NAMESPACE_BEGIN
|
|
|
|
// SSLVersionTest executes its test cases under all available protocol versions.
|
|
// Test cases call |Connect| to create a connection using context objects with
|
|
// the protocol version fixed to the current version under test.
|
|
class SSLVersionTest
|
|
: public ::testing::TestWithParam<::std::tuple<VersionParam, int>> {
|
|
protected:
|
|
SSLVersionTest() : cert_(GetTestCertificate()), key_(GetTestKey()) {}
|
|
|
|
void SetUp() { ResetContexts(); }
|
|
|
|
bssl::UniquePtr<SSL_CTX> CreateContext() const {
|
|
const SSL_METHOD *method = is_dtls() ? DTLS_method() : TLS_method();
|
|
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(method));
|
|
if (!ctx || !SSL_CTX_set_min_proto_version(ctx.get(), version()) ||
|
|
!SSL_CTX_set_max_proto_version(ctx.get(), version())) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (enable_read_ahead()) {
|
|
SSL_CTX_set_read_ahead(ctx.get(), 1);
|
|
SSL_CTX_set_default_read_buffer_len(ctx.get(), read_ahead_buffer_size());
|
|
}
|
|
return ctx;
|
|
}
|
|
|
|
void ResetContexts() {
|
|
ASSERT_TRUE(cert_);
|
|
ASSERT_TRUE(key_);
|
|
client_ctx_ = CreateContext();
|
|
ASSERT_TRUE(client_ctx_);
|
|
server_ctx_ = CreateContext();
|
|
ASSERT_TRUE(server_ctx_);
|
|
// Set up a server cert. Client certs can be set up explicitly.
|
|
ASSERT_TRUE(UseCertAndKey(server_ctx_.get()));
|
|
}
|
|
|
|
bool UseCertAndKey(SSL_CTX *ctx) const {
|
|
return SSL_CTX_use_certificate(ctx, cert_.get()) &&
|
|
SSL_CTX_use_PrivateKey(ctx, key_.get());
|
|
}
|
|
|
|
bool Connect(const ClientConfig &config = ClientConfig()) {
|
|
bool connected = ConnectClientAndServer(
|
|
&client_, &server_, client_ctx_.get(), server_ctx_.get(), config,
|
|
shed_handshake_config_);
|
|
if (connected) {
|
|
TransferServerSSL();
|
|
}
|
|
return connected;
|
|
}
|
|
|
|
VersionParam getVersionParam() const { return std::get<0>(GetParam()); }
|
|
|
|
void TransferServerSSL() {
|
|
if (!getVersionParam().transfer_ssl) {
|
|
return;
|
|
}
|
|
// |server_| is reset to hold the transferred SSL.
|
|
TransferSSL(&server_, server_ctx_.get(), nullptr);
|
|
}
|
|
|
|
uint16_t version() const { return getVersionParam().version; }
|
|
|
|
bool is_dtls() const {
|
|
return getVersionParam().ssl_method == VersionParam::is_dtls;
|
|
}
|
|
|
|
size_t read_ahead_buffer_size() const { return std::get<1>(GetParam()); }
|
|
|
|
bool enable_read_ahead() const { return read_ahead_buffer_size() != 0; }
|
|
|
|
void CheckCounterInit() {
|
|
EXPECT_EQ(SSL_CTX_sess_connect(client_ctx_.get()), 0);
|
|
EXPECT_EQ(SSL_CTX_sess_connect(server_ctx_.get()), 0);
|
|
EXPECT_EQ(SSL_CTX_sess_connect_good(client_ctx_.get()), 0);
|
|
EXPECT_EQ(SSL_CTX_sess_connect_good(server_ctx_.get()), 0);
|
|
EXPECT_EQ(SSL_CTX_sess_connect_renegotiate(client_ctx_.get()), 0);
|
|
EXPECT_EQ(SSL_CTX_sess_connect_renegotiate(server_ctx_.get()), 0);
|
|
EXPECT_EQ(SSL_CTX_sess_accept(client_ctx_.get()), 0);
|
|
EXPECT_EQ(SSL_CTX_sess_accept(server_ctx_.get()), 0);
|
|
EXPECT_EQ(SSL_CTX_sess_accept_renegotiate(client_ctx_.get()), 0);
|
|
EXPECT_EQ(SSL_CTX_sess_accept_renegotiate(server_ctx_.get()), 0);
|
|
EXPECT_EQ(SSL_CTX_sess_accept_good(client_ctx_.get()), 0);
|
|
EXPECT_EQ(SSL_CTX_sess_accept_good(server_ctx_.get()), 0);
|
|
EXPECT_EQ(SSL_CTX_sess_hits(client_ctx_.get()), 0);
|
|
EXPECT_EQ(SSL_CTX_sess_hits(server_ctx_.get()), 0);
|
|
EXPECT_EQ(SSL_CTX_sess_cb_hits(client_ctx_.get()), 0);
|
|
EXPECT_EQ(SSL_CTX_sess_cb_hits(server_ctx_.get()), 0);
|
|
EXPECT_EQ(SSL_CTX_sess_misses(client_ctx_.get()), 0);
|
|
EXPECT_EQ(SSL_CTX_sess_misses(server_ctx_.get()), 0);
|
|
EXPECT_EQ(SSL_CTX_sess_timeouts(client_ctx_.get()), 0);
|
|
EXPECT_EQ(SSL_CTX_sess_timeouts(server_ctx_.get()), 0);
|
|
EXPECT_EQ(SSL_CTX_sess_cache_full(client_ctx_.get()), 0);
|
|
EXPECT_EQ(SSL_CTX_sess_cache_full(server_ctx_.get()), 0);
|
|
}
|
|
|
|
bool shed_handshake_config_ = true;
|
|
bssl::UniquePtr<SSL> client_, server_;
|
|
bssl::UniquePtr<SSL_CTX> server_ctx_, client_ctx_;
|
|
bssl::UniquePtr<X509> cert_;
|
|
bssl::UniquePtr<EVP_PKEY> key_;
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
WithVersion, SSLVersionTest,
|
|
testing::Combine(::testing::ValuesIn(kAllVersions),
|
|
testing::Values(0, 128, 512, 8192, 65535, 131072)),
|
|
[](const testing::TestParamInfo<::std::tuple<VersionParam, int>>
|
|
&test_info) {
|
|
std::string test_name =
|
|
std::string(std::get<0>(test_info.param).name) + "_BufferSize_";
|
|
return test_name + std::to_string(std::get<1>(test_info.param));
|
|
});
|
|
|
|
TEST_P(SSLVersionTest, SequenceNumber) {
|
|
CheckCounterInit();
|
|
ASSERT_TRUE(Connect());
|
|
// Counters should count one two-way successful connection.
|
|
EXPECT_EQ(SSL_CTX_sess_connect(client_ctx_.get()), 1);
|
|
EXPECT_EQ(SSL_CTX_sess_connect_good(client_ctx_.get()), 1);
|
|
EXPECT_EQ(SSL_CTX_sess_accept(server_ctx_.get()), 1);
|
|
EXPECT_EQ(SSL_CTX_sess_accept_good(server_ctx_.get()), 1);
|
|
|
|
// Drain any post-handshake messages to ensure there are no unread records
|
|
// on either end.
|
|
ASSERT_TRUE(FlushNewSessionTickets(client_.get(), server_.get()));
|
|
|
|
uint64_t client_read_seq = SSL_get_read_sequence(client_.get());
|
|
uint64_t client_write_seq = SSL_get_write_sequence(client_.get());
|
|
uint64_t server_read_seq = SSL_get_read_sequence(server_.get());
|
|
uint64_t server_write_seq = SSL_get_write_sequence(server_.get());
|
|
|
|
if (is_dtls()) {
|
|
// Both client and server must be at epoch 1.
|
|
EXPECT_EQ(EpochFromSequence(client_read_seq), 1);
|
|
EXPECT_EQ(EpochFromSequence(client_write_seq), 1);
|
|
EXPECT_EQ(EpochFromSequence(server_read_seq), 1);
|
|
EXPECT_EQ(EpochFromSequence(server_write_seq), 1);
|
|
|
|
// The next record to be written should exceed the largest received.
|
|
EXPECT_GT(client_write_seq, server_read_seq);
|
|
EXPECT_GT(server_write_seq, client_read_seq);
|
|
} else {
|
|
// The next record to be written should equal the next to be received.
|
|
EXPECT_EQ(client_write_seq, server_read_seq);
|
|
EXPECT_EQ(server_write_seq, client_read_seq);
|
|
}
|
|
|
|
// Send a record from client to server.
|
|
uint8_t byte = 0;
|
|
EXPECT_EQ(SSL_write(client_.get(), &byte, 1), 1);
|
|
EXPECT_EQ(SSL_read(server_.get(), &byte, 1), 1);
|
|
|
|
// The client write and server read sequence numbers should have
|
|
// incremented.
|
|
EXPECT_EQ(client_write_seq + 1, SSL_get_write_sequence(client_.get()));
|
|
EXPECT_EQ(server_read_seq + 1, SSL_get_read_sequence(server_.get()));
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, OneSidedShutdown) {
|
|
// SSL_shutdown is a no-op in DTLS.
|
|
if (is_dtls()) {
|
|
return;
|
|
}
|
|
ASSERT_TRUE(Connect());
|
|
|
|
// Shut down half the connection. |SSL_shutdown| will return 0 to signal only
|
|
// one side has shut down.
|
|
ASSERT_EQ(SSL_shutdown(client_.get()), 0);
|
|
|
|
// Reading from the server should consume the EOF.
|
|
uint8_t byte = 0;
|
|
ASSERT_EQ(SSL_read(server_.get(), &byte, 1), 0);
|
|
ASSERT_EQ(SSL_get_error(server_.get(), 0), SSL_ERROR_ZERO_RETURN);
|
|
|
|
// However, the server may continue to write data and then shut down the
|
|
// connection.
|
|
byte = 42;
|
|
ASSERT_EQ(SSL_write(server_.get(), &byte, 1), 1);
|
|
ASSERT_EQ(SSL_read(client_.get(), &byte, 1), 1);
|
|
ASSERT_EQ(byte, 42);
|
|
|
|
// The server may then shutdown the connection.
|
|
EXPECT_EQ(SSL_shutdown(server_.get()), 1);
|
|
EXPECT_EQ(SSL_shutdown(client_.get()), 1);
|
|
}
|
|
|
|
// Test that, after calling |SSL_shutdown|, |SSL_write| fails.
|
|
TEST_P(SSLVersionTest, WriteAfterShutdown) {
|
|
ASSERT_TRUE(Connect());
|
|
|
|
for (SSL *ssl : {client_.get(), server_.get()}) {
|
|
SCOPED_TRACE(SSL_is_server(ssl) ? "server" : "client");
|
|
|
|
bssl::UniquePtr<BIO> mem(BIO_new(BIO_s_mem()));
|
|
ASSERT_TRUE(mem);
|
|
SSL_set0_wbio(ssl, bssl::UpRef(mem).release());
|
|
|
|
// Shut down half the connection. |SSL_shutdown| will return 0 to signal
|
|
// only one side has shut down.
|
|
ASSERT_EQ(SSL_shutdown(ssl), 0);
|
|
|
|
// |ssl| should have written an alert to the transport.
|
|
const uint8_t *unused = nullptr;
|
|
size_t len = 0;
|
|
ASSERT_TRUE(BIO_mem_contents(mem.get(), &unused, &len));
|
|
EXPECT_NE(0u, len);
|
|
EXPECT_TRUE(BIO_reset(mem.get()));
|
|
|
|
// Writing should fail.
|
|
EXPECT_EQ(-1, SSL_write(ssl, "a", 1));
|
|
|
|
// Nothing should be written to the transport.
|
|
ASSERT_TRUE(BIO_mem_contents(mem.get(), &unused, &len));
|
|
EXPECT_EQ(0u, len);
|
|
}
|
|
}
|
|
|
|
// Test that, after sending a fatal alert in a failed |SSL_read|, |SSL_write|
|
|
// fails.
|
|
TEST_P(SSLVersionTest, WriteAfterReadSentFatalAlert) {
|
|
// Decryption failures are not fatal in DTLS.
|
|
if (is_dtls()) {
|
|
return;
|
|
}
|
|
|
|
ASSERT_TRUE(Connect());
|
|
|
|
// Save the write |BIO|s as the test will overwrite them.
|
|
bssl::UniquePtr<BIO> client_wbio = bssl::UpRef(SSL_get_wbio(client_.get()));
|
|
bssl::UniquePtr<BIO> server_wbio = bssl::UpRef(SSL_get_wbio(server_.get()));
|
|
|
|
for (bool test_server : {false, true}) {
|
|
SCOPED_TRACE(test_server ? "server" : "client");
|
|
SSL *ssl = test_server ? server_.get() : client_.get();
|
|
BIO *other_wbio = test_server ? client_wbio.get() : server_wbio.get();
|
|
|
|
bssl::UniquePtr<BIO> mem(BIO_new(BIO_s_mem()));
|
|
ASSERT_TRUE(mem);
|
|
SSL_set0_wbio(ssl, bssl::UpRef(mem).release());
|
|
|
|
// Read an invalid record from the peer.
|
|
static const uint8_t kInvalidRecord[] = "invalid record";
|
|
EXPECT_EQ(int{sizeof(kInvalidRecord)},
|
|
BIO_write(other_wbio, kInvalidRecord, sizeof(kInvalidRecord)));
|
|
char buf[256];
|
|
EXPECT_EQ(-1, SSL_read(ssl, buf, sizeof(buf)));
|
|
|
|
// |ssl| should have written an alert to the transport.
|
|
const uint8_t *unused = nullptr;
|
|
size_t len = 0;
|
|
ASSERT_TRUE(BIO_mem_contents(mem.get(), &unused, &len));
|
|
EXPECT_NE(0u, len);
|
|
EXPECT_TRUE(BIO_reset(mem.get()));
|
|
|
|
// Writing should fail.
|
|
EXPECT_EQ(-1, SSL_write(ssl, "a", 1));
|
|
|
|
// Nothing should be written to the transport.
|
|
ASSERT_TRUE(BIO_mem_contents(mem.get(), &unused, &len));
|
|
EXPECT_EQ(0u, len);
|
|
}
|
|
}
|
|
|
|
static int VerifySucceed(X509_STORE_CTX *store_ctx, void *arg) { return 1; }
|
|
|
|
// Test that, after sending a fatal alert from the handshake, |SSL_write| fails.
|
|
TEST_P(SSLVersionTest, WriteAfterHandshakeSentFatalAlert) {
|
|
for (bool test_server : {false, true}) {
|
|
SCOPED_TRACE(test_server ? "server" : "client");
|
|
|
|
bssl::UniquePtr<SSL> ssl(
|
|
SSL_new(test_server ? server_ctx_.get() : client_ctx_.get()));
|
|
ASSERT_TRUE(ssl);
|
|
if (test_server) {
|
|
SSL_set_accept_state(ssl.get());
|
|
} else {
|
|
SSL_set_connect_state(ssl.get());
|
|
}
|
|
|
|
std::vector<uint8_t> invalid;
|
|
if (is_dtls()) {
|
|
// In DTLS, invalid records are discarded. To cause the handshake to fail,
|
|
// use a valid handshake record with invalid contents.
|
|
invalid.push_back(SSL3_RT_HANDSHAKE);
|
|
invalid.push_back(DTLS1_VERSION >> 8);
|
|
invalid.push_back(DTLS1_VERSION & 0xff);
|
|
// epoch and sequence_number
|
|
for (int i = 0; i < 8; i++) {
|
|
invalid.push_back(0);
|
|
}
|
|
// A one-byte fragment is invalid.
|
|
invalid.push_back(0);
|
|
invalid.push_back(1);
|
|
// Arbitrary contents.
|
|
invalid.push_back(0);
|
|
} else {
|
|
invalid = {'i', 'n', 'v', 'a', 'l', 'i', 'd'};
|
|
}
|
|
bssl::UniquePtr<BIO> rbio(BIO_new_mem_buf(invalid.data(), invalid.size()));
|
|
ASSERT_TRUE(rbio);
|
|
SSL_set0_rbio(ssl.get(), rbio.release());
|
|
|
|
bssl::UniquePtr<BIO> mem(BIO_new(BIO_s_mem()));
|
|
ASSERT_TRUE(mem);
|
|
SSL_set0_wbio(ssl.get(), bssl::UpRef(mem).release());
|
|
|
|
// The handshake should fail.
|
|
EXPECT_EQ(-1, SSL_do_handshake(ssl.get()));
|
|
EXPECT_EQ(SSL_ERROR_SSL, SSL_get_error(ssl.get(), -1));
|
|
uint32_t err = ERR_get_error();
|
|
|
|
// |ssl| should have written an alert (and, in the client's case, a
|
|
// ClientHello) to the transport.
|
|
const uint8_t *unused = nullptr;
|
|
size_t len = 0;
|
|
ASSERT_TRUE(BIO_mem_contents(mem.get(), &unused, &len));
|
|
EXPECT_NE(0u, len);
|
|
EXPECT_TRUE(BIO_reset(mem.get()));
|
|
|
|
// Writing should fail, with the same error as the handshake.
|
|
EXPECT_EQ(-1, SSL_write(ssl.get(), "a", 1));
|
|
EXPECT_EQ(SSL_ERROR_SSL, SSL_get_error(ssl.get(), -1));
|
|
EXPECT_EQ(err, ERR_get_error());
|
|
|
|
// Nothing should be written to the transport.
|
|
ASSERT_TRUE(BIO_mem_contents(mem.get(), &unused, &len));
|
|
EXPECT_EQ(0u, len);
|
|
}
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, GetPeerCertificate) {
|
|
ASSERT_TRUE(UseCertAndKey(client_ctx_.get()));
|
|
|
|
// Configure both client and server to accept any certificate.
|
|
SSL_CTX_set_verify(client_ctx_.get(),
|
|
SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
|
|
nullptr);
|
|
SSL_CTX_set_cert_verify_callback(client_ctx_.get(), VerifySucceed, NULL);
|
|
SSL_CTX_set_verify(server_ctx_.get(),
|
|
SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
|
|
nullptr);
|
|
SSL_CTX_set_cert_verify_callback(server_ctx_.get(), VerifySucceed, NULL);
|
|
|
|
ASSERT_TRUE(Connect());
|
|
|
|
// Client and server should both see the leaf certificate.
|
|
bssl::UniquePtr<X509> peer(SSL_get_peer_certificate(server_.get()));
|
|
ASSERT_TRUE(peer);
|
|
ASSERT_EQ(X509_cmp(cert_.get(), peer.get()), 0);
|
|
|
|
peer.reset(SSL_get_peer_certificate(client_.get()));
|
|
ASSERT_TRUE(peer);
|
|
ASSERT_EQ(X509_cmp(cert_.get(), peer.get()), 0);
|
|
|
|
// However, for historical reasons, the X509 chain includes the leaf on the
|
|
// client, but does not on the server.
|
|
EXPECT_EQ(sk_X509_num(SSL_get_peer_cert_chain(client_.get())), 1u);
|
|
EXPECT_EQ(sk_CRYPTO_BUFFER_num(SSL_get0_peer_certificates(client_.get())),
|
|
1u);
|
|
|
|
EXPECT_EQ(sk_X509_num(SSL_get_peer_cert_chain(server_.get())), 0u);
|
|
EXPECT_EQ(sk_CRYPTO_BUFFER_num(SSL_get0_peer_certificates(server_.get())),
|
|
1u);
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, NoPeerCertificate) {
|
|
SSL_CTX_set_verify(server_ctx_.get(), SSL_VERIFY_PEER, nullptr);
|
|
SSL_CTX_set_cert_verify_callback(server_ctx_.get(), VerifySucceed, NULL);
|
|
SSL_CTX_set_cert_verify_callback(client_ctx_.get(), VerifySucceed, NULL);
|
|
|
|
ASSERT_TRUE(Connect());
|
|
|
|
// Server should not see a peer certificate.
|
|
bssl::UniquePtr<X509> peer(SSL_get_peer_certificate(server_.get()));
|
|
ASSERT_FALSE(peer);
|
|
ASSERT_FALSE(SSL_get0_peer_certificates(server_.get()));
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, RetainOnlySHA256OfCerts) {
|
|
uint8_t *cert_der = NULL;
|
|
int cert_der_len = i2d_X509(cert_.get(), &cert_der);
|
|
ASSERT_GE(cert_der_len, 0);
|
|
bssl::UniquePtr<uint8_t> free_cert_der(cert_der);
|
|
|
|
uint8_t cert_sha256[SHA256_DIGEST_LENGTH];
|
|
SHA256(cert_der, cert_der_len, cert_sha256);
|
|
|
|
ASSERT_TRUE(UseCertAndKey(client_ctx_.get()));
|
|
|
|
// Configure both client and server to accept any certificate, but the
|
|
// server must retain only the SHA-256 of the peer.
|
|
SSL_CTX_set_verify(client_ctx_.get(),
|
|
SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
|
|
nullptr);
|
|
SSL_CTX_set_verify(server_ctx_.get(),
|
|
SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
|
|
nullptr);
|
|
SSL_CTX_set_cert_verify_callback(client_ctx_.get(), VerifySucceed, NULL);
|
|
SSL_CTX_set_cert_verify_callback(server_ctx_.get(), VerifySucceed, NULL);
|
|
SSL_CTX_set_retain_only_sha256_of_client_certs(server_ctx_.get(), 1);
|
|
|
|
ASSERT_TRUE(Connect());
|
|
|
|
// The peer certificate has been dropped.
|
|
bssl::UniquePtr<X509> peer(SSL_get_peer_certificate(server_.get()));
|
|
EXPECT_FALSE(peer);
|
|
|
|
SSL_SESSION *session = SSL_get_session(server_.get());
|
|
EXPECT_TRUE(SSL_SESSION_has_peer_sha256(session));
|
|
|
|
const uint8_t *peer_sha256 = nullptr;
|
|
size_t peer_sha256_len = 0;
|
|
SSL_SESSION_get0_peer_sha256(session, &peer_sha256, &peer_sha256_len);
|
|
EXPECT_EQ(Bytes(cert_sha256), Bytes(peer_sha256, peer_sha256_len));
|
|
}
|
|
|
|
static int SwitchSessionIDContextSNI(SSL *ssl, int *out_alert, void *arg) {
|
|
static const uint8_t kContext[] = {3};
|
|
|
|
if (!SSL_set_session_id_context(ssl, kContext, sizeof(kContext))) {
|
|
return SSL_TLSEXT_ERR_ALERT_FATAL;
|
|
}
|
|
|
|
return SSL_TLSEXT_ERR_OK;
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, SessionIDContext) {
|
|
static const uint8_t kContext1[] = {1};
|
|
static const uint8_t kContext2[] = {2};
|
|
|
|
ASSERT_TRUE(SSL_CTX_set_session_id_context(server_ctx_.get(), kContext1,
|
|
sizeof(kContext1)));
|
|
|
|
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_session_cache_mode(server_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
|
|
bssl::UniquePtr<SSL_SESSION> session =
|
|
CreateClientSession(client_ctx_.get(), server_ctx_.get());
|
|
ASSERT_TRUE(session);
|
|
|
|
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
|
|
session.get(),
|
|
true /* expect session reused */));
|
|
|
|
// Change the session ID context.
|
|
ASSERT_TRUE(SSL_CTX_set_session_id_context(server_ctx_.get(), kContext2,
|
|
sizeof(kContext2)));
|
|
|
|
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
|
|
session.get(),
|
|
false /* expect session not reused */));
|
|
|
|
// Change the session ID context back and install an SNI callback to switch
|
|
// it.
|
|
ASSERT_TRUE(SSL_CTX_set_session_id_context(server_ctx_.get(), kContext1,
|
|
sizeof(kContext1)));
|
|
|
|
SSL_CTX_set_tlsext_servername_callback(server_ctx_.get(),
|
|
SwitchSessionIDContextSNI);
|
|
|
|
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
|
|
session.get(),
|
|
false /* expect session not reused */));
|
|
|
|
// Switch the session ID context with the early callback instead.
|
|
SSL_CTX_set_tlsext_servername_callback(server_ctx_.get(), nullptr);
|
|
SSL_CTX_set_select_certificate_cb(
|
|
server_ctx_.get(),
|
|
[](const SSL_CLIENT_HELLO *client_hello) -> ssl_select_cert_result_t {
|
|
static const uint8_t kContext[] = {3};
|
|
|
|
if (!SSL_set_session_id_context(client_hello->ssl, kContext,
|
|
sizeof(kContext))) {
|
|
return ssl_select_cert_error;
|
|
}
|
|
|
|
return ssl_select_cert_success;
|
|
});
|
|
|
|
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
|
|
session.get(),
|
|
false /* expect session not reused */));
|
|
}
|
|
|
|
static timeval g_current_time;
|
|
|
|
static void CurrentTimeCallback(const SSL *ssl, timeval *out_clock) {
|
|
*out_clock = g_current_time;
|
|
}
|
|
|
|
|
|
static bssl::UniquePtr<SSL_SESSION> ExpectSessionRenewed(SSL_CTX *client_ctx,
|
|
SSL_CTX *server_ctx,
|
|
SSL_SESSION *session) {
|
|
g_last_session = nullptr;
|
|
SSL_CTX_sess_set_new_cb(client_ctx, SaveLastSession);
|
|
|
|
bssl::UniquePtr<SSL> client, server;
|
|
ClientConfig config;
|
|
config.session = session;
|
|
if (!ConnectClientAndServer(&client, &server, client_ctx, server_ctx,
|
|
config) ||
|
|
!FlushNewSessionTickets(client.get(), server.get())) {
|
|
fprintf(stderr, "Failed to connect client and server.\n");
|
|
return nullptr;
|
|
}
|
|
|
|
if (SSL_session_reused(client.get()) != SSL_session_reused(server.get())) {
|
|
fprintf(stderr, "Client and server were inconsistent.\n");
|
|
return nullptr;
|
|
}
|
|
|
|
if (!SSL_session_reused(client.get())) {
|
|
fprintf(stderr, "Session was not reused.\n");
|
|
return nullptr;
|
|
}
|
|
|
|
SSL_CTX_sess_set_new_cb(client_ctx, nullptr);
|
|
|
|
if (!g_last_session) {
|
|
fprintf(stderr, "Client did not receive a renewed session.\n");
|
|
return nullptr;
|
|
}
|
|
return std::move(g_last_session);
|
|
}
|
|
|
|
static const size_t kTicketKeyLen = 48;
|
|
|
|
static void ExpectTicketKeyChanged(SSL_CTX *ctx, uint8_t *inout_key,
|
|
bool changed) {
|
|
uint8_t new_key[kTicketKeyLen];
|
|
// May return 0, 1 or 48.
|
|
ASSERT_EQ(SSL_CTX_get_tlsext_ticket_keys(ctx, new_key, kTicketKeyLen), 1);
|
|
if (changed) {
|
|
ASSERT_NE(Bytes(inout_key, kTicketKeyLen), Bytes(new_key));
|
|
} else {
|
|
ASSERT_EQ(Bytes(inout_key, kTicketKeyLen), Bytes(new_key));
|
|
}
|
|
OPENSSL_memcpy(inout_key, new_key, kTicketKeyLen);
|
|
}
|
|
|
|
|
|
|
|
static int RenewTicketCallback(SSL *ssl, uint8_t *key_name, uint8_t *iv,
|
|
EVP_CIPHER_CTX *ctx, HMAC_CTX *hmac_ctx,
|
|
int encrypt) {
|
|
static const uint8_t kZeros[16] = {0};
|
|
|
|
if (encrypt) {
|
|
OPENSSL_memcpy(key_name, kZeros, sizeof(kZeros));
|
|
RAND_bytes(iv, 16);
|
|
} else if (OPENSSL_memcmp(key_name, kZeros, 16) != 0) {
|
|
return 0;
|
|
}
|
|
|
|
if (!HMAC_Init_ex(hmac_ctx, kZeros, sizeof(kZeros), EVP_sha256(), NULL) ||
|
|
!EVP_CipherInit_ex(ctx, EVP_aes_128_cbc(), NULL, kZeros, iv, encrypt)) {
|
|
return -1;
|
|
}
|
|
|
|
// Returning two from the callback in decrypt mode renews the
|
|
// session in TLS 1.2 and below.
|
|
return encrypt ? 1 : 2;
|
|
}
|
|
|
|
static bool GetServerTicketTime(long *out, const SSL_SESSION *session) {
|
|
const uint8_t *ticket = nullptr;
|
|
size_t ticket_len = 0;
|
|
SSL_SESSION_get0_ticket(session, &ticket, &ticket_len);
|
|
if (ticket_len < 16 + 16 + SHA256_DIGEST_LENGTH) {
|
|
return false;
|
|
}
|
|
|
|
const uint8_t *ciphertext = ticket + 16 + 16;
|
|
size_t len = ticket_len - 16 - 16 - SHA256_DIGEST_LENGTH;
|
|
std::unique_ptr<uint8_t[]> plaintext(new uint8_t[len]);
|
|
|
|
#if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
|
|
// Fuzzer-mode tickets are unencrypted.
|
|
OPENSSL_memcpy(plaintext.get(), ciphertext, len);
|
|
#else
|
|
static const uint8_t kZeros[16] = {0};
|
|
const uint8_t *iv = ticket + 16;
|
|
bssl::ScopedEVP_CIPHER_CTX ctx;
|
|
int len1 = 0, len2 = 0;
|
|
if (len > INT_MAX ||
|
|
!EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_cbc(), nullptr, kZeros, iv) ||
|
|
!EVP_DecryptUpdate(ctx.get(), plaintext.get(), &len1, ciphertext,
|
|
static_cast<int>(len)) ||
|
|
!EVP_DecryptFinal_ex(ctx.get(), plaintext.get() + len1, &len2)) {
|
|
return false;
|
|
}
|
|
|
|
len = static_cast<size_t>(len1 + len2);
|
|
#endif
|
|
|
|
bssl::UniquePtr<SSL_CTX> ssl_ctx(SSL_CTX_new(TLS_method()));
|
|
if (!ssl_ctx) {
|
|
return false;
|
|
}
|
|
bssl::UniquePtr<SSL_SESSION> server_session(
|
|
SSL_SESSION_from_bytes(plaintext.get(), len, ssl_ctx.get()));
|
|
if (!server_session) {
|
|
return false;
|
|
}
|
|
|
|
*out = SSL_SESSION_get_time(server_session.get());
|
|
return true;
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, SessionTimeout) {
|
|
for (bool server_test : {false, true}) {
|
|
SCOPED_TRACE(server_test);
|
|
|
|
ASSERT_NO_FATAL_FAILURE(ResetContexts());
|
|
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_session_cache_mode(server_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
|
|
static const time_t kStartTime = 1000;
|
|
g_current_time.tv_sec = kStartTime;
|
|
|
|
// We are willing to use a longer lifetime for TLS 1.3 sessions as
|
|
// resumptions still perform ECDHE.
|
|
const time_t timeout = version() == TLS1_3_VERSION
|
|
? SSL_DEFAULT_SESSION_PSK_DHE_TIMEOUT
|
|
: SSL_DEFAULT_SESSION_TIMEOUT;
|
|
|
|
// Both client and server must enforce session timeouts. We configure the
|
|
// other side with a frozen clock so it never expires tickets.
|
|
if (server_test) {
|
|
SSL_CTX_set_current_time_cb(client_ctx_.get(), FrozenTimeCallback);
|
|
SSL_CTX_set_current_time_cb(server_ctx_.get(), CurrentTimeCallback);
|
|
} else {
|
|
SSL_CTX_set_current_time_cb(client_ctx_.get(), CurrentTimeCallback);
|
|
SSL_CTX_set_current_time_cb(server_ctx_.get(), FrozenTimeCallback);
|
|
}
|
|
|
|
// Configure a ticket callback which renews tickets.
|
|
SSL_CTX_set_tlsext_ticket_key_cb(server_ctx_.get(), RenewTicketCallback);
|
|
|
|
bssl::UniquePtr<SSL_SESSION> session =
|
|
CreateClientSession(client_ctx_.get(), server_ctx_.get());
|
|
ASSERT_TRUE(session);
|
|
|
|
// Advance the clock just behind the timeout.
|
|
g_current_time.tv_sec += timeout - 1;
|
|
|
|
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
|
|
session.get(),
|
|
true /* expect session reused */));
|
|
|
|
// Advance the clock one more second.
|
|
g_current_time.tv_sec++;
|
|
|
|
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
|
|
session.get(),
|
|
false /* expect session not reused */));
|
|
|
|
// Rewind the clock to before the session was minted.
|
|
g_current_time.tv_sec = kStartTime - 1;
|
|
|
|
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
|
|
session.get(),
|
|
false /* expect session not reused */));
|
|
|
|
// Renew the session 10 seconds before expiration.
|
|
time_t new_start_time = kStartTime + timeout - 10;
|
|
g_current_time.tv_sec = new_start_time;
|
|
bssl::UniquePtr<SSL_SESSION> new_session = ExpectSessionRenewed(
|
|
client_ctx_.get(), server_ctx_.get(), session.get());
|
|
ASSERT_TRUE(new_session);
|
|
|
|
// This new session is not the same object as before.
|
|
EXPECT_NE(session.get(), new_session.get());
|
|
|
|
// Check the sessions have timestamps measured from issuance.
|
|
long session_time = 0;
|
|
if (server_test) {
|
|
ASSERT_TRUE(GetServerTicketTime(&session_time, new_session.get()));
|
|
} else {
|
|
session_time = SSL_SESSION_get_time(new_session.get());
|
|
}
|
|
|
|
ASSERT_EQ(session_time, g_current_time.tv_sec);
|
|
|
|
if (version() == TLS1_3_VERSION) {
|
|
// Renewal incorporates fresh key material in TLS 1.3, so we extend the
|
|
// lifetime TLS 1.3.
|
|
g_current_time.tv_sec = new_start_time + timeout - 1;
|
|
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
|
|
new_session.get(),
|
|
true /* expect session reused */));
|
|
|
|
// The new session expires after the new timeout.
|
|
g_current_time.tv_sec = new_start_time + timeout + 1;
|
|
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
|
|
new_session.get(),
|
|
false /* expect session ot reused */));
|
|
|
|
// Renew the session until it begins just past the auth timeout.
|
|
time_t auth_end_time = kStartTime + SSL_DEFAULT_SESSION_AUTH_TIMEOUT;
|
|
while (new_start_time < auth_end_time - 1000) {
|
|
// Get as close as possible to target start time.
|
|
new_start_time =
|
|
std::min(auth_end_time - 1000, new_start_time + timeout - 1);
|
|
g_current_time.tv_sec = new_start_time;
|
|
new_session = ExpectSessionRenewed(client_ctx_.get(), server_ctx_.get(),
|
|
new_session.get());
|
|
ASSERT_TRUE(new_session);
|
|
}
|
|
|
|
// Now the session's lifetime is bound by the auth timeout.
|
|
g_current_time.tv_sec = auth_end_time - 1;
|
|
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
|
|
new_session.get(),
|
|
true /* expect session reused */));
|
|
|
|
g_current_time.tv_sec = auth_end_time + 1;
|
|
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
|
|
new_session.get(),
|
|
false /* expect session ot reused */));
|
|
} else {
|
|
// The new session is usable just before the old expiration.
|
|
g_current_time.tv_sec = kStartTime + timeout - 1;
|
|
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
|
|
new_session.get(),
|
|
true /* expect session reused */));
|
|
|
|
// Renewal does not extend the lifetime, so it is not usable beyond the
|
|
// old expiration.
|
|
g_current_time.tv_sec = kStartTime + timeout + 1;
|
|
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
|
|
new_session.get(),
|
|
false /* expect session not reused */));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
TEST_P(SSLVersionTest, DefaultTicketKeyInitialization) {
|
|
// Do not make static and const. See t/P118709392.
|
|
// It can trigger https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95189 leading
|
|
// to transient errors.
|
|
uint8_t kZeroKey[kTicketKeyLen] = {0};
|
|
uint8_t ticket_key[kTicketKeyLen];
|
|
ASSERT_EQ(1, SSL_CTX_get_tlsext_ticket_keys(server_ctx_.get(), ticket_key,
|
|
kTicketKeyLen));
|
|
ASSERT_NE(0, OPENSSL_memcmp(ticket_key, kZeroKey, kTicketKeyLen));
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, DefaultTicketKeyRotation) {
|
|
static const time_t kStartTime = 1001;
|
|
g_current_time.tv_sec = kStartTime;
|
|
|
|
// We use session reuse as a proxy for ticket decryption success, hence
|
|
// disable session timeouts.
|
|
SSL_CTX_set_timeout(server_ctx_.get(), std::numeric_limits<uint32_t>::max());
|
|
SSL_CTX_set_session_psk_dhe_timeout(server_ctx_.get(),
|
|
std::numeric_limits<uint32_t>::max());
|
|
|
|
SSL_CTX_set_current_time_cb(client_ctx_.get(), FrozenTimeCallback);
|
|
SSL_CTX_set_current_time_cb(server_ctx_.get(), CurrentTimeCallback);
|
|
|
|
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_session_cache_mode(server_ctx_.get(), SSL_SESS_CACHE_OFF);
|
|
|
|
// Initialize ticket_key with the current key and check that it was
|
|
// initialized to something, not all zeros.
|
|
uint8_t ticket_key[kTicketKeyLen] = {0};
|
|
TRACED_CALL(ExpectTicketKeyChanged(server_ctx_.get(), ticket_key,
|
|
true /* changed */));
|
|
|
|
// Verify ticket resumption actually works.
|
|
bssl::UniquePtr<SSL> client, server;
|
|
bssl::UniquePtr<SSL_SESSION> session =
|
|
CreateClientSession(client_ctx_.get(), server_ctx_.get());
|
|
ASSERT_TRUE(session);
|
|
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
|
|
session.get(), true /* reused */));
|
|
|
|
// Advance time to just before key rotation.
|
|
g_current_time.tv_sec += SSL_DEFAULT_TICKET_KEY_ROTATION_INTERVAL - 1;
|
|
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
|
|
session.get(), true /* reused */));
|
|
TRACED_CALL(ExpectTicketKeyChanged(server_ctx_.get(), ticket_key,
|
|
false /* NOT changed */));
|
|
|
|
// Force key rotation.
|
|
g_current_time.tv_sec += 1;
|
|
bssl::UniquePtr<SSL_SESSION> new_session =
|
|
CreateClientSession(client_ctx_.get(), server_ctx_.get());
|
|
TRACED_CALL(ExpectTicketKeyChanged(server_ctx_.get(), ticket_key,
|
|
true /* changed */));
|
|
|
|
// Resumption with both old and new ticket should work.
|
|
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
|
|
session.get(), true /* reused */));
|
|
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
|
|
new_session.get(), true /* reused */));
|
|
TRACED_CALL(ExpectTicketKeyChanged(server_ctx_.get(), ticket_key,
|
|
false /* NOT changed */));
|
|
|
|
// Force key rotation again. Resumption with the old ticket now fails.
|
|
g_current_time.tv_sec += SSL_DEFAULT_TICKET_KEY_ROTATION_INTERVAL;
|
|
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
|
|
session.get(), false /* NOT reused */));
|
|
TRACED_CALL(ExpectTicketKeyChanged(server_ctx_.get(), ticket_key,
|
|
true /* changed */));
|
|
|
|
// But resumption with the newer session still works.
|
|
TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(),
|
|
new_session.get(), true /* reused */));
|
|
}
|
|
|
|
static int SwitchContext(SSL *ssl, int *out_alert, void *arg) {
|
|
SSL_CTX *ctx = reinterpret_cast<SSL_CTX *>(arg);
|
|
SSL_set_SSL_CTX(ssl, ctx);
|
|
return SSL_TLSEXT_ERR_OK;
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, SNICallback) {
|
|
bssl::UniquePtr<X509> cert2 = GetECDSATestCertificate();
|
|
ASSERT_TRUE(cert2);
|
|
bssl::UniquePtr<EVP_PKEY> key2 = GetECDSATestKey();
|
|
ASSERT_TRUE(key2);
|
|
|
|
// Test that switching the |SSL_CTX| at the SNI callback behaves correctly.
|
|
static const uint16_t kECDSAWithSHA256 = SSL_SIGN_ECDSA_SECP256R1_SHA256;
|
|
|
|
static const uint8_t kSCTList[] = {0, 6, 0, 4, 5, 6, 7, 8};
|
|
static const uint8_t kOCSPResponse[] = {1, 2, 3, 4};
|
|
|
|
bssl::UniquePtr<SSL_CTX> server_ctx2 = CreateContext();
|
|
ASSERT_TRUE(server_ctx2);
|
|
ASSERT_TRUE(SSL_CTX_use_certificate(server_ctx2.get(), cert2.get()));
|
|
ASSERT_TRUE(SSL_CTX_use_PrivateKey(server_ctx2.get(), key2.get()));
|
|
ASSERT_TRUE(SSL_CTX_set_signed_cert_timestamp_list(
|
|
server_ctx2.get(), kSCTList, sizeof(kSCTList)));
|
|
ASSERT_TRUE(SSL_CTX_set_ocsp_response(server_ctx2.get(), kOCSPResponse,
|
|
sizeof(kOCSPResponse)));
|
|
// Historically signing preferences would be lost in some cases with the
|
|
// SNI callback, which triggers the TLS 1.2 SHA-1 default. To ensure
|
|
// this doesn't happen when |version| is TLS 1.2, configure the private
|
|
// key to only sign SHA-256.
|
|
ASSERT_TRUE(SSL_CTX_set_signing_algorithm_prefs(server_ctx2.get(),
|
|
&kECDSAWithSHA256, 1));
|
|
|
|
SSL_CTX_set_tlsext_servername_callback(server_ctx_.get(), SwitchContext);
|
|
SSL_CTX_set_tlsext_servername_arg(server_ctx_.get(), server_ctx2.get());
|
|
|
|
SSL_CTX_enable_signed_cert_timestamps(client_ctx_.get());
|
|
SSL_CTX_enable_ocsp_stapling(client_ctx_.get());
|
|
|
|
ASSERT_TRUE(Connect());
|
|
|
|
// The client should have received |cert2|.
|
|
bssl::UniquePtr<X509> peer(SSL_get_peer_certificate(client_.get()));
|
|
ASSERT_TRUE(peer);
|
|
EXPECT_EQ(X509_cmp(peer.get(), cert2.get()), 0);
|
|
|
|
// The client should have received |server_ctx2|'s SCT list.
|
|
const uint8_t *data = nullptr;
|
|
size_t len = 0;
|
|
SSL_get0_signed_cert_timestamp_list(client_.get(), &data, &len);
|
|
EXPECT_EQ(Bytes(kSCTList), Bytes(data, len));
|
|
|
|
// The client should have received |server_ctx2|'s OCSP response.
|
|
SSL_get0_ocsp_response(client_.get(), &data, &len);
|
|
EXPECT_EQ(Bytes(kOCSPResponse), Bytes(data, len));
|
|
}
|
|
|
|
|
|
TEST_P(SSLVersionTest, Version) {
|
|
ASSERT_TRUE(Connect());
|
|
|
|
EXPECT_EQ(SSL_version(client_.get()), version());
|
|
EXPECT_EQ(SSL_version(server_.get()), version());
|
|
|
|
// Test the version name is reported as expected.
|
|
const char *version_name = GetVersionName(version());
|
|
EXPECT_EQ(strcmp(version_name, SSL_get_version(client_.get())), 0);
|
|
EXPECT_EQ(strcmp(version_name, SSL_get_version(server_.get())), 0);
|
|
|
|
// Test SSL_SESSION reports the same name.
|
|
const char *client_name =
|
|
SSL_SESSION_get_version(SSL_get_session(client_.get()));
|
|
const char *server_name =
|
|
SSL_SESSION_get_version(SSL_get_session(server_.get()));
|
|
EXPECT_EQ(strcmp(version_name, client_name), 0);
|
|
EXPECT_EQ(strcmp(version_name, server_name), 0);
|
|
|
|
// Client/server version equality asserted above, assert equality for cipher
|
|
// here.
|
|
ASSERT_TRUE(SSL_get_current_cipher(client_.get()));
|
|
ASSERT_TRUE(SSL_get_current_cipher(server_.get()));
|
|
EXPECT_EQ(SSL_get_current_cipher(client_.get())->id,
|
|
SSL_get_current_cipher(server_.get())->id);
|
|
const uint16_t version = SSL_version(client_.get());
|
|
if (version == TLS1_2_VERSION || version == TLS1_3_VERSION) {
|
|
const char *version_str = SSL_get_version(client_.get());
|
|
EXPECT_STREQ(version_str,
|
|
SSL_CIPHER_get_version(SSL_get_current_cipher(client_.get())));
|
|
} else if (version == DTLS1_2_VERSION) { // ciphers don't differentiate D/TLS
|
|
EXPECT_STREQ("TLSv1.2",
|
|
SSL_CIPHER_get_version(SSL_get_current_cipher(client_.get())));
|
|
} else {
|
|
EXPECT_STREQ("TLSv1/SSLv3",
|
|
SSL_CIPHER_get_version(SSL_get_current_cipher(client_.get())));
|
|
}
|
|
}
|
|
|
|
// Tests that that |SSL_get_pending_cipher| is available during the ALPN
|
|
// selection callback.
|
|
TEST_P(SSLVersionTest, ALPNCipherAvailable) {
|
|
ASSERT_TRUE(UseCertAndKey(client_ctx_.get()));
|
|
|
|
static const uint8_t kALPNProtos[] = {0x03, 'f', 'o', 'o'};
|
|
ASSERT_EQ(SSL_CTX_set_alpn_protos(client_ctx_.get(), kALPNProtos,
|
|
sizeof(kALPNProtos)),
|
|
0);
|
|
|
|
// The ALPN callback does not fail the handshake on error, so have the
|
|
// callback write a boolean.
|
|
std::pair<uint16_t, bool> callback_state(version(), false);
|
|
SSL_CTX_set_alpn_select_cb(
|
|
server_ctx_.get(),
|
|
[](SSL *ssl, const uint8_t **out, uint8_t *out_len, const uint8_t *in,
|
|
unsigned in_len, void *arg) -> int {
|
|
auto state = reinterpret_cast<std::pair<uint16_t, bool> *>(arg);
|
|
if (SSL_get_pending_cipher(ssl) != nullptr &&
|
|
SSL_version(ssl) == state->first) {
|
|
state->second = true;
|
|
}
|
|
return SSL_TLSEXT_ERR_NOACK;
|
|
},
|
|
&callback_state);
|
|
|
|
ASSERT_TRUE(Connect());
|
|
|
|
ASSERT_TRUE(callback_state.second);
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, SSLClearSessionResumption) {
|
|
// Skip this for TLS 1.3. TLS 1.3's ticket mechanism is incompatible with this
|
|
// API pattern.
|
|
if (version() == TLS1_3_VERSION) {
|
|
return;
|
|
}
|
|
|
|
shed_handshake_config_ = false;
|
|
ASSERT_TRUE(Connect());
|
|
|
|
EXPECT_FALSE(SSL_session_reused(client_.get()));
|
|
EXPECT_FALSE(SSL_session_reused(server_.get()));
|
|
|
|
// Reset everything.
|
|
ASSERT_TRUE(SSL_clear(client_.get()));
|
|
ASSERT_TRUE(SSL_clear(server_.get()));
|
|
|
|
// Attempt to connect a second time.
|
|
ASSERT_TRUE(CompleteHandshakes(client_.get(), server_.get()));
|
|
|
|
// |SSL_clear| should implicitly offer the previous session to the server.
|
|
EXPECT_TRUE(SSL_session_reused(client_.get()));
|
|
EXPECT_TRUE(SSL_session_reused(server_.get()));
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, SSLClearFailsWithShedding) {
|
|
shed_handshake_config_ = false;
|
|
ASSERT_TRUE(Connect());
|
|
ASSERT_TRUE(CompleteHandshakes(client_.get(), server_.get()));
|
|
|
|
// Reset everything.
|
|
ASSERT_TRUE(SSL_clear(client_.get()));
|
|
ASSERT_TRUE(SSL_clear(server_.get()));
|
|
|
|
// Now enable shedding, and connect a second time.
|
|
shed_handshake_config_ = true;
|
|
ASSERT_TRUE(Connect());
|
|
ASSERT_TRUE(CompleteHandshakes(client_.get(), server_.get()));
|
|
|
|
// |SSL_clear| should now fail.
|
|
ASSERT_FALSE(SSL_clear(client_.get()));
|
|
ASSERT_FALSE(SSL_clear(server_.get()));
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, SSLClientCiphers) {
|
|
// Client ciphers ARE NOT SERIALIZED, so skip tests that rely on transfer or
|
|
// serialization of |ssl| and accompanying objects under test.
|
|
if (getVersionParam().transfer_ssl) {
|
|
return;
|
|
}
|
|
|
|
EXPECT_FALSE(SSL_get_client_ciphers(client_.get()));
|
|
EXPECT_FALSE(SSL_get_client_ciphers(server_.get()));
|
|
|
|
shed_handshake_config_ = false;
|
|
ASSERT_TRUE(Connect());
|
|
|
|
// The client should still have no view of the server's preferences, but the
|
|
// server should have seen at least one cipher from the client.
|
|
EXPECT_FALSE(SSL_get_client_ciphers(client_.get()));
|
|
EXPECT_GT(sk_SSL_CIPHER_num(SSL_get_client_ciphers(server_.get())),
|
|
(size_t)0);
|
|
|
|
// With config shedding disabled, clearing |server| shouldn't error and
|
|
// should reset server's client ciphers
|
|
ASSERT_TRUE(SSL_clear(server_.get()));
|
|
EXPECT_FALSE(SSL_get_client_ciphers(server_.get()));
|
|
|
|
shed_handshake_config_ = true;
|
|
ASSERT_TRUE(Connect());
|
|
|
|
// These should be unaffected by config shedding
|
|
EXPECT_FALSE(SSL_get_client_ciphers(client_.get()));
|
|
EXPECT_GT(sk_SSL_CIPHER_num(SSL_get_client_ciphers(server_.get())),
|
|
(size_t)0);
|
|
}
|
|
|
|
|
|
|
|
TEST_P(SSLVersionTest, AutoChain) {
|
|
cert_ = GetChainTestCertificate();
|
|
ASSERT_TRUE(cert_);
|
|
key_ = GetChainTestKey();
|
|
ASSERT_TRUE(key_);
|
|
bssl::UniquePtr<X509> intermediate = GetChainTestIntermediate();
|
|
ASSERT_TRUE(intermediate);
|
|
|
|
ASSERT_TRUE(UseCertAndKey(client_ctx_.get()));
|
|
ASSERT_TRUE(UseCertAndKey(server_ctx_.get()));
|
|
|
|
// Configure both client and server to accept any certificate. Add
|
|
// |intermediate| to the cert store.
|
|
ASSERT_TRUE(X509_STORE_add_cert(SSL_CTX_get_cert_store(client_ctx_.get()),
|
|
intermediate.get()));
|
|
ASSERT_TRUE(X509_STORE_add_cert(SSL_CTX_get_cert_store(server_ctx_.get()),
|
|
intermediate.get()));
|
|
SSL_CTX_set_verify(client_ctx_.get(),
|
|
SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
|
|
nullptr);
|
|
SSL_CTX_set_verify(server_ctx_.get(),
|
|
SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
|
|
nullptr);
|
|
SSL_CTX_set_cert_verify_callback(client_ctx_.get(), VerifySucceed, NULL);
|
|
SSL_CTX_set_cert_verify_callback(server_ctx_.get(), VerifySucceed, NULL);
|
|
|
|
// By default, the client and server should each only send the leaf.
|
|
ASSERT_TRUE(Connect());
|
|
|
|
EXPECT_TRUE(
|
|
ChainsEqual(SSL_get_peer_full_cert_chain(client_.get()), {cert_.get()}));
|
|
EXPECT_TRUE(
|
|
ChainsEqual(SSL_get_peer_full_cert_chain(server_.get()), {cert_.get()}));
|
|
// This test overrides the verification logic with VerifySucceed to always
|
|
// succeed without actually verifying anything and setting the verified chain
|
|
// on success
|
|
EXPECT_EQ(SSL_get0_verified_chain(server_.get()), nullptr);
|
|
EXPECT_EQ(SSL_get0_verified_chain(client_.get()), nullptr);
|
|
|
|
// If auto-chaining is enabled, then the intermediate is sent.
|
|
SSL_CTX_clear_mode(client_ctx_.get(), SSL_MODE_NO_AUTO_CHAIN);
|
|
SSL_CTX_clear_mode(server_ctx_.get(), SSL_MODE_NO_AUTO_CHAIN);
|
|
ASSERT_TRUE(Connect());
|
|
|
|
EXPECT_TRUE(ChainsEqual(SSL_get_peer_full_cert_chain(client_.get()),
|
|
{cert_.get(), intermediate.get()}));
|
|
EXPECT_TRUE(ChainsEqual(SSL_get_peer_full_cert_chain(server_.get()),
|
|
{cert_.get(), intermediate.get()}));
|
|
|
|
// Auto-chaining does not override explicitly-configured intermediates.
|
|
ASSERT_TRUE(SSL_CTX_add1_chain_cert(client_ctx_.get(), cert_.get()));
|
|
ASSERT_TRUE(SSL_CTX_add1_chain_cert(server_ctx_.get(), cert_.get()));
|
|
ASSERT_TRUE(Connect());
|
|
|
|
EXPECT_TRUE(ChainsEqual(SSL_get_peer_full_cert_chain(client_.get()),
|
|
{cert_.get(), cert_.get()}));
|
|
|
|
EXPECT_TRUE(ChainsEqual(SSL_get_peer_full_cert_chain(server_.get()),
|
|
{cert_.get(), cert_.get()}));
|
|
}
|
|
|
|
|
|
// These tests test multi-threaded behavior. They are intended to run with
|
|
// ThreadSanitizer.
|
|
#if defined(OPENSSL_THREADS)
|
|
TEST_P(SSLVersionTest, SessionCacheThreads) {
|
|
SSL_CTX_set_options(server_ctx_.get(), SSL_OP_NO_TICKET);
|
|
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_session_cache_mode(server_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
|
|
if (version() == TLS1_3_VERSION) {
|
|
// Our TLS 1.3 implementation does not support stateful resumption.
|
|
ASSERT_FALSE(CreateClientSession(client_ctx_.get(), server_ctx_.get()));
|
|
return;
|
|
}
|
|
|
|
// Establish two client sessions to test with.
|
|
bssl::UniquePtr<SSL_SESSION> session1 =
|
|
CreateClientSession(client_ctx_.get(), server_ctx_.get());
|
|
ASSERT_TRUE(session1);
|
|
bssl::UniquePtr<SSL_SESSION> session2 =
|
|
CreateClientSession(client_ctx_.get(), server_ctx_.get());
|
|
ASSERT_TRUE(session2);
|
|
|
|
auto connect_with_session = [&](SSL_SESSION *session) {
|
|
ClientConfig config;
|
|
config.session = session;
|
|
UniquePtr<SSL> client, server;
|
|
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx_.get(),
|
|
server_ctx_.get(), config));
|
|
};
|
|
|
|
// Resume sessions in parallel with establishing new ones.
|
|
{
|
|
std::vector<std::thread> threads;
|
|
threads.emplace_back([&] { connect_with_session(nullptr); });
|
|
threads.emplace_back([&] { connect_with_session(nullptr); });
|
|
threads.emplace_back([&] { connect_with_session(session1.get()); });
|
|
threads.emplace_back([&] { connect_with_session(session1.get()); });
|
|
threads.emplace_back([&] { connect_with_session(session2.get()); });
|
|
threads.emplace_back([&] { connect_with_session(session2.get()); });
|
|
for (auto &thread : threads) {
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
// Hit the maximum session cache size across multiple threads, to test the
|
|
// size enforcement logic.
|
|
size_t over_limit = 2;
|
|
size_t limit = SSL_CTX_sess_number(server_ctx_.get()) + over_limit;
|
|
SSL_CTX_sess_set_cache_size(server_ctx_.get(), limit);
|
|
{
|
|
std::vector<std::thread> threads;
|
|
for (int i = 0; i < 4; i++) {
|
|
threads.emplace_back([&]() {
|
|
connect_with_session(nullptr);
|
|
EXPECT_LE(SSL_CTX_sess_number(server_ctx_.get()), limit);
|
|
});
|
|
}
|
|
for (auto &thread : threads) {
|
|
thread.join();
|
|
}
|
|
EXPECT_EQ(SSL_CTX_sess_number(server_ctx_.get()), limit);
|
|
// We go over the cache limit by |over_limit|.
|
|
EXPECT_EQ(SSL_CTX_sess_cache_full(server_ctx_.get()), (int)over_limit);
|
|
}
|
|
|
|
// Reset the session cache, this time with a mock clock.
|
|
ASSERT_NO_FATAL_FAILURE(ResetContexts());
|
|
SSL_CTX_set_options(server_ctx_.get(), SSL_OP_NO_TICKET);
|
|
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_session_cache_mode(server_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_current_time_cb(server_ctx_.get(), CurrentTimeCallback);
|
|
|
|
// Make some sessions at an arbitrary start time. Then expire them.
|
|
g_current_time.tv_sec = 1000;
|
|
bssl::UniquePtr<SSL_SESSION> expired_session1 =
|
|
CreateClientSession(client_ctx_.get(), server_ctx_.get());
|
|
ASSERT_TRUE(expired_session1);
|
|
bssl::UniquePtr<SSL_SESSION> expired_session2 =
|
|
CreateClientSession(client_ctx_.get(), server_ctx_.get());
|
|
ASSERT_TRUE(expired_session2);
|
|
g_current_time.tv_sec += 100 * SSL_DEFAULT_SESSION_TIMEOUT;
|
|
|
|
session1 = CreateClientSession(client_ctx_.get(), server_ctx_.get());
|
|
ASSERT_TRUE(session1);
|
|
|
|
// Every 256 connections, we flush stale sessions from the session cache. Test
|
|
// this logic is correctly synchronized with other connection attempts.
|
|
static const int kNumConnections = 256;
|
|
{
|
|
std::vector<std::thread> threads;
|
|
threads.emplace_back([&] {
|
|
for (int i = 0; i < kNumConnections; i++) {
|
|
connect_with_session(nullptr);
|
|
}
|
|
});
|
|
threads.emplace_back([&] {
|
|
for (int i = 0; i < kNumConnections; i++) {
|
|
connect_with_session(nullptr);
|
|
}
|
|
});
|
|
threads.emplace_back([&] {
|
|
// Never connect with |expired_session2|. The session cache eagerly
|
|
// removes expired sessions when it sees them. Leaving |expired_session2|
|
|
// untouched ensures it is instead cleared by periodic flushing.
|
|
for (int i = 0; i < kNumConnections; i++) {
|
|
connect_with_session(expired_session1.get());
|
|
}
|
|
});
|
|
threads.emplace_back([&] {
|
|
for (int i = 0; i < kNumConnections; i++) {
|
|
connect_with_session(session1.get());
|
|
}
|
|
});
|
|
for (auto &thread : threads) {
|
|
thread.join();
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, SessionTicketThreads) {
|
|
for (bool renew_ticket : {false, true}) {
|
|
SCOPED_TRACE(renew_ticket);
|
|
ASSERT_NO_FATAL_FAILURE(ResetContexts());
|
|
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_session_cache_mode(server_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
if (renew_ticket) {
|
|
SSL_CTX_set_tlsext_ticket_key_cb(server_ctx_.get(), RenewTicketCallback);
|
|
}
|
|
|
|
// Establish two client sessions to test with.
|
|
bssl::UniquePtr<SSL_SESSION> session1 =
|
|
CreateClientSession(client_ctx_.get(), server_ctx_.get());
|
|
ASSERT_TRUE(session1);
|
|
bssl::UniquePtr<SSL_SESSION> session2 =
|
|
CreateClientSession(client_ctx_.get(), server_ctx_.get());
|
|
ASSERT_TRUE(session2);
|
|
|
|
auto connect_with_session = [&](SSL_SESSION *session) {
|
|
ClientConfig config;
|
|
config.session = session;
|
|
UniquePtr<SSL> client, server;
|
|
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx_.get(),
|
|
server_ctx_.get(), config));
|
|
};
|
|
|
|
// Resume sessions in parallel with establishing new ones.
|
|
{
|
|
std::vector<std::thread> threads;
|
|
threads.emplace_back([&] { connect_with_session(nullptr); });
|
|
threads.emplace_back([&] { connect_with_session(nullptr); });
|
|
threads.emplace_back([&] { connect_with_session(session1.get()); });
|
|
threads.emplace_back([&] { connect_with_session(session1.get()); });
|
|
threads.emplace_back([&] { connect_with_session(session2.get()); });
|
|
threads.emplace_back([&] { connect_with_session(session2.get()); });
|
|
for (auto &thread : threads) {
|
|
thread.join();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif // OPENSSL_THREADS
|
|
|
|
|
|
TEST_P(SSLVersionTest, SSLWriteRetry) {
|
|
if (is_dtls()) {
|
|
return;
|
|
}
|
|
|
|
for (bool enable_partial_write : {false, true}) {
|
|
SCOPED_TRACE(enable_partial_write);
|
|
|
|
// Connect a client and server.
|
|
ASSERT_TRUE(Connect());
|
|
|
|
if (enable_partial_write) {
|
|
SSL_set_mode(client_.get(), SSL_MODE_ENABLE_PARTIAL_WRITE);
|
|
}
|
|
|
|
// Write without reading until the buffer is full and we have an unfinished
|
|
// write. Keep a count so we may reread it again later. "hello!" will be
|
|
// written in two chunks, "hello" and "!".
|
|
char data[] = "hello!";
|
|
static const int kChunkLen = 5; // The length of "hello".
|
|
unsigned count = 0;
|
|
for (;;) {
|
|
int ret = SSL_write(client_.get(), data, kChunkLen);
|
|
if (ret <= 0) {
|
|
ASSERT_EQ(SSL_get_error(client_.get(), ret), SSL_ERROR_WANT_WRITE);
|
|
break;
|
|
}
|
|
ASSERT_EQ(ret, 5);
|
|
count++;
|
|
}
|
|
|
|
// Retrying with the same parameters is legal.
|
|
ASSERT_EQ(
|
|
SSL_get_error(client_.get(), SSL_write(client_.get(), data, kChunkLen)),
|
|
SSL_ERROR_WANT_WRITE);
|
|
|
|
// Retrying with the same buffer but shorter length is not legal.
|
|
ASSERT_EQ(SSL_get_error(client_.get(),
|
|
SSL_write(client_.get(), data, kChunkLen - 1)),
|
|
SSL_ERROR_SSL);
|
|
ASSERT_TRUE(ExpectSingleError(ERR_LIB_SSL, SSL_R_BAD_WRITE_RETRY));
|
|
|
|
// Retrying with a different buffer pointer is not legal.
|
|
char data2[] = "hello";
|
|
ASSERT_EQ(SSL_get_error(client_.get(),
|
|
SSL_write(client_.get(), data2, kChunkLen)),
|
|
SSL_ERROR_SSL);
|
|
ASSERT_TRUE(ExpectSingleError(ERR_LIB_SSL, SSL_R_BAD_WRITE_RETRY));
|
|
|
|
// With |SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER|, the buffer may move.
|
|
SSL_set_mode(client_.get(), SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
|
|
ASSERT_EQ(SSL_get_error(client_.get(),
|
|
SSL_write(client_.get(), data2, kChunkLen)),
|
|
SSL_ERROR_WANT_WRITE);
|
|
|
|
// |SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER| does not disable length checks.
|
|
ASSERT_EQ(SSL_get_error(client_.get(),
|
|
SSL_write(client_.get(), data2, kChunkLen - 1)),
|
|
SSL_ERROR_SSL);
|
|
ASSERT_TRUE(ExpectSingleError(ERR_LIB_SSL, SSL_R_BAD_WRITE_RETRY));
|
|
|
|
// Retrying with a larger buffer is legal.
|
|
ASSERT_EQ(SSL_get_error(client_.get(),
|
|
SSL_write(client_.get(), data, kChunkLen + 1)),
|
|
SSL_ERROR_WANT_WRITE);
|
|
|
|
// Drain the buffer.
|
|
char buf[20];
|
|
for (unsigned i = 0; i < count; i++) {
|
|
ASSERT_EQ(SSL_read(server_.get(), buf, sizeof(buf)), kChunkLen);
|
|
ASSERT_EQ(OPENSSL_memcmp(buf, "hello", kChunkLen), 0);
|
|
}
|
|
|
|
// Now that there is space, a retry with a larger buffer should flush the
|
|
// pending record, skip over that many bytes of input (on assumption they
|
|
// are the same), and write the remainder. If SSL_MODE_ENABLE_PARTIAL_WRITE
|
|
// is set, this will complete in two steps.
|
|
char data_longer[] = "_____!!!!!";
|
|
if (enable_partial_write) {
|
|
ASSERT_EQ(SSL_write(client_.get(), data_longer, 2 * kChunkLen),
|
|
kChunkLen);
|
|
ASSERT_EQ(SSL_write(client_.get(), data_longer + kChunkLen, kChunkLen),
|
|
kChunkLen);
|
|
} else {
|
|
ASSERT_EQ(SSL_write(client_.get(), data_longer, 2 * kChunkLen),
|
|
2 * kChunkLen);
|
|
}
|
|
|
|
// Check the last write was correct. The data will be spread over two
|
|
// records, so SSL_read returns twice.
|
|
ASSERT_EQ(SSL_read(server_.get(), buf, sizeof(buf)), kChunkLen);
|
|
ASSERT_EQ(OPENSSL_memcmp(buf, "hello", kChunkLen), 0);
|
|
ASSERT_EQ(SSL_read(server_.get(), buf, sizeof(buf)), kChunkLen);
|
|
ASSERT_EQ(OPENSSL_memcmp(buf, "!!!!!", kChunkLen), 0);
|
|
|
|
// Fill the transport buffer again. This time only leave room for one
|
|
// record.
|
|
count = 0;
|
|
for (;;) {
|
|
int ret = SSL_write(client_.get(), data, kChunkLen);
|
|
if (ret <= 0) {
|
|
ASSERT_EQ(SSL_get_error(client_.get(), ret), SSL_ERROR_WANT_WRITE);
|
|
break;
|
|
}
|
|
ASSERT_EQ(ret, 5);
|
|
count++;
|
|
}
|
|
ASSERT_EQ(SSL_read(server_.get(), buf, sizeof(buf)), kChunkLen);
|
|
ASSERT_EQ(OPENSSL_memcmp(buf, "hello", kChunkLen), 0);
|
|
count--;
|
|
|
|
// Retry the last write, with a longer input. The first half is the most
|
|
// recently failed write, from filling the buffer. |SSL_write| should write
|
|
// that to the transport, and then attempt to write the second half.
|
|
int ret = SSL_write(client_.get(), data_longer, 2 * kChunkLen);
|
|
if (enable_partial_write) {
|
|
// If partial writes are allowed, the write will succeed partially.
|
|
ASSERT_EQ(ret, kChunkLen);
|
|
|
|
// Check the first half and make room for another record.
|
|
ASSERT_EQ(SSL_read(server_.get(), buf, sizeof(buf)), kChunkLen);
|
|
ASSERT_EQ(OPENSSL_memcmp(buf, "hello", kChunkLen), 0);
|
|
count--;
|
|
|
|
// Finish writing the input.
|
|
ASSERT_EQ(SSL_write(client_.get(), data_longer + kChunkLen, kChunkLen),
|
|
kChunkLen);
|
|
} else {
|
|
if (enable_read_ahead()) {
|
|
// The client and server are sharing a BIO_pair which by default only
|
|
// allows 17 * 1024 bytes to be buffered in the shared BIO. This test
|
|
// relies on the buffer being full here. But if the client is reading
|
|
// ahead it is pulling data out of the BIO_pair's buffer and into it's
|
|
// own SSLBuffer freeing up space for the write above
|
|
ASSERT_EQ(ret, 2 * kChunkLen);
|
|
} else {
|
|
// Otherwise, although the first half made it to the transport, the
|
|
// second half is blocked.
|
|
ASSERT_EQ(ret, -1);
|
|
ASSERT_EQ(SSL_get_error(client_.get(), -1), SSL_ERROR_WANT_WRITE);
|
|
// Check the first half and make room for another record.
|
|
ASSERT_EQ(SSL_read(server_.get(), buf, sizeof(buf)), kChunkLen);
|
|
ASSERT_EQ(OPENSSL_memcmp(buf, "hello", kChunkLen), 0);
|
|
count--;
|
|
|
|
// Retrying with fewer bytes than previously attempted is an error. If
|
|
// the input length is less than the number of bytes successfully
|
|
// written, the check happens at a different point, with a different
|
|
// error.
|
|
//
|
|
// TODO(davidben): Should these cases use the same error?
|
|
ASSERT_EQ(
|
|
SSL_get_error(client_.get(),
|
|
SSL_write(client_.get(), data_longer, kChunkLen - 1)),
|
|
SSL_ERROR_SSL);
|
|
ASSERT_TRUE(ExpectSingleError(ERR_LIB_SSL, SSL_R_BAD_LENGTH));
|
|
|
|
// Complete the write with the correct retry.
|
|
ASSERT_EQ(SSL_write(client_.get(), data_longer, 2 * kChunkLen),
|
|
2 * kChunkLen);
|
|
}
|
|
}
|
|
|
|
// Drain the input and ensure everything was written correctly.
|
|
for (unsigned i = 0; i < count; i++) {
|
|
ASSERT_EQ(SSL_read(server_.get(), buf, sizeof(buf)), kChunkLen);
|
|
ASSERT_EQ(OPENSSL_memcmp(buf, "hello", kChunkLen), 0);
|
|
}
|
|
|
|
// The final write is spread over two records.
|
|
ASSERT_EQ(SSL_read(server_.get(), buf, sizeof(buf)), kChunkLen);
|
|
ASSERT_EQ(OPENSSL_memcmp(buf, "hello", kChunkLen), 0);
|
|
ASSERT_EQ(SSL_read(server_.get(), buf, sizeof(buf)), kChunkLen);
|
|
ASSERT_EQ(OPENSSL_memcmp(buf, "!!!!!", kChunkLen), 0);
|
|
}
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, RecordCallback) {
|
|
for (bool test_server : {true, false}) {
|
|
SCOPED_TRACE(test_server);
|
|
ASSERT_NO_FATAL_FAILURE(ResetContexts());
|
|
|
|
bool read_seen = false;
|
|
bool write_seen = false;
|
|
auto cb = [&](int is_write, int cb_version, int cb_type, const void *buf,
|
|
size_t len, SSL *ssl) {
|
|
if (cb_type != SSL3_RT_HEADER) {
|
|
return;
|
|
}
|
|
|
|
// The callback does not report a version for records.
|
|
EXPECT_EQ(0, cb_version);
|
|
|
|
if (is_write) {
|
|
write_seen = true;
|
|
} else {
|
|
read_seen = true;
|
|
}
|
|
|
|
// Sanity-check that the record header is plausible.
|
|
CBS cbs;
|
|
CBS_init(&cbs, reinterpret_cast<const uint8_t *>(buf), len);
|
|
uint8_t type = 0;
|
|
uint16_t record_version = 0, length = 0;
|
|
ASSERT_TRUE(CBS_get_u8(&cbs, &type));
|
|
ASSERT_TRUE(CBS_get_u16(&cbs, &record_version));
|
|
EXPECT_EQ(record_version & 0xff00, version() & 0xff00);
|
|
if (is_dtls()) {
|
|
uint16_t epoch = 0;
|
|
ASSERT_TRUE(CBS_get_u16(&cbs, &epoch));
|
|
EXPECT_TRUE(epoch == 0 || epoch == 1) << "Invalid epoch: " << epoch;
|
|
ASSERT_TRUE(CBS_skip(&cbs, 6));
|
|
}
|
|
ASSERT_TRUE(CBS_get_u16(&cbs, &length));
|
|
EXPECT_EQ(0u, CBS_len(&cbs));
|
|
};
|
|
using CallbackType = decltype(cb);
|
|
SSL_CTX *ctx = test_server ? server_ctx_.get() : client_ctx_.get();
|
|
SSL_CTX_set_msg_callback(
|
|
ctx, [](int is_write, int cb_version, int cb_type, const void *buf,
|
|
size_t len, SSL *ssl, void *arg) {
|
|
CallbackType *cb_ptr = reinterpret_cast<CallbackType *>(arg);
|
|
(*cb_ptr)(is_write, cb_version, cb_type, buf, len, ssl);
|
|
});
|
|
SSL_CTX_set_msg_callback_arg(ctx, &cb);
|
|
|
|
ASSERT_TRUE(Connect());
|
|
|
|
EXPECT_TRUE(read_seen);
|
|
EXPECT_TRUE(write_seen);
|
|
}
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, GetServerName) {
|
|
ClientConfig config;
|
|
config.servername = "host1";
|
|
|
|
SSL_CTX_set_tlsext_servername_callback(
|
|
server_ctx_.get(), [](SSL *ssl, int *out_alert, void *arg) -> int {
|
|
// During the handshake, |SSL_get_servername| must match |config|.
|
|
ClientConfig *config_p = reinterpret_cast<ClientConfig *>(arg);
|
|
EXPECT_STREQ(config_p->servername.c_str(),
|
|
SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name));
|
|
return SSL_TLSEXT_ERR_OK;
|
|
});
|
|
SSL_CTX_set_tlsext_servername_arg(server_ctx_.get(), &config);
|
|
|
|
ASSERT_TRUE(Connect(config));
|
|
// After the handshake, it must also be available.
|
|
EXPECT_STREQ(config.servername.c_str(),
|
|
SSL_get_servername(server_.get(), TLSEXT_NAMETYPE_host_name));
|
|
|
|
// Establish a session under host1.
|
|
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_session_cache_mode(server_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
bssl::UniquePtr<SSL_SESSION> session =
|
|
CreateClientSession(client_ctx_.get(), server_ctx_.get(), config);
|
|
|
|
// If the client resumes a session with a different name, |SSL_get_servername|
|
|
// must return the new name.
|
|
ASSERT_TRUE(session);
|
|
config.session = session.get();
|
|
config.servername = "host2";
|
|
ASSERT_TRUE(Connect(config));
|
|
EXPECT_STREQ(config.servername.c_str(),
|
|
SSL_get_servername(server_.get(), TLSEXT_NAMETYPE_host_name));
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, GetState) {
|
|
ClientConfig config;
|
|
ASSERT_TRUE(Connect(config));
|
|
int server_state = SSL_get_state(server_.get());
|
|
EXPECT_EQ(server_state, TLS_ST_OK);
|
|
EXPECT_EQ(server_state, SSL_ST_OK);
|
|
int client_state = SSL_state(client_.get());
|
|
EXPECT_EQ(client_state, TLS_ST_OK);
|
|
EXPECT_EQ(client_state, SSL_ST_OK);
|
|
}
|
|
|
|
// Test that session cache mode bits are honored in the client session callback.
|
|
TEST_P(SSLVersionTest, ClientSessionCacheMode) {
|
|
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_OFF);
|
|
EXPECT_FALSE(CreateClientSession(client_ctx_.get(), server_ctx_.get()));
|
|
|
|
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_CLIENT);
|
|
EXPECT_TRUE(CreateClientSession(client_ctx_.get(), server_ctx_.get()));
|
|
|
|
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_SERVER);
|
|
EXPECT_FALSE(CreateClientSession(client_ctx_.get(), server_ctx_.get()));
|
|
}
|
|
|
|
// Test that all versions survive tiny write buffers. In particular, TLS 1.3
|
|
// NewSessionTickets are written post-handshake. Servers that block
|
|
// |SSL_do_handshake| on writing them will deadlock if clients are not draining
|
|
// the buffer. Test that we do not do this.
|
|
TEST_P(SSLVersionTest, SmallBuffer) {
|
|
// DTLS is a datagram protocol and requires packet-sized buffers.
|
|
if (is_dtls()) {
|
|
return;
|
|
}
|
|
|
|
// Test both flushing NewSessionTickets with a zero-sized write and
|
|
// non-zero-sized write.
|
|
for (bool use_zero_write : {false, true}) {
|
|
SCOPED_TRACE(use_zero_write);
|
|
|
|
g_last_session = nullptr;
|
|
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_sess_set_new_cb(client_ctx_.get(), SaveLastSession);
|
|
|
|
bssl::UniquePtr<SSL> client(SSL_new(client_ctx_.get())),
|
|
server(SSL_new(server_ctx_.get()));
|
|
ASSERT_TRUE(client);
|
|
ASSERT_TRUE(server);
|
|
SSL_set_connect_state(client.get());
|
|
SSL_set_accept_state(server.get());
|
|
|
|
// Use a tiny buffer.
|
|
BIO *bio1 = nullptr, *bio2 = nullptr;
|
|
ASSERT_TRUE(BIO_new_bio_pair(&bio1, 1, &bio2, 1));
|
|
|
|
// SSL_set_bio takes ownership.
|
|
SSL_set_bio(client.get(), bio1, bio1);
|
|
SSL_set_bio(server.get(), bio2, bio2);
|
|
|
|
ASSERT_TRUE(CompleteHandshakes(client.get(), server.get()));
|
|
if (version() >= TLS1_3_VERSION) {
|
|
// The post-handshake ticket should not have been processed yet.
|
|
EXPECT_FALSE(g_last_session);
|
|
}
|
|
|
|
if (use_zero_write) {
|
|
ASSERT_TRUE(FlushNewSessionTickets(client.get(), server.get()));
|
|
EXPECT_TRUE(g_last_session);
|
|
}
|
|
|
|
// Send some data from server to client. If |use_zero_write| is false, this
|
|
// will also flush the NewSessionTickets.
|
|
static const char kMessage[] = "hello world";
|
|
char buf[sizeof(kMessage)];
|
|
for (;;) {
|
|
int server_ret = SSL_write(server.get(), kMessage, sizeof(kMessage));
|
|
int server_err = SSL_get_error(server.get(), server_ret);
|
|
int client_ret = SSL_read(client.get(), buf, sizeof(buf));
|
|
int client_err = SSL_get_error(client.get(), client_ret);
|
|
|
|
// The server will write a single record, so every iteration should see
|
|
// |SSL_ERROR_WANT_WRITE| and |SSL_ERROR_WANT_READ|, until the final
|
|
// iteration, where both will complete.
|
|
if (server_ret > 0) {
|
|
EXPECT_EQ(server_ret, static_cast<int>(sizeof(kMessage)));
|
|
EXPECT_EQ(client_ret, static_cast<int>(sizeof(kMessage)));
|
|
EXPECT_EQ(Bytes(buf), Bytes(kMessage));
|
|
break;
|
|
}
|
|
|
|
ASSERT_EQ(server_ret, -1);
|
|
ASSERT_EQ(server_err, SSL_ERROR_WANT_WRITE);
|
|
ASSERT_EQ(client_ret, -1);
|
|
ASSERT_EQ(client_err, SSL_ERROR_WANT_READ);
|
|
}
|
|
|
|
// The NewSessionTickets should have been flushed and processed.
|
|
EXPECT_TRUE(g_last_session);
|
|
}
|
|
}
|
|
|
|
|
|
TEST_P(SSLVersionTest, SessionVersion) {
|
|
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_session_cache_mode(server_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
|
|
bssl::UniquePtr<SSL_SESSION> session =
|
|
CreateClientSession(client_ctx_.get(), server_ctx_.get());
|
|
ASSERT_TRUE(session);
|
|
EXPECT_EQ(version(), SSL_SESSION_get_protocol_version(session.get()));
|
|
|
|
// Sessions in TLS 1.3 and later should be single-use.
|
|
EXPECT_EQ(version() == TLS1_3_VERSION,
|
|
!!SSL_SESSION_should_be_single_use(session.get()));
|
|
|
|
// Making fake sessions for testing works.
|
|
session.reset(SSL_SESSION_new(client_ctx_.get()));
|
|
ASSERT_TRUE(session);
|
|
ASSERT_TRUE(SSL_SESSION_set_protocol_version(session.get(), version()));
|
|
EXPECT_EQ(version(), SSL_SESSION_get_protocol_version(session.get()));
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, SSLPending) {
|
|
UniquePtr<SSL> ssl(SSL_new(client_ctx_.get()));
|
|
ASSERT_TRUE(ssl);
|
|
EXPECT_EQ(0, SSL_pending(ssl.get()));
|
|
|
|
ASSERT_TRUE(Connect());
|
|
EXPECT_EQ(0, SSL_pending(client_.get()));
|
|
EXPECT_EQ(0, SSL_has_pending(client_.get()));
|
|
|
|
ASSERT_EQ(5, SSL_write(server_.get(), "hello", 5));
|
|
ASSERT_EQ(5, SSL_write(server_.get(), "world", 5));
|
|
EXPECT_EQ(0, SSL_pending(client_.get()));
|
|
EXPECT_EQ(0, SSL_has_pending(client_.get()));
|
|
|
|
char buf[10];
|
|
ASSERT_EQ(1, SSL_peek(client_.get(), buf, 1));
|
|
EXPECT_EQ(5, SSL_pending(client_.get()));
|
|
EXPECT_EQ(1, SSL_has_pending(client_.get()));
|
|
|
|
ASSERT_EQ(1, SSL_read(client_.get(), buf, 1));
|
|
EXPECT_EQ(4, SSL_pending(client_.get()));
|
|
EXPECT_EQ(1, SSL_has_pending(client_.get()));
|
|
|
|
ASSERT_EQ(4, SSL_read(client_.get(), buf, 10));
|
|
EXPECT_EQ(0, SSL_pending(client_.get()));
|
|
if (is_dtls()) {
|
|
// In DTLS, the two records would have been read as a single datagram and
|
|
// buffered inside |client_|. Thus, |SSL_has_pending| should return true.
|
|
//
|
|
// This test is slightly unrealistic. It relies on |ConnectClientAndServer|
|
|
// using a |BIO| pair, which does not preserve datagram boundaries. Reading
|
|
// 1 byte, then 4 bytes, from the first record also relies on
|
|
// https://crbug.com/boringssl/65. But it does test the codepaths. When
|
|
// fixing either of these bugs, this test may need to be redone.
|
|
EXPECT_EQ(1, SSL_has_pending(client_.get()));
|
|
} else {
|
|
// In TLS if read ahead is enabled the two records would also have been read
|
|
// in a single call.
|
|
EXPECT_EQ(enable_read_ahead(), SSL_has_pending(client_.get()));
|
|
}
|
|
|
|
ASSERT_EQ(2, SSL_read(client_.get(), buf, 2));
|
|
EXPECT_EQ(3, SSL_pending(client_.get()));
|
|
EXPECT_EQ(1, SSL_has_pending(client_.get()));
|
|
}
|
|
|
|
// Identical test to the |SSLPending| test suite above, but with
|
|
// |SSL_(read/peek/write)_ex| operations instead.
|
|
TEST_P(SSLVersionTest, SSLPendingEx) {
|
|
UniquePtr<SSL> ssl(SSL_new(client_ctx_.get()));
|
|
ASSERT_TRUE(ssl);
|
|
EXPECT_EQ(0, SSL_pending(ssl.get()));
|
|
|
|
ASSERT_TRUE(Connect());
|
|
EXPECT_EQ(0, SSL_pending(client_.get()));
|
|
EXPECT_EQ(0, SSL_has_pending(client_.get()));
|
|
|
|
size_t buf_len = 0;
|
|
ASSERT_EQ(1, SSL_write_ex(server_.get(), "hello", 5, &buf_len));
|
|
ASSERT_EQ(buf_len, (size_t)5);
|
|
ASSERT_EQ(1, SSL_write_ex(server_.get(), "world", 5, &buf_len));
|
|
ASSERT_EQ(buf_len, (size_t)5);
|
|
|
|
EXPECT_EQ(0, SSL_pending(client_.get()));
|
|
EXPECT_EQ(0, SSL_has_pending(client_.get()));
|
|
|
|
char buf[10];
|
|
ASSERT_EQ(1, SSL_peek_ex(client_.get(), buf, 1, &buf_len));
|
|
ASSERT_EQ(buf_len, (size_t)1);
|
|
EXPECT_EQ(5, SSL_pending(client_.get()));
|
|
EXPECT_EQ(1, SSL_has_pending(client_.get()));
|
|
|
|
ASSERT_EQ(1, SSL_read_ex(client_.get(), buf, 1, &buf_len));
|
|
ASSERT_EQ(buf_len, (size_t)1);
|
|
EXPECT_EQ(4, SSL_pending(client_.get()));
|
|
EXPECT_EQ(1, SSL_has_pending(client_.get()));
|
|
|
|
ASSERT_EQ(1, SSL_read_ex(client_.get(), buf, 10, &buf_len));
|
|
ASSERT_EQ(buf_len, (size_t)4);
|
|
EXPECT_EQ(0, SSL_pending(client_.get()));
|
|
if (is_dtls()) {
|
|
EXPECT_EQ(1, SSL_has_pending(client_.get()));
|
|
} else {
|
|
// In TLS if read ahead is enabled the two records would also have been read
|
|
// in a single call.
|
|
EXPECT_EQ(enable_read_ahead(), SSL_has_pending(client_.get()));
|
|
}
|
|
|
|
ASSERT_EQ(1, SSL_read_ex(client_.get(), buf, 2, &buf_len));
|
|
ASSERT_EQ(buf_len, (size_t)2);
|
|
EXPECT_EQ(3, SSL_pending(client_.get()));
|
|
EXPECT_EQ(1, SSL_has_pending(client_.get()));
|
|
|
|
// 0-sized IO with valid inputs should succeed but not read/write nor effect
|
|
// buffer state. However, NULL |read_bytes|/|written| pointer should fail.
|
|
const int client_pending = SSL_pending(client_.get());
|
|
ASSERT_EQ(1, SSL_read_ex(client_.get(), (void *)"", 0, &buf_len));
|
|
ASSERT_EQ(0UL, buf_len);
|
|
ASSERT_EQ(client_pending, SSL_pending(client_.get()));
|
|
ASSERT_EQ(1, SSL_write_ex(client_.get(), (void *)"", 0, &buf_len));
|
|
ASSERT_EQ(0UL, buf_len);
|
|
ASSERT_EQ(client_pending, SSL_pending(client_.get()));
|
|
ASSERT_EQ(0, SSL_read_ex(client_.get(), (void *)"", 0, nullptr));
|
|
ASSERT_EQ(0, SSL_write_ex(client_.get(), (void *)"", 0, nullptr));
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, ReadAhead) {
|
|
ASSERT_TRUE(Connect());
|
|
size_t buf_len = 0;
|
|
std::string test_string = "Hello, world!";
|
|
for (char &i : test_string) {
|
|
ASSERT_EQ(1, SSL_write_ex(server_.get(), &i, 1, &buf_len));
|
|
}
|
|
|
|
char buf[13];
|
|
size_t starting_size = BIO_pending(client_.get()->rbio.get());
|
|
|
|
ASSERT_NE(0UL, starting_size);
|
|
ASSERT_EQ(1, SSL_read_ex(client_.get(), buf, 1, &buf_len));
|
|
if (enable_read_ahead() || is_dtls()) {
|
|
// Even though we didn't request the full string (13 * record overhead)
|
|
// everything should be read from the BIO
|
|
if (read_ahead_buffer_size() > starting_size || is_dtls()) {
|
|
ASSERT_EQ(0UL, BIO_pending(client_.get()->rbio.get()));
|
|
} else {
|
|
// Depending on TLS version each record will be a different size and a
|
|
// variable but non-zero amount of data will remain
|
|
ASSERT_NE(0UL, BIO_pending(client_.get()->rbio.get()));
|
|
}
|
|
} else {
|
|
// Only the requested 1 byte + TLS record overhead should have been read,
|
|
// the remaining 12 letters each in its own record should be in the BIO
|
|
// not the SSLBuffer
|
|
ASSERT_NE(0UL, BIO_pending(client_.get()->rbio.get()));
|
|
}
|
|
}
|
|
|
|
|
|
TEST_P(SSLVersionTest, VerifyBeforeCertRequest) {
|
|
// Configure the server to request client certificates.
|
|
SSL_CTX_set_custom_verify(
|
|
server_ctx_.get(), SSL_VERIFY_PEER,
|
|
[](SSL *ssl, uint8_t *out_alert) { return ssl_verify_ok; });
|
|
|
|
// Configure the client to reject the server certificate.
|
|
SSL_CTX_set_custom_verify(
|
|
client_ctx_.get(), SSL_VERIFY_PEER,
|
|
[](SSL *ssl, uint8_t *out_alert) { return ssl_verify_invalid; });
|
|
|
|
// cert_cb should not be called. Verification should fail first.
|
|
SSL_CTX_set_cert_cb(
|
|
client_ctx_.get(),
|
|
[](SSL *ssl, void *arg) {
|
|
ADD_FAILURE() << "cert_cb unexpectedly called";
|
|
return 0;
|
|
},
|
|
nullptr);
|
|
|
|
bssl::UniquePtr<SSL> client, server;
|
|
EXPECT_FALSE(ConnectClientAndServer(&client, &server, client_ctx_.get(),
|
|
server_ctx_.get()));
|
|
}
|
|
|
|
// Test that ticket-based sessions on the client get fake session IDs.
|
|
TEST_P(SSLVersionTest, FakeIDsForTickets) {
|
|
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_session_cache_mode(server_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
|
|
bssl::UniquePtr<SSL_SESSION> session =
|
|
CreateClientSession(client_ctx_.get(), server_ctx_.get());
|
|
ASSERT_TRUE(session);
|
|
|
|
EXPECT_TRUE(SSL_SESSION_has_ticket(session.get()));
|
|
unsigned session_id_length = 0;
|
|
SSL_SESSION_get_id(session.get(), &session_id_length);
|
|
EXPECT_NE(session_id_length, 0u);
|
|
}
|
|
|
|
|
|
// Functions which access properties on the negotiated session are thread-safe
|
|
// where needed. Prior to TLS 1.3, clients resuming sessions and servers
|
|
// performing stateful resumption will share an underlying SSL_SESSION object,
|
|
// potentially across threads.
|
|
TEST_P(SSLVersionTest, SessionPropertiesThreads) {
|
|
if (version() == TLS1_3_VERSION) {
|
|
// Our TLS 1.3 implementation does not support stateful resumption.
|
|
ASSERT_FALSE(CreateClientSession(client_ctx_.get(), server_ctx_.get()));
|
|
return;
|
|
}
|
|
|
|
SSL_CTX_set_options(server_ctx_.get(), SSL_OP_NO_TICKET);
|
|
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_session_cache_mode(server_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
|
|
ASSERT_TRUE(UseCertAndKey(client_ctx_.get()));
|
|
ASSERT_TRUE(UseCertAndKey(server_ctx_.get()));
|
|
|
|
// Configure mutual authentication, so we have more session state.
|
|
SSL_CTX_set_custom_verify(
|
|
client_ctx_.get(), SSL_VERIFY_PEER,
|
|
[](SSL *ssl, uint8_t *out_alert) { return ssl_verify_ok; });
|
|
SSL_CTX_set_custom_verify(
|
|
server_ctx_.get(), SSL_VERIFY_PEER,
|
|
[](SSL *ssl, uint8_t *out_alert) { return ssl_verify_ok; });
|
|
|
|
// Establish a client session to test with.
|
|
bssl::UniquePtr<SSL_SESSION> session =
|
|
CreateClientSession(client_ctx_.get(), server_ctx_.get());
|
|
ASSERT_TRUE(session);
|
|
|
|
// Resume with it twice.
|
|
UniquePtr<SSL> ssls[4];
|
|
ClientConfig config;
|
|
config.session = session.get();
|
|
ASSERT_TRUE(ConnectClientAndServer(&ssls[0], &ssls[1], client_ctx_.get(),
|
|
server_ctx_.get(), config));
|
|
ASSERT_TRUE(ConnectClientAndServer(&ssls[2], &ssls[3], client_ctx_.get(),
|
|
server_ctx_.get(), config));
|
|
|
|
// Read properties in parallel.
|
|
auto read_properties = [](const SSL *ssl) {
|
|
EXPECT_TRUE(SSL_get_peer_cert_chain(ssl));
|
|
bssl::UniquePtr<X509> peer(SSL_get_peer_certificate(ssl));
|
|
EXPECT_TRUE(peer);
|
|
STACK_OF(X509) *verified_chain = SSL_get0_verified_chain(ssl);
|
|
// This test sets a custom verifier callback which doesn't actually do any
|
|
// verification
|
|
EXPECT_FALSE(verified_chain);
|
|
EXPECT_TRUE(SSL_get_current_cipher(ssl));
|
|
EXPECT_TRUE(SSL_get_group_id(ssl));
|
|
};
|
|
|
|
std::vector<std::thread> threads;
|
|
for (const auto &ssl_ptr : ssls) {
|
|
const SSL *ssl = ssl_ptr.get();
|
|
threads.emplace_back([=] { read_properties(ssl); });
|
|
}
|
|
for (auto &thread : threads) {
|
|
thread.join();
|
|
}
|
|
// Session has been resumed twice.
|
|
EXPECT_EQ(SSL_CTX_sess_hits(server_ctx_.get()), 2);
|
|
EXPECT_EQ(SSL_CTX_sess_hits(client_ctx_.get()), 2);
|
|
}
|
|
|
|
|
|
TEST_P(SSLVersionTest, SimpleVerifiedChain) {
|
|
ASSERT_TRUE(UseCertAndKey(server_ctx_.get()));
|
|
ASSERT_TRUE(X509_STORE_add_cert(SSL_CTX_get_cert_store(client_ctx_.get()),
|
|
cert_.get()));
|
|
SSL_CTX_set_verify(client_ctx_.get(),
|
|
SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
|
|
nullptr);
|
|
X509_VERIFY_PARAM_set_flags(SSL_CTX_get0_param(client_ctx_.get()),
|
|
X509_V_FLAG_NO_CHECK_TIME);
|
|
|
|
|
|
UniquePtr<SSL> client_ssl, server_ssl;
|
|
ClientConfig config;
|
|
ASSERT_TRUE(ConnectClientAndServer(
|
|
&client_ssl, &server_ssl, client_ctx_.get(), server_ctx_.get(), config));
|
|
|
|
STACK_OF(X509) *client_chain = SSL_get_peer_full_cert_chain(client_ssl.get());
|
|
STACK_OF(X509) *verified_client_chain =
|
|
SSL_get0_verified_chain(client_ssl.get());
|
|
EXPECT_TRUE(verified_client_chain);
|
|
|
|
STACK_OF(X509) *verified_server_chain =
|
|
SSL_get0_verified_chain(server_ssl.get());
|
|
// The client didn't send a certificate so the server shouldn't have anything
|
|
EXPECT_FALSE(verified_server_chain);
|
|
|
|
// UseCertAndKey sets a single cert that is directly trusted, it is the only
|
|
// one sent, and only one needed for verification
|
|
EXPECT_EQ(sk_X509_num(client_chain), 1UL);
|
|
EXPECT_EQ(X509_cmp(sk_X509_value(client_chain, 0), cert_.get()), 0);
|
|
|
|
EXPECT_EQ(sk_X509_num(verified_client_chain), 1UL);
|
|
EXPECT_EQ(X509_cmp(sk_X509_value(verified_client_chain, 0), cert_.get()), 0);
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, VerifiedChain) {
|
|
ASSERT_TRUE(X509_STORE_add_cert(SSL_CTX_get_cert_store(client_ctx_.get()),
|
|
cert_.get()));
|
|
SSL_CTX_set_verify(client_ctx_.get(),
|
|
SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
|
|
nullptr);
|
|
X509_VERIFY_PARAM_set_flags(SSL_CTX_get0_param(client_ctx_.get()),
|
|
X509_V_FLAG_NO_CHECK_TIME);
|
|
|
|
// UseCertAndKey sets the leaf cert the server will use and ensures the client
|
|
// trusts the server's cert
|
|
ASSERT_TRUE(UseCertAndKey(server_ctx_.get()));
|
|
|
|
// Add two extra certs to the chain
|
|
bssl::UniquePtr<STACK_OF(X509)> chain(sk_X509_new_null());
|
|
bssl::UniquePtr<X509> cert1 = GetECDSATestCertificate();
|
|
ASSERT_TRUE(sk_X509_push(chain.get(), cert1.get()));
|
|
X509_up_ref(cert1.get());
|
|
|
|
bssl::UniquePtr<X509> cert2 = GetTestCertificate();
|
|
ASSERT_TRUE(sk_X509_push(chain.get(), cert2.get()));
|
|
X509_up_ref(cert2.get());
|
|
|
|
SSL_CTX_set1_chain(server_ctx_.get(), chain.get());
|
|
|
|
UniquePtr<SSL> client_ssl, server_ssl;
|
|
ClientConfig config;
|
|
ASSERT_TRUE(ConnectClientAndServer(
|
|
&client_ssl, &server_ssl, client_ctx_.get(), server_ctx_.get(), config));
|
|
|
|
// The client didn't send a certificate so the server shouldn't have anything
|
|
STACK_OF(X509) *verified_client_chain =
|
|
SSL_get0_verified_chain(server_ssl.get());
|
|
EXPECT_FALSE(verified_client_chain);
|
|
STACK_OF(X509) *client_chain = SSL_get_peer_full_cert_chain(server_ssl.get());
|
|
EXPECT_FALSE(client_chain);
|
|
|
|
// The server sent a chain that the client can verify, the client directly
|
|
// trusts the server's certificate
|
|
STACK_OF(X509) *verified_server_chain =
|
|
SSL_get0_verified_chain(client_ssl.get());
|
|
EXPECT_EQ(sk_X509_num(verified_server_chain), 1UL);
|
|
EXPECT_EQ(X509_cmp(sk_X509_value(verified_server_chain, 0), cert_.get()), 0);
|
|
|
|
// The server sent two extra certs that are unneeded for verification,
|
|
// but it is included in the unverified chain
|
|
STACK_OF(X509) *server_chain = SSL_get_peer_full_cert_chain(client_ssl.get());
|
|
EXPECT_EQ(sk_X509_num(server_chain), 3UL);
|
|
EXPECT_EQ(X509_cmp(sk_X509_value(server_chain, 0), cert_.get()), 0);
|
|
EXPECT_EQ(X509_cmp(sk_X509_value(server_chain, 1), cert1.get()), 0);
|
|
EXPECT_EQ(X509_cmp(sk_X509_value(server_chain, 2), cert2.get()), 0);
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, FailedHandshakeVerifiedChain) {
|
|
SSL_CTX_set_verify(client_ctx_.get(),
|
|
SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
|
|
nullptr);
|
|
X509_VERIFY_PARAM_set_flags(SSL_CTX_get0_param(client_ctx_.get()),
|
|
X509_V_FLAG_NO_CHECK_TIME);
|
|
|
|
ASSERT_TRUE(UseCertAndKey(server_ctx_.get()));
|
|
UniquePtr<SSL> client_ssl, server_ssl;
|
|
|
|
ASSERT_TRUE(CreateClientAndServer(&client_ssl, &server_ssl, client_ctx_.get(),
|
|
server_ctx_.get()));
|
|
ASSERT_FALSE(CompleteHandshakes(client_ssl.get(), server_ssl.get()));
|
|
EXPECT_NE(SSL_get_verify_result(client_ssl.get()), X509_V_OK);
|
|
|
|
STACK_OF(X509) *client_chain = SSL_get_peer_full_cert_chain(client_ssl.get());
|
|
ASSERT_TRUE(client_chain);
|
|
EXPECT_EQ(sk_X509_num(client_chain), 1UL);
|
|
EXPECT_EQ(X509_cmp(sk_X509_value(client_chain, 0), cert_.get()), 0);
|
|
|
|
|
|
// For a failed handshake SSL_get0_verified_chain will return null
|
|
STACK_OF(X509) *verified_client_chain =
|
|
SSL_get0_verified_chain(client_ssl.get());
|
|
EXPECT_FALSE(verified_client_chain);
|
|
}
|
|
|
|
|
|
TEST_P(SSLVersionTest, SessionMissCache) {
|
|
if (version() == TLS1_3_VERSION) {
|
|
// Our TLS 1.3 implementation does not support stateful resumption.
|
|
ASSERT_FALSE(CreateClientSession(client_ctx_.get(), server_ctx_.get()));
|
|
return;
|
|
}
|
|
|
|
SSL_CTX_set_options(server_ctx_.get(), SSL_OP_NO_TICKET);
|
|
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_session_cache_mode(server_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_current_time_cb(server_ctx_.get(), CurrentTimeCallback);
|
|
|
|
ClientConfig config;
|
|
bssl::UniquePtr<SSL> client, server;
|
|
// Make some sessions at an arbitrary start time. Then expire them.
|
|
g_current_time.tv_sec = 1000;
|
|
bssl::UniquePtr<SSL_SESSION> expired_session =
|
|
CreateClientSession(client_ctx_.get(), server_ctx_.get());
|
|
ASSERT_TRUE(expired_session);
|
|
g_current_time.tv_sec += 100 * SSL_DEFAULT_SESSION_TIMEOUT;
|
|
|
|
static const int kNumConnections = 2;
|
|
config.session = expired_session.get();
|
|
EXPECT_TRUE(ConnectClientAndServer(&client, &server, client_ctx_.get(),
|
|
server_ctx_.get(), config));
|
|
EXPECT_TRUE(ConnectClientAndServer(&client, &server, client_ctx_.get(),
|
|
server_ctx_.get(), config));
|
|
|
|
// First connection is not an |sess_miss|, but failed to connect successfully.
|
|
// Subsequent connections will all be both timeouts and misses.
|
|
EXPECT_EQ(SSL_CTX_sess_misses(server_ctx_.get()), kNumConnections - 1);
|
|
EXPECT_EQ(SSL_CTX_sess_timeouts(server_ctx_.get()), kNumConnections);
|
|
// Check that |sess_hits| is not incorrectly incremented on either end.
|
|
EXPECT_EQ(SSL_CTX_sess_hits(client_ctx_.get()), 0);
|
|
EXPECT_EQ(SSL_CTX_sess_hits(server_ctx_.get()), 0);
|
|
}
|
|
|
|
// Callback function to force an external session cache counter update.
|
|
// This intentionally always returns a value, to verify that the counter is
|
|
// updated as intended.
|
|
// Allocating any memory within the callback function will cause the address
|
|
// sanitizers to fail, so we manage the memory externally.
|
|
static bssl::UniquePtr<SSL_SESSION> ssl_session;
|
|
static SSL_SESSION *get_session(SSL *ssl, const unsigned char *id, int idlen,
|
|
int *do_copy) {
|
|
return ssl_session.release();
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, SessionExternalCacheHit) {
|
|
if (version() == TLS1_3_VERSION) {
|
|
// Our TLS 1.3 implementation does not support stateful resumption.
|
|
ASSERT_FALSE(CreateClientSession(client_ctx_.get(), server_ctx_.get()));
|
|
return;
|
|
}
|
|
|
|
SSL_CTX_set_options(server_ctx_.get(), SSL_OP_NO_TICKET);
|
|
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_session_cache_mode(
|
|
server_ctx_.get(), SSL_SESS_CACHE_BOTH | SSL_SESS_CACHE_NO_INTERNAL);
|
|
SSL_CTX_sess_set_get_cb(server_ctx_.get(), get_session);
|
|
|
|
ClientConfig config;
|
|
bssl::UniquePtr<SSL> client, server;
|
|
bssl::UniquePtr<SSL_SESSION> session =
|
|
CreateClientSession(client_ctx_.get(), server_ctx_.get());
|
|
|
|
static const int kNumConnections = 2;
|
|
config.session = session.get();
|
|
for (int i = 0; i < kNumConnections; i++) {
|
|
ssl_session.reset(session.get());
|
|
EXPECT_TRUE(ConnectClientAndServer(&client, &server, client_ctx_.get(),
|
|
server_ctx_.get(), config));
|
|
}
|
|
EXPECT_EQ(SSL_CTX_sess_cb_hits(server_ctx_.get()), kNumConnections);
|
|
}
|
|
|
|
|
|
TEST_P(SSLVersionTest, DoubleSSLError) {
|
|
// Connect the inner SSL connections.
|
|
ASSERT_TRUE(Connect());
|
|
|
|
// Make a pair of |BIO|s which wrap |client_| and |server_|.
|
|
UniquePtr<BIO_METHOD> bio_method(BIO_meth_new(0, nullptr));
|
|
ASSERT_TRUE(bio_method);
|
|
ASSERT_TRUE(BIO_meth_set_read(
|
|
bio_method.get(), [](BIO *bio, char *out, int len) -> int {
|
|
SSL *ssl = static_cast<SSL *>(BIO_get_data(bio));
|
|
int ret = SSL_read(ssl, out, len);
|
|
int ssl_ret = SSL_get_error(ssl, ret);
|
|
if (ssl_ret == SSL_ERROR_WANT_READ) {
|
|
BIO_set_retry_read(bio);
|
|
}
|
|
return ret;
|
|
}));
|
|
ASSERT_TRUE(BIO_meth_set_write(
|
|
bio_method.get(), [](BIO *bio, const char *in, int len) -> int {
|
|
SSL *ssl = static_cast<SSL *>(BIO_get_data(bio));
|
|
int ret = SSL_write(ssl, in, len);
|
|
int ssl_ret = SSL_get_error(ssl, ret);
|
|
if (ssl_ret == SSL_ERROR_WANT_WRITE) {
|
|
BIO_set_retry_write(bio);
|
|
}
|
|
return ret;
|
|
}));
|
|
ASSERT_TRUE(BIO_meth_set_ctrl(
|
|
bio_method.get(), [](BIO *bio, int cmd, long larg, void *parg) -> long {
|
|
// |SSL| objects require |BIO_flush| support.
|
|
if (cmd == BIO_CTRL_FLUSH) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}));
|
|
|
|
UniquePtr<BIO> client_bio(BIO_new(bio_method.get()));
|
|
ASSERT_TRUE(client_bio);
|
|
BIO_set_data(client_bio.get(), client_.get());
|
|
BIO_set_init(client_bio.get(), 1);
|
|
|
|
UniquePtr<BIO> server_bio(BIO_new(bio_method.get()));
|
|
ASSERT_TRUE(server_bio);
|
|
BIO_set_data(server_bio.get(), server_.get());
|
|
BIO_set_init(server_bio.get(), 1);
|
|
|
|
// Wrap the inner connections in another layer of SSL.
|
|
UniquePtr<SSL> client_outer(SSL_new(client_ctx_.get()));
|
|
ASSERT_TRUE(client_outer);
|
|
SSL_set_connect_state(client_outer.get());
|
|
SSL_set_bio(client_outer.get(), client_bio.get(), client_bio.get());
|
|
client_bio.release(); // |SSL_set_bio| takes ownership.
|
|
|
|
UniquePtr<SSL> server_outer(SSL_new(server_ctx_.get()));
|
|
ASSERT_TRUE(server_outer);
|
|
SSL_set_accept_state(server_outer.get());
|
|
SSL_set_bio(server_outer.get(), server_bio.get(), server_bio.get());
|
|
server_bio.release(); // |SSL_set_bio| takes ownership.
|
|
|
|
// Configure |client_outer| to reject the server certificate.
|
|
SSL_set_custom_verify(
|
|
client_outer.get(), SSL_VERIFY_PEER,
|
|
[](SSL *ssl, uint8_t *out_alert) -> ssl_verify_result_t {
|
|
return ssl_verify_invalid;
|
|
});
|
|
|
|
for (;;) {
|
|
int client_ret = SSL_do_handshake(client_outer.get());
|
|
int client_err = SSL_get_error(client_outer.get(), client_ret);
|
|
if (client_err != SSL_ERROR_WANT_READ &&
|
|
client_err != SSL_ERROR_WANT_WRITE) {
|
|
// The client handshake should terminate on a certificate verification
|
|
// error.
|
|
EXPECT_EQ(SSL_ERROR_SSL, client_err);
|
|
EXPECT_TRUE(ErrorEquals(ERR_peek_error(), ERR_LIB_SSL,
|
|
SSL_R_CERTIFICATE_VERIFY_FAILED));
|
|
break;
|
|
}
|
|
|
|
// Run the server handshake and continue.
|
|
int server_ret = SSL_do_handshake(server_outer.get());
|
|
int server_err = SSL_get_error(server_outer.get(), server_ret);
|
|
ASSERT_TRUE(server_err == SSL_ERROR_NONE ||
|
|
server_err == SSL_ERROR_WANT_READ ||
|
|
server_err == SSL_ERROR_WANT_WRITE);
|
|
}
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, SameKeyResume) {
|
|
uint8_t key[48];
|
|
RAND_bytes(key, sizeof(key));
|
|
|
|
bssl::UniquePtr<SSL_CTX> server_ctx2 = CreateContext();
|
|
ASSERT_TRUE(server_ctx2);
|
|
ASSERT_TRUE(UseCertAndKey(server_ctx2.get()));
|
|
ASSERT_TRUE(
|
|
SSL_CTX_set_tlsext_ticket_keys(server_ctx_.get(), key, sizeof(key)));
|
|
ASSERT_TRUE(
|
|
SSL_CTX_set_tlsext_ticket_keys(server_ctx2.get(), key, sizeof(key)));
|
|
|
|
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_session_cache_mode(server_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_session_cache_mode(server_ctx2.get(), SSL_SESS_CACHE_BOTH);
|
|
|
|
// Establish a session for |server_ctx_|.
|
|
bssl::UniquePtr<SSL_SESSION> session =
|
|
CreateClientSession(client_ctx_.get(), server_ctx_.get());
|
|
ASSERT_TRUE(session);
|
|
ClientConfig config;
|
|
config.session = session.get();
|
|
|
|
// No hits before we resume the connection
|
|
EXPECT_EQ(SSL_CTX_sess_hits(client_ctx_.get()), 0);
|
|
EXPECT_EQ(SSL_CTX_sess_hits(server_ctx_.get()), 0);
|
|
EXPECT_EQ(SSL_CTX_sess_hits(server_ctx2.get()), 0);
|
|
|
|
// Resuming with |server_ctx_| again works.
|
|
bssl::UniquePtr<SSL> client, server;
|
|
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx_.get(),
|
|
server_ctx_.get(), config));
|
|
EXPECT_TRUE(SSL_session_reused(client.get()));
|
|
EXPECT_TRUE(SSL_session_reused(server.get()));
|
|
|
|
// Resuming with |server_ctx2| also works.
|
|
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx_.get(),
|
|
server_ctx2.get(), config));
|
|
EXPECT_TRUE(SSL_session_reused(client.get()));
|
|
EXPECT_TRUE(SSL_session_reused(server.get()));
|
|
|
|
// By this point, the session has been resumed twice on the client side and
|
|
// once for each server context.
|
|
EXPECT_EQ(SSL_CTX_sess_hits(client_ctx_.get()), 2);
|
|
EXPECT_EQ(SSL_CTX_sess_hits(server_ctx_.get()), 1);
|
|
EXPECT_EQ(SSL_CTX_sess_hits(server_ctx2.get()), 1);
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, DifferentKeyNoResume) {
|
|
uint8_t key1[48], key2[48];
|
|
RAND_bytes(key1, sizeof(key1));
|
|
RAND_bytes(key2, sizeof(key2));
|
|
|
|
bssl::UniquePtr<SSL_CTX> server_ctx2 = CreateContext();
|
|
ASSERT_TRUE(server_ctx2);
|
|
ASSERT_TRUE(UseCertAndKey(server_ctx2.get()));
|
|
ASSERT_TRUE(
|
|
SSL_CTX_set_tlsext_ticket_keys(server_ctx_.get(), key1, sizeof(key1)));
|
|
ASSERT_TRUE(
|
|
SSL_CTX_set_tlsext_ticket_keys(server_ctx2.get(), key2, sizeof(key2)));
|
|
|
|
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_session_cache_mode(server_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_session_cache_mode(server_ctx2.get(), SSL_SESS_CACHE_BOTH);
|
|
|
|
// Establish a session for |server_ctx_|.
|
|
bssl::UniquePtr<SSL_SESSION> session =
|
|
CreateClientSession(client_ctx_.get(), server_ctx_.get());
|
|
ASSERT_TRUE(session);
|
|
ClientConfig config;
|
|
config.session = session.get();
|
|
|
|
// Resuming with |server_ctx_| again works.
|
|
bssl::UniquePtr<SSL> client, server;
|
|
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx_.get(),
|
|
server_ctx_.get(), config));
|
|
EXPECT_TRUE(SSL_session_reused(client.get()));
|
|
EXPECT_TRUE(SSL_session_reused(server.get()));
|
|
|
|
// Resuming with |server_ctx2| does not work.
|
|
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx_.get(),
|
|
server_ctx2.get(), config));
|
|
EXPECT_FALSE(SSL_session_reused(client.get()));
|
|
EXPECT_FALSE(SSL_session_reused(server.get()));
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, UnrelatedServerNoResume) {
|
|
bssl::UniquePtr<SSL_CTX> server_ctx2 = CreateContext();
|
|
ASSERT_TRUE(server_ctx2);
|
|
ASSERT_TRUE(UseCertAndKey(server_ctx2.get()));
|
|
|
|
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_session_cache_mode(server_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_session_cache_mode(server_ctx2.get(), SSL_SESS_CACHE_BOTH);
|
|
|
|
// Establish a session for |server_ctx_|.
|
|
bssl::UniquePtr<SSL_SESSION> session =
|
|
CreateClientSession(client_ctx_.get(), server_ctx_.get());
|
|
ASSERT_TRUE(session);
|
|
ClientConfig config;
|
|
config.session = session.get();
|
|
|
|
// Resuming with |server_ctx_| again works.
|
|
bssl::UniquePtr<SSL> client, server;
|
|
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx_.get(),
|
|
server_ctx_.get(), config));
|
|
EXPECT_TRUE(SSL_session_reused(client.get()));
|
|
EXPECT_TRUE(SSL_session_reused(server.get()));
|
|
|
|
// Resuming with |server_ctx2| does not work.
|
|
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx_.get(),
|
|
server_ctx2.get(), config));
|
|
EXPECT_FALSE(SSL_session_reused(client.get()));
|
|
EXPECT_FALSE(SSL_session_reused(server.get()));
|
|
}
|
|
|
|
static Span<const uint8_t> SessionIDOf(const SSL *ssl) {
|
|
const SSL_SESSION *session = SSL_get_session(ssl);
|
|
unsigned len = 0;
|
|
const uint8_t *data = SSL_SESSION_get_id(session, &len);
|
|
return MakeConstSpan(data, len);
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, TicketSessionIDsMatch) {
|
|
// This checks that the session IDs at client and server match after a ticket
|
|
// resumption. It's unclear whether this should be true, but Envoy depends
|
|
// on it in their tests so this will give an early signal if we break it.
|
|
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_session_cache_mode(server_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
|
|
bssl::UniquePtr<SSL_SESSION> session =
|
|
CreateClientSession(client_ctx_.get(), server_ctx_.get());
|
|
|
|
bssl::UniquePtr<SSL> client, server;
|
|
ClientConfig config;
|
|
config.session = session.get();
|
|
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx_.get(),
|
|
server_ctx_.get(), config));
|
|
EXPECT_TRUE(SSL_session_reused(client.get()));
|
|
EXPECT_TRUE(SSL_session_reused(server.get()));
|
|
|
|
EXPECT_EQ(Bytes(SessionIDOf(client.get())), Bytes(SessionIDOf(server.get())));
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, PeerTmpKey) {
|
|
if (getVersionParam().transfer_ssl) {
|
|
// The peer's temporary key is not within the boundary of the SSL transfer
|
|
// feature.
|
|
GTEST_SKIP();
|
|
}
|
|
|
|
ASSERT_TRUE(Connect());
|
|
for (SSL *ssl : {client_.get(), server_.get()}) {
|
|
SCOPED_TRACE(SSL_is_server(ssl) ? "server" : "client");
|
|
EVP_PKEY *key = nullptr;
|
|
if (getVersionParam().version == TLS1_3_VERSION) {
|
|
// TLS 1.3 default should be using X25519MLKEM768 as the key exchange.
|
|
// We expect SSL_R_UNKNOWN_KEY_EXCHANGE_TYPE because there is no EVP_PKEY type
|
|
// for hybrid keys, only individual X25519 or MLKEM768 keys.
|
|
ERR_clear_error();
|
|
EXPECT_FALSE(SSL_get_peer_tmp_key(ssl, &key));
|
|
ErrorEquals(ERR_get_error(), ERR_LIB_SSL, SSL_R_UNKNOWN_KEY_EXCHANGE_TYPE);
|
|
} else {
|
|
EXPECT_TRUE(SSL_get_peer_tmp_key(ssl, &key));
|
|
EXPECT_EQ(EVP_PKEY_id(key), EVP_PKEY_X25519);
|
|
bssl::UniquePtr<EVP_PKEY> pkey(key);
|
|
}
|
|
}
|
|
|
|
// Check that x25519 works.
|
|
ASSERT_TRUE(SSL_CTX_set1_groups_list(server_ctx_.get(), "x25519"));
|
|
ASSERT_TRUE(Connect());
|
|
for (SSL *ssl : {client_.get(), server_.get()}) {
|
|
SCOPED_TRACE(SSL_is_server(ssl) ? "server" : "client");
|
|
EVP_PKEY *key = nullptr;
|
|
EXPECT_TRUE(SSL_get_peer_tmp_key(ssl, &key));
|
|
EXPECT_EQ(EVP_PKEY_id(key), EVP_PKEY_X25519);
|
|
bssl::UniquePtr<EVP_PKEY> pkey(key);
|
|
}
|
|
|
|
// Check that EC Groups for the key exchange also work.
|
|
ASSERT_TRUE(SSL_CTX_set1_groups_list(server_ctx_.get(), "P-384"));
|
|
ASSERT_TRUE(Connect());
|
|
for (SSL *ssl : {client_.get(), server_.get()}) {
|
|
SCOPED_TRACE(SSL_is_server(ssl) ? "server" : "client");
|
|
EVP_PKEY *key = nullptr;
|
|
EXPECT_TRUE(SSL_get_peer_tmp_key(ssl, &key));
|
|
EXPECT_EQ(EVP_PKEY_id(key), EVP_PKEY_EC);
|
|
EC_KEY *ec_key = EVP_PKEY_get0_EC_KEY(key);
|
|
EXPECT_TRUE(ec_key);
|
|
EXPECT_EQ(EC_KEY_get0_group(ec_key), EC_group_p384());
|
|
bssl::UniquePtr<EVP_PKEY> pkey(key);
|
|
}
|
|
}
|
|
|
|
TEST_P(SSLVersionTest, GetFinished) {
|
|
// Test that contents of |finished| and the peer's |finished| align.
|
|
ASSERT_TRUE(Connect());
|
|
for (SSL *ssl : {client_.get(), server_.get()}) {
|
|
SCOPED_TRACE(SSL_is_server(ssl) ? "server" : "client");
|
|
size_t finished_size = SSL_get_finished(ssl, nullptr, 0);
|
|
EXPECT_TRUE(finished_size);
|
|
bssl::UniquePtr<uint8_t> finished((uint8_t *)OPENSSL_malloc(finished_size));
|
|
ASSERT_TRUE(finished);
|
|
EXPECT_TRUE(SSL_get_finished(ssl, finished.get(), finished_size));
|
|
|
|
size_t peer_finished_size = SSL_get_peer_finished(ssl, nullptr, 0);
|
|
EXPECT_TRUE(peer_finished_size);
|
|
bssl::UniquePtr<uint8_t> peer_finished(
|
|
(uint8_t *)OPENSSL_malloc(peer_finished_size));
|
|
ASSERT_TRUE(peer_finished);
|
|
EXPECT_TRUE(SSL_get_finished(ssl, peer_finished.get(), peer_finished_size));
|
|
|
|
EXPECT_EQ(Bytes(finished.get(), finished_size),
|
|
Bytes(peer_finished.get(), peer_finished_size));
|
|
}
|
|
}
|
|
|
|
// Test the specific scenario that was failing: buffer
|
|
// serialization/deserialization with sizes larger than uint16_t max (65535)
|
|
TEST(SSLBufferSizeFailureTest, SerDeLargeBuffer) {
|
|
// Create a buffer with capacity larger than the old uint16_t limit of 65535
|
|
SSLBuffer buffer;
|
|
const size_t large_capacity = 70000;
|
|
|
|
// Test that we can allocate a buffer larger than 65535 bytes
|
|
EXPECT_TRUE(buffer.EnsureCap(0, large_capacity));
|
|
|
|
// Fill the buffer with test data
|
|
std::vector<uint8_t> test_data(large_capacity);
|
|
for (size_t i = 0; i < large_capacity; i++) {
|
|
test_data[i] = static_cast<uint8_t>(i % 256);
|
|
}
|
|
|
|
// Write data to the buffer using the correct API
|
|
OPENSSL_memcpy(buffer.data(), test_data.data(), test_data.size());
|
|
buffer.DidWrite(test_data.size());
|
|
EXPECT_EQ(buffer.size(), large_capacity);
|
|
|
|
// Test serialization (this would have failed before the fix due to uint16_t
|
|
// overflow)
|
|
bssl::ScopedCBB cbb;
|
|
EXPECT_TRUE(CBB_init(cbb.get(), 0));
|
|
EXPECT_TRUE(buffer.DoSerialization(*cbb.get()));
|
|
|
|
uint8_t *serialized_data = nullptr;
|
|
size_t serialized_len = 0;
|
|
EXPECT_TRUE(CBB_finish(cbb.get(), &serialized_data, &serialized_len));
|
|
bssl::UniquePtr<uint8_t> serialized_ptr(serialized_data);
|
|
EXPECT_GT(serialized_len, 0u);
|
|
|
|
// Test deserialization (this would have failed before the fix)
|
|
SSLBuffer deserialized_buffer;
|
|
CBS cbs;
|
|
CBS_init(&cbs, serialized_data, serialized_len);
|
|
EXPECT_TRUE(deserialized_buffer.DoDeserialization(cbs));
|
|
|
|
// Verify the deserialized buffer has the correct size and capacity
|
|
EXPECT_EQ(deserialized_buffer.size(), large_capacity);
|
|
EXPECT_GE(deserialized_buffer.cap(), large_capacity);
|
|
|
|
// Verify the data integrity
|
|
EXPECT_EQ(0, OPENSSL_memcmp(deserialized_buffer.data(), test_data.data(),
|
|
large_capacity));
|
|
}
|
|
|
|
// Test that specifically targets the internal buffer allocation logic
|
|
TEST(SSLVersionTest, InternalBufferAllocationLimits) {
|
|
// This test directly exercises the SSLBuffer class to ensure it properly
|
|
// handles large buffer size requests
|
|
SSLBuffer buffer;
|
|
|
|
// Test various sizes around the boundary
|
|
EXPECT_TRUE(buffer.EnsureCap(5, 65535)); // Old limit
|
|
buffer.Clear();
|
|
|
|
EXPECT_TRUE(buffer.EnsureCap(5, 65536)); // Just above old limit
|
|
buffer.Clear();
|
|
|
|
// These should fail - beyond the maximum capacity
|
|
EXPECT_FALSE(buffer.EnsureCap(5, UINT32_MAX));
|
|
EXPECT_FALSE(buffer.EnsureCap(5, SIZE_MAX));
|
|
}
|
|
|
|
BSSL_NAMESPACE_END
|