1
0
mirror of https://github.com/facebookincubator/mvfst.git synced 2025-04-18 17:24:03 +03:00
mvfst/quic/client/handshake/ClientHandshake.cpp
Hani Damlaj 00e67c1bf9 mvfst License Header Update
Reviewed By: lnicco

Differential Revision: D33587012

fbshipit-source-id: 972eb440f0156c9c04aa6e8787561b18295c1a97
2022-01-18 13:56:12 -08:00

229 lines
7.3 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 <quic/client/handshake/ClientHandshake.h>
#include <quic/client/handshake/ClientTransportParametersExtension.h>
#include <quic/client/state/ClientStateMachine.h>
#include <quic/fizz/client/handshake/QuicPskCache.h>
#include <quic/state/QuicStreamFunctions.h>
namespace quic {
ClientHandshake::ClientHandshake(QuicClientConnectionState* conn)
: conn_(conn) {}
void ClientHandshake::connect(
folly::Optional<std::string> hostname,
std::shared_ptr<ClientTransportParametersExtension> transportParams) {
transportParams_ = std::move(transportParams);
folly::Optional<CachedServerTransportParameters> cachedServerTransportParams =
connectImpl(std::move(hostname));
throwOnError();
if (conn_->zeroRttWriteCipher) {
if (conn_->qLogger) {
conn_->qLogger->addTransportStateUpdate(kZeroRttAttempted);
}
// If zero rtt write cipher is derived, it means the cached psk was valid
DCHECK(cachedServerTransportParams);
cacheServerInitialParams(
*conn_,
cachedServerTransportParams->initialMaxData,
cachedServerTransportParams->initialMaxStreamDataBidiLocal,
cachedServerTransportParams->initialMaxStreamDataBidiRemote,
cachedServerTransportParams->initialMaxStreamDataUni,
cachedServerTransportParams->initialMaxStreamsBidi,
cachedServerTransportParams->initialMaxStreamsUni);
updateTransportParamsFromCachedEarlyParams(
*conn_, *cachedServerTransportParams);
}
}
void ClientHandshake::doHandshake(
std::unique_ptr<folly::IOBuf> data,
EncryptionLevel encryptionLevel) {
if (!data) {
return;
}
// TODO: deal with clear text alert messages. It's possible that a MITM who
// mucks with the finished messages could cause the decryption to be invalid
// on the server, which would result in a cleartext close or a cleartext
// alert. We currently switch to 1-rtt ciphers immediately for reads and
// throw away the cleartext cipher for reads, this would result in us
// dropping the alert and timing out instead.
if (phase_ == Phase::Initial) {
// This could be an HRR or a cleartext alert.
handshakeInitiated();
}
// First add it to the right read buffer.
switch (encryptionLevel) {
case EncryptionLevel::Initial:
initialReadBuf_.append(std::move(data));
break;
case EncryptionLevel::Handshake:
handshakeReadBuf_.append(std::move(data));
break;
case EncryptionLevel::EarlyData:
case EncryptionLevel::AppData:
appDataReadBuf_.append(std::move(data));
break;
default:
LOG(FATAL) << "Unhandled EncryptionLevel";
}
// Get the current buffer type the transport is accepting.
waitForData_ = false;
while (!waitForData_) {
switch (getReadRecordLayerEncryptionLevel()) {
case EncryptionLevel::Initial:
processSocketData(initialReadBuf_);
break;
case EncryptionLevel::Handshake:
processSocketData(handshakeReadBuf_);
break;
case EncryptionLevel::EarlyData:
case EncryptionLevel::AppData:
processSocketData(appDataReadBuf_);
break;
default:
LOG(FATAL) << "Unhandled EncryptionLevel";
}
throwOnError();
}
}
void ClientHandshake::handshakeConfirmed() {
phase_ = Phase::Established;
}
ClientHandshake::Phase ClientHandshake::getPhase() const {
return phase_;
}
folly::Optional<ServerTransportParameters>
ClientHandshake::getServerTransportParams() {
return transportParams_->getServerTransportParams();
}
folly::Optional<bool> ClientHandshake::getZeroRttRejected() {
return zeroRttRejected_;
}
void ClientHandshake::computeCiphers(CipherKind kind, folly::ByteRange secret) {
std::unique_ptr<Aead> aead;
std::unique_ptr<PacketNumberCipher> packetNumberCipher;
std::tie(aead, packetNumberCipher) = buildCiphers(kind, secret);
switch (kind) {
case CipherKind::HandshakeWrite:
conn_->handshakeWriteCipher = std::move(aead);
conn_->handshakeWriteHeaderCipher = std::move(packetNumberCipher);
break;
case CipherKind::HandshakeRead:
conn_->readCodec->setHandshakeReadCipher(std::move(aead));
conn_->readCodec->setHandshakeHeaderCipher(std::move(packetNumberCipher));
break;
case CipherKind::OneRttWrite:
conn_->oneRttWriteCipher = std::move(aead);
conn_->oneRttWriteHeaderCipher = std::move(packetNumberCipher);
break;
case CipherKind::OneRttRead:
conn_->readCodec->setOneRttReadCipher(std::move(aead));
conn_->readCodec->setOneRttHeaderCipher(std::move(packetNumberCipher));
break;
case CipherKind::ZeroRttWrite:
getClientConn()->zeroRttWriteCipher = std::move(aead);
getClientConn()->zeroRttWriteHeaderCipher = std::move(packetNumberCipher);
break;
default:
// Report error?
break;
}
}
void ClientHandshake::raiseError(folly::exception_wrapper error) {
error_ = std::move(error);
}
void ClientHandshake::throwOnError() {
if (error_) {
error_.throw_exception();
}
}
void ClientHandshake::waitForData() {
waitForData_ = true;
}
void ClientHandshake::writeDataToStream(
EncryptionLevel encryptionLevel,
Buf data) {
if (encryptionLevel == EncryptionLevel::AppData) {
// Don't write 1-rtt handshake data on the client.
return;
}
auto cryptoStream = getCryptoStream(*conn_->cryptoState, encryptionLevel);
writeDataToQuicStream(*cryptoStream, std::move(data));
}
void ClientHandshake::handshakeInitiated() {
CHECK(phase_ == Phase::Initial);
phase_ = Phase::Handshake;
}
void ClientHandshake::computeZeroRttCipher() {
VLOG(10) << "Computing Client zero rtt keys";
earlyDataAttempted_ = true;
}
void ClientHandshake::computeOneRttCipher(bool earlyDataAccepted) {
// The 1-rtt handshake should have succeeded if we know that the early
// write failed. We currently treat the data as lost.
// TODO: we need to deal with HRR based rejection as well, however we don't
// have an API right now.
if (earlyDataAttempted_ && !earlyDataAccepted) {
if (matchEarlyParameters()) {
zeroRttRejected_ = true;
} else {
// TODO: support app retry of zero rtt data.
error_ = folly::make_exception_wrapper<QuicInternalException>(
"Changing parameters when early data attempted not supported",
LocalErrorCode::EARLY_DATA_REJECTED);
return;
}
} else if (earlyDataAttempted_ && earlyDataAccepted) {
zeroRttRejected_ = false;
}
// After a successful handshake we should send packets with the type of
// ClientCleartext. We assume that by the time we get the data for the QUIC
// stream, the server would have also acked all the client initial packets.
CHECK(phase_ == Phase::Handshake);
phase_ = Phase::OneRttKeysDerived;
}
QuicClientConnectionState* ClientHandshake::getClientConn() {
return conn_;
}
const QuicClientConnectionState* ClientHandshake::getClientConn() const {
return conn_;
}
const std::shared_ptr<ClientTransportParametersExtension>&
ClientHandshake::getClientTransportParameters() const {
return transportParams_;
}
void ClientHandshake::setZeroRttRejectedForTest(bool rejected) {
zeroRttRejected_ = rejected;
}
} // namespace quic