diff --git a/webrtc/p2p/base/testturnserver.h b/webrtc/p2p/base/testturnserver.h index 5091e8e415..80f259fdc9 100644 --- a/webrtc/p2p/base/testturnserver.h +++ b/webrtc/p2p/base/testturnserver.h @@ -18,6 +18,8 @@ #include "webrtc/p2p/base/stun.h" #include "webrtc/p2p/base/turnserver.h" #include "webrtc/rtc_base/asyncudpsocket.h" +#include "webrtc/rtc_base/ssladapter.h" +#include "webrtc/rtc_base/sslidentity.h" #include "webrtc/rtc_base/thread.h" namespace cricket { @@ -81,14 +83,28 @@ class TestTurnServer : public TurnAuthInterface { server_.AddInternalSocket( rtc::AsyncUDPSocket::Create(thread_->socketserver(), int_addr), proto); - } else if (proto == cricket::PROTO_TCP) { + } else if (proto == cricket::PROTO_TCP || proto == cricket::PROTO_TLS) { // For TCP we need to create a server socket which can listen for incoming // new connections. rtc::AsyncSocket* socket = thread_->socketserver()->CreateAsyncSocket(SOCK_STREAM); + if (proto == cricket::PROTO_TLS) { + // For TLS, wrap the TCP socket with an SSL adapter. The adapter must + // be configured with a self-signed certificate for testing. + // Additionally, the client will not present a valid certificate, so we + // must not fail when checking the peer's identity. + rtc::SSLAdapter* adapter = rtc::SSLAdapter::Create(socket); + adapter->SetRole(rtc::SSL_SERVER); + adapter->SetIdentity( + rtc::SSLIdentity::Generate("test turn server", rtc::KeyParams())); + adapter->set_ignore_bad_cert(true); + socket = adapter; + } socket->Bind(int_addr); socket->Listen(5); server_.AddInternalServerSocket(socket, proto); + } else { + RTC_NOTREACHED() << "Unknown protocol type: " << proto; } } diff --git a/webrtc/p2p/base/turnport.cc b/webrtc/p2p/base/turnport.cc index 2828feac3f..4dfe06d13a 100644 --- a/webrtc/p2p/base/turnport.cc +++ b/webrtc/p2p/base/turnport.cc @@ -698,7 +698,8 @@ void TurnPort::OnResolveResult(rtc::AsyncResolverInterface* resolver) { // one of the reason could be due to DNS queries blocked by firewall. // In such cases we will try to connect to the server with hostname, assuming // socket layer will resolve the hostname through a HTTP proxy (if any). - if (resolver_->GetError() != 0 && server_address_.proto == PROTO_TCP) { + if (resolver_->GetError() != 0 && (server_address_.proto == PROTO_TCP || + server_address_.proto == PROTO_TLS)) { if (!CreateTurnClientSocket()) { OnAllocateError(); } @@ -818,7 +819,8 @@ void TurnPort::OnMessage(rtc::Message* message) { // Since it's TCP, we have to delete the connected socket and reconnect // with the alternate server. PrepareAddress will send stun binding once // the new socket is connected. - RTC_DCHECK(server_address().proto == PROTO_TCP); + RTC_DCHECK(server_address().proto == PROTO_TCP || + server_address().proto == PROTO_TLS); RTC_DCHECK(!SharedSocket()); delete socket_; socket_ = NULL; diff --git a/webrtc/p2p/base/turnport_unittest.cc b/webrtc/p2p/base/turnport_unittest.cc index fccf041f14..7c6f72bda7 100644 --- a/webrtc/p2p/base/turnport_unittest.cc +++ b/webrtc/p2p/base/turnport_unittest.cc @@ -267,6 +267,14 @@ class TurnPortTest : public testing::Test, // This TURN port will be the controlling. turn_port_->SetIceRole(ICEROLE_CONTROLLING); ConnectSignals(); + + if (server_address.proto == cricket::PROTO_TLS) { + // The test TURN server has a self-signed certificate so will not pass + // the normal client validation. Instruct the client to ignore certificate + // errors for testing only. + turn_port_->SetTlsCertPolicy( + TlsCertPolicy::TLS_CERT_POLICY_INSECURE_NO_CHECK); + } } void CreateSharedTurnPort(const std::string& username, @@ -336,6 +344,10 @@ class TurnPortTest : public testing::Test, // The virtual socket server will delay by a fixed half a round trip // for a TCP connection. return kSimulatedRtt / 2; + case PROTO_TLS: + // TLS operates over TCP and additionally has a round of HELLO for + // negotiating ciphers and a round for exchanging certificates. + return 2 * kSimulatedRtt + TimeToConnect(PROTO_TCP); case PROTO_UDP: default: // UDP requires no round trips to set up the connection. @@ -536,6 +548,7 @@ class TurnPortTest : public testing::Test, PrepareTurnAndUdpPorts(protocol_type); // Send ping from UDP to TURN. + ASSERT_GE(turn_port_->Candidates().size(), 1U); Connection* conn1 = udp_port_->CreateConnection( turn_port_->Candidates()[0], Port::ORIGIN_MESSAGE); ASSERT_TRUE(conn1 != NULL); @@ -710,6 +723,12 @@ TEST_F(TurnPortTest, TestReconstructedServerUrlForTcp) { TestReconstructedServerUrl(PROTO_TCP, "turn:99.99.99.4:3478?transport=tcp"); } +TEST_F(TurnPortTest, TestReconstructedServerUrlForTls) { + turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TLS); + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTlsProtoAddr); + TestReconstructedServerUrl(PROTO_TLS, "turns:99.99.99.4:3478?transport=tcp"); +} + // Do a normal TURN allocation. TEST_F(TurnPortTest, TestTurnAllocate) { CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); @@ -829,6 +848,18 @@ TEST_F(TurnPortTest, TestTurnTcpOnAddressResolveFailure) { EXPECT_EQ(SOCKET_ERROR, turn_port_->error()); } +// Testing turn port will attempt to create TLS socket on address resolution +// failure. +TEST_F(TurnPortTest, TestTurnTlsOnAddressResolveFailure) { + turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TLS); + CreateTurnPort(kTurnUsername, kTurnPassword, + ProtocolAddress(rtc::SocketAddress("www.google.invalid", 3478), + PROTO_TLS)); + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_error_, kResolverTimeout); + EXPECT_EQ(SOCKET_ERROR, turn_port_->error()); +} + // In case of UDP on address resolve failure, TurnPort will not create socket // and return allocate failure. TEST_F(TurnPortTest, TestTurnUdpOnAddressResolveFailure) { @@ -1073,6 +1104,10 @@ TEST_F(TurnPortTest, TestTurnAlternateServerTCP) { TestTurnAlternateServer(PROTO_TCP); } +TEST_F(TurnPortTest, TestTurnAlternateServerTLS) { + TestTurnAlternateServer(PROTO_TLS); +} + // Test that we fail when we redirect to an address different from // current IP family. TEST_F(TurnPortTest, TestTurnAlternateServerV4toV6UDP) { @@ -1083,6 +1118,10 @@ TEST_F(TurnPortTest, TestTurnAlternateServerV4toV6TCP) { TestTurnAlternateServerV4toV6(PROTO_TCP); } +TEST_F(TurnPortTest, TestTurnAlternateServerV4toV6TLS) { + TestTurnAlternateServerV4toV6(PROTO_TLS); +} + // Test try-alternate-server catches the case of pingpong. TEST_F(TurnPortTest, TestTurnAlternateServerPingPongUDP) { TestTurnAlternateServerPingPong(PROTO_UDP); @@ -1092,6 +1131,10 @@ TEST_F(TurnPortTest, TestTurnAlternateServerPingPongTCP) { TestTurnAlternateServerPingPong(PROTO_TCP); } +TEST_F(TurnPortTest, TestTurnAlternateServerPingPongTLS) { + TestTurnAlternateServerPingPong(PROTO_TLS); +} + // Test try-alternate-server catch the case of repeated server. TEST_F(TurnPortTest, TestTurnAlternateServerDetectRepetitionUDP) { TestTurnAlternateServerDetectRepetition(PROTO_UDP); @@ -1101,6 +1144,10 @@ TEST_F(TurnPortTest, TestTurnAlternateServerDetectRepetitionTCP) { TestTurnAlternateServerDetectRepetition(PROTO_TCP); } +TEST_F(TurnPortTest, TestTurnAlternateServerDetectRepetitionTLS) { + TestTurnAlternateServerDetectRepetition(PROTO_TCP); +} + // Test catching the case of a redirect to loopback. TEST_F(TurnPortTest, TestTurnAlternateServerLoopbackUdpIpv4) { TestTurnAlternateServerLoopback(PROTO_UDP, false); @@ -1118,6 +1165,14 @@ TEST_F(TurnPortTest, TestTurnAlternateServerLoopbackTcpIpv6) { TestTurnAlternateServerLoopback(PROTO_TCP, true); } +TEST_F(TurnPortTest, TestTurnAlternateServerLoopbackTlsIpv4) { + TestTurnAlternateServerLoopback(PROTO_TLS, false); +} + +TEST_F(TurnPortTest, TestTurnAlternateServerLoopbackTlsIpv6) { + TestTurnAlternateServerLoopback(PROTO_TLS, true); +} + // Do a TURN allocation and try to send a packet to it from the outside. // The packet should be dropped. Then, try to send a packet from TURN to the // outside. It should reach its destination. Finally, try again from the @@ -1140,6 +1195,13 @@ TEST_F(TurnPortTest, TestTurnTcpConnection) { TestTurnConnection(PROTO_TCP); } +// Test that we can establish a TLS connection with TURN server. +TEST_F(TurnPortTest, TestTurnTlsConnection) { + turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TLS); + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTlsProtoAddr); + TestTurnConnection(PROTO_TLS); +} + // Test that if a connection on a TURN port is destroyed, the TURN port can // still receive ping on that connection as if it is from an unknown address. // If the connection is created again, it will be used to receive ping. @@ -1154,17 +1216,6 @@ TEST_F(TurnPortTest, TestDestroyTurnConnectionUsingSharedSocket) { TestDestroyTurnConnection(); } -// Test that we fail to create a connection when we want to use TLS over TCP. -// This test should be removed once we have TLS support. -TEST_F(TurnPortTest, TestTurnTlsTcpConnectionFails) { - ProtocolAddress secure_addr(kTurnTlsProtoAddr.address, - kTurnTlsProtoAddr.proto); - CreateTurnPort(kTurnUsername, kTurnPassword, secure_addr); - turn_port_->PrepareAddress(); - EXPECT_TRUE_SIMULATED_WAIT(turn_error_, kSimulatedRtt * 2, fake_clock_); - ASSERT_EQ(0U, turn_port_->Candidates().size()); -} - // Run TurnConnectionTest with one-time-use nonce feature. // Here server will send a 438 STALE_NONCE error message for // every TURN transaction. @@ -1250,6 +1301,14 @@ TEST_F(TurnPortTest, TestTurnSendDataTurnTcpToUdp) { EXPECT_EQ(TCP_PROTOCOL_NAME, turn_port_->Candidates()[0].relay_protocol()); } +// Do a TURN allocation, establish a TLS connection, and send some data. +TEST_F(TurnPortTest, TestTurnSendDataTurnTlsToUdp) { + turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TLS); + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTlsProtoAddr); + TestTurnSendData(PROTO_TLS); + EXPECT_EQ(TLS_PROTOCOL_NAME, turn_port_->Candidates()[0].relay_protocol()); +} + // Test TURN fails to make a connection from IPv6 address to a server which has // IPv4 address. TEST_F(TurnPortTest, TestTurnLocalIPv6AddressServerIPv4) { @@ -1357,6 +1416,12 @@ TEST_F(TurnPortTest, TestTurnTCPReleaseAllocation) { TestTurnReleaseAllocation(PROTO_TCP); } +TEST_F(TurnPortTest, TestTurnTLSReleaseAllocation) { + turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TLS); + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTlsProtoAddr); + TestTurnReleaseAllocation(PROTO_TLS); +} + // This test verifies any FD's are not leaked after TurnPort is destroyed. // https://code.google.com/p/webrtc/issues/detail?id=2651 #if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) diff --git a/webrtc/rtc_base/openssladapter.cc b/webrtc/rtc_base/openssladapter.cc index 11473ac225..64eb0ab77e 100644 --- a/webrtc/rtc_base/openssladapter.cc +++ b/webrtc/rtc_base/openssladapter.cc @@ -279,6 +279,7 @@ OpenSSLAdapter::OpenSSLAdapter(AsyncSocket* socket, : SSLAdapter(socket), factory_(factory), state_(SSL_NONE), + role_(SSL_CLIENT), ssl_read_needs_write_(false), ssl_write_needs_read_(false), restartable_(false), @@ -307,6 +308,30 @@ void OpenSSLAdapter::SetMode(SSLMode mode) { ssl_mode_ = mode; } +void OpenSSLAdapter::SetIdentity(SSLIdentity* identity) { + RTC_DCHECK(!identity_); + identity_.reset(static_cast(identity)); +} + +void OpenSSLAdapter::SetRole(SSLRole role) { + role_ = role; +} + +AsyncSocket* OpenSSLAdapter::Accept(SocketAddress* paddr) { + RTC_DCHECK(role_ == SSL_SERVER); + AsyncSocket* socket = SSLAdapter::Accept(paddr); + if (!socket) { + return nullptr; + } + + SSLAdapter* adapter = SSLAdapter::Create(socket); + adapter->SetIdentity(identity_->GetReference()); + adapter->SetRole(rtc::SSL_SERVER); + adapter->set_ignore_bad_cert(ignore_bad_cert()); + adapter->StartSSL("", false); + return adapter; +} + int OpenSSLAdapter::StartSSL(const char* hostname, bool restartable) { if (state_ != SSL_NONE) return -1; @@ -347,6 +372,12 @@ int OpenSSLAdapter::BeginSSL() { goto ssl_error; } + if (identity_ && !identity_->ConfigureIdentity(ssl_ctx_)) { + SSL_CTX_free(ssl_ctx_); + err = -1; + goto ssl_error; + } + bio = BIO_new_socket(socket_); if (!bio) { err = -1; @@ -423,7 +454,7 @@ int OpenSSLAdapter::ContinueSSL() { // Clear the DTLS timer Thread::Current()->Clear(this, MSG_TIMEOUT); - int code = SSL_connect(ssl_); + int code = (role_ == SSL_CLIENT) ? SSL_connect(ssl_) : SSL_accept(ssl_); switch (SSL_get_error(ssl_, code)) { case SSL_ERROR_NONE: if (!SSLPostConnectionCheck(ssl_, ssl_host_name_.c_str())) { @@ -496,6 +527,7 @@ void OpenSSLAdapter::Cleanup() { SSL_CTX_free(ssl_ctx_); ssl_ctx_ = nullptr; } + identity_.reset(); // Clear the DTLS timer Thread::Current()->Clear(this, MSG_TIMEOUT); diff --git a/webrtc/rtc_base/openssladapter.h b/webrtc/rtc_base/openssladapter.h index 4b49efd25f..b57ea8fd33 100644 --- a/webrtc/rtc_base/openssladapter.h +++ b/webrtc/rtc_base/openssladapter.h @@ -16,6 +16,7 @@ #include "webrtc/rtc_base/buffer.h" #include "webrtc/rtc_base/messagehandler.h" #include "webrtc/rtc_base/messagequeue.h" +#include "webrtc/rtc_base/opensslidentity.h" #include "webrtc/rtc_base/ssladapter.h" typedef struct ssl_st SSL; @@ -38,6 +39,9 @@ class OpenSSLAdapter : public SSLAdapter, public MessageHandler { ~OpenSSLAdapter() override; void SetMode(SSLMode mode) override; + void SetIdentity(SSLIdentity* identity) override; + void SetRole(SSLRole role) override; + AsyncSocket* Accept(SocketAddress* paddr) override; int StartSSL(const char* hostname, bool restartable) override; int Send(const void* pv, size_t cb) override; int SendTo(const void* pv, size_t cb, const SocketAddress& addr) override; @@ -107,6 +111,8 @@ class OpenSSLAdapter : public SSLAdapter, public MessageHandler { OpenSSLAdapterFactory* factory_; SSLState state_; + std::unique_ptr identity_; + SSLRole role_; bool ssl_read_needs_write_; bool ssl_write_needs_read_; // If true, socket will retain SSL configuration after Close. diff --git a/webrtc/rtc_base/ssladapter.h b/webrtc/rtc_base/ssladapter.h index 6b12035d2f..87e7debc76 100644 --- a/webrtc/rtc_base/ssladapter.h +++ b/webrtc/rtc_base/ssladapter.h @@ -53,6 +53,12 @@ class SSLAdapter : public AsyncSocketAdapter { // Do DTLS or TLS (default is TLS, if unspecified) virtual void SetMode(SSLMode mode) = 0; + // Set the certificate this socket will present to incoming clients. + virtual void SetIdentity(SSLIdentity* identity) = 0; + + // Choose whether the socket acts as a server socket or client socket. + virtual void SetRole(SSLRole role) = 0; + // StartSSL returns 0 if successful. // If StartSSL is called while the socket is closed or connecting, the SSL // negotiation will begin as soon as the socket connects.