/* * Copyright 2016 The WebRTC Project Authors. All rights reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "webrtc/p2p/quic/quictransportchannel.h" #include #include "net/quic/crypto/proof_source.h" #include "net/quic/crypto/proof_verifier.h" #include "net/quic/crypto/quic_crypto_client_config.h" #include "net/quic/crypto/quic_crypto_server_config.h" #include "net/quic/quic_connection.h" #include "net/quic/quic_crypto_client_stream.h" #include "net/quic/quic_crypto_server_stream.h" #include "net/quic/quic_packet_writer.h" #include "net/quic/quic_protocol.h" #include "webrtc/base/checks.h" #include "webrtc/base/helpers.h" #include "webrtc/base/logging.h" #include "webrtc/base/socket.h" #include "webrtc/base/thread.h" #include "webrtc/p2p/base/common.h" namespace { // QUIC public header constants for net::QuicConnection. These are arbitrary // given that |channel_| only receives packets specific to this channel, // in which case we already know the QUIC packets have the correct destination. const net::QuicConnectionId kConnectionId = 0; const net::IPAddress kConnectionIpAddress(0, 0, 0, 0); const net::IPEndPoint kConnectionIpEndpoint(kConnectionIpAddress, 0); // Arbitrary server port number for net::QuicCryptoClientConfig. const int kQuicServerPort = 0; // QUIC connection timeout. This is large so that |channel_| can // be responsible for connection timeout. const int kIdleConnectionStateLifetime = 1000; // seconds // Length of HKDF input keying material, equal to its number of bytes. // https://tools.ietf.org/html/rfc5869#section-2.2. // TODO(mikescarlett): Verify that input keying material length is correct. const size_t kInputKeyingMaterialLength = 32; // We don't pull the RTP constants from rtputils.h, to avoid a layer violation. const size_t kMinRtpPacketLen = 12; bool IsRtpPacket(const char* data, size_t len) { const uint8_t* u = reinterpret_cast(data); return (len >= kMinRtpPacketLen && (u[0] & 0xC0) == 0x80); } // Function for detecting QUIC packets based off // https://tools.ietf.org/html/draft-tsvwg-quic-protocol-02#section-6. const size_t kMinQuicPacketLen = 2; bool IsQuicPacket(const char* data, size_t len) { const uint8_t* u = reinterpret_cast(data); return (len >= kMinQuicPacketLen && (u[0] & 0x80) == 0); } // Used by QuicCryptoServerConfig to provide dummy proof credentials. // TODO(mikescarlett): Remove when secure P2P QUIC handshake is possible. class DummyProofSource : public net::ProofSource { public: DummyProofSource() {} ~DummyProofSource() override {} // ProofSource override. bool GetProof(const net::IPAddress& server_ip, const std::string& hostname, const std::string& server_config, net::QuicVersion quic_version, base::StringPiece chlo_hash, bool ecdsa_ok, scoped_refptr* out_chain, std::string* out_signature, std::string* out_leaf_cert_sct) override { LOG(LS_INFO) << "GetProof() providing dummy credentials for insecure QUIC"; std::vector certs; certs.push_back("Dummy cert"); *out_chain = new ProofSource::Chain(certs); *out_signature = "Dummy signature"; *out_leaf_cert_sct = "Dummy timestamp"; return true; } }; // Used by QuicCryptoClientConfig to ignore the peer's credentials // and establish an insecure QUIC connection. // TODO(mikescarlett): Remove when secure P2P QUIC handshake is possible. class InsecureProofVerifier : public net::ProofVerifier { public: InsecureProofVerifier() {} ~InsecureProofVerifier() override {} // ProofVerifier override. net::QuicAsyncStatus VerifyProof( const std::string& hostname, const uint16_t port, const std::string& server_config, net::QuicVersion quic_version, base::StringPiece chlo_hash, const std::vector& certs, const std::string& cert_sct, const std::string& signature, const net::ProofVerifyContext* context, std::string* error_details, std::unique_ptr* verify_details, net::ProofVerifierCallback* callback) override { LOG(LS_INFO) << "VerifyProof() ignoring credentials and returning success"; return net::QUIC_SUCCESS; } }; } // namespace namespace cricket { QuicTransportChannel::QuicTransportChannel(TransportChannelImpl* channel) : TransportChannelImpl(channel->transport_name(), channel->component()), worker_thread_(rtc::Thread::Current()), channel_(channel), helper_(worker_thread_) { channel_->SignalWritableState.connect(this, &QuicTransportChannel::OnWritableState); channel_->SignalReadPacket.connect(this, &QuicTransportChannel::OnReadPacket); channel_->SignalSentPacket.connect(this, &QuicTransportChannel::OnSentPacket); channel_->SignalReadyToSend.connect(this, &QuicTransportChannel::OnReadyToSend); channel_->SignalGatheringState.connect( this, &QuicTransportChannel::OnGatheringState); channel_->SignalCandidateGathered.connect( this, &QuicTransportChannel::OnCandidateGathered); channel_->SignalRoleConflict.connect(this, &QuicTransportChannel::OnRoleConflict); channel_->SignalRouteChange.connect(this, &QuicTransportChannel::OnRouteChange); channel_->SignalSelectedCandidatePairChanged.connect( this, &QuicTransportChannel::OnSelectedCandidatePairChanged); channel_->SignalStateChanged.connect( this, &QuicTransportChannel::OnChannelStateChanged); channel_->SignalReceivingState.connect( this, &QuicTransportChannel::OnReceivingState); // Set the QUIC connection timeout. config_.SetIdleConnectionStateLifetime( net::QuicTime::Delta::FromSeconds(kIdleConnectionStateLifetime), net::QuicTime::Delta::FromSeconds(kIdleConnectionStateLifetime)); // Set the bytes reserved for the QUIC connection ID to zero. config_.SetBytesForConnectionIdToSend(0); } QuicTransportChannel::~QuicTransportChannel() {} bool QuicTransportChannel::SetLocalCertificate( const rtc::scoped_refptr& certificate) { if (!certificate) { LOG_J(LS_ERROR, this) << "No local certificate was supplied. Not doing QUIC."; return false; } if (!local_certificate_) { local_certificate_ = certificate; return true; } if (certificate == local_certificate_) { // This may happen during renegotiation. LOG_J(LS_INFO, this) << "Ignoring identical certificate"; return true; } LOG_J(LS_ERROR, this) << "Local certificate of the QUIC connection already set. " "Can't change the local certificate once it's active."; return false; } rtc::scoped_refptr QuicTransportChannel::GetLocalCertificate() const { return local_certificate_; } bool QuicTransportChannel::SetSslRole(rtc::SSLRole role) { if (ssl_role_ && *ssl_role_ == role) { LOG_J(LS_WARNING, this) << "Ignoring SSL Role identical to current role."; return true; } if (quic_state_ != QUIC_TRANSPORT_CONNECTED) { ssl_role_ = rtc::Optional(role); return true; } LOG_J(LS_ERROR, this) << "SSL Role can't be reversed after the session is setup."; return false; } bool QuicTransportChannel::GetSslRole(rtc::SSLRole* role) const { if (!ssl_role_) { return false; } *role = *ssl_role_; return true; } bool QuicTransportChannel::SetRemoteFingerprint(const std::string& digest_alg, const uint8_t* digest, size_t digest_len) { if (digest_alg.empty()) { RTC_DCHECK(!digest_len); LOG_J(LS_ERROR, this) << "Remote peer doesn't support digest algorithm."; return false; } std::string remote_fingerprint_value(reinterpret_cast(digest), digest_len); // Once we have the local certificate, the same remote fingerprint can be set // multiple times. This may happen during renegotiation. if (remote_fingerprint_ && remote_fingerprint_->value == remote_fingerprint_value && remote_fingerprint_->algorithm == digest_alg) { LOG_J(LS_INFO, this) << "Ignoring identical remote fingerprint and algorithm"; return true; } remote_fingerprint_ = rtc::Optional(RemoteFingerprint()); remote_fingerprint_->value = remote_fingerprint_value; remote_fingerprint_->algorithm = digest_alg; return true; } bool QuicTransportChannel::ExportKeyingMaterial(const std::string& label, const uint8_t* context, size_t context_len, bool use_context, uint8_t* result, size_t result_len) { std::string quic_context(reinterpret_cast(context), context_len); std::string quic_result; if (!quic_->ExportKeyingMaterial(label, quic_context, result_len, &quic_result)) { return false; } quic_result.copy(reinterpret_cast(result), result_len); return true; } bool QuicTransportChannel::GetSrtpCryptoSuite(int* cipher) { *cipher = rtc::SRTP_AES128_CM_SHA1_80; return true; } // Called from upper layers to send a media packet. int QuicTransportChannel::SendPacket(const char* data, size_t size, const rtc::PacketOptions& options, int flags) { if ((flags & PF_SRTP_BYPASS) && IsRtpPacket(data, size)) { return channel_->SendPacket(data, size, options); } LOG(LS_ERROR) << "Failed to send an invalid SRTP bypass packet using QUIC."; return -1; } // The state transition logic here is as follows: // - Before the QUIC handshake is complete, the QUIC channel is unwritable. // - When |channel_| goes writable we start the QUIC handshake. // - Once the QUIC handshake completes, the state is that of the // |channel_| again. void QuicTransportChannel::OnWritableState(TransportChannel* channel) { ASSERT(rtc::Thread::Current() == worker_thread_); ASSERT(channel == channel_.get()); LOG_J(LS_VERBOSE, this) << "QuicTransportChannel: channel writable state changed to " << channel_->writable(); switch (quic_state_) { case QUIC_TRANSPORT_NEW: // Start the QUIC handshake when |channel_| is writable. // This will fail if the SSL role or remote fingerprint are not set. // Otherwise failure could result from network or QUIC errors. MaybeStartQuic(); break; case QUIC_TRANSPORT_CONNECTED: // Note: SignalWritableState fired by set_writable. set_writable(channel_->writable()); if (HasDataToWrite()) { OnCanWrite(); } break; case QUIC_TRANSPORT_CONNECTING: // This channel is not writable until the QUIC handshake finishes. It // might have been write blocked. if (HasDataToWrite()) { OnCanWrite(); } break; case QUIC_TRANSPORT_CLOSED: // TODO(mikescarlett): Allow the QUIC connection to be reset if it drops // due to a non-failure. break; } } void QuicTransportChannel::OnReceivingState(TransportChannel* channel) { ASSERT(rtc::Thread::Current() == worker_thread_); ASSERT(channel == channel_.get()); LOG_J(LS_VERBOSE, this) << "QuicTransportChannel: channel receiving state changed to " << channel_->receiving(); if (quic_state_ == QUIC_TRANSPORT_CONNECTED) { // Note: SignalReceivingState fired by set_receiving. set_receiving(channel_->receiving()); } } void QuicTransportChannel::OnReadPacket(TransportChannel* channel, const char* data, size_t size, const rtc::PacketTime& packet_time, int flags) { ASSERT(rtc::Thread::Current() == worker_thread_); ASSERT(channel == channel_.get()); ASSERT(flags == 0); switch (quic_state_) { case QUIC_TRANSPORT_NEW: // This would occur if other peer is ready to start QUIC but this peer // hasn't started QUIC. LOG_J(LS_INFO, this) << "Dropping packet received before QUIC started."; break; case QUIC_TRANSPORT_CONNECTING: case QUIC_TRANSPORT_CONNECTED: // We should only get QUIC or SRTP packets; STUN's already been demuxed. // Is this potentially a QUIC packet? if (IsQuicPacket(data, size)) { if (!HandleQuicPacket(data, size)) { LOG_J(LS_ERROR, this) << "Failed to handle QUIC packet."; return; } } else { // If this is an RTP packet, signal upwards as a bypass packet. if (!IsRtpPacket(data, size)) { LOG_J(LS_ERROR, this) << "Received unexpected non-QUIC, non-RTP packet."; return; } SignalReadPacket(this, data, size, packet_time, PF_SRTP_BYPASS); } break; case QUIC_TRANSPORT_CLOSED: // This shouldn't be happening. Drop the packet. break; } } void QuicTransportChannel::OnSentPacket(TransportChannel* channel, const rtc::SentPacket& sent_packet) { ASSERT(rtc::Thread::Current() == worker_thread_); SignalSentPacket(this, sent_packet); } void QuicTransportChannel::OnReadyToSend(TransportChannel* channel) { if (writable()) { SignalReadyToSend(this); } } void QuicTransportChannel::OnGatheringState(TransportChannelImpl* channel) { ASSERT(channel == channel_.get()); SignalGatheringState(this); } void QuicTransportChannel::OnCandidateGathered(TransportChannelImpl* channel, const Candidate& c) { ASSERT(channel == channel_.get()); SignalCandidateGathered(this, c); } void QuicTransportChannel::OnRoleConflict(TransportChannelImpl* channel) { ASSERT(channel == channel_.get()); SignalRoleConflict(this); } void QuicTransportChannel::OnRouteChange(TransportChannel* channel, const Candidate& candidate) { ASSERT(channel == channel_.get()); SignalRouteChange(this, candidate); } void QuicTransportChannel::OnSelectedCandidatePairChanged( TransportChannel* channel, CandidatePairInterface* selected_candidate_pair, int last_sent_packet_id) { ASSERT(channel == channel_.get()); SignalSelectedCandidatePairChanged(this, selected_candidate_pair, last_sent_packet_id); } void QuicTransportChannel::OnChannelStateChanged( TransportChannelImpl* channel) { ASSERT(channel == channel_.get()); SignalStateChanged(this); } bool QuicTransportChannel::MaybeStartQuic() { if (!channel_->writable()) { LOG_J(LS_ERROR, this) << "Couldn't start QUIC handshake."; return false; } if (!CreateQuicSession() || !StartQuicHandshake()) { LOG_J(LS_WARNING, this) << "Underlying channel is writable but cannot start " "the QUIC handshake."; return false; } // Verify connection is not closed due to QUIC bug or network failure. // A closed connection should not happen since |channel_| is writable. if (!quic_->connection()->connected()) { LOG_J(LS_ERROR, this) << "QUIC connection should not be closed if underlying " "channel is writable."; return false; } // Indicate that |quic_| is ready to receive QUIC packets. set_quic_state(QUIC_TRANSPORT_CONNECTING); return true; } bool QuicTransportChannel::CreateQuicSession() { if (!ssl_role_ || !remote_fingerprint_) { return false; } net::Perspective perspective = (*ssl_role_ == rtc::SSL_CLIENT) ? net::Perspective::IS_CLIENT : net::Perspective::IS_SERVER; bool owns_writer = false; std::unique_ptr connection(new net::QuicConnection( kConnectionId, kConnectionIpEndpoint, &helper_, this, owns_writer, perspective, net::QuicSupportedVersions())); quic_.reset(new QuicSession(std::move(connection), config_)); quic_->SignalHandshakeComplete.connect( this, &QuicTransportChannel::OnHandshakeComplete); quic_->SignalConnectionClosed.connect( this, &QuicTransportChannel::OnConnectionClosed); quic_->SignalIncomingStream.connect(this, &QuicTransportChannel::OnIncomingStream); return true; } bool QuicTransportChannel::StartQuicHandshake() { if (*ssl_role_ == rtc::SSL_CLIENT) { // Unique identifier for remote peer. net::QuicServerId server_id(remote_fingerprint_->value, kQuicServerPort); // Perform authentication of remote peer; owned by QuicCryptoClientConfig. // TODO(mikescarlett): Actually verify proof. net::ProofVerifier* proof_verifier = new InsecureProofVerifier(); quic_crypto_client_config_.reset( new net::QuicCryptoClientConfig(proof_verifier)); net::QuicCryptoClientStream* crypto_stream = new net::QuicCryptoClientStream(server_id, quic_.get(), new net::ProofVerifyContext(), quic_crypto_client_config_.get(), this); quic_->StartClientHandshake(crypto_stream); LOG_J(LS_INFO, this) << "QuicTransportChannel: Started client handshake."; } else { RTC_DCHECK_EQ(*ssl_role_, rtc::SSL_SERVER); // Provide credentials to remote peer; owned by QuicCryptoServerConfig. // TODO(mikescarlett): Actually provide credentials. net::ProofSource* proof_source = new DummyProofSource(); // Input keying material to HKDF, per http://tools.ietf.org/html/rfc5869. // This is pseudorandom so that HKDF-Extract outputs a pseudorandom key, // since QuicCryptoServerConfig does not use a salt value. std::string source_address_token_secret; if (!rtc::CreateRandomString(kInputKeyingMaterialLength, &source_address_token_secret)) { LOG_J(LS_ERROR, this) << "Error generating input keying material for HKDF."; return false; } quic_crypto_server_config_.reset(new net::QuicCryptoServerConfig( source_address_token_secret, helper_.GetRandomGenerator(), proof_source)); // Provide server with serialized config string to prove ownership. net::QuicCryptoServerConfig::ConfigOptions options; quic_crypto_server_config_->AddDefaultConfig(helper_.GetRandomGenerator(), helper_.GetClock(), options); quic_compressed_certs_cache_.reset(new net::QuicCompressedCertsCache( net::QuicCompressedCertsCache::kQuicCompressedCertsCacheSize)); // TODO(mikescarlett): Add support for stateless rejects. bool use_stateless_rejects_if_peer_supported = false; net::QuicCryptoServerStream* crypto_stream = new net::QuicCryptoServerStream(quic_crypto_server_config_.get(), quic_compressed_certs_cache_.get(), use_stateless_rejects_if_peer_supported, quic_.get()); quic_->StartServerHandshake(crypto_stream); LOG_J(LS_INFO, this) << "QuicTransportChannel: Started server handshake."; } return true; } bool QuicTransportChannel::HandleQuicPacket(const char* data, size_t size) { ASSERT(rtc::Thread::Current() == worker_thread_); return quic_->OnReadPacket(data, size); } net::WriteResult QuicTransportChannel::WritePacket( const char* buffer, size_t buf_len, const net::IPAddress& self_address, const net::IPEndPoint& peer_address, net::PerPacketOptions* options) { // QUIC should never call this if IsWriteBlocked, but just in case... if (IsWriteBlocked()) { return net::WriteResult(net::WRITE_STATUS_BLOCKED, EWOULDBLOCK); } // TODO(mikescarlett): Figure out how to tell QUIC "I dropped your packet, but // don't block" without the QUIC connection tearing itself down. int sent = channel_->SendPacket(buffer, buf_len, rtc::PacketOptions()); int bytes_written = sent > 0 ? sent : 0; return net::WriteResult(net::WRITE_STATUS_OK, bytes_written); } // TODO(mikescarlett): Implement check for whether |channel_| is currently // write blocked so that |quic_| does not try to write packet. This is // necessary because |channel_| can be writable yet write blocked and // channel_->GetError() is not flushed when there is no error. bool QuicTransportChannel::IsWriteBlocked() const { return !channel_->writable(); } void QuicTransportChannel::OnHandshakeComplete() { set_quic_state(QUIC_TRANSPORT_CONNECTED); set_writable(true); // OnReceivingState might have been called before the QUIC channel was // connected, in which case the QUIC channel is now receiving. if (channel_->receiving()) { set_receiving(true); } } void QuicTransportChannel::OnConnectionClosed(net::QuicErrorCode error, bool from_peer) { LOG_J(LS_INFO, this) << "Connection closed by " << (from_peer ? "other" : "this") << " peer " << "with QUIC error " << error; // TODO(mikescarlett): Allow the QUIC session to be reset when the connection // does not close due to failure. set_quic_state(QUIC_TRANSPORT_CLOSED); set_writable(false); SignalClosed(); } void QuicTransportChannel::OnProofValid( const net::QuicCryptoClientConfig::CachedState& cached) { LOG_J(LS_INFO, this) << "Cached proof marked valid"; } void QuicTransportChannel::OnProofVerifyDetailsAvailable( const net::ProofVerifyDetails& verify_details) { LOG_J(LS_INFO, this) << "Proof verify details available from" << " QuicCryptoClientStream"; } bool QuicTransportChannel::HasDataToWrite() const { return quic_ && quic_->HasDataToWrite(); } void QuicTransportChannel::OnCanWrite() { RTC_DCHECK(quic_ != nullptr); quic_->connection()->OnCanWrite(); } void QuicTransportChannel::set_quic_state(QuicTransportState state) { LOG_J(LS_VERBOSE, this) << "set_quic_state from:" << quic_state_ << " to " << state; quic_state_ = state; } ReliableQuicStream* QuicTransportChannel::CreateQuicStream() { if (quic_) { net::SpdyPriority priority = 0; // Priority of the QUIC stream return quic_->CreateOutgoingDynamicStream(priority); } return nullptr; } void QuicTransportChannel::OnIncomingStream(ReliableQuicStream* stream) { SignalIncomingStream(stream); } } // namespace cricket