quic/fizz/client/handshake/FizzClientHandshake.cpp (295 lines of code) (raw):
/*
* 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 <quic/fizz/client/handshake/FizzClientHandshake.h>
#include <quic/client/state/ClientStateMachine.h>
#include <quic/codec/QuicPacketBuilder.h>
#include <quic/fizz/client/handshake/FizzClientExtensions.h>
#include <quic/fizz/client/handshake/FizzClientQuicHandshakeContext.h>
#include <quic/fizz/client/handshake/QuicPskCache.h>
#include <quic/fizz/handshake/FizzBridge.h>
#include <quic/fizz/handshake/FizzRetryIntegrityTagGenerator.h>
#include <fizz/client/EarlyDataRejectionPolicy.h>
#include <fizz/protocol/Protocol.h>
#include <fizz/crypto/aead/AESGCM128.h>
namespace quic {
FizzClientHandshake::FizzClientHandshake(
QuicClientConnectionState* conn,
std::shared_ptr<FizzClientQuicHandshakeContext> fizzContext,
std::unique_ptr<FizzCryptoFactory> cryptoFactory)
: ClientHandshake(conn),
cryptoFactory_(std::move(cryptoFactory)),
fizzContext_(std::move(fizzContext)) {
CHECK(cryptoFactory_->getFizzFactory());
}
folly::Optional<CachedServerTransportParameters>
FizzClientHandshake::connectImpl(folly::Optional<std::string> hostname) {
// Look up psk
folly::Optional<QuicCachedPsk> quicCachedPsk = getPsk(hostname);
folly::Optional<fizz::client::CachedPsk> cachedPsk;
folly::Optional<CachedServerTransportParameters> transportParams;
if (quicCachedPsk) {
cachedPsk = std::move(quicCachedPsk->cachedPsk);
transportParams = std::move(quicCachedPsk->transportParams);
}
// Setup context for this handshake.
auto context = std::make_shared<fizz::client::FizzClientContext>(
*fizzContext_->getContext());
context->setFactory(cryptoFactory_->getFizzFactory());
context->setSupportedCiphers({fizz::CipherSuite::TLS_AES_128_GCM_SHA256});
context->setCompatibilityMode(false);
// Since Draft-17, EOED should not be sent
context->setOmitEarlyRecordLayer(true);
processActions(machine_.processConnect(
state_,
std::move(context),
fizzContext_->getCertificateVerifier(),
std::move(hostname),
std::move(cachedPsk),
std::make_shared<FizzClientExtensions>(getClientTransportParameters()),
folly::none));
return transportParams;
}
folly::Optional<QuicCachedPsk> FizzClientHandshake::getPsk(
const folly::Optional<std::string>& hostname) const {
auto quicCachedPsk = fizzContext_->getPsk(hostname);
if (!quicCachedPsk) {
return folly::none;
}
// TODO T32658838 better API to disable early data for current connection
const QuicClientConnectionState* conn = getClientConn();
if (!conn->transportSettings.attemptEarlyData) {
quicCachedPsk->cachedPsk.maxEarlyDataSize = 0;
} else if (
conn->earlyDataAppParamsValidator &&
!conn->earlyDataAppParamsValidator(
quicCachedPsk->cachedPsk.alpn,
folly::IOBuf::copyBuffer(quicCachedPsk->appParams))) {
quicCachedPsk->cachedPsk.maxEarlyDataSize = 0;
// Do not remove psk here, will let application decide
}
return quicCachedPsk;
}
void FizzClientHandshake::removePsk(
const folly::Optional<std::string>& hostname) {
fizzContext_->removePsk(hostname);
}
const CryptoFactory& FizzClientHandshake::getCryptoFactory() const {
return *cryptoFactory_;
}
const folly::Optional<std::string>&
FizzClientHandshake::getApplicationProtocol() const {
auto& earlyDataParams = state_.earlyDataParams();
if (earlyDataParams) {
return earlyDataParams->alpn;
} else {
return state_.alpn();
}
}
bool FizzClientHandshake::verifyRetryIntegrityTag(
const ConnectionId& originalDstConnId,
const RetryPacket& retryPacket) {
PseudoRetryPacketBuilder pseudoRetryPacketBuilder(
retryPacket.initialByte,
retryPacket.header.getSourceConnId(),
retryPacket.header.getDestinationConnId(),
originalDstConnId,
retryPacket.header.getVersion(),
folly::IOBuf::copyBuffer(retryPacket.header.getToken()));
Buf pseudoRetryPacket = std::move(pseudoRetryPacketBuilder).buildPacket();
FizzRetryIntegrityTagGenerator retryIntegrityTagGenerator;
auto expectedIntegrityTag = retryIntegrityTagGenerator.getRetryIntegrityTag(
retryPacket.header.getVersion(), pseudoRetryPacket.get());
return folly::IOBufEqualTo()(
*expectedIntegrityTag, *retryPacket.integrityTag);
}
bool FizzClientHandshake::isTLSResumed() const {
auto pskType = state_.pskType();
return pskType && *pskType == fizz::PskType::Resumption;
}
EncryptionLevel FizzClientHandshake::getReadRecordLayerEncryptionLevel() {
return getEncryptionLevelFromFizz(
state_.readRecordLayer()->getEncryptionLevel());
}
void FizzClientHandshake::processSocketData(folly::IOBufQueue& queue) {
processActions(
machine_.processSocketData(state_, queue, fizz::Aead::AeadOptions()));
}
bool FizzClientHandshake::matchEarlyParameters() {
return fizz::client::earlyParametersMatch(state_);
}
std::pair<std::unique_ptr<Aead>, std::unique_ptr<PacketNumberCipher>>
FizzClientHandshake::buildCiphers(CipherKind kind, folly::ByteRange secret) {
bool isEarlyTraffic = kind == CipherKind::ZeroRttWrite;
fizz::CipherSuite cipher =
isEarlyTraffic ? state_.earlyDataParams()->cipher : *state_.cipher();
std::unique_ptr<fizz::KeyScheduler> keySchedulerPtr = isEarlyTraffic
? state_.context()->getFactory()->makeKeyScheduler(cipher)
: nullptr;
fizz::KeyScheduler& keyScheduler =
isEarlyTraffic ? *keySchedulerPtr : *state_.keyScheduler();
auto aead = FizzAead::wrap(fizz::Protocol::deriveRecordAeadWithLabel(
*state_.context()->getFactory(),
keyScheduler,
cipher,
secret,
kQuicKeyLabel,
kQuicIVLabel));
auto packetNumberCipher = cryptoFactory_->makePacketNumberCipher(secret);
return {std::move(aead), std::move(packetNumberCipher)};
}
void FizzClientHandshake::onNewCachedPsk(
fizz::client::NewCachedPsk& newCachedPsk) noexcept {
QuicClientConnectionState* conn = getClientConn();
DCHECK(conn->version.has_value());
DCHECK(conn->serverInitialParamsSet_);
QuicCachedPsk quicCachedPsk;
quicCachedPsk.cachedPsk = std::move(newCachedPsk.psk);
quicCachedPsk.transportParams = getServerCachedTransportParameters(*conn);
if (conn->earlyDataAppParamsGetter) {
auto appParams = conn->earlyDataAppParamsGetter();
if (appParams) {
quicCachedPsk.appParams = appParams->moveToFbString().toStdString();
}
}
fizzContext_->putPsk(state_.sni(), std::move(quicCachedPsk));
}
class FizzClientHandshake::ActionMoveVisitor {
public:
explicit ActionMoveVisitor(FizzClientHandshake& client) : client_(client) {}
void operator()(fizz::DeliverAppData&) {
client_.raiseError(folly::make_exception_wrapper<QuicTransportException>(
"Invalid app data on crypto stream",
TransportErrorCode::PROTOCOL_VIOLATION));
}
void operator()(fizz::WriteToSocket& write) {
for (auto& content : write.contents) {
auto encryptionLevel =
getEncryptionLevelFromFizz(content.encryptionLevel);
client_.writeDataToStream(encryptionLevel, std::move(content.data));
}
}
void operator()(fizz::client::ReportEarlyHandshakeSuccess&) {
client_.computeZeroRttCipher();
}
void operator()(fizz::client::ReportHandshakeSuccess& handshakeSuccess) {
client_.computeOneRttCipher(handshakeSuccess.earlyDataAccepted);
}
void operator()(fizz::client::ReportEarlyWriteFailed&) {
LOG(DFATAL) << "QUIC TLS app data write";
}
void operator()(fizz::ReportError& err) {
auto errMsg = err.error.what();
if (errMsg.empty()) {
errMsg = "Error during handshake";
}
auto fe = err.error.get_exception<fizz::FizzException>();
if (fe && fe->getAlert()) {
auto alertNum =
static_cast<std::underlying_type<TransportErrorCode>::type>(
fe->getAlert().value());
alertNum += static_cast<std::underlying_type<TransportErrorCode>::type>(
TransportErrorCode::CRYPTO_ERROR);
client_.raiseError(folly::make_exception_wrapper<QuicTransportException>(
errMsg.toStdString(), static_cast<TransportErrorCode>(alertNum)));
} else {
client_.raiseError(folly::make_exception_wrapper<QuicTransportException>(
errMsg.toStdString(),
static_cast<TransportErrorCode>(
fizz::AlertDescription::internal_error)));
}
}
void operator()(fizz::WaitForData&) {
client_.waitForData();
}
void operator()(fizz::client::MutateState& mutator) {
mutator(client_.state_);
}
void operator()(fizz::client::NewCachedPsk& newCachedPsk) {
client_.onNewCachedPsk(newCachedPsk);
}
void operator()(fizz::EndOfData&) {
client_.raiseError(folly::make_exception_wrapper<QuicTransportException>(
"unexpected close notify", TransportErrorCode::INTERNAL_ERROR));
}
void operator()(fizz::SecretAvailable& secretAvailable) {
switch (secretAvailable.secret.type.type()) {
case fizz::SecretType::Type::EarlySecrets_E:
switch (*secretAvailable.secret.type.asEarlySecrets()) {
case fizz::EarlySecrets::ClientEarlyTraffic:
client_.computeCiphers(
CipherKind::ZeroRttWrite,
folly::range(secretAvailable.secret.secret));
break;
default:
break;
}
break;
case fizz::SecretType::Type::HandshakeSecrets_E:
switch (*secretAvailable.secret.type.asHandshakeSecrets()) {
case fizz::HandshakeSecrets::ClientHandshakeTraffic:
client_.computeCiphers(
CipherKind::HandshakeWrite,
folly::range(secretAvailable.secret.secret));
break;
case fizz::HandshakeSecrets::ServerHandshakeTraffic:
client_.computeCiphers(
CipherKind::HandshakeRead,
folly::range(secretAvailable.secret.secret));
break;
}
break;
case fizz::SecretType::Type::AppTrafficSecrets_E:
switch (*secretAvailable.secret.type.asAppTrafficSecrets()) {
case fizz::AppTrafficSecrets::ClientAppTraffic:
client_.computeCiphers(
CipherKind::OneRttWrite,
folly::range(secretAvailable.secret.secret));
break;
case fizz::AppTrafficSecrets::ServerAppTraffic:
client_.computeCiphers(
CipherKind::OneRttRead,
folly::range(secretAvailable.secret.secret));
break;
}
break;
case fizz::SecretType::Type::MasterSecrets_E:
break;
}
}
private:
FizzClientHandshake& client_;
};
void FizzClientHandshake::processActions(fizz::client::Actions actions) {
ActionMoveVisitor visitor(*this);
for (auto& action : actions) {
switch (action.type()) {
case fizz::client::Action::Type::DeliverAppData_E:
visitor(*action.asDeliverAppData());
break;
case fizz::client::Action::Type::WriteToSocket_E:
visitor(*action.asWriteToSocket());
break;
case fizz::client::Action::Type::ReportHandshakeSuccess_E:
visitor(*action.asReportHandshakeSuccess());
break;
case fizz::client::Action::Type::ReportEarlyHandshakeSuccess_E:
visitor(*action.asReportEarlyHandshakeSuccess());
break;
case fizz::client::Action::Type::ReportEarlyWriteFailed_E:
visitor(*action.asReportEarlyWriteFailed());
break;
case fizz::client::Action::Type::ReportError_E:
visitor(*action.asReportError());
break;
case fizz::client::Action::Type::EndOfData_E:
visitor(*action.asEndOfData());
break;
case fizz::client::Action::Type::MutateState_E:
visitor(*action.asMutateState());
break;
case fizz::client::Action::Type::WaitForData_E:
visitor(*action.asWaitForData());
break;
case fizz::client::Action::Type::NewCachedPsk_E:
visitor(*action.asNewCachedPsk());
break;
case fizz::client::Action::Type::SecretAvailable_E:
visitor(*action.asSecretAvailable());
break;
}
}
}
} // namespace quic