From e7748674eec286c90fc48705f935ff99175796da Mon Sep 17 00:00:00 2001 From: mikescarlett Date: Fri, 29 Apr 2016 20:20:54 -0700 Subject: [PATCH] Allow TransportController to create a QuicTransportChannel A QuicTransport is implemented that subclasses Transport and takes ownership of the QuicTransportChannel/P2PTransportChannel. Split from CL https://codereview.webrtc.org/1844803002/. BUG= Review-Url: https://codereview.webrtc.org/1856943002 Cr-Commit-Position: refs/heads/master@{#12575} --- webrtc/base/sslfingerprint.cc | 2 +- webrtc/base/sslfingerprint.h | 2 +- webrtc/p2p/base/dtlstransport.h | 103 +--------- webrtc/p2p/base/faketransportcontroller.h | 2 + webrtc/p2p/base/transport.cc | 103 ++++++++++ webrtc/p2p/base/transport.h | 14 ++ webrtc/p2p/base/transport_unittest.cc | 177 ++++++++++++++++++ webrtc/p2p/base/transportcontroller.cc | 9 + webrtc/p2p/base/transportcontroller.h | 5 + webrtc/p2p/p2p.gyp | 3 + webrtc/p2p/quic/quictransport.cc | 114 +++++++++++ webrtc/p2p/quic/quictransport.h | 63 +++++++ webrtc/p2p/quic/quictransport_unittest.cc | 159 ++++++++++++++++ webrtc/p2p/quic/quictransportchannel.cc | 18 +- webrtc/p2p/quic/quictransportchannel.h | 2 +- .../p2p/quic/quictransportchannel_unittest.cc | 20 +- 16 files changed, 678 insertions(+), 118 deletions(-) create mode 100644 webrtc/p2p/quic/quictransport.cc create mode 100644 webrtc/p2p/quic/quictransport.h create mode 100644 webrtc/p2p/quic/quictransport_unittest.cc diff --git a/webrtc/base/sslfingerprint.cc b/webrtc/base/sslfingerprint.cc index 1939b4fd0b..2c3e1e974b 100644 --- a/webrtc/base/sslfingerprint.cc +++ b/webrtc/base/sslfingerprint.cc @@ -85,7 +85,7 @@ std::string SSLFingerprint::GetRfc4572Fingerprint() const { return fingerprint; } -std::string SSLFingerprint::ToString() { +std::string SSLFingerprint::ToString() const { std::string fp_str = algorithm; fp_str.append(" "); fp_str.append(GetRfc4572Fingerprint()); diff --git a/webrtc/base/sslfingerprint.h b/webrtc/base/sslfingerprint.h index 1413a4cd27..4ffb2b0524 100644 --- a/webrtc/base/sslfingerprint.h +++ b/webrtc/base/sslfingerprint.h @@ -41,7 +41,7 @@ struct SSLFingerprint { std::string GetRfc4572Fingerprint() const; - std::string ToString(); + std::string ToString() const; std::string algorithm; rtc::CopyOnWriteBuffer digest; diff --git a/webrtc/p2p/base/dtlstransport.h b/webrtc/p2p/base/dtlstransport.h index 2ff2ea5db8..a4bf383f0a 100644 --- a/webrtc/p2p/base/dtlstransport.h +++ b/webrtc/p2p/base/dtlstransport.h @@ -66,27 +66,11 @@ class DtlsTransport : public Base { rtc::SSLFingerprint* local_fp = Base::local_description()->identity_fingerprint.get(); - if (local_fp) { - // Sanity check local fingerprint. - if (certificate_) { - std::unique_ptr local_fp_tmp( - rtc::SSLFingerprint::Create(local_fp->algorithm, - certificate_->identity())); - ASSERT(local_fp_tmp.get() != NULL); - if (!(*local_fp_tmp == *local_fp)) { - std::ostringstream desc; - desc << "Local fingerprint does not match identity. Expected: "; - desc << local_fp_tmp->ToString(); - desc << " Got: " << local_fp->ToString(); - return BadTransportDescription(desc.str(), error_desc); - } - } else { - return BadTransportDescription( - "Local fingerprint provided but no identity available.", - error_desc); - } - } else { + if (!local_fp) { certificate_ = nullptr; + } else if (!Base::VerifyCertificateFingerprint(certificate_.get(), local_fp, + error_desc)) { + return false; } if (!channel->SetLocalCertificate(certificate_)) { @@ -105,96 +89,23 @@ class DtlsTransport : public Base { "transport descriptions are negotiated"; return BadTransportDescription(msg, error_desc); } - rtc::SSLFingerprint* local_fp = Base::local_description()->identity_fingerprint.get(); rtc::SSLFingerprint* remote_fp = Base::remote_description()->identity_fingerprint.get(); - if (remote_fp && local_fp) { remote_fingerprint_.reset(new rtc::SSLFingerprint(*remote_fp)); - - // From RFC 4145, section-4.1, The following are the values that the - // 'setup' attribute can take in an offer/answer exchange: - // Offer Answer - // ________________ - // active passive / holdconn - // passive active / holdconn - // actpass active / passive / holdconn - // holdconn holdconn - // - // Set the role that is most conformant with RFC 5763, Section 5, bullet 1 - // The endpoint MUST use the setup attribute defined in [RFC4145]. - // The endpoint that is the offerer MUST use the setup attribute - // value of setup:actpass and be prepared to receive a client_hello - // before it receives the answer. The answerer MUST use either a - // setup attribute value of setup:active or setup:passive. Note that - // if the answerer uses setup:passive, then the DTLS handshake will - // not begin until the answerer is received, which adds additional - // latency. setup:active allows the answer and the DTLS handshake to - // occur in parallel. Thus, setup:active is RECOMMENDED. Whichever - // party is active MUST initiate a DTLS handshake by sending a - // ClientHello over each flow (host/port quartet). - // IOW - actpass and passive modes should be treated as server and - // active as client. - ConnectionRole local_connection_role = - Base::local_description()->connection_role; - ConnectionRole remote_connection_role = - Base::remote_description()->connection_role; - - bool is_remote_server = false; - if (local_role == CA_OFFER) { - if (local_connection_role != CONNECTIONROLE_ACTPASS) { - return BadTransportDescription( - "Offerer must use actpass value for setup attribute.", - error_desc); - } - - if (remote_connection_role == CONNECTIONROLE_ACTIVE || - remote_connection_role == CONNECTIONROLE_PASSIVE || - remote_connection_role == CONNECTIONROLE_NONE) { - is_remote_server = (remote_connection_role == CONNECTIONROLE_PASSIVE); - } else { - const std::string msg = - "Answerer must use either active or passive value " - "for setup attribute."; - return BadTransportDescription(msg, error_desc); - } - // If remote is NONE or ACTIVE it will act as client. - } else { - if (remote_connection_role != CONNECTIONROLE_ACTPASS && - remote_connection_role != CONNECTIONROLE_NONE) { - return BadTransportDescription( - "Offerer must use actpass value for setup attribute.", - error_desc); - } - - if (local_connection_role == CONNECTIONROLE_ACTIVE || - local_connection_role == CONNECTIONROLE_PASSIVE) { - is_remote_server = (local_connection_role == CONNECTIONROLE_ACTIVE); - } else { - const std::string msg = - "Answerer must use either active or passive value " - "for setup attribute."; - return BadTransportDescription(msg, error_desc); - } - - // If local is passive, local will act as server. + if (!Base::NegotiateRole(local_role, &secure_role_, error_desc)) { + return false; } - - secure_role_ = is_remote_server ? rtc::SSL_CLIENT : - rtc::SSL_SERVER; - } else if (local_fp && (local_role == CA_ANSWER)) { return BadTransportDescription( "Local fingerprint supplied when caller didn't offer DTLS.", error_desc); } else { // We are not doing DTLS - remote_fingerprint_.reset(new rtc::SSLFingerprint( - "", NULL, 0)); + remote_fingerprint_.reset(new rtc::SSLFingerprint("", nullptr, 0)); } - // Now run the negotiation for the base class. return Base::NegotiateTransportDescription(local_role, error_desc); } diff --git a/webrtc/p2p/base/faketransportcontroller.h b/webrtc/p2p/base/faketransportcontroller.h index e2bdc1085e..25e5e81939 100644 --- a/webrtc/p2p/base/faketransportcontroller.h +++ b/webrtc/p2p/base/faketransportcontroller.h @@ -406,6 +406,8 @@ class FakeTransport : public Transport { using Transport::local_description; using Transport::remote_description; + using Transport::VerifyCertificateFingerprint; + using Transport::NegotiateRole; protected: TransportChannelImpl* CreateTransportChannel(int component) override { diff --git a/webrtc/p2p/base/transport.cc b/webrtc/p2p/base/transport.cc index 89f05e861c..3e45d20737 100644 --- a/webrtc/p2p/base/transport.cc +++ b/webrtc/p2p/base/transport.cc @@ -401,4 +401,107 @@ bool Transport::NegotiateTransportDescription(ContentAction local_role, return true; } +bool Transport::VerifyCertificateFingerprint( + const rtc::RTCCertificate* certificate, + const rtc::SSLFingerprint* fingerprint, + std::string* error_desc) const { + if (!fingerprint) { + return BadTransportDescription("No fingerprint.", error_desc); + } + if (!certificate) { + return BadTransportDescription( + "Fingerprint provided but no identity available.", error_desc); + } + rtc::scoped_ptr fp_tmp(rtc::SSLFingerprint::Create( + fingerprint->algorithm, certificate->identity())); + ASSERT(fp_tmp.get() != NULL); + if (*fp_tmp == *fingerprint) { + return true; + } + std::ostringstream desc; + desc << "Local fingerprint does not match identity. Expected: "; + desc << fp_tmp->ToString(); + desc << " Got: " << fingerprint->ToString(); + return BadTransportDescription(desc.str(), error_desc); +} + +bool Transport::NegotiateRole(ContentAction local_role, + rtc::SSLRole* ssl_role, + std::string* error_desc) const { + RTC_DCHECK(ssl_role); + if (!local_description() || !remote_description()) { + const std::string msg = + "Local and Remote description must be set before " + "transport descriptions are negotiated"; + return BadTransportDescription(msg, error_desc); + } + + // From RFC 4145, section-4.1, The following are the values that the + // 'setup' attribute can take in an offer/answer exchange: + // Offer Answer + // ________________ + // active passive / holdconn + // passive active / holdconn + // actpass active / passive / holdconn + // holdconn holdconn + // + // Set the role that is most conformant with RFC 5763, Section 5, bullet 1 + // The endpoint MUST use the setup attribute defined in [RFC4145]. + // The endpoint that is the offerer MUST use the setup attribute + // value of setup:actpass and be prepared to receive a client_hello + // before it receives the answer. The answerer MUST use either a + // setup attribute value of setup:active or setup:passive. Note that + // if the answerer uses setup:passive, then the DTLS handshake will + // not begin until the answerer is received, which adds additional + // latency. setup:active allows the answer and the DTLS handshake to + // occur in parallel. Thus, setup:active is RECOMMENDED. Whichever + // party is active MUST initiate a DTLS handshake by sending a + // ClientHello over each flow (host/port quartet). + // IOW - actpass and passive modes should be treated as server and + // active as client. + ConnectionRole local_connection_role = local_description()->connection_role; + ConnectionRole remote_connection_role = remote_description()->connection_role; + + bool is_remote_server = false; + if (local_role == CA_OFFER) { + if (local_connection_role != CONNECTIONROLE_ACTPASS) { + return BadTransportDescription( + "Offerer must use actpass value for setup attribute.", error_desc); + } + + if (remote_connection_role == CONNECTIONROLE_ACTIVE || + remote_connection_role == CONNECTIONROLE_PASSIVE || + remote_connection_role == CONNECTIONROLE_NONE) { + is_remote_server = (remote_connection_role == CONNECTIONROLE_PASSIVE); + } else { + const std::string msg = + "Answerer must use either active or passive value " + "for setup attribute."; + return BadTransportDescription(msg, error_desc); + } + // If remote is NONE or ACTIVE it will act as client. + } else { + if (remote_connection_role != CONNECTIONROLE_ACTPASS && + remote_connection_role != CONNECTIONROLE_NONE) { + return BadTransportDescription( + "Offerer must use actpass value for setup attribute.", error_desc); + } + + if (local_connection_role == CONNECTIONROLE_ACTIVE || + local_connection_role == CONNECTIONROLE_PASSIVE) { + is_remote_server = (local_connection_role == CONNECTIONROLE_ACTIVE); + } else { + const std::string msg = + "Answerer must use either active or passive value " + "for setup attribute."; + return BadTransportDescription(msg, error_desc); + } + + // If local is passive, local will act as server. + } + + *ssl_role = is_remote_server ? rtc::SSL_CLIENT : rtc::SSL_SERVER; + return true; +} + } // namespace cricket diff --git a/webrtc/p2p/base/transport.h b/webrtc/p2p/base/transport.h index 46360ccf04..e31d37a6f6 100644 --- a/webrtc/p2p/base/transport.h +++ b/webrtc/p2p/base/transport.h @@ -315,6 +315,20 @@ class Transport : public sigslot::has_slots<> { TransportChannelImpl* channel, std::string* error_desc); + // Returns false if the certificate's identity does not match the fingerprint, + // or either is NULL. + virtual bool VerifyCertificateFingerprint( + const rtc::RTCCertificate* certificate, + const rtc::SSLFingerprint* fingerprint, + std::string* error_desc) const; + + // Negotiates the SSL role based off the offer and answer as specified by + // RFC 4145, section-4.1. Returns false if the SSL role cannot be determined + // from the local description and remote description. + virtual bool NegotiateRole(ContentAction local_role, + rtc::SSLRole* ssl_role, + std::string* error_desc) const; + private: // If a candidate is not acceptable, returns false and sets error. // Call this before calling OnRemoteCandidates. diff --git a/webrtc/p2p/base/transport_unittest.cc b/webrtc/p2p/base/transport_unittest.cc index 658c143a11..fba72b43de 100644 --- a/webrtc/p2p/base/transport_unittest.cc +++ b/webrtc/p2p/base/transport_unittest.cc @@ -235,3 +235,180 @@ TEST_F(TransportTest, TestGetStats) { EXPECT_EQ(1, stats.channel_stats[0].component); } +// Tests that VerifyCertificateFingerprint only returns true when the +// certificate matches the fingerprint. +TEST_F(TransportTest, TestVerifyCertificateFingerprint) { + std::string error_desc; + EXPECT_FALSE( + transport_->VerifyCertificateFingerprint(nullptr, nullptr, &error_desc)); + rtc::KeyType key_types[] = {rtc::KT_RSA, rtc::KT_ECDSA}; + + for (auto& key_type : key_types) { + rtc::scoped_refptr certificate = + rtc::RTCCertificate::Create(rtc::scoped_ptr( + rtc::SSLIdentity::Generate("testing", key_type))); + ASSERT_NE(nullptr, certificate); + + std::string digest_algorithm; + ASSERT_TRUE(certificate->ssl_certificate().GetSignatureDigestAlgorithm( + &digest_algorithm)); + ASSERT_FALSE(digest_algorithm.empty()); + rtc::scoped_ptr good_fingerprint( + rtc::SSLFingerprint::Create(digest_algorithm, certificate->identity())); + ASSERT_NE(nullptr, good_fingerprint); + + EXPECT_TRUE(transport_->VerifyCertificateFingerprint( + certificate.get(), good_fingerprint.get(), &error_desc)); + EXPECT_FALSE(transport_->VerifyCertificateFingerprint( + certificate.get(), nullptr, &error_desc)); + EXPECT_FALSE(transport_->VerifyCertificateFingerprint( + nullptr, good_fingerprint.get(), &error_desc)); + + rtc::SSLFingerprint bad_fingerprint = *good_fingerprint; + bad_fingerprint.digest.AppendData("0", 1); + EXPECT_FALSE(transport_->VerifyCertificateFingerprint( + certificate.get(), &bad_fingerprint, &error_desc)); + } +} + +// Tests that NegotiateRole sets the SSL role correctly. +TEST_F(TransportTest, TestNegotiateRole) { + TransportDescription local_desc(kIceUfrag1, kIcePwd1); + TransportDescription remote_desc(kIceUfrag2, kIcePwd2); + + struct NegotiateRoleParams { + cricket::ConnectionRole local_role; + cricket::ConnectionRole remote_role; + cricket::ContentAction local_action; + cricket::ContentAction remote_action; + }; + + rtc::SSLRole ssl_role; + std::string error_desc; + + // Parameters which set the SSL role to SSL_CLIENT. + NegotiateRoleParams valid_client_params[] = { + {cricket::CONNECTIONROLE_ACTIVE, cricket::CONNECTIONROLE_ACTPASS, + cricket::CA_ANSWER, cricket::CA_OFFER}, + {cricket::CONNECTIONROLE_ACTIVE, cricket::CONNECTIONROLE_ACTPASS, + cricket::CA_PRANSWER, cricket::CA_OFFER}, + {cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_PASSIVE, + cricket::CA_OFFER, cricket::CA_ANSWER}, + {cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_PASSIVE, + cricket::CA_OFFER, cricket::CA_PRANSWER}}; + + for (auto& param : valid_client_params) { + local_desc.connection_role = param.local_role; + remote_desc.connection_role = param.remote_role; + + ASSERT_TRUE(transport_->SetRemoteTransportDescription( + remote_desc, param.remote_action, nullptr)); + ASSERT_TRUE(transport_->SetLocalTransportDescription( + local_desc, param.local_action, nullptr)); + EXPECT_TRUE( + transport_->NegotiateRole(param.local_action, &ssl_role, &error_desc)); + EXPECT_EQ(rtc::SSL_CLIENT, ssl_role); + } + + // Parameters which set the SSL role to SSL_SERVER. + NegotiateRoleParams valid_server_params[] = { + {cricket::CONNECTIONROLE_PASSIVE, cricket::CONNECTIONROLE_ACTPASS, + cricket::CA_ANSWER, cricket::CA_OFFER}, + {cricket::CONNECTIONROLE_PASSIVE, cricket::CONNECTIONROLE_ACTPASS, + cricket::CA_PRANSWER, cricket::CA_OFFER}, + {cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_ACTIVE, + cricket::CA_OFFER, cricket::CA_ANSWER}, + {cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_ACTIVE, + cricket::CA_OFFER, cricket::CA_PRANSWER}}; + + for (auto& param : valid_server_params) { + local_desc.connection_role = param.local_role; + remote_desc.connection_role = param.remote_role; + + ASSERT_TRUE(transport_->SetRemoteTransportDescription( + remote_desc, param.remote_action, nullptr)); + ASSERT_TRUE(transport_->SetLocalTransportDescription( + local_desc, param.local_action, nullptr)); + EXPECT_TRUE( + transport_->NegotiateRole(param.local_action, &ssl_role, &error_desc)); + EXPECT_EQ(rtc::SSL_SERVER, ssl_role); + } + + // Invalid parameters due to both peers having a duplicate role. + NegotiateRoleParams duplicate_params[] = { + {cricket::CONNECTIONROLE_ACTIVE, cricket::CONNECTIONROLE_ACTIVE, + cricket::CA_ANSWER, cricket::CA_OFFER}, + {cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_ACTPASS, + cricket::CA_ANSWER, cricket::CA_OFFER}, + {cricket::CONNECTIONROLE_PASSIVE, cricket::CONNECTIONROLE_PASSIVE, + cricket::CA_ANSWER, cricket::CA_OFFER}, + {cricket::CONNECTIONROLE_ACTIVE, cricket::CONNECTIONROLE_ACTIVE, + cricket::CA_PRANSWER, cricket::CA_OFFER}, + {cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_ACTPASS, + cricket::CA_PRANSWER, cricket::CA_OFFER}, + {cricket::CONNECTIONROLE_PASSIVE, cricket::CONNECTIONROLE_PASSIVE, + cricket::CA_PRANSWER, cricket::CA_OFFER}, + {cricket::CONNECTIONROLE_ACTIVE, cricket::CONNECTIONROLE_ACTIVE, + cricket::CA_OFFER, cricket::CA_ANSWER}, + {cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_ACTPASS, + cricket::CA_OFFER, cricket::CA_ANSWER}, + {cricket::CONNECTIONROLE_PASSIVE, cricket::CONNECTIONROLE_PASSIVE, + cricket::CA_OFFER, cricket::CA_ANSWER}, + {cricket::CONNECTIONROLE_ACTIVE, cricket::CONNECTIONROLE_ACTIVE, + cricket::CA_OFFER, cricket::CA_PRANSWER}, + {cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_ACTPASS, + cricket::CA_OFFER, cricket::CA_PRANSWER}, + {cricket::CONNECTIONROLE_PASSIVE, cricket::CONNECTIONROLE_PASSIVE, + cricket::CA_OFFER, cricket::CA_PRANSWER}}; + + for (auto& param : duplicate_params) { + local_desc.connection_role = param.local_role; + remote_desc.connection_role = param.remote_role; + + ASSERT_TRUE(transport_->SetRemoteTransportDescription( + remote_desc, param.remote_action, nullptr)); + ASSERT_TRUE(transport_->SetLocalTransportDescription( + local_desc, param.local_action, nullptr)); + EXPECT_FALSE( + transport_->NegotiateRole(param.local_action, &ssl_role, &error_desc)); + } + + // Invalid parameters due to the offerer not using ACTPASS. + NegotiateRoleParams offerer_without_actpass_params[] = { + {cricket::CONNECTIONROLE_ACTIVE, cricket::CONNECTIONROLE_PASSIVE, + cricket::CA_ANSWER, cricket::CA_OFFER}, + {cricket::CONNECTIONROLE_PASSIVE, cricket::CONNECTIONROLE_ACTIVE, + cricket::CA_ANSWER, cricket::CA_OFFER}, + {cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_PASSIVE, + cricket::CA_ANSWER, cricket::CA_OFFER}, + {cricket::CONNECTIONROLE_ACTIVE, cricket::CONNECTIONROLE_PASSIVE, + cricket::CA_PRANSWER, cricket::CA_OFFER}, + {cricket::CONNECTIONROLE_PASSIVE, cricket::CONNECTIONROLE_ACTIVE, + cricket::CA_PRANSWER, cricket::CA_OFFER}, + {cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_PASSIVE, + cricket::CA_PRANSWER, cricket::CA_OFFER}, + {cricket::CONNECTIONROLE_ACTIVE, cricket::CONNECTIONROLE_PASSIVE, + cricket::CA_OFFER, cricket::CA_ANSWER}, + {cricket::CONNECTIONROLE_PASSIVE, cricket::CONNECTIONROLE_ACTIVE, + cricket::CA_OFFER, cricket::CA_ANSWER}, + {cricket::CONNECTIONROLE_PASSIVE, cricket::CONNECTIONROLE_ACTPASS, + cricket::CA_OFFER, cricket::CA_ANSWER}, + {cricket::CONNECTIONROLE_ACTIVE, cricket::CONNECTIONROLE_PASSIVE, + cricket::CA_OFFER, cricket::CA_PRANSWER}, + {cricket::CONNECTIONROLE_PASSIVE, cricket::CONNECTIONROLE_ACTIVE, + cricket::CA_OFFER, cricket::CA_PRANSWER}, + {cricket::CONNECTIONROLE_PASSIVE, cricket::CONNECTIONROLE_ACTPASS, + cricket::CA_OFFER, cricket::CA_PRANSWER}}; + + for (auto& param : offerer_without_actpass_params) { + local_desc.connection_role = param.local_role; + remote_desc.connection_role = param.remote_role; + + ASSERT_TRUE(transport_->SetRemoteTransportDescription( + remote_desc, param.remote_action, nullptr)); + ASSERT_TRUE(transport_->SetLocalTransportDescription( + local_desc, param.local_action, nullptr)); + EXPECT_FALSE( + transport_->NegotiateRole(param.local_action, &ssl_role, &error_desc)); + } +} diff --git a/webrtc/p2p/base/transportcontroller.cc b/webrtc/p2p/base/transportcontroller.cc index 24f6481b48..2eb198ed9a 100644 --- a/webrtc/p2p/base/transportcontroller.cc +++ b/webrtc/p2p/base/transportcontroller.cc @@ -20,6 +20,10 @@ #include "webrtc/p2p/base/p2ptransport.h" #include "webrtc/p2p/base/port.h" +#ifdef HAVE_QUIC +#include "webrtc/p2p/quic/quictransport.h" +#endif // HAVE_QUIC + namespace cricket { enum { @@ -219,6 +223,11 @@ Transport* TransportController::CreateTransport_w( const std::string& transport_name) { RTC_DCHECK(worker_thread_->IsCurrent()); +#ifdef HAVE_QUIC + if (quic_) { + return new QuicTransport(transport_name, port_allocator(), certificate_); + } +#endif // HAVE_QUIC Transport* transport = new DtlsTransport( transport_name, port_allocator(), certificate_); return transport; diff --git a/webrtc/p2p/base/transportcontroller.h b/webrtc/p2p/base/transportcontroller.h index 8cb5986128..f84c55179c 100644 --- a/webrtc/p2p/base/transportcontroller.h +++ b/webrtc/p2p/base/transportcontroller.h @@ -91,6 +91,9 @@ class TransportController : public sigslot::has_slots<>, virtual void DestroyTransportChannel_w(const std::string& transport_name, int component); + void use_quic() { quic_ = true; } + bool quic() const { return quic_; } + // All of these signals are fired on the signalling thread. // If any transport failed => failed, @@ -222,6 +225,8 @@ class TransportController : public sigslot::has_slots<>, uint64_t ice_tiebreaker_ = rtc::CreateRandomId64(); rtc::scoped_refptr certificate_; rtc::AsyncInvoker invoker_; + // True if QUIC is used instead of DTLS. + bool quic_ = false; }; } // namespace cricket diff --git a/webrtc/p2p/p2p.gyp b/webrtc/p2p/p2p.gyp index 5c9a575f8f..50941c8682 100644 --- a/webrtc/p2p/p2p.gyp +++ b/webrtc/p2p/p2p.gyp @@ -107,6 +107,8 @@ 'quic/quicconnectionhelper.h', 'quic/quicsession.cc', 'quic/quicsession.h', + 'quic/quictransport.cc', + 'quic/quictransport.h', 'quic/quictransportchannel.cc', 'quic/quictransportchannel.h', 'quic/reliablequicstream.cc', @@ -182,6 +184,7 @@ 'sources': [ 'quic/quicconnectionhelper_unittest.cc', 'quic/quicsession_unittest.cc', + 'quic/quictransport_unittest.cc', 'quic/quictransportchannel_unittest.cc', 'quic/reliablequicstream_unittest.cc', ], diff --git a/webrtc/p2p/quic/quictransport.cc b/webrtc/p2p/quic/quictransport.cc new file mode 100644 index 0000000000..51f9a2b0a3 --- /dev/null +++ b/webrtc/p2p/quic/quictransport.cc @@ -0,0 +1,114 @@ +/* + * 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/quictransport.h" + +#include "webrtc/p2p/base/p2ptransportchannel.h" + +namespace cricket { + +QuicTransport::QuicTransport( + const std::string& name, + PortAllocator* allocator, + const rtc::scoped_refptr& certificate) + : Transport(name, allocator), local_certificate_(certificate) {} + +QuicTransport::~QuicTransport() { + DestroyAllChannels(); +} + +void QuicTransport::SetLocalCertificate( + const rtc::scoped_refptr& certificate) { + local_certificate_ = certificate; +} +bool QuicTransport::GetLocalCertificate( + rtc::scoped_refptr* certificate) { + if (!local_certificate_) { + return false; + } + *certificate = local_certificate_; + return true; +} + +bool QuicTransport::ApplyLocalTransportDescription( + TransportChannelImpl* channel, + std::string* error_desc) { + rtc::SSLFingerprint* local_fp = + local_description()->identity_fingerprint.get(); + if (!VerifyCertificateFingerprint(local_certificate_.get(), local_fp, + error_desc)) { + return false; + } + if (!channel->SetLocalCertificate(local_certificate_)) { + return BadTransportDescription("Failed to set local identity.", error_desc); + } + return Transport::ApplyLocalTransportDescription(channel, error_desc); +} + +bool QuicTransport::NegotiateTransportDescription(ContentAction action, + std::string* error_desc) { + if (!local_description() || !remote_description()) { + const std::string msg = + "Local and Remote description must be set before " + "transport descriptions are negotiated"; + return BadTransportDescription(msg, error_desc); + } + rtc::SSLFingerprint* local_fp = + local_description()->identity_fingerprint.get(); + rtc::SSLFingerprint* remote_fp = + remote_description()->identity_fingerprint.get(); + if (!local_fp || !remote_fp) { + return BadTransportDescription("Fingerprints must be supplied for QUIC.", + error_desc); + } + remote_fingerprint_.reset(new rtc::SSLFingerprint(*remote_fp)); + if (!NegotiateRole(action, &local_role_, error_desc)) { + return false; + } + // Now run the negotiation for the Transport class. + return Transport::NegotiateTransportDescription(action, error_desc); +} + +QuicTransportChannel* QuicTransport::CreateTransportChannel(int component) { + P2PTransportChannel* ice_channel = + new P2PTransportChannel(name(), component, port_allocator()); + return new QuicTransportChannel(ice_channel); +} + +void QuicTransport::DestroyTransportChannel(TransportChannelImpl* channel) { + delete channel; +} + +bool QuicTransport::GetSslRole(rtc::SSLRole* ssl_role) const { + ASSERT(ssl_role != NULL); + *ssl_role = local_role_; + return true; +} + +bool QuicTransport::ApplyNegotiatedTransportDescription( + TransportChannelImpl* channel, + std::string* error_desc) { + // Set ssl role and remote fingerprint. These are required for QUIC setup. + if (!channel->SetSslRole(local_role_)) { + return BadTransportDescription("Failed to set ssl role for the channel.", + error_desc); + } + // Apply remote fingerprint. + if (!channel->SetRemoteFingerprint( + remote_fingerprint_->algorithm, + reinterpret_cast(remote_fingerprint_->digest.data()), + remote_fingerprint_->digest.size())) { + return BadTransportDescription("Failed to apply remote fingerprint.", + error_desc); + } + return Transport::ApplyNegotiatedTransportDescription(channel, error_desc); +} + +} // namespace cricket diff --git a/webrtc/p2p/quic/quictransport.h b/webrtc/p2p/quic/quictransport.h new file mode 100644 index 0000000000..053ba61335 --- /dev/null +++ b/webrtc/p2p/quic/quictransport.h @@ -0,0 +1,63 @@ +/* + * 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. + */ + +#ifndef WEBRTC_P2P_QUIC_QUICTRANSPORT_H_ +#define WEBRTC_P2P_QUIC_QUICTRANSPORT_H_ + +#include +#include + +#include "webrtc/p2p/base/transport.h" +#include "webrtc/p2p/quic/quictransportchannel.h" + +namespace cricket { + +class P2PTransportChannel; +class PortAllocator; + +// TODO(mikescarlett): Refactor to avoid code duplication with DtlsTransport. +class QuicTransport : public Transport { + public: + QuicTransport(const std::string& name, + PortAllocator* allocator, + const rtc::scoped_refptr& certificate); + + ~QuicTransport() override; + + // Transport overrides. + void SetLocalCertificate( + const rtc::scoped_refptr& certificate) override; + bool GetLocalCertificate( + rtc::scoped_refptr* certificate) override; + bool SetSslMaxProtocolVersion(rtc::SSLProtocolVersion version) override { + return true; // Not needed by QUIC + } + bool GetSslRole(rtc::SSLRole* ssl_role) const override; + + protected: + // Transport overrides. + QuicTransportChannel* CreateTransportChannel(int component) override; + void DestroyTransportChannel(TransportChannelImpl* channel) override; + bool ApplyLocalTransportDescription(TransportChannelImpl* channel, + std::string* error_desc) override; + bool NegotiateTransportDescription(ContentAction action, + std::string* error_desc) override; + bool ApplyNegotiatedTransportDescription(TransportChannelImpl* channel, + std::string* error_desc) override; + + private: + rtc::scoped_refptr local_certificate_; + rtc::SSLRole local_role_ = rtc::SSL_CLIENT; + rtc::scoped_ptr remote_fingerprint_; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_QUIC_QUICTRANSPORT_H_ diff --git a/webrtc/p2p/quic/quictransport_unittest.cc b/webrtc/p2p/quic/quictransport_unittest.cc new file mode 100644 index 0000000000..bc99f6bd31 --- /dev/null +++ b/webrtc/p2p/quic/quictransport_unittest.cc @@ -0,0 +1,159 @@ +/* + * 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/quictransport.h" + +#include +#include + +#include "webrtc/base/gunit.h" +#include "webrtc/base/rtccertificate.h" +#include "webrtc/base/sslidentity.h" + +using cricket::TransportChannelImpl; +using cricket::QuicTransport; +using cricket::Transport; +using cricket::TransportDescription; + +static const char kIceUfrag1[] = "TESTICEUFRAG0001"; +static const char kIcePwd1[] = "TESTICEPWD00000000000001"; + +static const char kIceUfrag2[] = "TESTICEUFRAG0002"; +static const char kIcePwd2[] = "TESTICEPWD00000000000002"; + +static rtc::scoped_refptr CreateCertificate( + std::string name) { + return rtc::RTCCertificate::Create(rtc::scoped_ptr( + rtc::SSLIdentity::Generate(name, rtc::KT_DEFAULT))); +} + +static rtc::scoped_ptr CreateFingerprint( + rtc::RTCCertificate* cert) { + std::string digest_algorithm; + cert->ssl_certificate().GetSignatureDigestAlgorithm(&digest_algorithm); + return rtc::scoped_ptr( + rtc::SSLFingerprint::Create(digest_algorithm, cert->identity())); +} + +class QuicTransportTest : public testing::Test { + public: + QuicTransportTest() : transport_("testing", nullptr, nullptr) {} + + void SetTransportDescription(cricket::ConnectionRole local_role, + cricket::ConnectionRole remote_role, + cricket::ContentAction local_action, + cricket::ContentAction remote_action, + rtc::SSLRole expected_ssl_role) { + TransportChannelImpl* channel = transport_.CreateChannel(1); + ASSERT_NE(nullptr, channel); + + rtc::scoped_refptr local_certificate( + CreateCertificate("local")); + ASSERT_NE(nullptr, local_certificate); + transport_.SetLocalCertificate(local_certificate); + + rtc::scoped_ptr local_fingerprint = + CreateFingerprint(local_certificate.get()); + ASSERT_NE(nullptr, local_fingerprint); + TransportDescription local_desc(std::vector(), kIceUfrag1, + kIcePwd1, cricket::ICEMODE_FULL, local_role, + local_fingerprint.get()); + ASSERT_TRUE(transport_.SetLocalTransportDescription(local_desc, + local_action, nullptr)); + // The certificate is applied to QuicTransportChannel when the local + // description is set. + rtc::scoped_refptr channel_local_certificate = + channel->GetLocalCertificate(); + ASSERT_NE(nullptr, channel_local_certificate); + EXPECT_EQ(local_certificate, channel_local_certificate); + rtc::scoped_ptr remote_fingerprint = + CreateFingerprint(CreateCertificate("remote").get()); + // NegotiateTransportDescription was not called yet. The SSL role should + // not be set and neither should the remote fingerprint. + rtc::scoped_ptr role(new rtc::SSLRole()); + EXPECT_FALSE(channel->GetSslRole(role.get())); + // Setting the remote description should set the SSL role. + ASSERT_NE(nullptr, remote_fingerprint); + TransportDescription remote_desc(std::vector(), kIceUfrag2, + kIcePwd2, cricket::ICEMODE_FULL, + remote_role, remote_fingerprint.get()); + ASSERT_TRUE(transport_.SetRemoteTransportDescription( + remote_desc, remote_action, nullptr)); + ASSERT_TRUE(channel->GetSslRole(role.get())); + // SSL role should be client because the remote description is an ANSWER. + EXPECT_EQ(expected_ssl_role, *role); + } + + protected: + QuicTransport transport_; +}; + +// Test setting the local certificate. +TEST_F(QuicTransportTest, SetLocalCertificate) { + rtc::scoped_refptr local_certificate( + CreateCertificate("local")); + ASSERT_NE(nullptr, local_certificate); + rtc::scoped_refptr transport_local_certificate; + EXPECT_FALSE(transport_.GetLocalCertificate(&transport_local_certificate)); + transport_.SetLocalCertificate(local_certificate); + ASSERT_TRUE(transport_.GetLocalCertificate(&transport_local_certificate)); + ASSERT_NE(nullptr, transport_local_certificate); + EXPECT_EQ(local_certificate, transport_local_certificate); +} + +// Test setting the ICE role. +TEST_F(QuicTransportTest, SetIceRole) { + TransportChannelImpl* channel1 = transport_.CreateChannel(1); + ASSERT_NE(nullptr, channel1); + transport_.SetIceRole(cricket::ICEROLE_CONTROLLING); + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, transport_.ice_role()); + TransportChannelImpl* channel2 = transport_.CreateChannel(2); + ASSERT_NE(nullptr, channel2); + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel1->GetIceRole()); + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel2->GetIceRole()); +} + +// Test setting the ICE tie breaker. +TEST_F(QuicTransportTest, SetIceTiebreaker) { + transport_.SetIceTiebreaker(1u); + EXPECT_EQ(1u, transport_.IceTiebreaker()); +} + +// Test setting the local and remote descriptions for a SSL client. +TEST_F(QuicTransportTest, SetLocalAndRemoteTransportDescriptionClient) { + SetTransportDescription(cricket::CONNECTIONROLE_ACTPASS, + cricket::CONNECTIONROLE_PASSIVE, cricket::CA_OFFER, + cricket::CA_ANSWER, rtc::SSL_CLIENT); +} + +// Test setting the local and remote descriptions for a SSL server. +TEST_F(QuicTransportTest, SetLocalAndRemoteTransportDescriptionServer) { + SetTransportDescription(cricket::CONNECTIONROLE_ACTPASS, + cricket::CONNECTIONROLE_ACTIVE, cricket::CA_OFFER, + cricket::CA_ANSWER, rtc::SSL_SERVER); +} + +// Test creation and destruction of channels. +TEST_F(QuicTransportTest, CreateAndDestroyChannels) { + TransportChannelImpl* channel1 = transport_.CreateChannel(1); + ASSERT_NE(nullptr, channel1); + EXPECT_TRUE(transport_.HasChannel(1)); + EXPECT_EQ(channel1, transport_.GetChannel(1)); + TransportChannelImpl* channel2 = transport_.CreateChannel(2); + ASSERT_NE(nullptr, channel2); + EXPECT_TRUE(transport_.HasChannel(2)); + EXPECT_EQ(channel2, transport_.GetChannel(2)); + transport_.DestroyChannel(1); + EXPECT_FALSE(transport_.HasChannel(1)); + EXPECT_EQ(nullptr, transport_.GetChannel(1)); + transport_.DestroyChannel(2); + EXPECT_FALSE(transport_.HasChannel(2)); + EXPECT_EQ(nullptr, transport_.GetChannel(2)); +} diff --git a/webrtc/p2p/quic/quictransportchannel.cc b/webrtc/p2p/quic/quictransportchannel.cc index b3f918071c..968faee7bd 100644 --- a/webrtc/p2p/quic/quictransportchannel.cc +++ b/webrtc/p2p/quic/quictransportchannel.cc @@ -274,7 +274,7 @@ int QuicTransportChannel::SendPacket(const char* data, // |channel_| again. void QuicTransportChannel::OnWritableState(TransportChannel* channel) { ASSERT(rtc::Thread::Current() == worker_thread_); - ASSERT(channel == channel_); + ASSERT(channel == channel_.get()); LOG_J(LS_VERBOSE, this) << "QuicTransportChannel: channel writable state changed to " << channel_->writable(); @@ -308,7 +308,7 @@ void QuicTransportChannel::OnWritableState(TransportChannel* channel) { void QuicTransportChannel::OnReceivingState(TransportChannel* channel) { ASSERT(rtc::Thread::Current() == worker_thread_); - ASSERT(channel == channel_); + ASSERT(channel == channel_.get()); LOG_J(LS_VERBOSE, this) << "QuicTransportChannel: channel receiving state changed to " << channel_->receiving(); @@ -324,7 +324,7 @@ void QuicTransportChannel::OnReadPacket(TransportChannel* channel, const rtc::PacketTime& packet_time, int flags) { ASSERT(rtc::Thread::Current() == worker_thread_); - ASSERT(channel == channel_); + ASSERT(channel == channel_.get()); ASSERT(flags == 0); switch (quic_state_) { @@ -371,24 +371,24 @@ void QuicTransportChannel::OnReadyToSend(TransportChannel* channel) { } void QuicTransportChannel::OnGatheringState(TransportChannelImpl* channel) { - ASSERT(channel == channel_); + ASSERT(channel == channel_.get()); SignalGatheringState(this); } void QuicTransportChannel::OnCandidateGathered(TransportChannelImpl* channel, const Candidate& c) { - ASSERT(channel == channel_); + ASSERT(channel == channel_.get()); SignalCandidateGathered(this, c); } void QuicTransportChannel::OnRoleConflict(TransportChannelImpl* channel) { - ASSERT(channel == channel_); + ASSERT(channel == channel_.get()); SignalRoleConflict(this); } void QuicTransportChannel::OnRouteChange(TransportChannel* channel, const Candidate& candidate) { - ASSERT(channel == channel_); + ASSERT(channel == channel_.get()); SignalRouteChange(this, candidate); } @@ -396,13 +396,13 @@ void QuicTransportChannel::OnSelectedCandidatePairChanged( TransportChannel* channel, CandidatePairInterface* selected_candidate_pair, int last_sent_packet_id) { - ASSERT(channel == channel_); + ASSERT(channel == channel_.get()); SignalSelectedCandidatePairChanged(this, selected_candidate_pair, last_sent_packet_id); } void QuicTransportChannel::OnConnectionRemoved(TransportChannelImpl* channel) { - ASSERT(channel == channel_); + ASSERT(channel == channel_.get()); SignalConnectionRemoved(this); } diff --git a/webrtc/p2p/quic/quictransportchannel.h b/webrtc/p2p/quic/quictransportchannel.h index 847af7f02f..2ce17f8f90 100644 --- a/webrtc/p2p/quic/quictransportchannel.h +++ b/webrtc/p2p/quic/quictransportchannel.h @@ -273,7 +273,7 @@ class QuicTransportChannel : public TransportChannelImpl, rtc::Thread* worker_thread_; // Underlying channel which is responsible for connecting with the remote peer // and sending/receiving packets across the network. - TransportChannelImpl* const channel_; + std::unique_ptr channel_; // Connectivity state of QuicTransportChannel. QuicTransportState quic_state_ = QUIC_TRANSPORT_NEW; // QUIC session which establishes the crypto handshake and converts data diff --git a/webrtc/p2p/quic/quictransportchannel_unittest.cc b/webrtc/p2p/quic/quictransportchannel_unittest.cc index dba07ebf2b..0e16390a89 100644 --- a/webrtc/p2p/quic/quictransportchannel_unittest.cc +++ b/webrtc/p2p/quic/quictransportchannel_unittest.cc @@ -94,15 +94,15 @@ class QuicTestPeer : public sigslot::has_slots<> { explicit QuicTestPeer(const std::string& name) : name_(name), bytes_sent_(0), - ice_channel_(name_, 0), - quic_channel_(&ice_channel_), + ice_channel_(new FailableTransportChannel(name_, 0)), + quic_channel_(ice_channel_), incoming_stream_count_(0) { quic_channel_.SignalReadPacket.connect( this, &QuicTestPeer::OnTransportChannelReadPacket); quic_channel_.SignalIncomingStream.connect(this, &QuicTestPeer::OnIncomingStream); quic_channel_.SignalClosed.connect(this, &QuicTestPeer::OnClosed); - ice_channel_.SetAsync(true); + ice_channel_->SetAsync(true); rtc::scoped_refptr local_cert = rtc::RTCCertificate::Create(std::unique_ptr( rtc::SSLIdentity::Generate(name_, rtc::KT_DEFAULT))); @@ -112,13 +112,13 @@ class QuicTestPeer : public sigslot::has_slots<> { // Connects |ice_channel_| to that of the other peer. void Connect(QuicTestPeer* other_peer) { - ice_channel_.Connect(); - other_peer->ice_channel_.Connect(); - ice_channel_.SetDestination(&other_peer->ice_channel_); + ice_channel_->Connect(); + other_peer->ice_channel_->Connect(); + ice_channel_->SetDestination(other_peer->ice_channel_); } // Disconnects |ice_channel_|. - void Disconnect() { ice_channel_.SetDestination(nullptr); } + void Disconnect() { ice_channel_->SetDestination(nullptr); } // Generates ICE credentials and passes them to |quic_channel_|. void SetIceParameters(IceRole local_ice_role, @@ -189,13 +189,13 @@ class QuicTestPeer : public sigslot::has_slots<> { void ClearBytesReceived() { bytes_received_ = 0; } - void SetWriteError(int error) { ice_channel_.SetError(error); } + void SetWriteError(int error) { ice_channel_->SetError(error); } size_t bytes_received() const { return bytes_received_; } size_t bytes_sent() const { return bytes_sent_; } - FailableTransportChannel* ice_channel() { return &ice_channel_; } + FailableTransportChannel* ice_channel() { return ice_channel_; } QuicTransportChannel* quic_channel() { return &quic_channel_; } @@ -230,7 +230,7 @@ class QuicTestPeer : public sigslot::has_slots<> { std::string name_; // Channel name. size_t bytes_sent_; // Bytes sent by QUIC channel. size_t bytes_received_; // Bytes received by QUIC channel. - FailableTransportChannel ice_channel_; // Simulates an ICE channel. + FailableTransportChannel* ice_channel_; // Simulates an ICE channel. QuicTransportChannel quic_channel_; // QUIC channel to test. std::unique_ptr local_fingerprint_; ReliableQuicStream* incoming_quic_stream_ = nullptr;