Make maximum SSL version configurable through PeerConnectionFactory::Options

This can be used to activate DTLS 1.2 through a command-line flag from Chromium
later.

BUG=chromium:428343
R=jiayl@webrtc.org, juberti@google.com

Review URL: https://webrtc-codereview.appspot.com/54509004

Cr-Commit-Position: refs/heads/master@{#9328}
diff --git a/talk/app/webrtc/peerconnection_unittest.cc b/talk/app/webrtc/peerconnection_unittest.cc
index fe712e8..99ee9be 100644
--- a/talk/app/webrtc/peerconnection_unittest.cc
+++ b/talk/app/webrtc/peerconnection_unittest.cc
@@ -82,6 +82,7 @@
 using webrtc::MockSetSessionDescriptionObserver;
 using webrtc::MockStatsObserver;
 using webrtc::PeerConnectionInterface;
+using webrtc::PeerConnectionFactory;
 using webrtc::SessionDescriptionInterface;
 using webrtc::StreamCollectionInterface;
 
@@ -501,7 +502,8 @@
         video_decoder_factory_enabled_(false),
         signaling_message_receiver_(NULL) {
   }
-  bool Init(const MediaConstraintsInterface* constraints) {
+  bool Init(const MediaConstraintsInterface* constraints,
+            const PeerConnectionFactory::Options* options) {
     EXPECT_TRUE(!peer_connection_);
     EXPECT_TRUE(!peer_connection_factory_);
     allocator_factory_ = webrtc::FakePortAllocatorFactory::Create();
@@ -523,6 +525,9 @@
     if (!peer_connection_factory_) {
       return false;
     }
+    if (options) {
+      peer_connection_factory_->SetOptions(*options);
+    }
     peer_connection_ = CreatePeerConnection(allocator_factory_.get(),
                                             constraints);
     return peer_connection_.get() != NULL;
@@ -619,9 +624,10 @@
  public:
   static JsepTestClient* CreateClient(
       const std::string& id,
-      const MediaConstraintsInterface* constraints) {
+      const MediaConstraintsInterface* constraints,
+      const PeerConnectionFactory::Options* options) {
     JsepTestClient* client(new JsepTestClient(id));
-    if (!client->Init(constraints)) {
+    if (!client->Init(constraints, options)) {
       delete client;
       return NULL;
     }
@@ -967,10 +973,19 @@
 
   bool CreateTestClients(MediaConstraintsInterface* init_constraints,
                          MediaConstraintsInterface* recv_constraints) {
+    return CreateTestClients(init_constraints, NULL, recv_constraints, NULL);
+  }
+
+  bool CreateTestClients(MediaConstraintsInterface* init_constraints,
+                         PeerConnectionFactory::Options* init_options,
+                         MediaConstraintsInterface* recv_constraints,
+                         PeerConnectionFactory::Options* recv_options) {
     initiating_client_.reset(SignalingClass::CreateClient("Caller: ",
-                                                          init_constraints));
+                                                          init_constraints,
+                                                          init_options));
     receiving_client_.reset(SignalingClass::CreateClient("Callee: ",
-                                                         recv_constraints));
+                                                         recv_constraints,
+                                                         recv_options));
     if (!initiating_client_ || !receiving_client_) {
       return false;
     }
@@ -1307,13 +1322,77 @@
       kMaxWaitForStatsMs);
 }
 
