summaryrefslogtreecommitdiff
path: root/src/ssl/ssl_test.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/ssl/ssl_test.cc')
-rw-r--r--src/ssl/ssl_test.cc514
1 files changed, 335 insertions, 179 deletions
diff --git a/src/ssl/ssl_test.cc b/src/ssl/ssl_test.cc
index d01b6495..6f180c74 100644
--- a/src/ssl/ssl_test.cc
+++ b/src/ssl/ssl_test.cc
@@ -737,103 +737,77 @@ static bool DecodeBase64(std::vector<uint8_t> *out, const char *in) {
return true;
}
-static bool TestSSL_SESSIONEncoding(const char *input_b64) {
- const uint8_t *cptr;
- uint8_t *ptr;
-
- // Decode the input.
- std::vector<uint8_t> input;
- if (!DecodeBase64(&input, input_b64)) {
- return false;
- }
-
- // Verify the SSL_SESSION decodes.
- bssl::UniquePtr<SSL_CTX> ssl_ctx(SSL_CTX_new(TLS_method()));
- if (!ssl_ctx) {
- return false;
- }
- bssl::UniquePtr<SSL_SESSION> session(
- SSL_SESSION_from_bytes(input.data(), input.size(), ssl_ctx.get()));
- if (!session) {
- fprintf(stderr, "SSL_SESSION_from_bytes failed\n");
- return false;
- }
-
- // Verify the SSL_SESSION encoding round-trips.
- size_t encoded_len;
- bssl::UniquePtr<uint8_t> encoded;
- uint8_t *encoded_raw;
- if (!SSL_SESSION_to_bytes(session.get(), &encoded_raw, &encoded_len)) {
- fprintf(stderr, "SSL_SESSION_to_bytes failed\n");
- return false;
- }
- encoded.reset(encoded_raw);
- if (encoded_len != input.size() ||
- OPENSSL_memcmp(input.data(), encoded.get(), input.size()) != 0) {
- fprintf(stderr, "SSL_SESSION_to_bytes did not round-trip\n");
- hexdump(stderr, "Before: ", input.data(), input.size());
- hexdump(stderr, "After: ", encoded_raw, encoded_len);
- return false;
- }
-
- // Verify the SSL_SESSION also decodes with the legacy API.
- cptr = input.data();
- session.reset(d2i_SSL_SESSION(NULL, &cptr, input.size()));
- if (!session || cptr != input.data() + input.size()) {
- fprintf(stderr, "d2i_SSL_SESSION failed\n");
- return false;
- }
-
- // Verify the SSL_SESSION encoding round-trips via the legacy API.
- int len = i2d_SSL_SESSION(session.get(), NULL);
- if (len < 0 || (size_t)len != input.size()) {
- fprintf(stderr, "i2d_SSL_SESSION(NULL) returned invalid length\n");
- return false;
- }
-
- encoded.reset((uint8_t *)OPENSSL_malloc(input.size()));
- if (!encoded) {
- fprintf(stderr, "malloc failed\n");
- return false;
- }
-
- ptr = encoded.get();
- len = i2d_SSL_SESSION(session.get(), &ptr);
- if (len < 0 || (size_t)len != input.size()) {
- fprintf(stderr, "i2d_SSL_SESSION returned invalid length\n");
- return false;
- }
- if (ptr != encoded.get() + input.size()) {
- fprintf(stderr, "i2d_SSL_SESSION did not advance ptr correctly\n");
- return false;
- }
- if (OPENSSL_memcmp(input.data(), encoded.get(), input.size()) != 0) {
- fprintf(stderr, "i2d_SSL_SESSION did not round-trip\n");
- return false;
- }
-
- return true;
-}
-
-static bool TestBadSSL_SESSIONEncoding(const char *input_b64) {
- std::vector<uint8_t> input;
- if (!DecodeBase64(&input, input_b64)) {
- return false;
- }
-
- // Verify that the SSL_SESSION fails to decode.
- bssl::UniquePtr<SSL_CTX> ssl_ctx(SSL_CTX_new(TLS_method()));
- if (!ssl_ctx) {
- return false;
- }
- bssl::UniquePtr<SSL_SESSION> session(
- SSL_SESSION_from_bytes(input.data(), input.size(), ssl_ctx.get()));
- if (session) {
- fprintf(stderr, "SSL_SESSION_from_bytes unexpectedly succeeded\n");
- return false;
+TEST(SSLTest, SessionEncoding) {
+ for (const char *input_b64 : {
+ kOpenSSLSession,
+ kCustomSession,
+ kBoringSSLSession,
+ }) {
+ SCOPED_TRACE(std::string(input_b64));
+ // Decode the input.
+ std::vector<uint8_t> input;
+ ASSERT_TRUE(DecodeBase64(&input, input_b64));
+
+ // Verify the SSL_SESSION decodes.
+ bssl::UniquePtr<SSL_CTX> ssl_ctx(SSL_CTX_new(TLS_method()));
+ ASSERT_TRUE(ssl_ctx);
+ bssl::UniquePtr<SSL_SESSION> session(
+ SSL_SESSION_from_bytes(input.data(), input.size(), ssl_ctx.get()));
+ ASSERT_TRUE(session) << "SSL_SESSION_from_bytes failed";
+
+ // Verify the SSL_SESSION encoding round-trips.
+ size_t encoded_len;
+ bssl::UniquePtr<uint8_t> encoded;
+ uint8_t *encoded_raw;
+ ASSERT_TRUE(SSL_SESSION_to_bytes(session.get(), &encoded_raw, &encoded_len))
+ << "SSL_SESSION_to_bytes failed";
+ encoded.reset(encoded_raw);
+ EXPECT_EQ(Bytes(encoded.get(), encoded_len), Bytes(input))
+ << "SSL_SESSION_to_bytes did not round-trip";
+
+ // Verify the SSL_SESSION also decodes with the legacy API.
+ const uint8_t *cptr = input.data();
+ session.reset(d2i_SSL_SESSION(NULL, &cptr, input.size()));
+ ASSERT_TRUE(session) << "d2i_SSL_SESSION failed";
+ EXPECT_EQ(cptr, input.data() + input.size());
+
+ // Verify the SSL_SESSION encoding round-trips via the legacy API.
+ int len = i2d_SSL_SESSION(session.get(), NULL);
+ ASSERT_GT(len, 0) << "i2d_SSL_SESSION failed";
+ ASSERT_EQ(static_cast<size_t>(len), input.size())
+ << "i2d_SSL_SESSION(NULL) returned invalid length";
+
+ encoded.reset((uint8_t *)OPENSSL_malloc(input.size()));
+ ASSERT_TRUE(encoded);
+
+ uint8_t *ptr = encoded.get();
+ len = i2d_SSL_SESSION(session.get(), &ptr);
+ ASSERT_GT(len, 0) << "i2d_SSL_SESSION failed";
+ ASSERT_EQ(static_cast<size_t>(len), input.size())
+ << "i2d_SSL_SESSION(NULL) returned invalid length";
+ ASSERT_EQ(ptr, encoded.get() + input.size())
+ << "i2d_SSL_SESSION did not advance ptr correctly";
+ EXPECT_EQ(Bytes(encoded.get(), encoded_len), Bytes(input))
+ << "SSL_SESSION_to_bytes did not round-trip";
+ }
+
+ for (const char *input_b64 : {
+ kBadSessionExtraField,
+ kBadSessionVersion,
+ kBadSessionTrailingData,
+ }) {
+ SCOPED_TRACE(std::string(input_b64));
+ std::vector<uint8_t> input;
+ ASSERT_TRUE(DecodeBase64(&input, input_b64));
+
+ // Verify that the SSL_SESSION fails to decode.
+ bssl::UniquePtr<SSL_CTX> ssl_ctx(SSL_CTX_new(TLS_method()));
+ ASSERT_TRUE(ssl_ctx);
+ bssl::UniquePtr<SSL_SESSION> session(
+ SSL_SESSION_from_bytes(input.data(), input.size(), ssl_ctx.get()));
+ EXPECT_FALSE(session) << "SSL_SESSION_from_bytes unexpectedly succeeded";
+ ERR_clear_error();
}
- ERR_clear_error();
- return true;
}
static void ExpectDefaultVersion(uint16_t min_version, uint16_t max_version,
@@ -1087,63 +1061,67 @@ static size_t GetClientHelloLen(uint16_t max_version, uint16_t session_version,
return client_hello.size() - SSL3_RT_HEADER_LENGTH;
}
-struct PaddingTest {
- size_t input_len, padded_len;
-};
+TEST(SSLTest, Padding) {
+ struct PaddingVersions {
+ uint16_t max_version, session_version;
+ };
+ static const PaddingVersions kPaddingVersions[] = {
+ // Test the padding extension at TLS 1.2.
+ {TLS1_2_VERSION, TLS1_2_VERSION},
+ // Test the padding extension at TLS 1.3 with a TLS 1.2 session, so there
+ // will be no PSK binder after the padding extension.
+ {TLS1_3_VERSION, TLS1_2_VERSION},
+ // Test the padding extension at TLS 1.3 with a TLS 1.3 session, so there
+ // will be a PSK binder after the padding extension.
+ {TLS1_3_VERSION, TLS1_3_VERSION},
-static const PaddingTest kPaddingTests[] = {
- // ClientHellos of length below 0x100 do not require padding.
- {0xfe, 0xfe},
- {0xff, 0xff},
- // ClientHellos of length 0x100 through 0x1fb are padded up to 0x200.
- {0x100, 0x200},
- {0x123, 0x200},
- {0x1fb, 0x200},
- // ClientHellos of length 0x1fc through 0x1ff get padded beyond 0x200. The
- // padding extension takes a minimum of four bytes plus one required content
- // byte. (To work around yet more server bugs, we avoid empty final
- // extensions.)
- {0x1fc, 0x201},
- {0x1fd, 0x202},
- {0x1fe, 0x203},
- {0x1ff, 0x204},
- // Finally, larger ClientHellos need no padding.
- {0x200, 0x200},
- {0x201, 0x201},
-};
+ };
-static bool TestPaddingExtension(uint16_t max_version,
- uint16_t session_version) {
- // Sample a baseline length.
- size_t base_len = GetClientHelloLen(max_version, session_version, 1);
- if (base_len == 0) {
- return false;
- }
+ struct PaddingTest {
+ size_t input_len, padded_len;
+ };
+ static const PaddingTest kPaddingTests[] = {
+ // ClientHellos of length below 0x100 do not require padding.
+ {0xfe, 0xfe},
+ {0xff, 0xff},
+ // ClientHellos of length 0x100 through 0x1fb are padded up to 0x200.
+ {0x100, 0x200},
+ {0x123, 0x200},
+ {0x1fb, 0x200},
+ // ClientHellos of length 0x1fc through 0x1ff get padded beyond 0x200. The
+ // padding extension takes a minimum of four bytes plus one required
+ // content
+ // byte. (To work around yet more server bugs, we avoid empty final
+ // extensions.)
+ {0x1fc, 0x201},
+ {0x1fd, 0x202},
+ {0x1fe, 0x203},
+ {0x1ff, 0x204},
+ // Finally, larger ClientHellos need no padding.
+ {0x200, 0x200},
+ {0x201, 0x201},
+ };
- for (const PaddingTest &test : kPaddingTests) {
- if (base_len > test.input_len) {
- fprintf(stderr,
- "Baseline ClientHello too long (max_version = %04x, "
- "session_version = %04x).\n",
- max_version, session_version);
- return false;
- }
+ for (const PaddingVersions &versions : kPaddingVersions) {
+ SCOPED_TRACE(versions.max_version);
+ SCOPED_TRACE(versions.session_version);
- size_t padded_len = GetClientHelloLen(max_version, session_version,
- 1 + test.input_len - base_len);
- if (padded_len != test.padded_len) {
- fprintf(stderr,
- "%u-byte ClientHello padded to %u bytes, not %u (max_version = "
- "%04x, session_version = %04x).\n",
- static_cast<unsigned>(test.input_len),
- static_cast<unsigned>(padded_len),
- static_cast<unsigned>(test.padded_len), max_version,
- session_version);
- return false;
+ // Sample a baseline length.
+ size_t base_len =
+ GetClientHelloLen(versions.max_version, versions.session_version, 1);
+ ASSERT_NE(base_len, 0u) << "Baseline length could not be sampled";
+
+ for (const PaddingTest &test : kPaddingTests) {
+ SCOPED_TRACE(test.input_len);
+ ASSERT_LE(base_len, test.input_len) << "Baseline ClientHello too long";
+
+ size_t padded_len =
+ GetClientHelloLen(versions.max_version, versions.session_version,
+ 1 + test.input_len - base_len);
+ EXPECT_EQ(padded_len, test.padded_len)
+ << "ClientHello was not padded to expected length";
}
}
-
- return true;
}
static bssl::UniquePtr<X509> GetTestCertificate() {
@@ -1550,6 +1528,37 @@ static bool CompleteHandshakes(SSL *client, SSL *server) {
return true;
}
+static bool FlushNewSessionTickets(SSL *client, SSL *server) {
+ // NewSessionTickets are deferred on the server to |SSL_write|, and clients do
+ // not pick them up until |SSL_read|.
+ for (;;) {
+ int server_ret = SSL_write(server, nullptr, 0);
+ int server_err = SSL_get_error(server, server_ret);
+ // The server may either succeed (|server_ret| is zero) or block on write
+ // (|server_ret| is -1 and |server_err| is |SSL_ERROR_WANT_WRITE|).
+ if (server_ret > 0 ||
+ (server_ret < 0 && server_err != SSL_ERROR_WANT_WRITE)) {
+ fprintf(stderr, "Unexpected server result: %d %d\n", server_ret,
+ server_err);
+ return false;
+ }
+
+ int client_ret = SSL_read(client, nullptr, 0);
+ int client_err = SSL_get_error(client, client_ret);
+ // The client must always block on read.
+ if (client_ret != -1 || client_err != SSL_ERROR_WANT_READ) {
+ fprintf(stderr, "Unexpected client result: %d %d\n", client_ret,
+ client_err);
+ return false;
+ }
+
+ // The server flushed everything it had to write.
+ if (server_ret == 0) {
+ return true;
+ }
+ }
+}
+
struct ClientConfig {
SSL_SESSION *session = nullptr;
std::string servername;
@@ -1661,9 +1670,7 @@ TEST_P(SSLVersionTest, SequenceNumber) {
// Drain any post-handshake messages to ensure there are no unread records
// on either end.
- uint8_t byte = 0;
- ASSERT_LE(SSL_read(client_.get(), &byte, 1), 0);
- ASSERT_LE(SSL_read(server_.get(), &byte, 1), 0);
+ 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());
@@ -1687,6 +1694,7 @@ TEST_P(SSLVersionTest, SequenceNumber) {
}
// 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);
@@ -2084,14 +2092,12 @@ static bssl::UniquePtr<SSL_SESSION> CreateClientSession(
// Connect client and server to get a session.
bssl::UniquePtr<SSL> client, server;
if (!ConnectClientAndServer(&client, &server, client_ctx, server_ctx,
- config)) {
+ config) ||
+ !FlushNewSessionTickets(client.get(), server.get())) {
fprintf(stderr, "Failed to connect client and server.\n");
return nullptr;
}
- // Run the read loop to account for post-handshake tickets in TLS 1.3.
- SSL_read(client.get(), nullptr, 0);
-
SSL_CTX_sess_set_new_cb(client_ctx, nullptr);
if (!g_last_session) {
@@ -2125,7 +2131,8 @@ static bssl::UniquePtr<SSL_SESSION> ExpectSessionRenewed(SSL_CTX *client_ctx,
ClientConfig config;
config.session = session;
if (!ConnectClientAndServer(&client, &server, client_ctx, server_ctx,
- config)) {
+ config) ||
+ !FlushNewSessionTickets(client.get(), server.get())) {
fprintf(stderr, "Failed to connect client and server.\n");
return nullptr;
}
@@ -2140,9 +2147,6 @@ static bssl::UniquePtr<SSL_SESSION> ExpectSessionRenewed(SSL_CTX *client_ctx,
return nullptr;
}
- // Run the read loop to account for post-handshake tickets in TLS 1.3.
- SSL_read(client.get(), nullptr, 0);
-
SSL_CTX_sess_set_new_cb(client_ctx, nullptr);
if (!g_last_session) {
@@ -3055,6 +3059,82 @@ TEST_P(SSLVersionTest, ClientSessionCacheMode) {
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, *bio2;
+ 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(SSLTest, AddChainCertHack) {
// Ensure that we don't accidently break the hack that we have in place to
// keep curl and serf happy when they use an |X509| even after transfering
@@ -3514,9 +3594,7 @@ TEST_P(TicketAEADMethodTest, Resume) {
EXPECT_FALSE(SSL_session_reused(client.get()));
EXPECT_FALSE(SSL_session_reused(server.get()));
- // Run the read loop to account for post-handshake tickets in TLS 1.3.
- SSL_read(client.get(), nullptr, 0);
-
+ ASSERT_TRUE(FlushNewSessionTickets(client.get(), server.get()));
bssl::UniquePtr<SSL_SESSION> session = std::move(g_last_session);
ConnectClientAndServerWithTicketMethod(&client, &server, client_ctx.get(),
server_ctx.get(), retry_count,
@@ -4606,7 +4684,7 @@ TEST_P(SSLVersionTest, SessionPropertiesThreads) {
thread.join();
}
}
-#endif
+#endif // OPENSSL_THREADS
constexpr size_t kNumQUICLevels = 4;
static_assert(ssl_encryption_initial < kNumQUICLevels,
@@ -5263,23 +5341,101 @@ TEST_F(QUICMethodTest, BadPostHandshake) {
EXPECT_EQ(SSL_process_quic_post_handshake(client_.get()), 0);
}
-// TODO(davidben): Convert this file to GTest properly.
-TEST(SSLTest, AllTests) {
- if (!TestSSL_SESSIONEncoding(kOpenSSLSession) ||
- !TestSSL_SESSIONEncoding(kCustomSession) ||
- !TestSSL_SESSIONEncoding(kBoringSSLSession) ||
- !TestBadSSL_SESSIONEncoding(kBadSessionExtraField) ||
- !TestBadSSL_SESSIONEncoding(kBadSessionVersion) ||
- !TestBadSSL_SESSIONEncoding(kBadSessionTrailingData) ||
- // Test the padding extension at TLS 1.2.
- !TestPaddingExtension(TLS1_2_VERSION, TLS1_2_VERSION) ||
- // Test the padding extension at TLS 1.3 with a TLS 1.2 session, so there
- // will be no PSK binder after the padding extension.
- !TestPaddingExtension(TLS1_3_VERSION, TLS1_2_VERSION) ||
- // Test the padding extension at TLS 1.3 with a TLS 1.3 session, so there
- // will be a PSK binder after the padding extension.
- !TestPaddingExtension(TLS1_3_VERSION, TLS1_3_VERSION)) {
- ADD_FAILURE() << "Tests failed";
+extern "C" {
+int BORINGSSL_enum_c_type_test(void);
+}
+
+TEST(SSLTest, EnumTypes) {
+ EXPECT_EQ(sizeof(int), sizeof(ssl_private_key_result_t));
+ EXPECT_EQ(1, BORINGSSL_enum_c_type_test());
+}
+
+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);
+ uint32_t err = ERR_peek_error();
+ EXPECT_EQ(ERR_LIB_SSL, ERR_GET_LIB(err));
+ EXPECT_EQ(SSL_R_CERTIFICATE_VERIFY_FAILED, ERR_GET_REASON(err));
+ 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);
}
}