1405 lines
54 KiB
C++
1405 lines
54 KiB
C++
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
// SPDX-License-Identifier: Apache-2.0 OR ISC
|
|
|
|
#include <openssl/ssl.h>
|
|
#include "../crypto/test/test_util.h"
|
|
#include "ssl_common_test.h"
|
|
|
|
BSSL_NAMESPACE_BEGIN
|
|
|
|
template <typename T>
|
|
class UnownedSSLExData {
|
|
public:
|
|
UnownedSSLExData() {
|
|
index_ = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
|
|
}
|
|
|
|
T *Get(const SSL *ssl) {
|
|
return index_ < 0 ? nullptr
|
|
: static_cast<T *>(SSL_get_ex_data(ssl, index_));
|
|
}
|
|
|
|
bool Set(SSL *ssl, T *t) {
|
|
return index_ >= 0 && SSL_set_ex_data(ssl, index_, t);
|
|
}
|
|
|
|
private:
|
|
int index_;
|
|
};
|
|
|
|
constexpr size_t kNumQUICLevels = 4;
|
|
static_assert(ssl_encryption_initial < kNumQUICLevels,
|
|
"kNumQUICLevels is wrong");
|
|
static_assert(ssl_encryption_early_data < kNumQUICLevels,
|
|
"kNumQUICLevels is wrong");
|
|
static_assert(ssl_encryption_handshake < kNumQUICLevels,
|
|
"kNumQUICLevels is wrong");
|
|
static_assert(ssl_encryption_application < kNumQUICLevels,
|
|
"kNumQUICLevels is wrong");
|
|
|
|
static const char *LevelToString(ssl_encryption_level_t level) {
|
|
switch (level) {
|
|
case ssl_encryption_initial:
|
|
return "initial";
|
|
case ssl_encryption_early_data:
|
|
return "early data";
|
|
case ssl_encryption_handshake:
|
|
return "handshake";
|
|
case ssl_encryption_application:
|
|
return "application";
|
|
}
|
|
return "<unknown>";
|
|
}
|
|
|
|
class MockQUICTransport {
|
|
public:
|
|
enum class Role { kClient, kServer };
|
|
|
|
explicit MockQUICTransport(Role role) : role_(role) {
|
|
// The caller is expected to configure initial secrets.
|
|
levels_[ssl_encryption_initial].write_secret = {1};
|
|
levels_[ssl_encryption_initial].read_secret = {1};
|
|
}
|
|
|
|
void set_peer(MockQUICTransport *peer) { peer_ = peer; }
|
|
|
|
bool has_alert() const { return has_alert_; }
|
|
ssl_encryption_level_t alert_level() const { return alert_level_; }
|
|
uint8_t alert() const { return alert_; }
|
|
|
|
bool PeerSecretsMatch(ssl_encryption_level_t level) const {
|
|
return levels_[level].write_secret == peer_->levels_[level].read_secret &&
|
|
levels_[level].read_secret == peer_->levels_[level].write_secret &&
|
|
levels_[level].cipher == peer_->levels_[level].cipher;
|
|
}
|
|
|
|
bool HasReadSecret(ssl_encryption_level_t level) const {
|
|
return !levels_[level].read_secret.empty();
|
|
}
|
|
|
|
bool HasWriteSecret(ssl_encryption_level_t level) const {
|
|
return !levels_[level].write_secret.empty();
|
|
}
|
|
|
|
void AllowOutOfOrderWrites() { allow_out_of_order_writes_ = true; }
|
|
|
|
bool SetReadSecret(ssl_encryption_level_t level, const SSL_CIPHER *cipher,
|
|
Span<const uint8_t> secret) {
|
|
if (HasReadSecret(level)) {
|
|
ADD_FAILURE() << LevelToString(level) << " read secret configured twice";
|
|
return false;
|
|
}
|
|
|
|
if (role_ == Role::kClient && level == ssl_encryption_early_data) {
|
|
ADD_FAILURE() << "Unexpected early data read secret";
|
|
return false;
|
|
}
|
|
|
|
ssl_encryption_level_t ack_level =
|
|
level == ssl_encryption_early_data ? ssl_encryption_application : level;
|
|
if (!HasWriteSecret(ack_level)) {
|
|
ADD_FAILURE() << LevelToString(level)
|
|
<< " read secret configured before ACK write secret";
|
|
return false;
|
|
}
|
|
|
|
if (cipher == nullptr) {
|
|
ADD_FAILURE() << "Unexpected null cipher";
|
|
return false;
|
|
}
|
|
|
|
if (level != ssl_encryption_early_data &&
|
|
SSL_CIPHER_get_id(cipher) != levels_[level].cipher) {
|
|
ADD_FAILURE() << "Cipher suite inconsistent";
|
|
return false;
|
|
}
|
|
|
|
levels_[level].read_secret.assign(secret.begin(), secret.end());
|
|
levels_[level].cipher = SSL_CIPHER_get_id(cipher);
|
|
return true;
|
|
}
|
|
|
|
bool SetWriteSecret(ssl_encryption_level_t level, const SSL_CIPHER *cipher,
|
|
Span<const uint8_t> secret) {
|
|
if (HasWriteSecret(level)) {
|
|
ADD_FAILURE() << LevelToString(level) << " write secret configured twice";
|
|
return false;
|
|
}
|
|
|
|
if (role_ == Role::kServer && level == ssl_encryption_early_data) {
|
|
ADD_FAILURE() << "Unexpected early data write secret";
|
|
return false;
|
|
}
|
|
|
|
if (cipher == nullptr) {
|
|
ADD_FAILURE() << "Unexpected null cipher";
|
|
return false;
|
|
}
|
|
|
|
levels_[level].write_secret.assign(secret.begin(), secret.end());
|
|
levels_[level].cipher = SSL_CIPHER_get_id(cipher);
|
|
return true;
|
|
}
|
|
|
|
bool WriteHandshakeData(ssl_encryption_level_t level,
|
|
Span<const uint8_t> data) {
|
|
if (levels_[level].write_secret.empty()) {
|
|
ADD_FAILURE() << LevelToString(level)
|
|
<< " write secret not yet configured";
|
|
return false;
|
|
}
|
|
|
|
// Although the levels are conceptually separate, BoringSSL finishes writing
|
|
// data from a previous level before installing keys for the next level.
|
|
if (!allow_out_of_order_writes_) {
|
|
switch (level) {
|
|
case ssl_encryption_early_data:
|
|
ADD_FAILURE() << "unexpected handshake data at early data level";
|
|
return false;
|
|
case ssl_encryption_initial:
|
|
if (!levels_[ssl_encryption_handshake].write_secret.empty()) {
|
|
ADD_FAILURE()
|
|
<< LevelToString(level)
|
|
<< " handshake data written after handshake keys installed";
|
|
return false;
|
|
}
|
|
OPENSSL_FALLTHROUGH;
|
|
case ssl_encryption_handshake:
|
|
if (!levels_[ssl_encryption_application].write_secret.empty()) {
|
|
ADD_FAILURE()
|
|
<< LevelToString(level)
|
|
<< " handshake data written after application keys installed";
|
|
return false;
|
|
}
|
|
OPENSSL_FALLTHROUGH;
|
|
case ssl_encryption_application:
|
|
break;
|
|
}
|
|
}
|
|
|
|
levels_[level].write_data.insert(levels_[level].write_data.end(),
|
|
data.begin(), data.end());
|
|
return true;
|
|
}
|
|
|
|
bool SendAlert(ssl_encryption_level_t level, uint8_t alert_value) {
|
|
if (has_alert_) {
|
|
ADD_FAILURE() << "duplicate alert sent";
|
|
return false;
|
|
}
|
|
|
|
if (levels_[level].write_secret.empty()) {
|
|
ADD_FAILURE() << LevelToString(level)
|
|
<< " write secret not yet configured";
|
|
return false;
|
|
}
|
|
|
|
has_alert_ = true;
|
|
alert_level_ = level;
|
|
alert_ = alert_value;
|
|
return true;
|
|
}
|
|
|
|
bool ReadHandshakeData(std::vector<uint8_t> *out,
|
|
ssl_encryption_level_t level,
|
|
size_t num = std::numeric_limits<size_t>::max()) {
|
|
if (levels_[level].read_secret.empty()) {
|
|
ADD_FAILURE() << "data read before keys configured in level " << level;
|
|
return false;
|
|
}
|
|
// The peer may not have configured any keys yet.
|
|
if (peer_->levels_[level].write_secret.empty()) {
|
|
out->clear();
|
|
return true;
|
|
}
|
|
// Check the peer computed the same key.
|
|
if (peer_->levels_[level].write_secret != levels_[level].read_secret) {
|
|
ADD_FAILURE() << "peer write key does not match read key in level "
|
|
<< level;
|
|
return false;
|
|
}
|
|
if (peer_->levels_[level].cipher != levels_[level].cipher) {
|
|
ADD_FAILURE() << "peer cipher does not match in level " << level;
|
|
return false;
|
|
}
|
|
std::vector<uint8_t> *peer_data = &peer_->levels_[level].write_data;
|
|
num = std::min(num, peer_data->size());
|
|
out->assign(peer_data->begin(), peer_data->begin() + num);
|
|
peer_data->erase(peer_data->begin(), peer_data->begin() + num);
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
Role role_;
|
|
MockQUICTransport *peer_ = nullptr;
|
|
|
|
bool allow_out_of_order_writes_ = false;
|
|
bool has_alert_ = false;
|
|
ssl_encryption_level_t alert_level_ = ssl_encryption_initial;
|
|
uint8_t alert_ = 0;
|
|
|
|
struct Level {
|
|
std::vector<uint8_t> write_data;
|
|
std::vector<uint8_t> write_secret;
|
|
std::vector<uint8_t> read_secret;
|
|
uint32_t cipher = 0;
|
|
};
|
|
Level levels_[kNumQUICLevels];
|
|
};
|
|
|
|
class MockQUICTransportPair {
|
|
public:
|
|
MockQUICTransportPair()
|
|
: client_(MockQUICTransport::Role::kClient),
|
|
server_(MockQUICTransport::Role::kServer) {
|
|
client_.set_peer(&server_);
|
|
server_.set_peer(&client_);
|
|
}
|
|
|
|
~MockQUICTransportPair() {
|
|
client_.set_peer(nullptr);
|
|
server_.set_peer(nullptr);
|
|
}
|
|
|
|
MockQUICTransport *client() { return &client_; }
|
|
MockQUICTransport *server() { return &server_; }
|
|
|
|
bool SecretsMatch(ssl_encryption_level_t level) const {
|
|
// We only need to check |HasReadSecret| and |HasWriteSecret| on |client_|.
|
|
// |PeerSecretsMatch| checks that |server_| is analogously configured.
|
|
return client_.PeerSecretsMatch(level) && client_.HasWriteSecret(level) &&
|
|
(level == ssl_encryption_early_data || client_.HasReadSecret(level));
|
|
}
|
|
|
|
private:
|
|
MockQUICTransport client_;
|
|
MockQUICTransport server_;
|
|
};
|
|
|
|
class QUICMethodTest : public testing::Test {
|
|
protected:
|
|
void SetUp() override {
|
|
client_ctx_.reset(SSL_CTX_new(TLS_method()));
|
|
server_ctx_ = CreateContextWithTestCertificate(TLS_method());
|
|
ASSERT_TRUE(client_ctx_);
|
|
ASSERT_TRUE(server_ctx_);
|
|
|
|
SSL_CTX_set_min_proto_version(server_ctx_.get(), TLS1_3_VERSION);
|
|
SSL_CTX_set_max_proto_version(server_ctx_.get(), TLS1_3_VERSION);
|
|
SSL_CTX_set_min_proto_version(client_ctx_.get(), TLS1_3_VERSION);
|
|
SSL_CTX_set_max_proto_version(client_ctx_.get(), TLS1_3_VERSION);
|
|
|
|
static const uint8_t kALPNProtos[] = {0x03, 'f', 'o', 'o'};
|
|
ASSERT_EQ(SSL_CTX_set_alpn_protos(client_ctx_.get(), kALPNProtos,
|
|
sizeof(kALPNProtos)),
|
|
0);
|
|
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 {
|
|
return SSL_select_next_proto(
|
|
const_cast<uint8_t **>(out), out_len, in, in_len,
|
|
kALPNProtos, sizeof(kALPNProtos)) == OPENSSL_NPN_NEGOTIATED
|
|
? SSL_TLSEXT_ERR_OK
|
|
: SSL_TLSEXT_ERR_NOACK;
|
|
},
|
|
nullptr);
|
|
}
|
|
|
|
static MockQUICTransport *TransportFromSSL(const SSL *ssl) {
|
|
return ex_data_.Get(ssl);
|
|
}
|
|
|
|
static bool ProvideHandshakeData(
|
|
SSL *ssl, size_t num = std::numeric_limits<size_t>::max()) {
|
|
MockQUICTransport *transport = TransportFromSSL(ssl);
|
|
ssl_encryption_level_t level = SSL_quic_read_level(ssl);
|
|
std::vector<uint8_t> data;
|
|
return transport->ReadHandshakeData(&data, level, num) &&
|
|
SSL_provide_quic_data(ssl, level, data.data(), data.size());
|
|
}
|
|
|
|
void AllowOutOfOrderWrites() { allow_out_of_order_writes_ = true; }
|
|
|
|
bool CreateClientAndServer() {
|
|
client_.reset(SSL_new(client_ctx_.get()));
|
|
server_.reset(SSL_new(server_ctx_.get()));
|
|
if (!client_ || !server_) {
|
|
return false;
|
|
}
|
|
|
|
SSL_set_connect_state(client_.get());
|
|
SSL_set_accept_state(server_.get());
|
|
|
|
transport_.reset(new MockQUICTransportPair);
|
|
if (!ex_data_.Set(client_.get(), transport_->client()) ||
|
|
!ex_data_.Set(server_.get(), transport_->server())) {
|
|
return false;
|
|
}
|
|
if (allow_out_of_order_writes_) {
|
|
transport_->client()->AllowOutOfOrderWrites();
|
|
transport_->server()->AllowOutOfOrderWrites();
|
|
}
|
|
static const uint8_t client_transport_params[] = {0};
|
|
if (!SSL_set_quic_transport_params(client_.get(), client_transport_params,
|
|
sizeof(client_transport_params)) ||
|
|
!SSL_set_quic_transport_params(server_.get(),
|
|
server_transport_params_.data(),
|
|
server_transport_params_.size()) ||
|
|
!SSL_set_quic_early_data_context(
|
|
server_.get(), server_quic_early_data_context_.data(),
|
|
server_quic_early_data_context_.size())) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
enum class ExpectedError {
|
|
kNoError,
|
|
kClientError,
|
|
kServerError,
|
|
};
|
|
|
|
// CompleteHandshakesForQUIC runs |SSL_do_handshake| on |client_| and
|
|
// |server_| until each completes once. It returns true on success and false
|
|
// on failure.
|
|
bool CompleteHandshakesForQUIC() {
|
|
return RunQUICHandshakesAndExpectError(ExpectedError::kNoError);
|
|
}
|
|
|
|
// Runs |SSL_do_handshake| on |client_| and |server_| until each completes
|
|
// once. If |expect_client_error| is true, it will return true only if the
|
|
// client handshake failed. Otherwise, it returns true if both handshakes
|
|
// succeed and false otherwise.
|
|
bool RunQUICHandshakesAndExpectError(ExpectedError expected_error) {
|
|
bool client_done = false, server_done = false;
|
|
while (!client_done || !server_done) {
|
|
if (!client_done) {
|
|
if (!ProvideHandshakeData(client_.get())) {
|
|
ADD_FAILURE() << "ProvideHandshakeData(client_) failed";
|
|
return false;
|
|
}
|
|
int client_ret = SSL_do_handshake(client_.get());
|
|
int client_err = SSL_get_error(client_.get(), client_ret);
|
|
if (client_ret == 1) {
|
|
client_done = true;
|
|
} else if (client_ret != -1 || client_err != SSL_ERROR_WANT_READ) {
|
|
if (expected_error == ExpectedError::kClientError) {
|
|
return true;
|
|
}
|
|
ADD_FAILURE() << "Unexpected client output: " << client_ret << " "
|
|
<< client_err;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!server_done) {
|
|
if (!ProvideHandshakeData(server_.get())) {
|
|
ADD_FAILURE() << "ProvideHandshakeData(server_) failed";
|
|
return false;
|
|
}
|
|
int server_ret = SSL_do_handshake(server_.get());
|
|
int server_err = SSL_get_error(server_.get(), server_ret);
|
|
if (server_ret == 1) {
|
|
server_done = true;
|
|
} else if (server_ret != -1 || server_err != SSL_ERROR_WANT_READ) {
|
|
if (expected_error == ExpectedError::kServerError) {
|
|
return true;
|
|
}
|
|
ADD_FAILURE() << "Unexpected server output: " << server_ret << " "
|
|
<< server_err;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return expected_error == ExpectedError::kNoError;
|
|
}
|
|
|
|
bssl::UniquePtr<SSL_SESSION> CreateClientSessionForQUIC() {
|
|
g_last_session = nullptr;
|
|
SSL_CTX_sess_set_new_cb(client_ctx_.get(), SaveLastSession);
|
|
if (!CreateClientAndServer() || !CompleteHandshakesForQUIC()) {
|
|
return nullptr;
|
|
}
|
|
|
|
// The server sent NewSessionTicket messages in the handshake.
|
|
if (!ProvideHandshakeData(client_.get()) ||
|
|
!SSL_process_quic_post_handshake(client_.get())) {
|
|
return nullptr;
|
|
}
|
|
|
|
return std::move(g_last_session);
|
|
}
|
|
|
|
void ExpectHandshakeSuccess() {
|
|
EXPECT_TRUE(transport_->SecretsMatch(ssl_encryption_application));
|
|
EXPECT_EQ(ssl_encryption_application, SSL_quic_read_level(client_.get()));
|
|
EXPECT_EQ(ssl_encryption_application, SSL_quic_write_level(client_.get()));
|
|
EXPECT_EQ(ssl_encryption_application, SSL_quic_read_level(server_.get()));
|
|
EXPECT_EQ(ssl_encryption_application, SSL_quic_write_level(server_.get()));
|
|
EXPECT_FALSE(transport_->client()->has_alert());
|
|
EXPECT_FALSE(transport_->server()->has_alert());
|
|
|
|
// SSL_do_handshake is now idempotent.
|
|
EXPECT_EQ(SSL_do_handshake(client_.get()), 1);
|
|
EXPECT_EQ(SSL_do_handshake(server_.get()), 1);
|
|
}
|
|
|
|
// Returns a default SSL_QUIC_METHOD. Individual methods may be overwritten by
|
|
// the test.
|
|
SSL_QUIC_METHOD DefaultQUICMethod() {
|
|
return SSL_QUIC_METHOD{
|
|
SetReadSecretCallback, SetWriteSecretCallback, AddHandshakeDataCallback,
|
|
FlushFlightCallback, SendAlertCallback,
|
|
};
|
|
}
|
|
|
|
static int SetReadSecretCallback(SSL *ssl, ssl_encryption_level_t level,
|
|
const SSL_CIPHER *cipher,
|
|
const uint8_t *secret, size_t secret_len) {
|
|
return TransportFromSSL(ssl)->SetReadSecret(
|
|
level, cipher, MakeConstSpan(secret, secret_len));
|
|
}
|
|
|
|
static int SetWriteSecretCallback(SSL *ssl, ssl_encryption_level_t level,
|
|
const SSL_CIPHER *cipher,
|
|
const uint8_t *secret, size_t secret_len) {
|
|
return TransportFromSSL(ssl)->SetWriteSecret(
|
|
level, cipher, MakeConstSpan(secret, secret_len));
|
|
}
|
|
|
|
static int AddHandshakeDataCallback(SSL *ssl,
|
|
enum ssl_encryption_level_t level,
|
|
const uint8_t *data, size_t len) {
|
|
EXPECT_EQ(level, SSL_quic_write_level(ssl));
|
|
return TransportFromSSL(ssl)->WriteHandshakeData(level,
|
|
MakeConstSpan(data, len));
|
|
}
|
|
|
|
static int FlushFlightCallback(SSL *ssl) { return 1; }
|
|
|
|
static int SendAlertCallback(SSL *ssl, ssl_encryption_level_t level,
|
|
uint8_t alert) {
|
|
EXPECT_EQ(level, SSL_quic_write_level(ssl));
|
|
return TransportFromSSL(ssl)->SendAlert(level, alert);
|
|
}
|
|
|
|
bssl::UniquePtr<SSL_CTX> client_ctx_;
|
|
bssl::UniquePtr<SSL_CTX> server_ctx_;
|
|
|
|
static UnownedSSLExData<MockQUICTransport> ex_data_;
|
|
std::unique_ptr<MockQUICTransportPair> transport_;
|
|
|
|
bssl::UniquePtr<SSL> client_;
|
|
bssl::UniquePtr<SSL> server_;
|
|
|
|
std::vector<uint8_t> server_transport_params_ = {1};
|
|
std::vector<uint8_t> server_quic_early_data_context_ = {2};
|
|
|
|
bool allow_out_of_order_writes_ = false;
|
|
};
|
|
|
|
UnownedSSLExData<MockQUICTransport> QUICMethodTest::ex_data_;
|
|
|
|
// Test a full handshake and resumption work.
|
|
TEST_F(QUICMethodTest, Basic) {
|
|
const SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
|
|
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);
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
ASSERT_TRUE(CompleteHandshakesForQUIC());
|
|
|
|
ExpectHandshakeSuccess();
|
|
EXPECT_FALSE(SSL_session_reused(client_.get()));
|
|
EXPECT_FALSE(SSL_session_reused(server_.get()));
|
|
|
|
// The server sent NewSessionTicket messages in the handshake.
|
|
EXPECT_FALSE(g_last_session);
|
|
ASSERT_TRUE(ProvideHandshakeData(client_.get()));
|
|
EXPECT_EQ(SSL_process_quic_post_handshake(client_.get()), 1);
|
|
EXPECT_TRUE(g_last_session);
|
|
|
|
// Create a second connection to verify resumption works.
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
bssl::UniquePtr<SSL_SESSION> session = std::move(g_last_session);
|
|
SSL_set_session(client_.get(), session.get());
|
|
|
|
ASSERT_TRUE(CompleteHandshakesForQUIC());
|
|
|
|
ExpectHandshakeSuccess();
|
|
EXPECT_TRUE(SSL_session_reused(client_.get()));
|
|
EXPECT_TRUE(SSL_session_reused(server_.get()));
|
|
}
|
|
|
|
// Test that HelloRetryRequest in QUIC works.
|
|
TEST_F(QUICMethodTest, HelloRetryRequest) {
|
|
const SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
|
|
// BoringSSL predicts the most preferred ECDH group, so using different
|
|
// preferences will trigger HelloRetryRequest.
|
|
static const int kClientPrefs[] = {NID_X25519, NID_X9_62_prime256v1};
|
|
ASSERT_TRUE(SSL_CTX_set1_groups(client_ctx_.get(), kClientPrefs,
|
|
OPENSSL_ARRAY_SIZE(kClientPrefs)));
|
|
static const int kServerPrefs[] = {NID_X9_62_prime256v1, NID_X25519};
|
|
ASSERT_TRUE(SSL_CTX_set1_groups(server_ctx_.get(), kServerPrefs,
|
|
OPENSSL_ARRAY_SIZE(kServerPrefs)));
|
|
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
ASSERT_TRUE(CompleteHandshakesForQUIC());
|
|
ExpectHandshakeSuccess();
|
|
}
|
|
|
|
// Test that the client does not send a legacy_session_id in the ClientHello.
|
|
TEST_F(QUICMethodTest, NoLegacySessionId) {
|
|
const SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
// Check that the session ID length is 0 in an early callback.
|
|
SSL_CTX_set_select_certificate_cb(
|
|
server_ctx_.get(),
|
|
[](const SSL_CLIENT_HELLO *client_hello) -> ssl_select_cert_result_t {
|
|
EXPECT_EQ(client_hello->session_id_len, 0u);
|
|
return ssl_select_cert_success;
|
|
});
|
|
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
ASSERT_TRUE(CompleteHandshakesForQUIC());
|
|
|
|
ExpectHandshakeSuccess();
|
|
}
|
|
|
|
// Test that, even in a 1-RTT handshake, the server installs keys at the right
|
|
// time. Half-RTT keys are available early, but 1-RTT read keys are deferred.
|
|
TEST_F(QUICMethodTest, HalfRTTKeys) {
|
|
const SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
|
|
// The client sends ClientHello.
|
|
ASSERT_EQ(SSL_do_handshake(client_.get()), -1);
|
|
ASSERT_EQ(SSL_ERROR_WANT_READ, SSL_get_error(client_.get(), -1));
|
|
|
|
// The server reads ClientHello and sends ServerHello..Finished.
|
|
ASSERT_TRUE(ProvideHandshakeData(server_.get()));
|
|
ASSERT_EQ(SSL_do_handshake(server_.get()), -1);
|
|
ASSERT_EQ(SSL_ERROR_WANT_READ, SSL_get_error(server_.get(), -1));
|
|
|
|
// At this point, the server has half-RTT write keys, but it cannot access
|
|
// 1-RTT read keys until client Finished.
|
|
EXPECT_TRUE(transport_->server()->HasWriteSecret(ssl_encryption_application));
|
|
EXPECT_FALSE(transport_->server()->HasReadSecret(ssl_encryption_application));
|
|
|
|
// Finish up the client and server handshakes.
|
|
ASSERT_TRUE(CompleteHandshakesForQUIC());
|
|
|
|
// Both sides can now exchange 1-RTT data.
|
|
ExpectHandshakeSuccess();
|
|
}
|
|
|
|
TEST_F(QUICMethodTest, ZeroRTTAccept) {
|
|
const SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
|
|
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_early_data_enabled(client_ctx_.get(), 1);
|
|
SSL_CTX_set_early_data_enabled(server_ctx_.get(), 1);
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
|
|
bssl::UniquePtr<SSL_SESSION> session = CreateClientSessionForQUIC();
|
|
ASSERT_TRUE(session);
|
|
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
SSL_set_session(client_.get(), session.get());
|
|
|
|
EXPECT_FALSE(SSL_get_client_ciphers(client_.get()));
|
|
EXPECT_FALSE(SSL_get_client_ciphers(server_.get()));
|
|
|
|
// The client handshake should return immediately into the early data state.
|
|
ASSERT_EQ(SSL_do_handshake(client_.get()), 1);
|
|
EXPECT_TRUE(SSL_in_early_data(client_.get()));
|
|
// The transport should have keys for sending 0-RTT data.
|
|
EXPECT_TRUE(transport_->client()->HasWriteSecret(ssl_encryption_early_data));
|
|
|
|
// The server will consume the ClientHello and also enter the early data
|
|
// state.
|
|
ASSERT_TRUE(ProvideHandshakeData(server_.get()));
|
|
ASSERT_EQ(SSL_do_handshake(server_.get()), 1);
|
|
EXPECT_TRUE(SSL_in_early_data(server_.get()));
|
|
EXPECT_TRUE(transport_->SecretsMatch(ssl_encryption_early_data));
|
|
// At this point, the server has half-RTT write keys, but it cannot access
|
|
// 1-RTT read keys until client Finished.
|
|
EXPECT_TRUE(transport_->server()->HasWriteSecret(ssl_encryption_application));
|
|
EXPECT_FALSE(transport_->server()->HasReadSecret(ssl_encryption_application));
|
|
// 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);
|
|
|
|
// Finish up the client and server handshakes.
|
|
ASSERT_TRUE(CompleteHandshakesForQUIC());
|
|
|
|
// Both sides can now exchange 1-RTT data.
|
|
ExpectHandshakeSuccess();
|
|
EXPECT_TRUE(SSL_session_reused(client_.get()));
|
|
EXPECT_TRUE(SSL_session_reused(server_.get()));
|
|
EXPECT_FALSE(SSL_in_early_data(client_.get()));
|
|
EXPECT_FALSE(SSL_in_early_data(server_.get()));
|
|
EXPECT_TRUE(SSL_early_data_accepted(client_.get()));
|
|
EXPECT_TRUE(SSL_early_data_accepted(server_.get()));
|
|
|
|
// Finish handling post-handshake messages after the first 0-RTT resumption.
|
|
EXPECT_TRUE(ProvideHandshakeData(client_.get()));
|
|
EXPECT_TRUE(SSL_process_quic_post_handshake(client_.get()));
|
|
|
|
// Perform a second 0-RTT resumption attempt, and confirm that 0-RTT is
|
|
// accepted again.
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
SSL_set_session(client_.get(), g_last_session.get());
|
|
|
|
// The client handshake should return immediately into the early data state.
|
|
ASSERT_EQ(SSL_do_handshake(client_.get()), 1);
|
|
EXPECT_TRUE(SSL_in_early_data(client_.get()));
|
|
// The transport should have keys for sending 0-RTT data.
|
|
EXPECT_TRUE(transport_->client()->HasWriteSecret(ssl_encryption_early_data));
|
|
|
|
// The server will consume the ClientHello and also enter the early data
|
|
// state.
|
|
ASSERT_TRUE(ProvideHandshakeData(server_.get()));
|
|
ASSERT_EQ(SSL_do_handshake(server_.get()), 1);
|
|
EXPECT_TRUE(SSL_in_early_data(server_.get()));
|
|
EXPECT_TRUE(transport_->SecretsMatch(ssl_encryption_early_data));
|
|
// At this point, the server has half-RTT write keys, but it cannot access
|
|
// 1-RTT read keys until client Finished.
|
|
EXPECT_TRUE(transport_->server()->HasWriteSecret(ssl_encryption_application));
|
|
EXPECT_FALSE(transport_->server()->HasReadSecret(ssl_encryption_application));
|
|
|
|
// Finish up the client and server handshakes.
|
|
ASSERT_TRUE(CompleteHandshakesForQUIC());
|
|
|
|
// Both sides can now exchange 1-RTT data.
|
|
ExpectHandshakeSuccess();
|
|
EXPECT_TRUE(SSL_session_reused(client_.get()));
|
|
EXPECT_TRUE(SSL_session_reused(server_.get()));
|
|
EXPECT_FALSE(SSL_in_early_data(client_.get()));
|
|
EXPECT_FALSE(SSL_in_early_data(server_.get()));
|
|
EXPECT_TRUE(SSL_early_data_accepted(client_.get()));
|
|
EXPECT_TRUE(SSL_early_data_accepted(server_.get()));
|
|
EXPECT_EQ(SSL_get_early_data_reason(client_.get()), ssl_early_data_accepted);
|
|
EXPECT_EQ(SSL_get_early_data_reason(server_.get()), ssl_early_data_accepted);
|
|
}
|
|
|
|
TEST_F(QUICMethodTest, ZeroRTTRejectMismatchedParameters) {
|
|
const SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
|
|
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_early_data_enabled(client_ctx_.get(), 1);
|
|
SSL_CTX_set_early_data_enabled(server_ctx_.get(), 1);
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
|
|
|
|
bssl::UniquePtr<SSL_SESSION> session = CreateClientSessionForQUIC();
|
|
ASSERT_TRUE(session);
|
|
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
static const uint8_t new_context[] = {4};
|
|
ASSERT_TRUE(SSL_set_quic_early_data_context(server_.get(), new_context,
|
|
sizeof(new_context)));
|
|
SSL_set_session(client_.get(), session.get());
|
|
|
|
// The client handshake should return immediately into the early data
|
|
// state.
|
|
ASSERT_EQ(SSL_do_handshake(client_.get()), 1);
|
|
EXPECT_TRUE(SSL_in_early_data(client_.get()));
|
|
// The transport should have keys for sending 0-RTT data.
|
|
EXPECT_TRUE(transport_->client()->HasWriteSecret(ssl_encryption_early_data));
|
|
|
|
// The server will consume the ClientHello, but it will not accept 0-RTT.
|
|
ASSERT_TRUE(ProvideHandshakeData(server_.get()));
|
|
ASSERT_EQ(SSL_do_handshake(server_.get()), -1);
|
|
ASSERT_EQ(SSL_ERROR_WANT_READ, SSL_get_error(server_.get(), -1));
|
|
EXPECT_FALSE(SSL_in_early_data(server_.get()));
|
|
EXPECT_FALSE(transport_->server()->HasReadSecret(ssl_encryption_early_data));
|
|
|
|
// The client consumes the server response and signals 0-RTT rejection.
|
|
for (;;) {
|
|
ASSERT_TRUE(ProvideHandshakeData(client_.get()));
|
|
ASSERT_EQ(-1, SSL_do_handshake(client_.get()));
|
|
int err = SSL_get_error(client_.get(), -1);
|
|
if (err == SSL_ERROR_EARLY_DATA_REJECTED) {
|
|
break;
|
|
}
|
|
ASSERT_EQ(SSL_ERROR_WANT_READ, err);
|
|
}
|
|
|
|
// As in TLS over TCP, 0-RTT rejection is sticky.
|
|
ASSERT_EQ(-1, SSL_do_handshake(client_.get()));
|
|
ASSERT_EQ(SSL_ERROR_EARLY_DATA_REJECTED, SSL_get_error(client_.get(), -1));
|
|
|
|
// Finish up the client and server handshakes.
|
|
SSL_reset_early_data_reject(client_.get());
|
|
ASSERT_TRUE(CompleteHandshakesForQUIC());
|
|
|
|
// Both sides can now exchange 1-RTT data.
|
|
ExpectHandshakeSuccess();
|
|
EXPECT_TRUE(SSL_session_reused(client_.get()));
|
|
EXPECT_TRUE(SSL_session_reused(server_.get()));
|
|
EXPECT_FALSE(SSL_in_early_data(client_.get()));
|
|
EXPECT_FALSE(SSL_in_early_data(server_.get()));
|
|
EXPECT_FALSE(SSL_early_data_accepted(client_.get()));
|
|
EXPECT_FALSE(SSL_early_data_accepted(server_.get()));
|
|
}
|
|
|
|
TEST_F(QUICMethodTest, NoZeroRTTTicketWithoutEarlyDataContext) {
|
|
server_quic_early_data_context_ = {};
|
|
const SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
|
|
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_early_data_enabled(client_ctx_.get(), 1);
|
|
SSL_CTX_set_early_data_enabled(server_ctx_.get(), 1);
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
|
|
bssl::UniquePtr<SSL_SESSION> session = CreateClientSessionForQUIC();
|
|
ASSERT_TRUE(session);
|
|
EXPECT_FALSE(SSL_SESSION_early_data_capable(session.get()));
|
|
}
|
|
|
|
TEST_F(QUICMethodTest, ZeroRTTReject) {
|
|
const SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
|
|
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_early_data_enabled(client_ctx_.get(), 1);
|
|
SSL_CTX_set_early_data_enabled(server_ctx_.get(), 1);
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
|
|
bssl::UniquePtr<SSL_SESSION> session = CreateClientSessionForQUIC();
|
|
ASSERT_TRUE(session);
|
|
|
|
for (bool reject_hrr : {false, true}) {
|
|
SCOPED_TRACE(reject_hrr);
|
|
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
if (reject_hrr) {
|
|
// Configure the server to prefer P-256, which will reject 0-RTT via
|
|
// HelloRetryRequest.
|
|
int p256 = NID_X9_62_prime256v1;
|
|
ASSERT_TRUE(SSL_set1_groups(server_.get(), &p256, 1));
|
|
} else {
|
|
// Disable 0-RTT on the server, so it will reject it.
|
|
SSL_set_early_data_enabled(server_.get(), 0);
|
|
}
|
|
SSL_set_session(client_.get(), session.get());
|
|
|
|
// The client handshake should return immediately into the early data state.
|
|
ASSERT_EQ(SSL_do_handshake(client_.get()), 1);
|
|
EXPECT_TRUE(SSL_in_early_data(client_.get()));
|
|
// The transport should have keys for sending 0-RTT data.
|
|
EXPECT_TRUE(
|
|
transport_->client()->HasWriteSecret(ssl_encryption_early_data));
|
|
|
|
// The server will consume the ClientHello, but it will not accept 0-RTT.
|
|
ASSERT_TRUE(ProvideHandshakeData(server_.get()));
|
|
ASSERT_EQ(SSL_do_handshake(server_.get()), -1);
|
|
ASSERT_EQ(SSL_ERROR_WANT_READ, SSL_get_error(server_.get(), -1));
|
|
EXPECT_FALSE(SSL_in_early_data(server_.get()));
|
|
EXPECT_FALSE(
|
|
transport_->server()->HasReadSecret(ssl_encryption_early_data));
|
|
|
|
// The client consumes the server response and signals 0-RTT rejection.
|
|
for (;;) {
|
|
ASSERT_TRUE(ProvideHandshakeData(client_.get()));
|
|
ASSERT_EQ(-1, SSL_do_handshake(client_.get()));
|
|
int err = SSL_get_error(client_.get(), -1);
|
|
if (err == SSL_ERROR_EARLY_DATA_REJECTED) {
|
|
break;
|
|
}
|
|
ASSERT_EQ(SSL_ERROR_WANT_READ, err);
|
|
}
|
|
|
|
// As in TLS over TCP, 0-RTT rejection is sticky.
|
|
ASSERT_EQ(-1, SSL_do_handshake(client_.get()));
|
|
ASSERT_EQ(SSL_ERROR_EARLY_DATA_REJECTED, SSL_get_error(client_.get(), -1));
|
|
|
|
// Finish up the client and server handshakes.
|
|
SSL_reset_early_data_reject(client_.get());
|
|
ASSERT_TRUE(CompleteHandshakesForQUIC());
|
|
|
|
// Both sides can now exchange 1-RTT data.
|
|
ExpectHandshakeSuccess();
|
|
EXPECT_TRUE(SSL_session_reused(client_.get()));
|
|
EXPECT_TRUE(SSL_session_reused(server_.get()));
|
|
EXPECT_FALSE(SSL_in_early_data(client_.get()));
|
|
EXPECT_FALSE(SSL_in_early_data(server_.get()));
|
|
EXPECT_FALSE(SSL_early_data_accepted(client_.get()));
|
|
EXPECT_FALSE(SSL_early_data_accepted(server_.get()));
|
|
}
|
|
}
|
|
|
|
TEST_F(QUICMethodTest, NoZeroRTTKeysBeforeReverify) {
|
|
const SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
|
|
SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
|
|
SSL_CTX_set_early_data_enabled(client_ctx_.get(), 1);
|
|
SSL_CTX_set_reverify_on_resume(client_ctx_.get(), 1);
|
|
SSL_CTX_set_early_data_enabled(server_ctx_.get(), 1);
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
|
|
bssl::UniquePtr<SSL_SESSION> session = CreateClientSessionForQUIC();
|
|
ASSERT_TRUE(session);
|
|
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
SSL_set_session(client_.get(), session.get());
|
|
|
|
// Configure the certificate (re)verification to never complete. The client
|
|
// handshake should pause.
|
|
SSL_set_custom_verify(
|
|
client_.get(), SSL_VERIFY_PEER,
|
|
[](SSL *ssl, uint8_t *out_alert) -> ssl_verify_result_t {
|
|
return ssl_verify_retry;
|
|
});
|
|
ASSERT_EQ(SSL_do_handshake(client_.get()), -1);
|
|
ASSERT_EQ(SSL_get_error(client_.get(), -1),
|
|
SSL_ERROR_WANT_CERTIFICATE_VERIFY);
|
|
|
|
// The early data keys have not yet been released.
|
|
EXPECT_FALSE(transport_->client()->HasWriteSecret(ssl_encryption_early_data));
|
|
|
|
// After the verification completes, the handshake progresses to the 0-RTT
|
|
// point and releases keys.
|
|
SSL_set_custom_verify(
|
|
client_.get(), SSL_VERIFY_PEER,
|
|
[](SSL *ssl, uint8_t *out_alert) -> ssl_verify_result_t {
|
|
return ssl_verify_ok;
|
|
});
|
|
ASSERT_EQ(SSL_do_handshake(client_.get()), 1);
|
|
EXPECT_TRUE(SSL_in_early_data(client_.get()));
|
|
EXPECT_TRUE(transport_->client()->HasWriteSecret(ssl_encryption_early_data));
|
|
}
|
|
|
|
// Test only releasing data to QUIC one byte at a time on request, to maximize
|
|
// state machine pauses. Additionally, test that existing asynchronous callbacks
|
|
// still work.
|
|
TEST_F(QUICMethodTest, Async) {
|
|
const SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
|
|
// Install an asynchronous certificate callback.
|
|
bool cert_cb_ok = false;
|
|
SSL_set_cert_cb(
|
|
server_.get(),
|
|
[](SSL *, void *arg) -> int {
|
|
return *static_cast<bool *>(arg) ? 1 : -1;
|
|
},
|
|
&cert_cb_ok);
|
|
|
|
for (;;) {
|
|
int client_ret = SSL_do_handshake(client_.get());
|
|
if (client_ret != 1) {
|
|
ASSERT_EQ(client_ret, -1);
|
|
ASSERT_EQ(SSL_get_error(client_.get(), client_ret), SSL_ERROR_WANT_READ);
|
|
ASSERT_TRUE(ProvideHandshakeData(client_.get(), 1));
|
|
}
|
|
|
|
int server_ret = SSL_do_handshake(server_.get());
|
|
if (server_ret != 1) {
|
|
ASSERT_EQ(server_ret, -1);
|
|
int ssl_err = SSL_get_error(server_.get(), server_ret);
|
|
switch (ssl_err) {
|
|
case SSL_ERROR_WANT_READ:
|
|
ASSERT_TRUE(ProvideHandshakeData(server_.get(), 1));
|
|
break;
|
|
case SSL_ERROR_WANT_X509_LOOKUP:
|
|
ASSERT_FALSE(cert_cb_ok);
|
|
cert_cb_ok = true;
|
|
break;
|
|
default:
|
|
FAIL() << "Unexpected SSL_get_error result: " << ssl_err;
|
|
}
|
|
}
|
|
|
|
if (client_ret == 1 && server_ret == 1) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
ExpectHandshakeSuccess();
|
|
}
|
|
|
|
// Test buffering write data until explicit flushes.
|
|
TEST_F(QUICMethodTest, Buffered) {
|
|
AllowOutOfOrderWrites();
|
|
|
|
struct BufferedFlight {
|
|
std::vector<uint8_t> data[kNumQUICLevels];
|
|
};
|
|
static UnownedSSLExData<BufferedFlight> buffered_flights;
|
|
|
|
auto add_handshake_data = [](SSL *ssl, enum ssl_encryption_level_t level,
|
|
const uint8_t *data, size_t len) -> int {
|
|
BufferedFlight *flight = buffered_flights.Get(ssl);
|
|
flight->data[level].insert(flight->data[level].end(), data, data + len);
|
|
return 1;
|
|
};
|
|
|
|
auto flush_flight = [](SSL *ssl) -> int {
|
|
BufferedFlight *flight = buffered_flights.Get(ssl);
|
|
for (size_t level = 0; level < kNumQUICLevels; level++) {
|
|
if (!flight->data[level].empty()) {
|
|
if (!TransportFromSSL(ssl)->WriteHandshakeData(
|
|
static_cast<ssl_encryption_level_t>(level),
|
|
flight->data[level])) {
|
|
return 0;
|
|
}
|
|
flight->data[level].clear();
|
|
}
|
|
}
|
|
return 1;
|
|
};
|
|
|
|
SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
quic_method.add_handshake_data = add_handshake_data;
|
|
quic_method.flush_flight = flush_flight;
|
|
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
|
|
BufferedFlight client_flight, server_flight;
|
|
ASSERT_TRUE(buffered_flights.Set(client_.get(), &client_flight));
|
|
ASSERT_TRUE(buffered_flights.Set(server_.get(), &server_flight));
|
|
|
|
ASSERT_TRUE(CompleteHandshakesForQUIC());
|
|
|
|
ExpectHandshakeSuccess();
|
|
}
|
|
|
|
// Test that excess data at one level is rejected. That is, if a single
|
|
// |SSL_provide_quic_data| call included both ServerHello and
|
|
// EncryptedExtensions in a single chunk, BoringSSL notices and rejects this on
|
|
// key change.
|
|
TEST_F(QUICMethodTest, ExcessProvidedData) {
|
|
AllowOutOfOrderWrites();
|
|
|
|
auto add_handshake_data = [](SSL *ssl, enum ssl_encryption_level_t level,
|
|
const uint8_t *data, size_t len) -> int {
|
|
// Switch everything to the initial level.
|
|
return TransportFromSSL(ssl)->WriteHandshakeData(ssl_encryption_initial,
|
|
MakeConstSpan(data, len));
|
|
};
|
|
|
|
SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
quic_method.add_handshake_data = add_handshake_data;
|
|
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
|
|
// Send the ClientHello and ServerHello through Finished.
|
|
ASSERT_EQ(SSL_do_handshake(client_.get()), -1);
|
|
ASSERT_EQ(SSL_get_error(client_.get(), -1), SSL_ERROR_WANT_READ);
|
|
ASSERT_TRUE(ProvideHandshakeData(server_.get()));
|
|
ASSERT_EQ(SSL_do_handshake(server_.get()), -1);
|
|
ASSERT_EQ(SSL_get_error(server_.get(), -1), SSL_ERROR_WANT_READ);
|
|
|
|
// The client is still waiting for the ServerHello at initial
|
|
// encryption.
|
|
ASSERT_EQ(ssl_encryption_initial, SSL_quic_read_level(client_.get()));
|
|
|
|
// |add_handshake_data| incorrectly wrote everything at the initial level, so
|
|
// this queues up ServerHello through Finished in one chunk.
|
|
ASSERT_TRUE(ProvideHandshakeData(client_.get()));
|
|
|
|
// The client reads ServerHello successfully, but then rejects the buffered
|
|
// EncryptedExtensions on key change.
|
|
ASSERT_EQ(SSL_do_handshake(client_.get()), -1);
|
|
ASSERT_EQ(SSL_get_error(client_.get(), -1), SSL_ERROR_SSL);
|
|
EXPECT_TRUE(
|
|
ErrorEquals(ERR_get_error(), ERR_LIB_SSL, SSL_R_EXCESS_HANDSHAKE_DATA));
|
|
|
|
// The client sends an alert in response to this. The alert is sent at
|
|
// handshake level because we install write secrets before read secrets and
|
|
// the error is discovered when installing the read secret. (How to send
|
|
// alerts on protocol syntax errors near key changes is ambiguous in general.)
|
|
ASSERT_TRUE(transport_->client()->has_alert());
|
|
EXPECT_EQ(transport_->client()->alert_level(), ssl_encryption_handshake);
|
|
EXPECT_EQ(transport_->client()->alert(), SSL_AD_UNEXPECTED_MESSAGE);
|
|
|
|
// Sanity-check handshake secrets. The error is discovered while setting the
|
|
// read secret, so only the write secret has been installed.
|
|
EXPECT_TRUE(transport_->client()->HasWriteSecret(ssl_encryption_handshake));
|
|
EXPECT_FALSE(transport_->client()->HasReadSecret(ssl_encryption_handshake));
|
|
}
|
|
|
|
// Test that |SSL_provide_quic_data| will reject data at the wrong level.
|
|
TEST_F(QUICMethodTest, ProvideWrongLevel) {
|
|
const SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
|
|
// Send the ClientHello and ServerHello through Finished.
|
|
ASSERT_EQ(SSL_do_handshake(client_.get()), -1);
|
|
ASSERT_EQ(SSL_get_error(client_.get(), -1), SSL_ERROR_WANT_READ);
|
|
ASSERT_TRUE(ProvideHandshakeData(server_.get()));
|
|
ASSERT_EQ(SSL_do_handshake(server_.get()), -1);
|
|
ASSERT_EQ(SSL_get_error(server_.get(), -1), SSL_ERROR_WANT_READ);
|
|
|
|
// The client is still waiting for the ServerHello at initial
|
|
// encryption.
|
|
ASSERT_EQ(ssl_encryption_initial, SSL_quic_read_level(client_.get()));
|
|
|
|
// Data cannot be provided at the next level.
|
|
std::vector<uint8_t> data;
|
|
ASSERT_TRUE(
|
|
transport_->client()->ReadHandshakeData(&data, ssl_encryption_initial));
|
|
ASSERT_FALSE(SSL_provide_quic_data(client_.get(), ssl_encryption_handshake,
|
|
data.data(), data.size()));
|
|
ERR_clear_error();
|
|
|
|
// Progress to EncryptedExtensions.
|
|
ASSERT_TRUE(SSL_provide_quic_data(client_.get(), ssl_encryption_initial,
|
|
data.data(), data.size()));
|
|
ASSERT_EQ(SSL_do_handshake(client_.get()), -1);
|
|
ASSERT_EQ(SSL_get_error(client_.get(), -1), SSL_ERROR_WANT_READ);
|
|
ASSERT_EQ(ssl_encryption_handshake, SSL_quic_read_level(client_.get()));
|
|
|
|
// Data cannot be provided at the previous level.
|
|
ASSERT_TRUE(
|
|
transport_->client()->ReadHandshakeData(&data, ssl_encryption_handshake));
|
|
ASSERT_FALSE(SSL_provide_quic_data(client_.get(), ssl_encryption_initial,
|
|
data.data(), data.size()));
|
|
}
|
|
|
|
TEST_F(QUICMethodTest, TooMuchData) {
|
|
const SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
|
|
size_t limit =
|
|
SSL_quic_max_handshake_flight_len(client_.get(), ssl_encryption_initial);
|
|
uint8_t b = 0;
|
|
for (size_t i = 0; i < limit; i++) {
|
|
ASSERT_TRUE(
|
|
SSL_provide_quic_data(client_.get(), ssl_encryption_initial, &b, 1));
|
|
}
|
|
|
|
EXPECT_FALSE(
|
|
SSL_provide_quic_data(client_.get(), ssl_encryption_initial, &b, 1));
|
|
}
|
|
|
|
// Provide invalid post-handshake data.
|
|
TEST_F(QUICMethodTest, BadPostHandshake) {
|
|
const SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
|
|
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);
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
ASSERT_TRUE(CompleteHandshakesForQUIC());
|
|
|
|
EXPECT_EQ(SSL_do_handshake(client_.get()), 1);
|
|
EXPECT_EQ(SSL_do_handshake(server_.get()), 1);
|
|
EXPECT_TRUE(transport_->SecretsMatch(ssl_encryption_application));
|
|
EXPECT_FALSE(transport_->client()->has_alert());
|
|
EXPECT_FALSE(transport_->server()->has_alert());
|
|
|
|
// Junk sent as part of post-handshake data should cause an error.
|
|
uint8_t kJunk[] = {0x17, 0x0, 0x0, 0x4, 0xB, 0xE, 0xE, 0xF};
|
|
ASSERT_TRUE(SSL_provide_quic_data(client_.get(), ssl_encryption_application,
|
|
kJunk, sizeof(kJunk)));
|
|
EXPECT_EQ(SSL_process_quic_post_handshake(client_.get()), 0);
|
|
}
|
|
|
|
|
|
static void ExpectReceivedTransportParamsEqual(const SSL *ssl,
|
|
Span<const uint8_t> expected) {
|
|
const uint8_t *received = nullptr;
|
|
size_t received_len = 0;
|
|
SSL_get_peer_quic_transport_params(ssl, &received, &received_len);
|
|
ASSERT_EQ(received_len, expected.size());
|
|
EXPECT_EQ(Bytes(received, received_len), Bytes(expected));
|
|
}
|
|
|
|
TEST_F(QUICMethodTest, SetTransportParameters) {
|
|
const SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
uint8_t kClientParams[] = {1, 2, 3, 4};
|
|
uint8_t kServerParams[] = {5, 6, 7};
|
|
ASSERT_TRUE(SSL_set_quic_transport_params(client_.get(), kClientParams,
|
|
sizeof(kClientParams)));
|
|
ASSERT_TRUE(SSL_set_quic_transport_params(server_.get(), kServerParams,
|
|
sizeof(kServerParams)));
|
|
|
|
ASSERT_TRUE(CompleteHandshakesForQUIC());
|
|
ExpectReceivedTransportParamsEqual(client_.get(), kServerParams);
|
|
ExpectReceivedTransportParamsEqual(server_.get(), kClientParams);
|
|
}
|
|
|
|
TEST_F(QUICMethodTest, SetTransportParamsInCallback) {
|
|
const SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
uint8_t kClientParams[] = {1, 2, 3, 4};
|
|
static uint8_t kServerParams[] = {5, 6, 7};
|
|
ASSERT_TRUE(SSL_set_quic_transport_params(client_.get(), kClientParams,
|
|
sizeof(kClientParams)));
|
|
SSL_CTX_set_tlsext_servername_callback(
|
|
server_ctx_.get(), [](SSL *ssl, int *out_alert, void *arg) -> int {
|
|
EXPECT_TRUE(SSL_set_quic_transport_params(ssl, kServerParams,
|
|
sizeof(kServerParams)));
|
|
return SSL_TLSEXT_ERR_OK;
|
|
});
|
|
|
|
ASSERT_TRUE(CompleteHandshakesForQUIC());
|
|
ExpectReceivedTransportParamsEqual(client_.get(), kServerParams);
|
|
ExpectReceivedTransportParamsEqual(server_.get(), kClientParams);
|
|
}
|
|
|
|
TEST_F(QUICMethodTest, ForbidCrossProtocolResumptionClient) {
|
|
const SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
|
|
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);
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
ASSERT_TRUE(CompleteHandshakesForQUIC());
|
|
|
|
ExpectHandshakeSuccess();
|
|
EXPECT_FALSE(SSL_session_reused(client_.get()));
|
|
EXPECT_FALSE(SSL_session_reused(server_.get()));
|
|
|
|
// The server sent NewSessionTicket messages in the handshake.
|
|
EXPECT_FALSE(g_last_session);
|
|
ASSERT_TRUE(ProvideHandshakeData(client_.get()));
|
|
EXPECT_EQ(SSL_process_quic_post_handshake(client_.get()), 1);
|
|
ASSERT_TRUE(g_last_session);
|
|
|
|
// Pretend that g_last_session came from a TLS-over-TCP connection.
|
|
g_last_session->is_quic = false;
|
|
|
|
// Create a second connection and verify that resumption does not occur with
|
|
// a session from a non-QUIC connection. This tests that the client does not
|
|
// offer over QUIC a session believed to be received over TCP. The server
|
|
// believes this is a QUIC session, so if the client offered the session, the
|
|
// server would have resumed it.
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
bssl::UniquePtr<SSL_SESSION> session = std::move(g_last_session);
|
|
SSL_set_session(client_.get(), session.get());
|
|
|
|
ASSERT_TRUE(CompleteHandshakesForQUIC());
|
|
ExpectHandshakeSuccess();
|
|
EXPECT_FALSE(SSL_session_reused(client_.get()));
|
|
EXPECT_FALSE(SSL_session_reused(server_.get()));
|
|
}
|
|
|
|
TEST_F(QUICMethodTest, ForbidCrossProtocolResumptionServer) {
|
|
const SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
|
|
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);
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
ASSERT_TRUE(CompleteHandshakesForQUIC());
|
|
|
|
ExpectHandshakeSuccess();
|
|
EXPECT_FALSE(SSL_session_reused(client_.get()));
|
|
EXPECT_FALSE(SSL_session_reused(server_.get()));
|
|
|
|
// The server sent NewSessionTicket messages in the handshake.
|
|
EXPECT_FALSE(g_last_session);
|
|
ASSERT_TRUE(ProvideHandshakeData(client_.get()));
|
|
EXPECT_EQ(SSL_process_quic_post_handshake(client_.get()), 1);
|
|
ASSERT_TRUE(g_last_session);
|
|
|
|
// Attempt a resumption with g_last_session using TLS_method.
|
|
bssl::UniquePtr<SSL_CTX> client_ctx(SSL_CTX_new(TLS_method()));
|
|
ASSERT_TRUE(client_ctx);
|
|
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), nullptr));
|
|
|
|
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());
|
|
|
|
// The TLS-over-TCP client will refuse to resume with a quic session, so
|
|
// mark is_quic = false to bypass the client check to test the server check.
|
|
g_last_session->is_quic = false;
|
|
SSL_set_session(client.get(), g_last_session.get());
|
|
|
|
BIO *bio1 = nullptr, *bio2 = nullptr;
|
|
ASSERT_TRUE(BIO_new_bio_pair(&bio1, 0, &bio2, 0));
|
|
|
|
// 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()));
|
|
|
|
EXPECT_FALSE(SSL_session_reused(client.get()));
|
|
EXPECT_FALSE(SSL_session_reused(server.get()));
|
|
}
|
|
|
|
TEST_F(QUICMethodTest, ClientRejectsMissingTransportParams) {
|
|
const SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
ASSERT_TRUE(SSL_set_quic_transport_params(server_.get(), nullptr, 0));
|
|
ASSERT_TRUE(RunQUICHandshakesAndExpectError(ExpectedError::kServerError));
|
|
}
|
|
|
|
TEST_F(QUICMethodTest, ServerRejectsMissingTransportParams) {
|
|
const SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
ASSERT_TRUE(SSL_set_quic_transport_params(client_.get(), nullptr, 0));
|
|
ASSERT_TRUE(RunQUICHandshakesAndExpectError(ExpectedError::kClientError));
|
|
}
|
|
|
|
TEST_F(QUICMethodTest, QuicLegacyCodepointEnabled) {
|
|
const SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
uint8_t kClientParams[] = {1, 2, 3, 4};
|
|
uint8_t kServerParams[] = {5, 6, 7};
|
|
SSL_set_quic_use_legacy_codepoint(client_.get(), 1);
|
|
SSL_set_quic_use_legacy_codepoint(server_.get(), 1);
|
|
ASSERT_TRUE(SSL_set_quic_transport_params(client_.get(), kClientParams,
|
|
sizeof(kClientParams)));
|
|
ASSERT_TRUE(SSL_set_quic_transport_params(server_.get(), kServerParams,
|
|
sizeof(kServerParams)));
|
|
|
|
ASSERT_TRUE(CompleteHandshakesForQUIC());
|
|
ExpectReceivedTransportParamsEqual(client_.get(), kServerParams);
|
|
ExpectReceivedTransportParamsEqual(server_.get(), kClientParams);
|
|
}
|
|
|
|
TEST_F(QUICMethodTest, QuicLegacyCodepointDisabled) {
|
|
const SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
uint8_t kClientParams[] = {1, 2, 3, 4};
|
|
uint8_t kServerParams[] = {5, 6, 7};
|
|
SSL_set_quic_use_legacy_codepoint(client_.get(), 0);
|
|
SSL_set_quic_use_legacy_codepoint(server_.get(), 0);
|
|
ASSERT_TRUE(SSL_set_quic_transport_params(client_.get(), kClientParams,
|
|
sizeof(kClientParams)));
|
|
ASSERT_TRUE(SSL_set_quic_transport_params(server_.get(), kServerParams,
|
|
sizeof(kServerParams)));
|
|
|
|
ASSERT_TRUE(CompleteHandshakesForQUIC());
|
|
ExpectReceivedTransportParamsEqual(client_.get(), kServerParams);
|
|
ExpectReceivedTransportParamsEqual(server_.get(), kClientParams);
|
|
}
|
|
|
|
TEST_F(QUICMethodTest, QuicLegacyCodepointClientOnly) {
|
|
const SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
uint8_t kClientParams[] = {1, 2, 3, 4};
|
|
uint8_t kServerParams[] = {5, 6, 7};
|
|
SSL_set_quic_use_legacy_codepoint(client_.get(), 1);
|
|
SSL_set_quic_use_legacy_codepoint(server_.get(), 0);
|
|
ASSERT_TRUE(SSL_set_quic_transport_params(client_.get(), kClientParams,
|
|
sizeof(kClientParams)));
|
|
ASSERT_TRUE(SSL_set_quic_transport_params(server_.get(), kServerParams,
|
|
sizeof(kServerParams)));
|
|
|
|
ASSERT_TRUE(RunQUICHandshakesAndExpectError(ExpectedError::kServerError));
|
|
}
|
|
|
|
TEST_F(QUICMethodTest, QuicLegacyCodepointServerOnly) {
|
|
const SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
uint8_t kClientParams[] = {1, 2, 3, 4};
|
|
uint8_t kServerParams[] = {5, 6, 7};
|
|
SSL_set_quic_use_legacy_codepoint(client_.get(), 0);
|
|
SSL_set_quic_use_legacy_codepoint(server_.get(), 1);
|
|
ASSERT_TRUE(SSL_set_quic_transport_params(client_.get(), kClientParams,
|
|
sizeof(kClientParams)));
|
|
ASSERT_TRUE(SSL_set_quic_transport_params(server_.get(), kServerParams,
|
|
sizeof(kServerParams)));
|
|
|
|
ASSERT_TRUE(RunQUICHandshakesAndExpectError(ExpectedError::kServerError));
|
|
}
|
|
|
|
// Test that the default QUIC code point is consistent with
|
|
// |TLSEXT_TYPE_quic_transport_parameters|. This test ensures we remember to
|
|
// update the two values together.
|
|
TEST_F(QUICMethodTest, QuicCodePointDefault) {
|
|
const SSL_QUIC_METHOD quic_method = DefaultQUICMethod();
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
|
|
ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
|
|
SSL_CTX_set_select_certificate_cb(
|
|
server_ctx_.get(),
|
|
[](const SSL_CLIENT_HELLO *client_hello) -> ssl_select_cert_result_t {
|
|
const uint8_t *data = nullptr;
|
|
size_t len = 0;
|
|
if (!SSL_early_callback_ctx_extension_get(
|
|
client_hello, TLSEXT_TYPE_quic_transport_parameters, &data,
|
|
&len)) {
|
|
ADD_FAILURE() << "Could not find quic_transport_parameters extension";
|
|
return ssl_select_cert_error;
|
|
}
|
|
return ssl_select_cert_success;
|
|
});
|
|
|
|
ASSERT_TRUE(CreateClientAndServer());
|
|
ASSERT_TRUE(CompleteHandshakesForQUIC());
|
|
}
|
|
|
|
|
|
BSSL_NAMESPACE_END
|