Files
platform-external-webrtc/webrtc/base/ssladapter_unittest.cc
pthatcher@webrtc.org a9b1ec0247 Support for DTLS in OpenSSLAdapter
1)  Added SetMode() to SSLAdapter and OpenSSLAdapter so the mode can be set to
     SSL_MODE_DTLS
 2)  OpenSSLAdapter overrides SendTo() and RecvFrom() to handle calls from
     TurnPort via AsyncUdpSocket
 3)  OpenSSLAdapter derives from MessageHandler to implement an internal DTLS
     timer
 4)  Updated SSLAdapter unit tests

BUG=
R=juberti@webrtc.org

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

git-svn-id: http://webrtc.googlecode.com/svn/trunk@7981 4adac7df-926f-26a2-2b94-8c16560cd09d
2014-12-29 23:00:14 +00:00

386 lines
11 KiB
C++

/*
* Copyright 2014 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 <string>
#include "webrtc/base/gunit.h"
#include "webrtc/base/ipaddress.h"
#include "webrtc/base/socketstream.h"
#include "webrtc/base/ssladapter.h"
#include "webrtc/base/sslstreamadapter.h"
#include "webrtc/base/stream.h"
#include "webrtc/base/virtualsocketserver.h"
static const int kTimeout = 5000;
static rtc::AsyncSocket* CreateSocket(const rtc::SSLMode& ssl_mode) {
rtc::SocketAddress address(rtc::IPAddress(INADDR_ANY), 0);
rtc::AsyncSocket* socket = rtc::Thread::Current()->
socketserver()->CreateAsyncSocket(
address.family(), (ssl_mode == rtc::SSL_MODE_DTLS) ?
SOCK_DGRAM : SOCK_STREAM);
socket->Bind(address);
return socket;
}
static std::string GetSSLProtocolName(const rtc::SSLMode& ssl_mode) {
return (ssl_mode == rtc::SSL_MODE_DTLS) ? "DTLS" : "TLS";
}
class SSLAdapterTestDummyClient : public sigslot::has_slots<> {
public:
explicit SSLAdapterTestDummyClient(const rtc::SSLMode& ssl_mode)
: ssl_mode_(ssl_mode) {
rtc::AsyncSocket* socket = CreateSocket(ssl_mode_);
ssl_adapter_.reset(rtc::SSLAdapter::Create(socket));
ssl_adapter_->SetMode(ssl_mode_);
// Ignore any certificate errors for the purpose of testing.
// Note: We do this only because we don't have a real certificate.
// NEVER USE THIS IN PRODUCTION CODE!
ssl_adapter_->set_ignore_bad_cert(true);
ssl_adapter_->SignalReadEvent.connect(this,
&SSLAdapterTestDummyClient::OnSSLAdapterReadEvent);
ssl_adapter_->SignalCloseEvent.connect(this,
&SSLAdapterTestDummyClient::OnSSLAdapterCloseEvent);
}
rtc::SocketAddress GetAddress() const {
return ssl_adapter_->GetLocalAddress();
}
rtc::AsyncSocket::ConnState GetState() const {
return ssl_adapter_->GetState();
}
const std::string& GetReceivedData() const {
return data_;
}
int Connect(const std::string& hostname, const rtc::SocketAddress& address) {
LOG(LS_INFO) << "Initiating connection with " << address;
int rv = ssl_adapter_->Connect(address);
if (rv == 0) {
LOG(LS_INFO) << "Starting " << GetSSLProtocolName(ssl_mode_)
<< " handshake with " << hostname;
if (ssl_adapter_->StartSSL(hostname.c_str(), false) != 0) {
return -1;
}
}
return rv;
}
int Close() {
return ssl_adapter_->Close();
}
int Send(const std::string& message) {
LOG(LS_INFO) << "Client sending '" << message << "'";
return ssl_adapter_->Send(message.data(), message.length());
}
void OnSSLAdapterReadEvent(rtc::AsyncSocket* socket) {
char buffer[4096] = "";
// Read data received from the server and store it in our internal buffer.
int read = socket->Recv(buffer, sizeof(buffer) - 1);
if (read != -1) {
buffer[read] = '\0';
LOG(LS_INFO) << "Client received '" << buffer << "'";
data_ += buffer;
}
}
void OnSSLAdapterCloseEvent(rtc::AsyncSocket* socket, int error) {
// OpenSSLAdapter signals handshake failure with a close event, but without
// closing the socket! Let's close the socket here. This way GetState() can
// return CS_CLOSED after failure.
if (socket->GetState() != rtc::AsyncSocket::CS_CLOSED) {
socket->Close();
}
}
private:
const rtc::SSLMode ssl_mode_;
rtc::scoped_ptr<rtc::SSLAdapter> ssl_adapter_;
std::string data_;
};
class SSLAdapterTestDummyServer : public sigslot::has_slots<> {
public:
explicit SSLAdapterTestDummyServer(const rtc::SSLMode& ssl_mode)
: ssl_mode_(ssl_mode) {
// Generate a key pair and a certificate for this host.
ssl_identity_.reset(rtc::SSLIdentity::Generate(GetHostname()));
server_socket_.reset(CreateSocket(ssl_mode_));
if (ssl_mode_ == rtc::SSL_MODE_TLS) {
server_socket_->SignalReadEvent.connect(this,
&SSLAdapterTestDummyServer::OnServerSocketReadEvent);
server_socket_->Listen(1);
}
LOG(LS_INFO) << ((ssl_mode_ == rtc::SSL_MODE_DTLS) ? "UDP" : "TCP")
<< " server listening on " << server_socket_->GetLocalAddress();
}
rtc::SocketAddress GetAddress() const {
return server_socket_->GetLocalAddress();
}
std::string GetHostname() const {
// Since we don't have a real certificate anyway, the value here doesn't
// really matter.
return "example.com";
}
const std::string& GetReceivedData() const {
return data_;
}
int Send(const std::string& message) {
if (ssl_stream_adapter_ == NULL
|| ssl_stream_adapter_->GetState() != rtc::SS_OPEN) {
// No connection yet.
return -1;
}
LOG(LS_INFO) << "Server sending '" << message << "'";
size_t written;
int error;
rtc::StreamResult r = ssl_stream_adapter_->Write(message.data(),
message.length(), &written, &error);
if (r == rtc::SR_SUCCESS) {
return written;
} else {
return -1;
}
}
void AcceptConnection(const rtc::SocketAddress& address) {
// Only a single connection is supported.
ASSERT_TRUE(ssl_stream_adapter_ == NULL);
// This is only for DTLS.
ASSERT_EQ(rtc::SSL_MODE_DTLS, ssl_mode_);
// Transfer ownership of the socket to the SSLStreamAdapter object.
rtc::AsyncSocket* socket = server_socket_.release();
socket->Connect(address);
DoHandshake(socket);
}
void OnServerSocketReadEvent(rtc::AsyncSocket* socket) {
// Only a single connection is supported.
ASSERT_TRUE(ssl_stream_adapter_ == NULL);
DoHandshake(server_socket_->Accept(NULL));
}
void OnSSLStreamAdapterEvent(rtc::StreamInterface* stream, int sig, int err) {
if (sig & rtc::SE_READ) {
char buffer[4096] = "";
size_t read;
int error;
// Read data received from the client and store it in our internal
// buffer.
rtc::StreamResult r = stream->Read(buffer,
sizeof(buffer) - 1, &read, &error);
if (r == rtc::SR_SUCCESS) {
buffer[read] = '\0';
LOG(LS_INFO) << "Server received '" << buffer << "'";
data_ += buffer;
}
}
}
private:
void DoHandshake(rtc::AsyncSocket* socket) {
rtc::SocketStream* stream = new rtc::SocketStream(socket);
ssl_stream_adapter_.reset(rtc::SSLStreamAdapter::Create(stream));
ssl_stream_adapter_->SetMode(ssl_mode_);
ssl_stream_adapter_->SetServerRole();
// SSLStreamAdapter is normally used for peer-to-peer communication, but
// here we're testing communication between a client and a server
// (e.g. a WebRTC-based application and an RFC 5766 TURN server), where
// clients are not required to provide a certificate during handshake.
// Accordingly, we must disable client authentication here.
ssl_stream_adapter_->set_client_auth_enabled(false);
ssl_stream_adapter_->SetIdentity(ssl_identity_->GetReference());
// Set a bogus peer certificate digest.
unsigned char digest[20];
size_t digest_len = sizeof(digest);
ssl_stream_adapter_->SetPeerCertificateDigest(rtc::DIGEST_SHA_1, digest,
digest_len);
ssl_stream_adapter_->StartSSLWithPeer();
ssl_stream_adapter_->SignalEvent.connect(this,
&SSLAdapterTestDummyServer::OnSSLStreamAdapterEvent);
}
const rtc::SSLMode ssl_mode_;
rtc::scoped_ptr<rtc::AsyncSocket> server_socket_;
rtc::scoped_ptr<rtc::SSLStreamAdapter> ssl_stream_adapter_;
rtc::scoped_ptr<rtc::SSLIdentity> ssl_identity_;
std::string data_;
};
class SSLAdapterTestBase : public testing::Test,
public sigslot::has_slots<> {
public:
explicit SSLAdapterTestBase(const rtc::SSLMode& ssl_mode)
: ssl_mode_(ssl_mode),
ss_scope_(new rtc::VirtualSocketServer(NULL)),
server_(new SSLAdapterTestDummyServer(ssl_mode_)),
client_(new SSLAdapterTestDummyClient(ssl_mode_)),
handshake_wait_(kTimeout) {
}
void SetHandshakeWait(int wait) {
handshake_wait_ = wait;
}
void TestHandshake(bool expect_success) {
int rv;
// The initial state is CS_CLOSED
ASSERT_EQ(rtc::AsyncSocket::CS_CLOSED, client_->GetState());
rv = client_->Connect(server_->GetHostname(), server_->GetAddress());
ASSERT_EQ(0, rv);
// Now the state should be CS_CONNECTING
ASSERT_EQ(rtc::AsyncSocket::CS_CONNECTING, client_->GetState());
if (ssl_mode_ == rtc::SSL_MODE_DTLS) {
// For DTLS, call AcceptConnection() with the client's address.
server_->AcceptConnection(client_->GetAddress());
}
if (expect_success) {
// If expecting success, the client should end up in the CS_CONNECTED
// state after handshake.
EXPECT_EQ_WAIT(rtc::AsyncSocket::CS_CONNECTED, client_->GetState(),
handshake_wait_);
LOG(LS_INFO) << GetSSLProtocolName(ssl_mode_) << " handshake complete.";
} else {
// On handshake failure the client should end up in the CS_CLOSED state.
EXPECT_EQ_WAIT(rtc::AsyncSocket::CS_CLOSED, client_->GetState(),
handshake_wait_);
LOG(LS_INFO) << GetSSLProtocolName(ssl_mode_) << " handshake failed.";
}
}
void TestTransfer(const std::string& message) {
int rv;
rv = client_->Send(message);
ASSERT_EQ(static_cast<int>(message.length()), rv);
// The server should have received the client's message.
EXPECT_EQ_WAIT(message, server_->GetReceivedData(), kTimeout);
rv = server_->Send(message);
ASSERT_EQ(static_cast<int>(message.length()), rv);
// The client should have received the server's message.
EXPECT_EQ_WAIT(message, client_->GetReceivedData(), kTimeout);
LOG(LS_INFO) << "Transfer complete.";
}
private:
const rtc::SSLMode ssl_mode_;
const rtc::SocketServerScope ss_scope_;
rtc::scoped_ptr<SSLAdapterTestDummyServer> server_;
rtc::scoped_ptr<SSLAdapterTestDummyClient> client_;
int handshake_wait_;
};
class SSLAdapterTestTLS : public SSLAdapterTestBase {
public:
SSLAdapterTestTLS() : SSLAdapterTestBase(rtc::SSL_MODE_TLS) {}
};
class SSLAdapterTestDTLS : public SSLAdapterTestBase {
public:
SSLAdapterTestDTLS() : SSLAdapterTestBase(rtc::SSL_MODE_DTLS) {}
};
#if SSL_USE_OPENSSL
// Basic tests: TLS
// Test that handshake works
TEST_F(SSLAdapterTestTLS, TestTLSConnect) {
TestHandshake(true);
}
// Test transfer between client and server
TEST_F(SSLAdapterTestTLS, TestTLSTransfer) {
TestHandshake(true);
TestTransfer("Hello, world!");
}
// Basic tests: DTLS
// Test that handshake works
TEST_F(SSLAdapterTestDTLS, TestDTLSConnect) {
TestHandshake(true);
}
// Test transfer between client and server
TEST_F(SSLAdapterTestDTLS, TestDTLSTransfer) {
TestHandshake(true);
TestTransfer("Hello, world!");
}
#endif // SSL_USE_OPENSSL