1
0
mirror of https://github.com/facebookincubator/mvfst.git synced 2025-11-10 21:22:20 +03:00
Files
mvfst/quic/client/handshake/ClientHandshake.cpp
Bonnie Xu 2040a13e40 Handling TLS Alert and convert it to a Quic connection error.
Summary:
Handles TLS Alert and converts it to a Quic connection error.
See https://quicwg.org/base-drafts/draft-ietf-quic-tls.html#tls-errors for details.

Note that TLS_HANDSHAKE_FAILED is no longer part of the Initial QUIC Transport Error Codes Entries.
See https://quicwg.org/base-drafts/draft-ietf-quic-transport.html, Initial QUIC Transport Error Codes Entries.

Reviewed By: mjoras

Differential Revision: D15456385

fbshipit-source-id: cec3208f4a01bbd00af0bdd94b0e59dc3e400f28
2019-05-29 14:26:05 -07:00

403 lines
13 KiB
C++

/*
* Copyright (c) Facebook, Inc. and its 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 <fizz/protocol/Protocol.h>
#include <quic/state/QuicStreamFunctions.h>
namespace quic {
ClientHandshake::ClientHandshake(QuicCryptoState& cryptoState)
: cryptoState_(cryptoState), visitor_(*this) {}
void ClientHandshake::connect(
std::shared_ptr<const fizz::client::FizzClientContext> context,
std::shared_ptr<const fizz::CertificateVerifier> verifier,
folly::Optional<std::string> hostname,
folly::Optional<fizz::client::CachedPsk> cachedPsk,
const std::shared_ptr<ClientTransportParametersExtension>& transportParams,
HandshakeCallback* callback) {
transportParams_ = transportParams;
callback_ = callback;
auto ctx = std::make_shared<fizz::client::FizzClientContext>(*context);
ctx->setFactory(std::make_shared<QuicFizzFactory>());
ctx->setCompatibilityMode(false);
// Since Draft-17, EOED should not be sent
ctx->setOmitEarlyRecordLayer(true);
processActions(machine_.processConnect(
state_,
std::move(ctx),
std::move(verifier),
std::move(hostname),
std::move(cachedPsk),
transportParams));
}
void ClientHandshake::doHandshake(
std::unique_ptr<folly::IOBuf> data,
fizz::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.
phase_ = Phase::Handshake;
}
// First add it to the right read buffer.
switch (encryptionLevel) {
case fizz::EncryptionLevel::Plaintext:
initialReadBuf_.append(std::move(data));
break;
case fizz::EncryptionLevel::Handshake:
handshakeReadBuf_.append(std::move(data));
break;
case fizz::EncryptionLevel::EarlyData:
case fizz::EncryptionLevel::AppTraffic:
appDataReadBuf_.append(std::move(data));
break;
}
// Get the current buffer type the transport is accepting.
waitForData_ = false;
while (!waitForData_) {
switch (state_.readRecordLayer()->getEncryptionLevel()) {
case fizz::EncryptionLevel::Plaintext:
processActions(machine_.processSocketData(state_, initialReadBuf_));
break;
case fizz::EncryptionLevel::Handshake:
processActions(machine_.processSocketData(state_, handshakeReadBuf_));
break;
case fizz::EncryptionLevel::EarlyData:
case fizz::EncryptionLevel::AppTraffic:
processActions(machine_.processSocketData(state_, appDataReadBuf_));
break;
}
if (error_) {
error_.throw_exception();
}
}
}
std::unique_ptr<Aead> ClientHandshake::getOneRttWriteCipher() {
if (error_) {
error_.throw_exception();
}
return std::move(oneRttWriteCipher_);
}
std::unique_ptr<Aead> ClientHandshake::getOneRttReadCipher() {
if (error_) {
error_.throw_exception();
}
return std::move(oneRttReadCipher_);
}
std::unique_ptr<Aead> ClientHandshake::getZeroRttWriteCipher() {
if (error_) {
error_.throw_exception();
}
return std::move(zeroRttWriteCipher_);
}
std::unique_ptr<Aead> ClientHandshake::getHandshakeReadCipher() {
if (error_) {
error_.throw_exception();
}
return std::move(handshakeReadCipher_);
}
std::unique_ptr<Aead> ClientHandshake::getHandshakeWriteCipher() {
if (error_) {
error_.throw_exception();
}
return std::move(handshakeWriteCipher_);
}
std::unique_ptr<PacketNumberCipher>
ClientHandshake::getOneRttReadHeaderCipher() {
if (error_) {
error_.throw_exception();
}
return std::move(oneRttReadHeaderCipher_);
}
std::unique_ptr<PacketNumberCipher>
ClientHandshake::getOneRttWriteHeaderCipher() {
if (error_) {
error_.throw_exception();
}
return std::move(oneRttWriteHeaderCipher_);
}
std::unique_ptr<PacketNumberCipher>
ClientHandshake::getHandshakeReadHeaderCipher() {
if (error_) {
error_.throw_exception();
}
return std::move(handshakeReadHeaderCipher_);
}
std::unique_ptr<PacketNumberCipher>
ClientHandshake::getHandshakeWriteHeaderCipher() {
if (error_) {
error_.throw_exception();
}
return std::move(handshakeWriteHeaderCipher_);
}
std::unique_ptr<PacketNumberCipher>
ClientHandshake::getZeroRttWriteHeaderCipher() {
if (error_) {
error_.throw_exception();
}
return std::move(zeroRttWriteHeaderCipher_);
}
/**
* Notify the crypto layer that we received one rtt protected data.
* This allows us to know that the peer has implicitly acked the 1-rtt keys.
*/
void ClientHandshake::onRecvOneRttProtectedData() {
if (phase_ != Phase::Established) {
phase_ = Phase::Established;
}
}
ClientHandshake::Phase ClientHandshake::getPhase() const {
return phase_;
}
folly::Optional<ServerTransportParameters>
ClientHandshake::getServerTransportParams() {
return transportParams_->getServerTransportParams();
}
bool ClientHandshake::isTLSResumed() const {
auto pskType = state_.pskType();
return pskType && *pskType == fizz::PskType::Resumption;
}
folly::Optional<bool> ClientHandshake::getZeroRttRejected() {
return std::move(zeroRttRejected_);
}
const fizz::client::State& ClientHandshake::getState() const {
return state_;
}
const folly::Optional<std::string>& ClientHandshake::getApplicationProtocol()
const {
auto& earlyDataParams = state_.earlyDataParams();
if (earlyDataParams) {
return earlyDataParams->alpn;
} else {
return state_.alpn();
}
}
void ClientHandshake::computeOneRttCipher(
const fizz::client::ReportHandshakeSuccess& handshakeSuccess) {
// 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_ && !handshakeSuccess.earlyDataAccepted) {
if (fizz::client::earlyParametersMatch(state_)) {
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;
}
}
// 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.
phase_ = Phase::OneRttKeysDerived;
}
void ClientHandshake::computeZeroRttCipher() {
VLOG(10) << "Computing Client zero rtt keys";
CHECK(state_.earlyDataParams().hasValue());
earlyDataAttempted_ = true;
}
void ClientHandshake::processActions(fizz::client::Actions actions) {
for (auto& action : actions) {
boost::apply_visitor(visitor_, action);
}
}
ClientHandshake::ActionMoveVisitor::ActionMoveVisitor(ClientHandshake& client)
: client_(client) {}
void ClientHandshake::ActionMoveVisitor::operator()(fizz::DeliverAppData&) {
client_.error_ = folly::make_exception_wrapper<QuicTransportException>(
"Invalid app data on crypto stream",
TransportErrorCode::PROTOCOL_VIOLATION);
}
void ClientHandshake::ActionMoveVisitor::operator()(
fizz::WriteToSocket& write) {
for (auto& content : write.contents) {
auto& cryptoState = client_.cryptoState_;
if (content.encryptionLevel == fizz::EncryptionLevel::AppTraffic) {
// Don't write 1-rtt handshake data on the client.
continue;
}
auto cryptoStream = getCryptoStream(cryptoState, content.encryptionLevel);
writeDataToQuicStream(*cryptoStream, std::move(content.data));
}
}
void ClientHandshake::ActionMoveVisitor::operator()(
fizz::client::ReportEarlyHandshakeSuccess&) {
client_.computeZeroRttCipher();
}
void ClientHandshake::ActionMoveVisitor::operator()(
fizz::client::ReportHandshakeSuccess& handshakeSuccess) {
client_.computeOneRttCipher(handshakeSuccess);
}
void ClientHandshake::ActionMoveVisitor::operator()(
fizz::client::ReportEarlyWriteFailed&) {
LOG(DFATAL) << "QUIC TLS app data write";
}
void ClientHandshake::ActionMoveVisitor::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_.error_ = folly::make_exception_wrapper<QuicTransportException>(
errMsg.toStdString(), static_cast<TransportErrorCode>(alertNum));
} else {
client_.error_ = folly::make_exception_wrapper<QuicTransportException>(
errMsg.toStdString(),
static_cast<TransportErrorCode>(
fizz::AlertDescription::internal_error));
}
}
void ClientHandshake::ActionMoveVisitor::operator()(fizz::WaitForData&) {
client_.waitForData_ = true;
}
void ClientHandshake::ActionMoveVisitor::operator()(
fizz::client::MutateState& mutator) {
mutator(client_.state_);
}
void ClientHandshake::ActionMoveVisitor::operator()(
fizz::client::NewCachedPsk& newCachedPsk) {
if (client_.callback_) {
client_.callback_->onNewCachedPsk(newCachedPsk);
}
}
void ClientHandshake::ActionMoveVisitor::operator()(fizz::EndOfData&) {
client_.error_ = folly::make_exception_wrapper<QuicTransportException>(
"unexpected close notify", TransportErrorCode::INTERNAL_ERROR);
}
void ClientHandshake::ActionMoveVisitor::operator()(
fizz::SecretAvailable& secretAvailable) {
QuicFizzFactory factory;
folly::variant_match(
secretAvailable.secret.type,
[&](fizz::EarlySecrets earlySecrets) {
switch (earlySecrets) {
case fizz::EarlySecrets::ClientEarlyTraffic: {
auto cipher = client_.state_.earlyDataParams()->cipher;
auto keyScheduler =
client_.state_.context()->getFactory()->makeKeyScheduler(
cipher);
client_.zeroRttWriteCipher_ =
fizz::Protocol::deriveRecordAeadWithLabel(
*client_.state_.context()->getFactory(),
*keyScheduler,
cipher,
folly::range(secretAvailable.secret.secret),
kQuicKeyLabel,
kQuicIVLabel);
client_.zeroRttWriteHeaderCipher_ = makePacketNumberCipher(
&factory, folly::range(secretAvailable.secret.secret), cipher);
break;
}
default:
break;
}
},
[&](fizz::HandshakeSecrets handshakeSecrets) {
auto aead = fizz::Protocol::deriveRecordAeadWithLabel(
*client_.state_.context()->getFactory(),
*client_.state_.keyScheduler(),
*client_.state_.cipher(),
folly::range(secretAvailable.secret.secret),
kQuicKeyLabel,
kQuicIVLabel);
auto headerCipher = makePacketNumberCipher(
&factory,
folly::range(secretAvailable.secret.secret),
*client_.state_.cipher());
switch (handshakeSecrets) {
case fizz::HandshakeSecrets::ClientHandshakeTraffic:
client_.handshakeWriteCipher_ = std::move(aead);
client_.handshakeWriteHeaderCipher_ = std::move(headerCipher);
break;
case fizz::HandshakeSecrets::ServerHandshakeTraffic:
client_.handshakeReadCipher_ = std::move(aead);
client_.handshakeReadHeaderCipher_ = std::move(headerCipher);
break;
}
},
[&](fizz::AppTrafficSecrets appSecrets) {
auto aead = fizz::Protocol::deriveRecordAeadWithLabel(
*client_.state_.context()->getFactory(),
*client_.state_.keyScheduler(),
*client_.state_.cipher(),
folly::range(secretAvailable.secret.secret),
kQuicKeyLabel,
kQuicIVLabel);
auto appHeaderCipher = makePacketNumberCipher(
&factory,
folly::range(secretAvailable.secret.secret),
*client_.state_.cipher());
switch (appSecrets) {
case fizz::AppTrafficSecrets::ClientAppTraffic:
client_.oneRttWriteCipher_ = std::move(aead);
client_.oneRttWriteHeaderCipher_ = std::move(appHeaderCipher);
break;
case fizz::AppTrafficSecrets::ServerAppTraffic:
client_.oneRttReadCipher_ = std::move(aead);
client_.oneRttReadHeaderCipher_ = std::move(appHeaderCipher);
break;
}
},
[&](auto) {});
}
} // namespace quic