-// Test that we can get negotiated ciphers.
-TEST_F(JsepPeerConnectionP2PTestClient, GetNegotiatedCiphersStats) {
-  ASSERT_TRUE(CreateTestClients());
+// Test that DTLS 1.0 is used if both sides only support DTLS 1.0.
+TEST_F(JsepPeerConnectionP2PTestClient, GetDtls12None) {
+  PeerConnectionFactory::Options init_options;
+  init_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_10;
+  PeerConnectionFactory::Options recv_options;
+  recv_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_10;
+  ASSERT_TRUE(CreateTestClients(NULL, &init_options, NULL, &recv_options));
   LocalP2PTest();
 
-  // TODO(jbauch): this should check for DTLS 1.0 / 1.2 when we have a way to
-  // enable DTLS 1.2 on peer connections.
+  EXPECT_EQ_WAIT(
+      rtc::SSLStreamAdapter::GetDefaultSslCipher(rtc::SSL_PROTOCOL_DTLS_10),
+      initializing_client()->GetDtlsCipherStats(),
+      kMaxWaitForStatsMs);
+
+  EXPECT_EQ_WAIT(
+      kDefaultSrtpCipher,
+      initializing_client()->GetSrtpCipherStats(),
+      kMaxWaitForStatsMs);
+}
+
+// Test that DTLS 1.2 is used if both ends support it.
+TEST_F(JsepPeerConnectionP2PTestClient, GetDtls12Both) {
+  PeerConnectionFactory::Options init_options;
+  init_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_12;
+  PeerConnectionFactory::Options recv_options;
+  recv_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_12;
+  ASSERT_TRUE(CreateTestClients(NULL, &init_options, NULL, &recv_options));
+  LocalP2PTest();
+
+  EXPECT_EQ_WAIT(
+      rtc::SSLStreamAdapter::GetDefaultSslCipher(rtc::SSL_PROTOCOL_DTLS_12),
+      initializing_client()->GetDtlsCipherStats(),
+      kMaxWaitForStatsMs);
+
+  EXPECT_EQ_WAIT(
+      kDefaultSrtpCipher,
+      initializing_client()->GetSrtpCipherStats(),
+      kMaxWaitForStatsMs);
+}
+
+// Test that DTLS 1.0 is used if the initator supports DTLS 1.2 and the
+// received supports 1.0.
+TEST_F(JsepPeerConnectionP2PTestClient, GetDtls12Init) {
+  PeerConnectionFactory::Options init_options;
+  init_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_12;
+  PeerConnectionFactory::Options recv_options;
+  recv_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_10;
+  ASSERT_TRUE(CreateTestClients(NULL, &init_options, NULL, &recv_options));
+  LocalP2PTest();
+
+  EXPECT_EQ_WAIT(
+      rtc::SSLStreamAdapter::GetDefaultSslCipher(rtc::SSL_PROTOCOL_DTLS_10),
+      initializing_client()->GetDtlsCipherStats(),
+      kMaxWaitForStatsMs);
+
+  EXPECT_EQ_WAIT(
+      kDefaultSrtpCipher,
+      initializing_client()->GetSrtpCipherStats(),
+      kMaxWaitForStatsMs);
+}
+
+// Test that DTLS 1.0 is used if the initator supports DTLS 1.0 and the
+// received supports 1.2.
+TEST_F(JsepPeerConnectionP2PTestClient, GetDtls12Recv) {
+  PeerConnectionFactory::Options init_options;
+  init_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_10;
+  PeerConnectionFactory::Options recv_options;
+  recv_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_12;
+  ASSERT_TRUE(CreateTestClients(NULL, &init_options, NULL, &recv_options));
+  LocalP2PTest();
+
   EXPECT_EQ_WAIT(
       rtc::SSLStreamAdapter::GetDefaultSslCipher(rtc::SSL_PROTOCOL_DTLS_10),
       initializing_client()->GetDtlsCipherStats(),
diff --git a/talk/app/webrtc/peerconnectioninterface.h b/talk/app/webrtc/peerconnectioninterface.h
index 329137f..59cc700 100644
--- a/talk/app/webrtc/peerconnectioninterface.h
+++ b/talk/app/webrtc/peerconnectioninterface.h
@@ -79,6 +79,7 @@
 #include "talk/app/webrtc/umametrics.h"
 #include "webrtc/base/fileutils.h"
 #include "webrtc/base/network.h"
+#include "webrtc/base/sslstreamadapter.h"
 #include "webrtc/base/socketaddress.h"
 
 namespace rtc {
@@ -518,7 +519,8 @@
     Options() :
       disable_encryption(false),
       disable_sctp_data_channels(false),
-      network_ignore_mask(rtc::kDefaultNetworkIgnoreMask) {
+      network_ignore_mask(rtc::kDefaultNetworkIgnoreMask),
+      ssl_max_version(rtc::SSL_PROTOCOL_DTLS_10) {
     }
     bool disable_encryption;
     bool disable_sctp_data_channels;
@@ -527,6 +529,11 @@
     // ADAPTER_TYPE_ETHERNET | ADAPTER_TYPE_LOOPBACK will ignore Ethernet and
     // loopback interfaces.
     int network_ignore_mask;
+
+    // Sets the maximum supported protocol version. The highest version
+    // supported by both ends will be used for the connection, i.e. if one
+    // party supports DTLS 1.0 and the other DTLS 1.2, DTLS 1.0 will be used.
+    rtc::SSLProtocolVersion ssl_max_version;
   };
 
   virtual void SetOptions(const Options& options) = 0;
diff --git a/talk/app/webrtc/webrtcsession.cc b/talk/app/webrtc/webrtcsession.cc
index e461365..a058129 100644
--- a/talk/app/webrtc/webrtcsession.cc
+++ b/talk/app/webrtc/webrtcsession.cc
@@ -524,6 +524,7 @@
     const PeerConnectionInterface::RTCConfiguration& rtc_configuration) {
   bundle_policy_ = rtc_configuration.bundle_policy;
   rtcp_mux_policy_ = rtc_configuration.rtcp_mux_policy;
+  SetSslMaxProtocolVersion(options.ssl_max_version);
 
   // TODO(perkj): Take |constraints| into consideration. Return false if not all
   // mandatory constraints can be fulfilled. Note that |constraints|
diff --git a/webrtc/p2p/base/dtlstransport.h b/webrtc/p2p/base/dtlstransport.h
index 8e17ea6..27cece4 100644
--- a/webrtc/p2p/base/dtlstransport.h
+++ b/webrtc/p2p/base/dtlstransport.h
@@ -33,7 +33,8 @@
                 rtc::SSLIdentity* identity)
       : Base(signaling_thread, worker_thread, content_name, allocator),
         identity_(identity),
-        secure_role_(rtc::SSL_CLIENT) {
+        secure_role_(rtc::SSL_CLIENT),
+        ssl_max_version_(rtc::SSL_PROTOCOL_DTLS_10) {
   }
 
   ~DtlsTransport() {
@@ -50,6 +51,11 @@
     return true;
   }
 
+  virtual bool SetSslMaxProtocolVersion_w(rtc::SSLProtocolVersion version) {
+    ssl_max_version_ = version;
+    return true;
+  }
+
   virtual bool ApplyLocalTransportDescription_w(TransportChannelImpl* channel,
                                                 std::string* error_desc) {
     rtc::SSLFingerprint* local_fp =
@@ -189,8 +195,10 @@
   }
 
   virtual DtlsTransportChannelWrapper* CreateTransportChannel(int component) {
-    return new DtlsTransportChannelWrapper(
+    DtlsTransportChannelWrapper* channel = new DtlsTransportChannelWrapper(
         this, Base::CreateTransportChannel(component));
+    channel->SetSslMaxProtocolVersion(ssl_max_version_);
+    return channel;
   }
 
   virtual void DestroyTransportChannel(TransportChannelImpl* channel) {
@@ -231,6 +239,7 @@
 
   rtc::SSLIdentity* identity_;
   rtc::SSLRole secure_role_;
+  rtc::SSLProtocolVersion ssl_max_version_;
   rtc::scoped_ptr<rtc::SSLFingerprint> remote_fingerprint_;
 };
 
diff --git a/webrtc/p2p/base/dtlstransportchannel.cc b/webrtc/p2p/base/dtlstransportchannel.cc
index 6a206d4..e73cf68 100644
--- a/webrtc/p2p/base/dtlstransportchannel.cc
+++ b/webrtc/p2p/base/dtlstransportchannel.cc
@@ -163,15 +163,16 @@
   return true;
 }
 
-void DtlsTransportChannelWrapper::SetMaxProtocolVersion(
+bool DtlsTransportChannelWrapper::SetSslMaxProtocolVersion(
     rtc::SSLProtocolVersion version) {
   if (dtls_state_ != STATE_NONE) {
     LOG(LS_ERROR) << "Not changing max. protocol version "
                   << "while DTLS is negotiating";
-    return;
+    return false;
   }
 
   ssl_max_version_ = version;
+  return true;
 }
 
 bool DtlsTransportChannelWrapper::SetSslRole(rtc::SSLRole role) {
diff --git a/webrtc/p2p/base/dtlstransportchannel.h b/webrtc/p2p/base/dtlstransportchannel.h
index a48d09f..1af5efd 100644
--- a/webrtc/p2p/base/dtlstransportchannel.h
+++ b/webrtc/p2p/base/dtlstransportchannel.h
@@ -129,7 +129,7 @@
     return channel_->SessionId();
   }
 
-  virtual void SetMaxProtocolVersion(rtc::SSLProtocolVersion version);
+  virtual bool SetSslMaxProtocolVersion(rtc::SSLProtocolVersion version);
 
   // Set up the ciphers to use for DTLS-SRTP. If this method is not called
   // before DTLS starts, or |ciphers| is empty, SRTP keys won't be negotiated.
diff --git a/webrtc/p2p/base/dtlstransportchannel_unittest.cc b/webrtc/p2p/base/dtlstransportchannel_unittest.cc
index 670f8f9..8c1c21c 100644
--- a/webrtc/p2p/base/dtlstransportchannel_unittest.cc
+++ b/webrtc/p2p/base/dtlstransportchannel_unittest.cc
@@ -90,7 +90,7 @@
           static_cast<cricket::DtlsTransportChannelWrapper*>(
               transport_->CreateChannel(i));
       ASSERT_TRUE(channel != NULL);
-      channel->SetMaxProtocolVersion(ssl_max_version_);
+      channel->SetSslMaxProtocolVersion(ssl_max_version_);
       channel->SignalWritableState.connect(this,
         &DtlsTestClient::OnTransportChannelWritableState);
       channel->SignalReadPacket.connect(this,
diff --git a/webrtc/p2p/base/session.cc b/webrtc/p2p/base/session.cc
index 8cbe128..d1f6e54 100644
--- a/webrtc/p2p/base/session.cc
+++ b/webrtc/p2p/base/session.cc
@@ -342,6 +342,7 @@
       transport_type_(NS_GINGLE_P2P),
       initiator_(initiator),
       identity_(NULL),
+      ssl_max_version_(rtc::SSL_PROTOCOL_DTLS_10),
       ice_tiebreaker_(rtc::CreateRandomId64()),
       role_switch_(false) {
   ASSERT(signaling_thread->IsCurrent());
@@ -404,6 +405,15 @@
   return true;
 }
 
+bool BaseSession::SetSslMaxProtocolVersion(rtc::SSLProtocolVersion version) {
+  if (state_ != STATE_INIT) {
+    return false;
+  }
+
+  ssl_max_version_ = version;
+  return true;
+}
+
 bool BaseSession::PushdownTransportDescription(ContentSource source,
                                                ContentAction action,
                                                std::string* error_desc) {
@@ -501,6 +511,7 @@
   Transport* transport = CreateTransport(content_name);
   transport->SetIceRole(initiator_ ? ICEROLE_CONTROLLING : ICEROLE_CONTROLLED);
   transport->SetIceTiebreaker(ice_tiebreaker_);
+  transport->SetSslMaxProtocolVersion(ssl_max_version_);
   // TODO: Connect all the Transport signals to TransportProxy
   // then to the BaseSession.
   transport->SignalConnecting.connect(
diff --git a/webrtc/p2p/base/session.h b/webrtc/p2p/base/session.h
index ba4206a..318a788 100644
--- a/webrtc/p2p/base/session.h
+++ b/webrtc/p2p/base/session.h
@@ -323,6 +323,8 @@
   // Specifies the identity to use in this session.
   bool SetIdentity(rtc::SSLIdentity* identity);
 
+  bool SetSslMaxProtocolVersion(rtc::SSLProtocolVersion version);
+
   bool PushdownTransportDescription(ContentSource source,
                                     ContentAction action,
                                     std::string* error_desc);
@@ -445,6 +447,7 @@
   const std::string transport_type_;
   bool initiator_;
   rtc::SSLIdentity* identity_;
+  rtc::SSLProtocolVersion ssl_max_version_;
   rtc::scoped_ptr<const SessionDescription> local_description_;
   rtc::scoped_ptr<SessionDescription> remote_description_;
   uint64 ice_tiebreaker_;
diff --git a/webrtc/p2p/base/transport.cc b/webrtc/p2p/base/transport.cc
index df452fd..0184af3 100644
--- a/webrtc/p2p/base/transport.cc
+++ b/webrtc/p2p/base/transport.cc
@@ -458,6 +458,11 @@
       &Transport::GetSslRole_w, this, ssl_role));
 }
 
+bool Transport::SetSslMaxProtocolVersion(rtc::SSLProtocolVersion version) {
+  return worker_thread_->Invoke<bool>(Bind(
+      &Transport::SetSslMaxProtocolVersion_w, this, version));
+}
+
 void Transport::OnRemoteCandidates(const std::vector<Candidate>& candidates) {
   for (std::vector<Candidate>::const_iterator iter = candidates.begin();
        iter != candidates.end();
diff --git a/webrtc/p2p/base/transport.h b/webrtc/p2p/base/transport.h
index c4ab03d..d7ebc9c 100644
--- a/webrtc/p2p/base/transport.h
+++ b/webrtc/p2p/base/transport.h
@@ -264,6 +264,9 @@
 
   virtual bool GetSslRole(rtc::SSLRole* ssl_role) const;
 
+  // Must be called before channel is starting to connect.
+  virtual bool SetSslMaxProtocolVersion(rtc::SSLProtocolVersion version);
+
  protected:
   // These are called by Create/DestroyChannel above in order to create or
   // destroy the appropriate type of channel.
@@ -320,6 +323,10 @@
     return false;
   }
 
+  virtual bool SetSslMaxProtocolVersion_w(rtc::SSLProtocolVersion version) {
+    return false;
+  }
+
  private:
   struct ChannelMapEntry {
     ChannelMapEntry() : impl_(NULL), candidates_allocated_(false), ref_(0) {}