webrtc/rtc_base/ssladapter_unittest.cc
Benjamin Wright 6e9c3df4d0 Added additional SSL certificate verification tests.
This changeset adds some additional tests to validate that the SSLAdapter
behaves as intended when used with a Custom SSL Certificate Verifier. It
validates the following scenarios:
1. Handshake succeeds on TLS handshakes if certificate verifier returns true.
2. Handshake fails on TLS handshakes if certificate verifier returns false.
3. Handshake succeeds on DTLS handshakes if certificate verifier returns true.
4. Handshake fails on DTLS handshakes if certificate verifier returns false.
5. Handshake succeeds on TLS transfers if certificate verifier returns true.
6. Handshake succeeds on DTLS transfers if certificate verifier returns true.

Bug: webrtc:9258
Change-Id: I48b72c9762a7023ece12d882ac4a05d9881bf9e6
Reviewed-on: https://webrtc-review.googlesource.com/75720
Commit-Queue: Benjamin Wright <benwright@webrtc.org>
Reviewed-by: Taylor Brandstetter <deadbeef@webrtc.org>
Reviewed-by: Karl Wiberg <kwiberg@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#23355}
2018-05-23 02:47:49 +00:00

617 lines
19 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 <memory>
#include <string>
#include <utility>
#include "rtc_base/gunit.h"
#include "rtc_base/ipaddress.h"
#include "rtc_base/ptr_util.h"
#include "rtc_base/socketstream.h"
#include "rtc_base/ssladapter.h"
#include "rtc_base/sslidentity.h"
#include "rtc_base/sslstreamadapter.h"
#include "rtc_base/stream.h"
#include "rtc_base/stringencode.h"
#include "rtc_base/virtualsocketserver.h"
#include "test/gmock.h"
using ::testing::_;
using ::testing::Return;
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";
}
// Simple mock for the certificate verifier.
class MockCertVerifier : public rtc::SSLCertificateVerifier {
public:
virtual ~MockCertVerifier() = default;
MOCK_METHOD1(Verify, bool(const rtc::SSLCertificate&));
};
// TODO(benwright) - Move to using INSTANTIATE_TEST_CASE_P instead of using
// duplicate test cases for simple parameter changes.
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_->SetIgnoreBadCert(true);
ssl_adapter_->SignalReadEvent.connect(this,
&SSLAdapterTestDummyClient::OnSSLAdapterReadEvent);
ssl_adapter_->SignalCloseEvent.connect(this,
&SSLAdapterTestDummyClient::OnSSLAdapterCloseEvent);
}
void SetIgnoreBadCert(bool ignore_bad_cert) {
ssl_adapter_->SetIgnoreBadCert(ignore_bad_cert);
}
void SetCertVerifier(rtc::SSLCertificateVerifier* ssl_cert_verifier) {
ssl_adapter_->SetCertVerifier(ssl_cert_verifier);
}
void SetAlpnProtocols(const std::vector<std::string>& protos) {
ssl_adapter_->SetAlpnProtocols(protos);
}
void SetEllipticCurves(const std::vector<std::string>& curves) {
ssl_adapter_->SetEllipticCurves(curves);
}
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) {
RTC_LOG(LS_INFO) << "Initiating connection with " << address.ToString();
int rv = ssl_adapter_->Connect(address);
if (rv == 0) {
RTC_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) {
RTC_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, nullptr);
if (read != -1) {
buffer[read] = '\0';
RTC_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_;
std::unique_ptr<rtc::SSLAdapter> ssl_adapter_;
std::string data_;
};
class SSLAdapterTestDummyServer : public sigslot::has_slots<> {
public:
explicit SSLAdapterTestDummyServer(const rtc::SSLMode& ssl_mode,
const rtc::KeyParams& key_params)
: ssl_mode_(ssl_mode) {
// Generate a key pair and a certificate for this host.
ssl_identity_.reset(rtc::SSLIdentity::Generate(GetHostname(), key_params));
server_socket_.reset(CreateSocket(ssl_mode_));
if (ssl_mode_ == rtc::SSL_MODE_TLS) {
server_socket_->SignalReadEvent.connect(this,
&SSLAdapterTestDummyServer::OnServerSocketReadEvent);
server_socket_->Listen(1);
}
RTC_LOG(LS_INFO) << ((ssl_mode_ == rtc::SSL_MODE_DTLS) ? "UDP" : "TCP")
<< " server listening on "
<< server_socket_->GetLocalAddress().ToString();
}
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_ == nullptr ||
ssl_stream_adapter_->GetState() != rtc::SS_OPEN) {
// No connection yet.
return -1;
}
RTC_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_ == nullptr);
// 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_ == nullptr);
DoHandshake(server_socket_->Accept(nullptr));
}
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';
RTC_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_->StartSSL();
ssl_stream_adapter_->SignalEvent.connect(this,
&SSLAdapterTestDummyServer::OnSSLStreamAdapterEvent);
}
const rtc::SSLMode ssl_mode_;
std::unique_ptr<rtc::AsyncSocket> server_socket_;
std::unique_ptr<rtc::SSLStreamAdapter> ssl_stream_adapter_;
std::unique_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,
const rtc::KeyParams& key_params)
: ssl_mode_(ssl_mode),
vss_(new rtc::VirtualSocketServer()),
thread_(vss_.get()),
server_(new SSLAdapterTestDummyServer(ssl_mode_, key_params)),
client_(new SSLAdapterTestDummyClient(ssl_mode_)),
handshake_wait_(kTimeout) {}
void SetHandshakeWait(int wait) {
handshake_wait_ = wait;
}
void SetIgnoreBadCert(bool ignore_bad_cert) {
client_->SetIgnoreBadCert(ignore_bad_cert);
}
void SetCertVerifier(rtc::SSLCertificateVerifier* ssl_cert_verifier) {
client_->SetCertVerifier(ssl_cert_verifier);
}
void SetAlpnProtocols(const std::vector<std::string>& protos) {
client_->SetAlpnProtocols(protos);
}
void SetEllipticCurves(const std::vector<std::string>& curves) {
client_->SetEllipticCurves(curves);
}
void SetMockCertVerifier(bool return_value) {
auto mock_verifier = rtc::MakeUnique<MockCertVerifier>();
EXPECT_CALL(*mock_verifier, Verify(_)).WillRepeatedly(Return(return_value));
cert_verifier_ =
std::unique_ptr<rtc::SSLCertificateVerifier>(std::move(mock_verifier));
SetIgnoreBadCert(false);
SetCertVerifier(cert_verifier_.get());
}
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_);
RTC_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_);
RTC_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);
RTC_LOG(LS_INFO) << "Transfer complete.";
}
protected:
const rtc::SSLMode ssl_mode_;
std::unique_ptr<rtc::VirtualSocketServer> vss_;
rtc::AutoSocketServerThread thread_;
std::unique_ptr<SSLAdapterTestDummyServer> server_;
std::unique_ptr<SSLAdapterTestDummyClient> client_;
std::unique_ptr<rtc::SSLCertificateVerifier> cert_verifier_;
int handshake_wait_;
};
class SSLAdapterTestTLS_RSA : public SSLAdapterTestBase {
public:
SSLAdapterTestTLS_RSA()
: SSLAdapterTestBase(rtc::SSL_MODE_TLS, rtc::KeyParams::RSA()) {}
};
class SSLAdapterTestTLS_ECDSA : public SSLAdapterTestBase {
public:
SSLAdapterTestTLS_ECDSA()
: SSLAdapterTestBase(rtc::SSL_MODE_TLS, rtc::KeyParams::ECDSA()) {}
};
class SSLAdapterTestDTLS_RSA : public SSLAdapterTestBase {
public:
SSLAdapterTestDTLS_RSA()
: SSLAdapterTestBase(rtc::SSL_MODE_DTLS, rtc::KeyParams::RSA()) {}
};
class SSLAdapterTestDTLS_ECDSA : public SSLAdapterTestBase {
public:
SSLAdapterTestDTLS_ECDSA()
: SSLAdapterTestBase(rtc::SSL_MODE_DTLS, rtc::KeyParams::ECDSA()) {}
};
// Basic tests: TLS
// Test that handshake works, using RSA
TEST_F(SSLAdapterTestTLS_RSA, TestTLSConnect) {
TestHandshake(true);
}
// Test that handshake works with a custom verifier that returns true. RSA.
TEST_F(SSLAdapterTestTLS_RSA, TestTLSConnectCustomCertVerifierSucceeds) {
SetMockCertVerifier(/*return_value=*/true);
TestHandshake(/*expect_success=*/true);
}
// Test that handshake fails with a custom verifier that returns false. RSA.
TEST_F(SSLAdapterTestTLS_RSA, TestTLSConnectCustomCertVerifierFails) {
SetMockCertVerifier(/*return_value=*/false);
TestHandshake(/*expect_success=*/false);
}
// Test that handshake works, using ECDSA
TEST_F(SSLAdapterTestTLS_ECDSA, TestTLSConnect) {
SetMockCertVerifier(/*return_value=*/true);
TestHandshake(/*expect_success=*/true);
}
// Test that handshake works with a custom verifier that returns true. ECDSA.
TEST_F(SSLAdapterTestTLS_ECDSA, TestTLSConnectCustomCertVerifierSucceeds) {
SetMockCertVerifier(/*return_value=*/true);
TestHandshake(/*expect_success=*/true);
}
// Test that handshake fails with a custom verifier that returns false. ECDSA.
TEST_F(SSLAdapterTestTLS_ECDSA, TestTLSConnectCustomCertVerifierFails) {
SetMockCertVerifier(/*return_value=*/false);
TestHandshake(/*expect_success=*/false);
}
// Test transfer between client and server, using RSA
TEST_F(SSLAdapterTestTLS_RSA, TestTLSTransfer) {
TestHandshake(true);
TestTransfer("Hello, world!");
}
// Test transfer between client and server, using RSA with custom cert verifier.
TEST_F(SSLAdapterTestTLS_RSA, TestTLSTransferCustomCertVerifier) {
SetMockCertVerifier(/*return_value=*/true);
TestHandshake(/*expect_success=*/true);
TestTransfer("Hello, world!");
}
TEST_F(SSLAdapterTestTLS_RSA, TestTLSTransferWithBlockedSocket) {
TestHandshake(true);
// Tell the underlying socket to simulate being blocked.
vss_->SetSendingBlocked(true);
std::string expected;
int rv;
// Send messages until the SSL socket adapter starts applying backpressure.
// Note that this may not occur immediately since there may be some amount of
// intermediate buffering (either in our code or in BoringSSL).
for (int i = 0; i < 1024; ++i) {
std::string message = "Hello, world: " + rtc::ToString(i);
rv = client_->Send(message);
if (rv != static_cast<int>(message.size())) {
// This test assumes either the whole message or none of it is sent.
ASSERT_EQ(-1, rv);
break;
}
expected += message;
}
// Assert that the loop above exited due to Send returning -1.
ASSERT_EQ(-1, rv);
// Try sending another message while blocked. -1 should be returned again and
// it shouldn't end up received by the server later.
EXPECT_EQ(-1, client_->Send("Never sent"));
// Unblock the underlying socket. All of the buffered messages should be sent
// without any further action.
vss_->SetSendingBlocked(false);
EXPECT_EQ_WAIT(expected, server_->GetReceivedData(), kTimeout);
// Send another message. This previously wasn't working
std::string final_message = "Fin.";
expected += final_message;
EXPECT_EQ(static_cast<int>(final_message.size()),
client_->Send(final_message));
EXPECT_EQ_WAIT(expected, server_->GetReceivedData(), kTimeout);
}
// Test transfer between client and server, using ECDSA
TEST_F(SSLAdapterTestTLS_ECDSA, TestTLSTransfer) {
TestHandshake(true);
TestTransfer("Hello, world!");
}
// Test transfer between client and server, using ECDSA with custom cert
// verifier.
TEST_F(SSLAdapterTestTLS_ECDSA, TestTLSTransferCustomCertVerifier) {
SetMockCertVerifier(/*return_value=*/true);
TestHandshake(/*expect_success=*/true);
TestTransfer("Hello, world!");
}
// Test transfer using ALPN with protos as h2 and http/1.1
TEST_F(SSLAdapterTestTLS_ECDSA, TestTLSALPN) {
std::vector<std::string> alpn_protos{"h2", "http/1.1"};
SetAlpnProtocols(alpn_protos);
TestHandshake(true);
TestTransfer("Hello, world!");
}
// Test transfer with TLS Elliptic curves set to "X25519:P-256:P-384:P-521"
TEST_F(SSLAdapterTestTLS_ECDSA, TestTLSEllipticCurves) {
std::vector<std::string> elliptic_curves{"X25519", "P-256", "P-384", "P-521"};
SetEllipticCurves(elliptic_curves);
TestHandshake(true);
TestTransfer("Hello, world!");
}
// Basic tests: DTLS
// Test that handshake works, using RSA
TEST_F(SSLAdapterTestDTLS_RSA, TestDTLSConnect) {
TestHandshake(true);
}
// Test that handshake works with a custom verifier that returns true. DTLS_RSA.
TEST_F(SSLAdapterTestDTLS_RSA, TestDTLSConnectCustomCertVerifierSucceeds) {
SetMockCertVerifier(/*return_value=*/true);
TestHandshake(/*expect_success=*/true);
}
// Test that handshake fails with a custom verifier that returns false.
// DTLS_RSA.
TEST_F(SSLAdapterTestDTLS_RSA, TestTLSConnectCustomCertVerifierFails) {
SetMockCertVerifier(/*return_value=*/false);
TestHandshake(/*expect_success=*/false);
}
// Test that handshake works, using ECDSA
TEST_F(SSLAdapterTestDTLS_ECDSA, TestDTLSConnect) {
TestHandshake(true);
}
// Test that handshake works with a custom verifier that returns true.
// DTLS_ECDSA.
TEST_F(SSLAdapterTestDTLS_ECDSA, TestDTLSConnectCustomCertVerifierSucceeds) {
SetMockCertVerifier(/*return_value=*/true);
TestHandshake(/*expect_success=*/true);
}
// Test that handshake fails with a custom verifier that returns false.
// DTLS_ECDSA.
TEST_F(SSLAdapterTestDTLS_ECDSA, TestTLSConnectCustomCertVerifierFails) {
SetMockCertVerifier(/*return_value=*/false);
TestHandshake(/*expect_success=*/false);
}
// Test transfer between client and server, using RSA
TEST_F(SSLAdapterTestDTLS_RSA, TestDTLSTransfer) {
TestHandshake(true);
TestTransfer("Hello, world!");
}
// Test transfer between client and server, using RSA with custom cert verifier.
TEST_F(SSLAdapterTestDTLS_RSA, TestDTLSTransferCustomCertVerifier) {
SetMockCertVerifier(/*return_value=*/true);
TestHandshake(/*expect_success=*/true);
TestTransfer("Hello, world!");
}
// Test transfer between client and server, using ECDSA
TEST_F(SSLAdapterTestDTLS_ECDSA, TestDTLSTransfer) {
TestHandshake(true);
TestTransfer("Hello, world!");
}
// Test transfer between client and server, using ECDSA with custom cert
// verifier.
TEST_F(SSLAdapterTestDTLS_ECDSA, TestDTLSTransferCustomCertVerifier) {
SetMockCertVerifier(/*return_value=*/true);
TestHandshake(/*expect_success=*/true);
TestTransfer("Hello, world!");
}