1
0
mirror of https://github.com/facebookincubator/mvfst.git synced 2025-08-09 20:42:44 +03:00
Files
mvfst/quic/fizz/client/handshake/test/FizzClientHandshakeTest.cpp
Aman Sharma bcbe5adce4 Introduce a ByteRange typealias
Summary: See title

Reviewed By: kvtsoy

Differential Revision: D73444489

fbshipit-source-id: f83566ce023e8237335d3bb43d89fc471f053afa
2025-04-22 23:17:46 -07:00

719 lines
24 KiB
C++

/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <fizz/protocol/ech/Decrypter.h>
#include <folly/FBString.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <fizz/backend/openssl/OpenSSL.h>
#include <fizz/client/test/Mocks.h>
#include <fizz/crypto/test/TestUtil.h>
#include <fizz/protocol/clock/test/Mocks.h>
#include <fizz/protocol/test/Mocks.h>
#include <fizz/server/Actions.h>
#include <fizz/server/test/Mocks.h>
#include <folly/io/async/SSLContext.h>
#include <folly/io/async/ScopedEventBaseThread.h>
#include <quic/client/handshake/ClientTransportParametersExtension.h>
#include <quic/client/state/ClientStateMachine.h>
#include <quic/common/test/TestUtils.h>
#include <quic/fizz/client/handshake/FizzClientHandshake.h>
#include <quic/fizz/client/handshake/FizzClientQuicHandshakeContext.h>
#include <quic/fizz/client/handshake/test/MockQuicPskCache.h>
#include <quic/fizz/handshake/FizzBridge.h>
#include <quic/fizz/handshake/QuicFizzFactory.h>
#include <quic/state/StateData.h>
using namespace testing;
namespace quic::test {
class ClientHandshakeTest : public Test, public boost::static_visitor<> {
public:
~ClientHandshakeTest() override = default;
ClientHandshakeTest() {}
virtual void setupClientAndServerContext() {
clientCtx = createClientCtx();
}
QuicVersion getVersion() {
return QuicVersion::MVFST;
}
virtual void connect() {
CHECK(!handshake
->connect(
hostname,
std::make_shared<ClientTransportParametersExtension>(
QuicVersion::MVFST,
folly::to<uint32_t>(kDefaultConnectionFlowControlWindow),
folly::to<uint32_t>(kDefaultStreamFlowControlWindow),
folly::to<uint32_t>(kDefaultStreamFlowControlWindow),
folly::to<uint32_t>(kDefaultStreamFlowControlWindow),
folly::to<uint32_t>(kDefaultMaxStreamsBidirectional),
folly::to<uint32_t>(kDefaultMaxStreamsUnidirectional),
kDefaultIdleTimeout,
kDefaultAckDelayExponent,
kDefaultUDPSendPacketLen,
kDefaultActiveConnectionIdLimit,
ConnectionId(std::vector<uint8_t>())))
.hasError());
}
void SetUp() override {
dg.reset(new DelayedHolder());
serverCtx = ::quic::test::createServerCtx();
serverCtx->setECHDecrypter(getECHDecrypter());
serverCtx->setOmitEarlyRecordLayer(true);
serverCtx->setClock(std::make_shared<fizz::test::MockClock>());
// Fizz is the name of the identity for our server certificate.
hostname = "Fizz";
setupClientAndServerContext();
verifier = std::make_shared<fizz::test::MockCertificateVerifier>();
auto handshakeFactory = FizzClientQuicHandshakeContext::Builder()
.setFizzClientContext(clientCtx)
.setCertificateVerifier(verifier)
.setPskCache(getPskCache())
.setECHPolicy(getECHPolicy())
.setECHRetryCallback(getECHRetryCallback())
.build();
conn.reset(new QuicClientConnectionState(handshakeFactory));
conn->readCodec = std::make_unique<QuicReadCodec>(QuicNodeType::Client);
cryptoState = conn->cryptoState.get();
handshake = conn->clientHandshakeLayer;
conn->transportSettings.attemptEarlyData = true;
std::vector<QuicVersion> supportedVersions = {getVersion()};
auto serverTransportParameters =
std::make_shared<ServerTransportParametersExtension>(
getVersion(),
folly::to<uint32_t>(kDefaultConnectionFlowControlWindow),
folly::to<uint32_t>(kDefaultStreamFlowControlWindow),
folly::to<uint32_t>(kDefaultStreamFlowControlWindow),
folly::to<uint32_t>(kDefaultStreamFlowControlWindow),
std::numeric_limits<uint32_t>::max(),
std::numeric_limits<uint32_t>::max(),
/*disableMigration=*/true,
kDefaultIdleTimeout,
kDefaultAckDelayExponent,
kDefaultUDPSendPacketLen,
generateStatelessResetToken(),
ConnectionId(std::vector<uint8_t>{0xff, 0xfe, 0xfd, 0xfc}),
ConnectionId(std::vector<uint8_t>()));
fizzServer.reset(
new fizz::server::
FizzServer<ClientHandshakeTest, fizz::server::ServerStateMachine>(
serverState, serverReadBuf, readAeadOptions, *this, dg.get()));
connect();
processHandshake();
fizzServer->accept(&evb, serverCtx, serverTransportParameters);
}
virtual std::shared_ptr<QuicPskCache> getPskCache() {
return nullptr;
}
virtual std::shared_ptr<fizz::ech::Decrypter> getECHDecrypter() {
return nullptr;
}
virtual std::shared_ptr<fizz::client::test::MockECHPolicy> getECHPolicy() {
return nullptr;
}
virtual std::shared_ptr<fizz::client::test::MockECHRetryCallback>
getECHRetryCallback() {
return nullptr;
}
void clientServerRound() {
auto writableBytes = getHandshakeWriteBytes();
serverReadBuf.append(std::move(writableBytes));
fizzServer->newTransportData();
evb.loop();
}
void serverClientRound() {
// Fake that the transport has set the version and initial params.
conn->version = QuicVersion::MVFST;
conn->serverInitialParamsSet_ = true;
evb.loop();
for (auto& write : serverOutput) {
for (auto& content : write.contents) {
auto encryptionLevel =
getEncryptionLevelFromFizz(content.encryptionLevel);
CHECK(!handshake->doHandshake(std::move(content.data), encryptionLevel)
.hasError());
}
}
processHandshake();
}
void processHandshake() {
auto oneRttWriteCipherTmp = std::move(conn->oneRttWriteCipher);
auto oneRttReadCipherTmp = conn->readCodec->getOneRttReadCipher();
auto zeroRttWriteCipherTmp = std::move(conn->zeroRttWriteCipher);
auto handshakeWriteCipherTmp = std::move(conn->handshakeWriteCipher);
auto handshakeReadCipherTmp = conn->readCodec->getHandshakeReadCipher();
if (oneRttWriteCipherTmp) {
oneRttWriteCipher = std::move(oneRttWriteCipherTmp);
}
if (oneRttReadCipherTmp) {
oneRttReadCipher = oneRttReadCipherTmp;
}
if (zeroRttWriteCipherTmp) {
zeroRttWriteCipher = std::move(zeroRttWriteCipherTmp);
}
if (handshakeWriteCipherTmp) {
handshakeWriteCipher = std::move(handshakeWriteCipherTmp);
}
if (handshakeReadCipherTmp) {
handshakeReadCipher = handshakeReadCipherTmp;
}
auto rejected = handshake->getZeroRttRejected();
if (rejected) {
zeroRttRejected = std::move(rejected);
}
}
void expectHandshakeCipher(bool expected) {
EXPECT_EQ(handshakeReadCipher != nullptr, expected);
EXPECT_EQ(handshakeWriteCipher != nullptr, expected);
}
void expectOneRttCipher(bool expected, bool oneRttOnly = false) {
if (expected) {
EXPECT_NE(oneRttReadCipher, nullptr);
EXPECT_NE(oneRttWriteCipher.get(), nullptr);
} else {
EXPECT_EQ(oneRttReadCipher, nullptr);
EXPECT_EQ(oneRttWriteCipher.get(), nullptr);
}
if (!oneRttOnly) {
EXPECT_EQ(zeroRttWriteCipher.get(), nullptr);
}
}
void expectZeroRttCipher(bool expected, bool expectOneRtt) {
if (expected) {
EXPECT_NE(zeroRttWriteCipher.get(), nullptr);
} else {
EXPECT_EQ(zeroRttWriteCipher.get(), nullptr);
}
expectOneRttCipher(expectOneRtt, true);
}
BufPtr getHandshakeWriteBytes() {
auto buf = folly::IOBuf::create(0);
if (!cryptoState->initialStream.writeBuffer.empty()) {
buf->prependChain(cryptoState->initialStream.writeBuffer.move());
}
if (!cryptoState->handshakeStream.writeBuffer.empty()) {
buf->prependChain(cryptoState->handshakeStream.writeBuffer.move());
}
if (!cryptoState->oneRttStream.writeBuffer.empty()) {
buf->prependChain(cryptoState->oneRttStream.writeBuffer.move());
}
return buf;
}
void operator()(fizz::DeliverAppData&) {
// do nothing here.
}
void operator()(fizz::WriteToSocket& write) {
serverOutput.push_back(std::move(write));
}
void operator()(fizz::server::ReportEarlyHandshakeSuccess&) {
earlyHandshakeSuccess = true;
}
void operator()(fizz::server::ReportHandshakeSuccess&) {
handshakeSuccess = true;
}
void operator()(fizz::ReportError& error) {
handshakeError = std::move(error);
}
void operator()(fizz::WaitForData&) {
fizzServer->waitForData();
}
void operator()(fizz::server::MutateState& mutator) {
mutator(serverState);
}
void operator()(fizz::server::AttemptVersionFallback&) {}
void operator()(fizz::SecretAvailable&) {}
void operator()(fizz::EndOfData&) {}
class DelayedHolder : public folly::DelayedDestruction {};
folly::EventBase evb;
std::unique_ptr<
QuicClientConnectionState,
folly::DelayedDestruction::Destructor>
conn{nullptr};
ClientHandshake* handshake;
QuicCryptoState* cryptoState;
std::string hostname;
fizz::server::ServerStateMachine machine;
fizz::server::State serverState;
std::unique_ptr<fizz::server::FizzServer<
ClientHandshakeTest,
fizz::server::ServerStateMachine>>
fizzServer;
std::vector<fizz::WriteToSocket> serverOutput;
bool handshakeSuccess{false};
bool earlyHandshakeSuccess{false};
Optional<fizz::ReportError> handshakeError;
folly::IOBufQueue serverReadBuf{folly::IOBufQueue::cacheChainLength()};
std::unique_ptr<DelayedHolder, folly::DelayedDestruction::Destructor> dg;
fizz::Aead::AeadOptions readAeadOptions;
std::unique_ptr<Aead> handshakeWriteCipher;
const Aead* handshakeReadCipher = nullptr;
std::unique_ptr<Aead> oneRttWriteCipher;
const Aead* oneRttReadCipher = nullptr;
std::unique_ptr<Aead> zeroRttWriteCipher;
Optional<bool> zeroRttRejected;
std::shared_ptr<fizz::test::MockCertificateVerifier> verifier;
std::shared_ptr<fizz::client::FizzClientContext> clientCtx;
std::shared_ptr<fizz::server::FizzServerContext> serverCtx;
};
TEST_F(ClientHandshakeTest, TestGetExportedKeyingMaterial) {
// Sanity check. getExportedKeyingMaterial () should return nullptr prior to
// an handshake.
auto ekm =
handshake->getExportedKeyingMaterial("EXPORTER-Some-Label", none, 32);
EXPECT_TRUE(!ekm.has_value());
clientServerRound();
serverClientRound();
handshake->handshakeConfirmed();
ekm = handshake->getExportedKeyingMaterial("EXPORTER-Some-Label", none, 32);
ASSERT_TRUE(ekm.has_value());
EXPECT_EQ(ekm->size(), 32);
ekm = handshake->getExportedKeyingMaterial(
"EXPORTER-Some-Label", ByteRange(), 32);
ASSERT_TRUE(ekm.has_value());
EXPECT_EQ(ekm->size(), 32);
}
TEST_F(ClientHandshakeTest, TestHandshakeSuccess) {
EXPECT_CALL(*verifier, verify(_));
clientServerRound();
EXPECT_EQ(handshake->getPhase(), ClientHandshake::Phase::Initial);
expectHandshakeCipher(false);
serverClientRound();
expectHandshakeCipher(true);
EXPECT_FALSE(zeroRttRejected.has_value());
EXPECT_EQ(handshake->getPhase(), ClientHandshake::Phase::OneRttKeysDerived);
clientServerRound();
expectOneRttCipher(true);
EXPECT_EQ(handshake->getPhase(), ClientHandshake::Phase::OneRttKeysDerived);
handshake->handshakeConfirmed();
EXPECT_EQ(handshake->getPhase(), ClientHandshake::Phase::Established);
EXPECT_FALSE(zeroRttRejected.has_value());
EXPECT_TRUE(handshakeSuccess);
}
TEST_F(ClientHandshakeTest, TestRetryIntegrityVerification) {
// Example obtained from Appendix-A.4 of the QUIC-TLS draft v29.
auto version = static_cast<QuicVersion>(0xff00001d);
uint8_t initialByte = 0xff;
std::vector<uint8_t> dcidVec = {};
ConnectionId dcid(dcidVec);
std::vector<uint8_t> scidVec = {
0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5};
ConnectionId scid(scidVec);
std::string retryToken = R"(token)";
LongHeader header(
LongHeader::Types::Retry, scid, dcid, 0, version, retryToken);
RetryPacket::IntegrityTagType integrityTag = {
0xd1,
0x69,
0x26,
0xd8,
0x1f,
0x6f,
0x9c,
0xa2,
0x95,
0x3a,
0x8a,
0xa4,
0x57,
0x5e,
0x1e,
0x49};
RetryPacket retryPacket(std::move(header), integrityTag, initialByte);
std::vector<uint8_t> odcidVec = {
0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08};
ConnectionId odcid(odcidVec);
EXPECT_TRUE(handshake->verifyRetryIntegrityTag(odcid, retryPacket));
}
TEST_F(ClientHandshakeTest, TestNoErrorAfterAppClose) {
EXPECT_CALL(*verifier, verify(_));
clientServerRound();
serverClientRound();
clientServerRound();
fizzServer->appClose();
evb.loop();
// RTT 1/2 server -> client
EXPECT_NO_THROW(serverClientRound());
expectOneRttCipher(true);
EXPECT_FALSE(zeroRttRejected.has_value());
EXPECT_TRUE(handshakeSuccess);
}
TEST_F(ClientHandshakeTest, TestAppBytesInterpretedAsHandshake) {
EXPECT_CALL(*verifier, verify(_));
clientServerRound();
serverClientRound();
clientServerRound();
fizz::AppWrite w;
w.data = folly::IOBuf::copyBuffer("hey");
fizzServer->appWrite(std::move(w));
evb.loop();
// RTT 1/2 server -> client
serverClientRound();
expectOneRttCipher(true);
EXPECT_FALSE(zeroRttRejected.has_value());
EXPECT_TRUE(handshakeSuccess);
}
class ClientHandshakeCallbackTest : public ClientHandshakeTest {
public:
void setupClientAndServerContext() override {
clientCtx = createClientCtx();
clientCtx->setSupportedVersions({fizz::ProtocolVersion::tls_1_3});
serverCtx->setSupportedVersions({fizz::ProtocolVersion::tls_1_3});
setupZeroRttOnServerCtx(*serverCtx, psk_);
}
void connect() override {
CHECK(!handshake
->connect(
hostname,
std::make_shared<ClientTransportParametersExtension>(
QuicVersion::MVFST,
folly::to<uint32_t>(kDefaultConnectionFlowControlWindow),
folly::to<uint32_t>(kDefaultStreamFlowControlWindow),
folly::to<uint32_t>(kDefaultStreamFlowControlWindow),
folly::to<uint32_t>(kDefaultStreamFlowControlWindow),
folly::to<uint32_t>(kDefaultMaxStreamsBidirectional),
folly::to<uint32_t>(kDefaultMaxStreamsUnidirectional),
kDefaultIdleTimeout,
kDefaultAckDelayExponent,
kDefaultUDPSendPacketLen,
kDefaultActiveConnectionIdLimit,
ConnectionId(std::vector<uint8_t>())))
.hasError());
}
protected:
QuicCachedPsk psk_;
};
TEST_F(ClientHandshakeCallbackTest, TestHandshakeSuccess) {
clientServerRound();
serverClientRound();
clientServerRound();
bool gotEarlyDataParams = false;
conn->earlyDataAppParamsGetter = [&]() -> BufPtr {
gotEarlyDataParams = true;
return {};
};
serverClientRound();
EXPECT_TRUE(gotEarlyDataParams);
}
class ClientHandshakeHRRTest : public ClientHandshakeTest {
public:
~ClientHandshakeHRRTest() override = default;
void setupClientAndServerContext() override {
clientCtx = createClientCtx();
clientCtx->setSupportedGroups(
{fizz::NamedGroup::secp256r1, fizz::NamedGroup::x25519});
clientCtx->setDefaultShares({fizz::NamedGroup::secp256r1});
serverCtx = createServerCtx();
serverCtx->setFactory(std::make_shared<QuicFizzFactory>());
serverCtx->setSupportedGroups({fizz::NamedGroup::x25519});
setupCtxWithTestCert(*serverCtx);
}
};
TEST_F(ClientHandshakeHRRTest, TestFullHRR) {
EXPECT_CALL(*verifier, verify(_));
clientServerRound();
expectHandshakeCipher(false);
EXPECT_EQ(handshake->getPhase(), ClientHandshake::Phase::Initial);
serverClientRound();
EXPECT_EQ(handshake->getPhase(), ClientHandshake::Phase::Handshake);
clientServerRound();
expectOneRttCipher(false);
EXPECT_EQ(handshake->getPhase(), ClientHandshake::Phase::Handshake);
serverClientRound();
expectHandshakeCipher(true);
EXPECT_EQ(handshake->getPhase(), ClientHandshake::Phase::OneRttKeysDerived);
clientServerRound();
expectOneRttCipher(true);
EXPECT_EQ(handshake->getPhase(), ClientHandshake::Phase::OneRttKeysDerived);
EXPECT_FALSE(zeroRttRejected.has_value());
EXPECT_TRUE(handshakeSuccess);
}
TEST_F(ClientHandshakeHRRTest, TestHRROnlyOneRound) {
EXPECT_CALL(*verifier, verify(_)).Times(0);
clientServerRound();
serverClientRound();
clientServerRound();
expectOneRttCipher(false);
EXPECT_FALSE(handshakeSuccess);
}
class ClientHandshakeZeroRttTest : public ClientHandshakeTest {
public:
~ClientHandshakeZeroRttTest() override = default;
void setupClientAndServerContext() override {
clientCtx = createClientCtx();
clientCtx->setSupportedVersions({fizz::ProtocolVersion::tls_1_3});
clientCtx->setSupportedAlpns({"h3", "hq"});
serverCtx->setSupportedVersions({fizz::ProtocolVersion::tls_1_3});
serverCtx->setSupportedAlpns({"h3"});
setupCtxWithTestCert(*serverCtx);
psk = setupZeroRttOnClientCtx(*clientCtx, hostname);
setupZeroRttServer();
}
std::shared_ptr<QuicPskCache> getPskCache() override {
if (!pskCache_) {
pskCache_ = std::make_shared<BasicQuicPskCache>();
pskCache_->putPsk(hostname, psk);
}
return pskCache_;
}
void connect() override {
CHECK(!handshake
->connect(
hostname,
std::make_shared<ClientTransportParametersExtension>(
QuicVersion::MVFST,
folly::to<uint32_t>(kDefaultConnectionFlowControlWindow),
folly::to<uint32_t>(kDefaultStreamFlowControlWindow),
folly::to<uint32_t>(kDefaultStreamFlowControlWindow),
folly::to<uint32_t>(kDefaultStreamFlowControlWindow),
folly::to<uint32_t>(kDefaultMaxStreamsBidirectional),
folly::to<uint32_t>(kDefaultMaxStreamsUnidirectional),
kDefaultIdleTimeout,
kDefaultAckDelayExponent,
kDefaultUDPSendPacketLen,
kDefaultActiveConnectionIdLimit,
ConnectionId(std::vector<uint8_t>())))
.hasError());
}
virtual void setupZeroRttServer() {
setupZeroRttOnServerCtx(*serverCtx, psk);
}
QuicCachedPsk psk;
std::shared_ptr<QuicPskCache> pskCache_;
};
TEST_F(ClientHandshakeZeroRttTest, TestZeroRttSuccess) {
clientServerRound();
EXPECT_EQ(handshake->getPhase(), ClientHandshake::Phase::Initial);
expectZeroRttCipher(true, false);
expectHandshakeCipher(false);
serverClientRound();
expectHandshakeCipher(true);
EXPECT_EQ(handshake->getPhase(), ClientHandshake::Phase::OneRttKeysDerived);
EXPECT_TRUE(zeroRttRejected.has_value());
EXPECT_FALSE(*zeroRttRejected);
expectZeroRttCipher(true, true);
clientServerRound();
handshake->handshakeConfirmed();
EXPECT_EQ(handshake->getPhase(), ClientHandshake::Phase::Established);
EXPECT_EQ(handshake->getApplicationProtocol(), "h3");
}
class ClientHandshakeZeroRttReject : public ClientHandshakeZeroRttTest {
public:
~ClientHandshakeZeroRttReject() override = default;
void setupZeroRttServer() override {}
};
TEST_F(ClientHandshakeZeroRttReject, TestZeroRttRejection) {
clientServerRound();
EXPECT_EQ(handshake->getPhase(), ClientHandshake::Phase::Initial);
expectZeroRttCipher(true, false);
expectHandshakeCipher(false);
serverClientRound();
expectHandshakeCipher(true);
EXPECT_EQ(handshake->getPhase(), ClientHandshake::Phase::OneRttKeysDerived);
EXPECT_TRUE(zeroRttRejected.value_or(false));
// We will still keep the zero rtt key lying around.
expectZeroRttCipher(true, true);
clientServerRound();
handshake->handshakeConfirmed();
EXPECT_EQ(handshake->getPhase(), ClientHandshake::Phase::Established);
}
class ClientHandshakeZeroRttRejectFail : public ClientHandshakeZeroRttTest {
public:
~ClientHandshakeZeroRttRejectFail() override = default;
void setupClientAndServerContext() override {
// set it up so that the identity will not match.
hostname = "foobar";
ClientHandshakeZeroRttTest::setupClientAndServerContext();
}
void setupZeroRttServer() override {}
};
TEST_F(ClientHandshakeZeroRttRejectFail, TestZeroRttRejectionParamsDontMatch) {
// Before the handshake, we have not check the early params.
ASSERT_FALSE(handshake->getCanResendZeroRtt().has_value());
clientServerRound();
// The server hasn't rejected zero-rtt yet so we should still have the psk.
ASSERT_TRUE(pskCache_->getPsk(hostname).has_value());
EXPECT_EQ(handshake->getPhase(), ClientHandshake::Phase::Initial);
expectHandshakeCipher(false);
expectZeroRttCipher(true, false);
EXPECT_NO_THROW(serverClientRound());
// After the handshake with rtt rejection, we should have checked the early
// params and marked them as invalid
ASSERT_TRUE(handshake->getCanResendZeroRtt().has_value());
EXPECT_FALSE(handshake->getCanResendZeroRtt().value());
}
class ClientHandshakeECHPolicyTest : public ClientHandshakeCallbackTest {
public:
void SetUp() override {
ClientHandshakeCallbackTest::SetUp();
auto handshakeBytes =
getHandshakeWriteBytes()->cloneCoalesced()->moveToFbString();
// Sanity Check: The original sni should not be encrypted when ECHPolicy is
// omitted from the FizzServerContext.
EXPECT_NE(handshakeBytes.find("Fizz"), handshakeBytes.size());
}
std::shared_ptr<fizz::ech::Decrypter> getECHDecrypter() override {
return echDecrypter;
}
std::shared_ptr<fizz::client::test::MockECHPolicy> getECHPolicy() override {
return echPolicy;
}
std::shared_ptr<fizz::client::test::MockECHRetryCallback>
getECHRetryCallback() override {
return echCallback;
}
fizz::ech::ECHConfigContentDraft getECHConfigContent() {
fizz::ech::HpkeSymmetricCipherSuite suite{
fizz::hpke::KDFId::Sha256, fizz::hpke::AeadId::TLS_AES_128_GCM_SHA256};
fizz::ech::ECHConfigContentDraft echConfigContent;
echConfigContent.key_config.config_id = 0xFB;
echConfigContent.key_config.kem_id = fizz::hpke::KEMId::secp256r1;
echConfigContent.key_config.public_key =
fizz::openssl::detail::encodeECPublicKey(
::fizz::test::getPublicKey(::fizz::test::kP256PublicKey));
echConfigContent.key_config.cipher_suites = {suite};
echConfigContent.maximum_name_length = 100;
echConfigContent.public_name = folly::IOBuf::copyBuffer("public.dummy.com");
return echConfigContent;
}
fizz::ech::ECHConfig getECHConfig() {
fizz::ech::ECHConfig config;
config.version = fizz::ech::ECHVersion::Draft15;
config.ech_config_content = fizz::encode(getECHConfigContent());
return config;
}
std::shared_ptr<fizz::client::test::MockECHPolicy> echPolicy;
std::shared_ptr<fizz::client::test::MockECHRetryCallback> echCallback;
std::shared_ptr<fizz::ech::ECHConfigManager> echDecrypter;
};
TEST_F(ClientHandshakeECHPolicyTest, TestECHPolicyHandshake) {
echPolicy = std::make_shared<fizz::client::test::MockECHPolicy>();
echCallback = std::make_shared<fizz::client::test::MockECHRetryCallback>();
EXPECT_CALL(*echPolicy, getConfig(_))
.WillOnce(Return(std::vector<fizz::ech::ECHConfig>{getECHConfig()}));
auto kex = fizz::openssl::makeOpenSSLECKeyExchange<fizz::P256>();
kex->setPrivateKey(fizz::test::getPrivateKey(fizz::test::kP256Key));
echDecrypter = std::make_shared<fizz::ech::ECHConfigManager>(
std::make_shared<fizz::DefaultFactory>());
echDecrypter->addDecryptionConfig(
fizz::ech::DecrypterParams{getECHConfig(), kex->clone()});
// Try handshake flow with ECHPolicy set on FizzClientContext.
quic::test::ClientHandshakeECHPolicyTest::SetUp();
auto handshakeBytes =
getHandshakeWriteBytes()->cloneCoalesced()->moveToFbString();
EXPECT_NE(handshakeBytes.find("public.dummy.com"), handshakeBytes.size());
}
} // namespace quic::test