blob: 2bad8ecfdcd2d1e59e69aed04797f391cf9681ad [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/third_party/quiche/src/quic/core/quic_crypto_server_stream.h"
#include <memory>
#include <string>
#include "absl/base/macros.h"
#include "absl/strings/string_view.h"
#include "third_party/boringssl/src/include/openssl/sha.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.h"
#include "net/third_party/quiche/src/common/platform/api/quiche_text_utils.h"
namespace quic {
class QuicCryptoServerStream::ProcessClientHelloCallback
: public ProcessClientHelloResultCallback {
public:
ProcessClientHelloCallback(
QuicCryptoServerStream* parent,
const QuicReferenceCountedPointer<
ValidateClientHelloResultCallback::Result>& result)
: parent_(parent), result_(result) {}
void Run(
QuicErrorCode error,
const std::string& error_details,
std::unique_ptr<CryptoHandshakeMessage> message,
std::unique_ptr<DiversificationNonce> diversification_nonce,
std::unique_ptr<ProofSource::Details> proof_source_details) override {
if (parent_ == nullptr) {
return;
}
parent_->FinishProcessingHandshakeMessageAfterProcessClientHello(
*result_, error, error_details, std::move(message),
std::move(diversification_nonce), std::move(proof_source_details));
}
void Cancel() { parent_ = nullptr; }
private:
QuicCryptoServerStream* parent_;
QuicReferenceCountedPointer<ValidateClientHelloResultCallback::Result>
result_;
};
QuicCryptoServerStream::QuicCryptoServerStream(
const QuicCryptoServerConfig* crypto_config,
QuicCompressedCertsCache* compressed_certs_cache,
QuicSession* session,
QuicCryptoServerStreamBase::Helper* helper)
: QuicCryptoServerStreamBase(session),
QuicCryptoHandshaker(this, session),
session_(session),
delegate_(session),
crypto_config_(crypto_config),
compressed_certs_cache_(compressed_certs_cache),
signed_config_(new QuicSignedServerConfig),
helper_(helper),
num_handshake_messages_(0),
num_handshake_messages_with_server_nonces_(0),
send_server_config_update_cb_(nullptr),
num_server_config_update_messages_sent_(0),
zero_rtt_attempted_(false),
chlo_packet_size_(0),
validate_client_hello_cb_(nullptr),
process_client_hello_cb_(nullptr),
encryption_established_(false),
one_rtt_keys_available_(false),
one_rtt_packet_decrypted_(false),
crypto_negotiated_params_(new QuicCryptoNegotiatedParameters) {}
QuicCryptoServerStream::~QuicCryptoServerStream() {
CancelOutstandingCallbacks();
}
void QuicCryptoServerStream::CancelOutstandingCallbacks() {
// Detach from the validation callback. Calling this multiple times is safe.
if (validate_client_hello_cb_ != nullptr) {
validate_client_hello_cb_->Cancel();
validate_client_hello_cb_ = nullptr;
}
if (send_server_config_update_cb_ != nullptr) {
send_server_config_update_cb_->Cancel();
send_server_config_update_cb_ = nullptr;
}
if (process_client_hello_cb_ != nullptr) {
process_client_hello_cb_->Cancel();
process_client_hello_cb_ = nullptr;
}
}
void QuicCryptoServerStream::OnHandshakeMessage(
const CryptoHandshakeMessage& message) {
QuicCryptoHandshaker::OnHandshakeMessage(message);
++num_handshake_messages_;
chlo_packet_size_ = session()->connection()->GetCurrentPacket().length();
// Do not process handshake messages after the handshake is confirmed.
if (one_rtt_keys_available_) {
OnUnrecoverableError(QUIC_CRYPTO_MESSAGE_AFTER_HANDSHAKE_COMPLETE,
"Unexpected handshake message from client");
return;
}
if (message.tag() != kCHLO) {
OnUnrecoverableError(QUIC_INVALID_CRYPTO_MESSAGE_TYPE,
"Handshake packet not CHLO");
return;
}
if (validate_client_hello_cb_ != nullptr ||
process_client_hello_cb_ != nullptr) {
// Already processing some other handshake message. The protocol
// does not allow for clients to send multiple handshake messages
// before the server has a chance to respond.
OnUnrecoverableError(QUIC_CRYPTO_MESSAGE_WHILE_VALIDATING_CLIENT_HELLO,
"Unexpected handshake message while processing CHLO");
return;
}
chlo_hash_ =
CryptoUtils::HashHandshakeMessage(message, Perspective::IS_SERVER);
std::unique_ptr<ValidateCallback> cb(new ValidateCallback(this));
DCHECK(validate_client_hello_cb_ == nullptr);
DCHECK(process_client_hello_cb_ == nullptr);
validate_client_hello_cb_ = cb.get();
crypto_config_->ValidateClientHello(
message, GetClientAddress(), session()->connection()->self_address(),
transport_version(), session()->connection()->clock(), signed_config_,
std::move(cb));
}
void QuicCryptoServerStream::FinishProcessingHandshakeMessage(
QuicReferenceCountedPointer<ValidateClientHelloResultCallback::Result>
result,
std::unique_ptr<ProofSource::Details> details) {
// Clear the callback that got us here.
DCHECK(validate_client_hello_cb_ != nullptr);
DCHECK(process_client_hello_cb_ == nullptr);
validate_client_hello_cb_ = nullptr;
std::unique_ptr<ProcessClientHelloCallback> cb(
new ProcessClientHelloCallback(this, result));
process_client_hello_cb_ = cb.get();
ProcessClientHello(result, std::move(details), std::move(cb));
}
void QuicCryptoServerStream::
FinishProcessingHandshakeMessageAfterProcessClientHello(
const ValidateClientHelloResultCallback::Result& result,
QuicErrorCode error,
const std::string& error_details,
std::unique_ptr<CryptoHandshakeMessage> reply,
std::unique_ptr<DiversificationNonce> diversification_nonce,
std::unique_ptr<ProofSource::Details> proof_source_details) {
// Clear the callback that got us here.
DCHECK(process_client_hello_cb_ != nullptr);
DCHECK(validate_client_hello_cb_ == nullptr);
process_client_hello_cb_ = nullptr;
proof_source_details_ = std::move(proof_source_details);
const CryptoHandshakeMessage& message = result.client_hello;
if (error != QUIC_NO_ERROR) {
OnUnrecoverableError(error, error_details);
return;
}
if (reply->tag() != kSHLO) {
session()->connection()->set_fully_pad_crypto_handshake_packets(
crypto_config_->pad_rej());
// Send REJ in plaintext.
SendHandshakeMessage(*reply, ENCRYPTION_INITIAL);
return;
}
// If we are returning a SHLO then we accepted the handshake. Now
// process the negotiated configuration options as part of the
// session config.
QuicConfig* config = session()->config();
OverrideQuicConfigDefaults(config);
std::string process_error_details;
const QuicErrorCode process_error =
config->ProcessPeerHello(message, CLIENT, &process_error_details);
if (process_error != QUIC_NO_ERROR) {
OnUnrecoverableError(process_error, process_error_details);
return;
}
session()->OnConfigNegotiated();
config->ToHandshakeMessage(reply.get(), session()->transport_version());
// Receiving a full CHLO implies the client is prepared to decrypt with
// the new server write key. We can start to encrypt with the new server
// write key.
//
// NOTE: the SHLO will be encrypted with the new server write key.
delegate_->OnNewEncryptionKeyAvailable(
ENCRYPTION_ZERO_RTT,
std::move(crypto_negotiated_params_->initial_crypters.encrypter));
delegate_->OnNewDecryptionKeyAvailable(
ENCRYPTION_ZERO_RTT,
std::move(crypto_negotiated_params_->initial_crypters.decrypter),
/*set_alternative_decrypter=*/false,
/*latch_once_used=*/false);
delegate_->SetDefaultEncryptionLevel(ENCRYPTION_ZERO_RTT);
delegate_->DiscardOldDecryptionKey(ENCRYPTION_INITIAL);
session()->connection()->SetDiversificationNonce(*diversification_nonce);
session()->connection()->set_fully_pad_crypto_handshake_packets(
crypto_config_->pad_shlo());
// Send SHLO in ENCRYPTION_ZERO_RTT.
SendHandshakeMessage(*reply, ENCRYPTION_ZERO_RTT);
delegate_->OnNewEncryptionKeyAvailable(
ENCRYPTION_FORWARD_SECURE,
std::move(crypto_negotiated_params_->forward_secure_crypters.encrypter));
delegate_->OnNewDecryptionKeyAvailable(
ENCRYPTION_FORWARD_SECURE,
std::move(crypto_negotiated_params_->forward_secure_crypters.decrypter),
/*set_alternative_decrypter=*/true,
/*latch_once_used=*/false);
encryption_established_ = true;
one_rtt_keys_available_ = true;
delegate_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
delegate_->DiscardOldEncryptionKey(ENCRYPTION_INITIAL);
}
void QuicCryptoServerStream::SendServerConfigUpdate(
const CachedNetworkParameters* cached_network_params) {
if (!one_rtt_keys_available_) {
return;
}
if (send_server_config_update_cb_ != nullptr) {
QUIC_DVLOG(1)
<< "Skipped server config update since one is already in progress";
return;
}
std::unique_ptr<SendServerConfigUpdateCallback> cb(
new SendServerConfigUpdateCallback(this));
send_server_config_update_cb_ = cb.get();
crypto_config_->BuildServerConfigUpdateMessage(
session()->transport_version(), chlo_hash_,
previous_source_address_tokens_, session()->connection()->self_address(),
GetClientAddress(), session()->connection()->clock(),
session()->connection()->random_generator(), compressed_certs_cache_,
*crypto_negotiated_params_, cached_network_params, std::move(cb));
}
QuicCryptoServerStream::SendServerConfigUpdateCallback::
SendServerConfigUpdateCallback(QuicCryptoServerStream* parent)
: parent_(parent) {}
void QuicCryptoServerStream::SendServerConfigUpdateCallback::Cancel() {
parent_ = nullptr;
}
// From BuildServerConfigUpdateMessageResultCallback
void QuicCryptoServerStream::SendServerConfigUpdateCallback::Run(
bool ok,
const CryptoHandshakeMessage& message) {
if (parent_ == nullptr) {
return;
}
parent_->FinishSendServerConfigUpdate(ok, message);
}
void QuicCryptoServerStream::FinishSendServerConfigUpdate(
bool ok,
const CryptoHandshakeMessage& message) {
// Clear the callback that got us here.
DCHECK(send_server_config_update_cb_ != nullptr);
send_server_config_update_cb_ = nullptr;
if (!ok) {
QUIC_DVLOG(1) << "Server: Failed to build server config update (SCUP)!";
return;
}
QUIC_DVLOG(1) << "Server: Sending server config update: "
<< message.DebugString();
if (!session()->use_write_or_buffer_data_at_level() &&
!QuicVersionUsesCryptoFrames(transport_version())) {
const QuicData& data = message.GetSerialized();
WriteOrBufferData(absl::string_view(data.data(), data.length()), false,
nullptr);
} else {
// Send server config update in ENCRYPTION_FORWARD_SECURE.
SendHandshakeMessage(message, ENCRYPTION_FORWARD_SECURE);
}
++num_server_config_update_messages_sent_;
}
bool QuicCryptoServerStream::IsZeroRtt() const {
return num_handshake_messages_ == 1 &&
num_handshake_messages_with_server_nonces_ == 0;
}
bool QuicCryptoServerStream::IsResumption() const {
// QUIC Crypto doesn't have a non-0-RTT resumption mode.
return IsZeroRtt();
}
int QuicCryptoServerStream::NumServerConfigUpdateMessagesSent() const {
return num_server_config_update_messages_sent_;
}
const CachedNetworkParameters*
QuicCryptoServerStream::PreviousCachedNetworkParams() const {
return previous_cached_network_params_.get();
}
bool QuicCryptoServerStream::ResumptionAttempted() const {
return zero_rtt_attempted_;
}
void QuicCryptoServerStream::SetPreviousCachedNetworkParams(
CachedNetworkParameters cached_network_params) {
previous_cached_network_params_.reset(
new CachedNetworkParameters(cached_network_params));
}
void QuicCryptoServerStream::OnPacketDecrypted(EncryptionLevel level) {
if (level == ENCRYPTION_FORWARD_SECURE) {
one_rtt_packet_decrypted_ = true;
delegate_->NeuterHandshakeData();
}
}
void QuicCryptoServerStream::OnHandshakeDoneReceived() {
DCHECK(false);
}
bool QuicCryptoServerStream::ShouldSendExpectCTHeader() const {
return signed_config_->proof.send_expect_ct_header;
}
const ProofSource::Details* QuicCryptoServerStream::ProofSourceDetails() const {
return proof_source_details_.get();
}
bool QuicCryptoServerStream::GetBase64SHA256ClientChannelID(
std::string* output) const {
if (!encryption_established() ||
crypto_negotiated_params_->channel_id.empty()) {
return false;
}
const std::string& channel_id(crypto_negotiated_params_->channel_id);
uint8_t digest[SHA256_DIGEST_LENGTH];
SHA256(reinterpret_cast<const uint8_t*>(channel_id.data()), channel_id.size(),
digest);
quiche::QuicheTextUtils::Base64Encode(digest, ABSL_ARRAYSIZE(digest), output);
return true;
}
ssl_early_data_reason_t QuicCryptoServerStream::EarlyDataReason() const {
if (IsZeroRtt()) {
return ssl_early_data_accepted;
}
if (zero_rtt_attempted_) {
return ssl_early_data_session_not_resumed;
}
return ssl_early_data_no_session_offered;
}
bool QuicCryptoServerStream::encryption_established() const {
return encryption_established_;
}
bool QuicCryptoServerStream::one_rtt_keys_available() const {
return one_rtt_keys_available_;
}
const QuicCryptoNegotiatedParameters&
QuicCryptoServerStream::crypto_negotiated_params() const {
return *crypto_negotiated_params_;
}
CryptoMessageParser* QuicCryptoServerStream::crypto_message_parser() {
return QuicCryptoHandshaker::crypto_message_parser();
}
HandshakeState QuicCryptoServerStream::GetHandshakeState() const {
return one_rtt_packet_decrypted_ ? HANDSHAKE_COMPLETE : HANDSHAKE_START;
}
void QuicCryptoServerStream::SetServerApplicationStateForResumption(
std::unique_ptr<ApplicationState> /*state*/) {
// QUIC Crypto doesn't need to remember any application state as part of doing
// 0-RTT resumption, so this function is a no-op.
}
size_t QuicCryptoServerStream::BufferSizeLimitForLevel(
EncryptionLevel level) const {
return QuicCryptoHandshaker::BufferSizeLimitForLevel(level);
}
bool QuicCryptoServerStream::KeyUpdateSupportedLocally() const {
return false;
}
std::unique_ptr<QuicDecrypter>
QuicCryptoServerStream::AdvanceKeysAndCreateCurrentOneRttDecrypter() {
// Key update is only defined in QUIC+TLS.
DCHECK(false);
return nullptr;
}
std::unique_ptr<QuicEncrypter>
QuicCryptoServerStream::CreateCurrentOneRttEncrypter() {
// Key update is only defined in QUIC+TLS.
DCHECK(false);
return nullptr;
}
void QuicCryptoServerStream::ProcessClientHello(
QuicReferenceCountedPointer<ValidateClientHelloResultCallback::Result>
result,
std::unique_ptr<ProofSource::Details> proof_source_details,
std::unique_ptr<ProcessClientHelloResultCallback> done_cb) {
proof_source_details_ = std::move(proof_source_details);
const CryptoHandshakeMessage& message = result->client_hello;
std::string error_details;
if (!helper_->CanAcceptClientHello(
message, GetClientAddress(), session()->connection()->peer_address(),
session()->connection()->self_address(), &error_details)) {
done_cb->Run(QUIC_HANDSHAKE_FAILED, error_details, nullptr, nullptr,
nullptr);
return;
}
absl::string_view user_agent_id;
message.GetStringPiece(quic::kUAID, &user_agent_id);
if (!session()->user_agent_id().has_value() && !user_agent_id.empty()) {
session()->SetUserAgentId(std::string(user_agent_id));
}
if (!result->info.server_nonce.empty()) {
++num_handshake_messages_with_server_nonces_;
}
if (num_handshake_messages_ == 1) {
// Client attempts zero RTT handshake by sending a non-inchoate CHLO.
absl::string_view public_value;
zero_rtt_attempted_ = message.GetStringPiece(kPUBS, &public_value);
}
// Store the bandwidth estimate from the client.
if (result->cached_network_params.bandwidth_estimate_bytes_per_second() > 0) {
previous_cached_network_params_.reset(
new CachedNetworkParameters(result->cached_network_params));
}
previous_source_address_tokens_ = result->info.source_address_tokens;
QuicConnection* connection = session()->connection();
crypto_config_->ProcessClientHello(
result, /*reject_only=*/false, connection->connection_id(),
connection->self_address(), GetClientAddress(), connection->version(),
session()->supported_versions(), connection->clock(),
connection->random_generator(), compressed_certs_cache_,
crypto_negotiated_params_, signed_config_,
QuicCryptoStream::CryptoMessageFramingOverhead(
transport_version(), connection->connection_id()),
chlo_packet_size_, std::move(done_cb));
}
void QuicCryptoServerStream::OverrideQuicConfigDefaults(
QuicConfig* /*config*/) {}
QuicCryptoServerStream::ValidateCallback::ValidateCallback(
QuicCryptoServerStream* parent)
: parent_(parent) {}
void QuicCryptoServerStream::ValidateCallback::Cancel() {
parent_ = nullptr;
}
void QuicCryptoServerStream::ValidateCallback::Run(
QuicReferenceCountedPointer<Result> result,
std::unique_ptr<ProofSource::Details> details) {
if (parent_ != nullptr) {
parent_->FinishProcessingHandshakeMessage(std::move(result),
std::move(details));
}
}
const QuicSocketAddress QuicCryptoServerStream::GetClientAddress() {
return session()->connection()->peer_address();
}
} // namespace quic