WIP: support configurable certificates/keys, and root trust CAs (#114)

* wip: tls options implemented in openssl

* update naming, remove #define guard

* assert compiled with USE_TLS for tls options

* apply autoformatter

* include tls options impl

* style cleanup; auto ssl_err

* ssl_err -> sslErr

* be explicit about SSL_VERIFY_NONE
This commit is contained in:
Matt DeBoer 2019-09-22 18:06:15 -07:00 committed by Benjamin Sergeant
parent 1769199d32
commit dd01f734c6
11 changed files with 222 additions and 70 deletions

View File

@ -35,6 +35,7 @@ set( IXWEBSOCKET_SOURCES
ixwebsocket/IXSocketConnect.cpp ixwebsocket/IXSocketConnect.cpp
ixwebsocket/IXSocketFactory.cpp ixwebsocket/IXSocketFactory.cpp
ixwebsocket/IXSocketServer.cpp ixwebsocket/IXSocketServer.cpp
ixwebsocket/IXSocketTLSOptions.cpp
ixwebsocket/IXUrlParser.cpp ixwebsocket/IXUrlParser.cpp
ixwebsocket/IXUserAgent.cpp ixwebsocket/IXUserAgent.cpp
ixwebsocket/IXWebSocket.cpp ixwebsocket/IXWebSocket.cpp
@ -67,6 +68,7 @@ set( IXWEBSOCKET_HEADERS
ixwebsocket/IXSocketConnect.h ixwebsocket/IXSocketConnect.h
ixwebsocket/IXSocketFactory.h ixwebsocket/IXSocketFactory.h
ixwebsocket/IXSocketServer.h ixwebsocket/IXSocketServer.h
ixwebsocket/IXSocketTLSOptions.h
ixwebsocket/IXUrlParser.h ixwebsocket/IXUrlParser.h
ixwebsocket/IXUtf8Validator.h ixwebsocket/IXUtf8Validator.h
ixwebsocket/IXUserAgent.h ixwebsocket/IXUserAgent.h

View File

@ -27,7 +27,8 @@
namespace ix namespace ix
{ {
std::shared_ptr<Socket> createSocket(bool tls, std::shared_ptr<Socket> createSocket(bool tls,
std::string& errorMsg) std::string& errorMsg,
const SocketTLSOptions& tlsOptions)
{ {
errorMsg.clear(); errorMsg.clear();
std::shared_ptr<Socket> socket; std::shared_ptr<Socket> socket;
@ -40,13 +41,13 @@ namespace ix
{ {
#ifdef IXWEBSOCKET_USE_TLS #ifdef IXWEBSOCKET_USE_TLS
#if defined(IXWEBSOCKET_USE_MBED_TLS) #if defined(IXWEBSOCKET_USE_MBED_TLS)
socket = std::make_shared<SocketMbedTLS>(); socket = std::make_shared<SocketMbedTLS>(tlsOptions);
#elif defined(__APPLE__) #elif defined(__APPLE__)
socket = std::make_shared<SocketAppleSSL>(); socket = std::make_shared<SocketAppleSSL>(tlsOptions);
#elif defined(_WIN32) #elif defined(_WIN32)
socket = std::make_shared<SocketSChannel>(); socket = std::make_shared<SocketSChannel>(tlsOptions);
#else #else
socket = std::make_shared<SocketOpenSSL>(); socket = std::make_shared<SocketOpenSSL>(tlsOptions);
#endif #endif
#else #else
errorMsg = "TLS support is not enabled on this platform."; errorMsg = "TLS support is not enabled on this platform.";
@ -62,8 +63,7 @@ namespace ix
return socket; return socket;
} }
std::shared_ptr<Socket> createSocket(int fd, std::shared_ptr<Socket> createSocket(int fd, std::string& errorMsg)
std::string& errorMsg)
{ {
errorMsg.clear(); errorMsg.clear();
@ -75,4 +75,4 @@ namespace ix
return socket; return socket;
} }
} } // namespace ix

View File

@ -10,10 +10,12 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include "IXSocketTLSOptions.h"
namespace ix namespace ix
{ {
class Socket; class Socket;
std::shared_ptr<Socket> createSocket(bool tls, std::string& errorMsg); std::shared_ptr<Socket> createSocket(bool tls, std::string& errorMsg, const SocketTLSOptions& tlsOptions);
std::shared_ptr<Socket> createSocket(int fd, std::string& errorMsg); std::shared_ptr<Socket> createSocket(int fd, std::string& errorMsg);
} // namespace ix } // namespace ix

View File

@ -7,14 +7,12 @@
*/ */
#include "IXSocketOpenSSL.h" #include "IXSocketOpenSSL.h"
#include "IXSocketConnect.h" #include "IXSocketConnect.h"
#include <cassert> #include <cassert>
#include <openssl/x509v3.h>
#include <fnmatch.h>
#include <errno.h> #include <errno.h>
#include <fnmatch.h>
#include <openssl/x509v3.h>
#define socketerrno errno #define socketerrno errno
namespace ix namespace ix
@ -22,9 +20,10 @@ namespace ix
std::atomic<bool> SocketOpenSSL::_openSSLInitializationSuccessful(false); std::atomic<bool> SocketOpenSSL::_openSSLInitializationSuccessful(false);
std::once_flag SocketOpenSSL::_openSSLInitFlag; std::once_flag SocketOpenSSL::_openSSLInitFlag;
SocketOpenSSL::SocketOpenSSL(int fd) : Socket(fd), SocketOpenSSL::SocketOpenSSL(const SocketTLSOptions& tlsOptions, int fd)
_ssl_connection(nullptr), : Socket(fd)
_ssl_context(nullptr) , _ssl_connection(nullptr)
, _ssl_context(nullptr)
{ {
std::call_once(_openSSLInitFlag, &SocketOpenSSL::openSSLInitialize, this); std::call_once(_openSSLInitFlag, &SocketOpenSSL::openSSLInitialize, this);
} }
@ -114,14 +113,17 @@ namespace ix
SSL_CTX* ctx = SSL_CTX_new(_ssl_method); SSL_CTX* ctx = SSL_CTX_new(_ssl_method);
if (ctx) if (ctx)
{ {
// To skip verification, pass in SSL_VERIFY_NONE if (!_tlsOptions.isPeerVerifyDisabled())
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER,
[](int preverify, X509_STORE_CTX*) -> int
{ {
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, [](int preverify, X509_STORE_CTX*) -> int {
return preverify; return preverify;
}); });
SSL_CTX_set_verify_depth(ctx, 4); SSL_CTX_set_verify_depth(ctx, 4);
} else {
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, nullptr);
}
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
} }
return ctx; return ctx;
@ -149,9 +151,8 @@ namespace ix
#if OPENSSL_VERSION_NUMBER < 0x10100000L #if OPENSSL_VERSION_NUMBER < 0x10100000L
// Check server name // Check server name
bool hostname_verifies_ok = false; bool hostname_verifies_ok = false;
STACK_OF(GENERAL_NAME) *san_names = STACK_OF(GENERAL_NAME)* san_names = (STACK_OF(GENERAL_NAME)*) X509_get_ext_d2i(
(STACK_OF(GENERAL_NAME)*) X509_get_ext_d2i((X509 *)server_cert, (X509*) server_cert, NID_subject_alt_name, NULL, NULL);
NID_subject_alt_name, NULL, NULL);
if (san_names) if (san_names)
{ {
for (int i = 0; i < sk_GENERAL_NAME_num(san_names); i++) for (int i = 0; i < sk_GENERAL_NAME_num(san_names); i++)
@ -173,12 +174,12 @@ namespace ix
if (!hostname_verifies_ok) if (!hostname_verifies_ok)
{ {
int cn_pos = X509_NAME_get_index_by_NID(X509_get_subject_name((X509 *)server_cert), int cn_pos = X509_NAME_get_index_by_NID(
NID_commonName, -1); X509_get_subject_name((X509*) server_cert), NID_commonName, -1);
if (cn_pos) if (cn_pos)
{ {
X509_NAME_ENTRY *cn_entry = X509_NAME_get_entry( X509_NAME_ENTRY* cn_entry =
X509_get_subject_name((X509 *)server_cert), cn_pos); X509_NAME_get_entry(X509_get_subject_name((X509*) server_cert), cn_pos);
if (cn_entry) if (cn_entry)
{ {
@ -265,12 +266,63 @@ namespace ix
} }
ERR_clear_error(); ERR_clear_error();
int cert_load_result = SSL_CTX_set_default_verify_paths(_ssl_context); if (_tlsOptions.isUsingClientCert())
if (cert_load_result == 0)
{ {
unsigned long ssl_err = ERR_get_error(); if (SSL_CTX_use_certificate_chain_file(_ssl_context,
_tlsOptions.certFile.c_str()) != 1)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_use_certificate_chain_file(\"" +
_tlsOptions.certFile + "\") failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
}
else if (SSL_CTX_use_PrivateKey_file(
_ssl_context, _tlsOptions.keyFile.c_str(), SSL_FILETYPE_PEM) != 1)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_use_PrivateKey_file(\"" +
_tlsOptions.keyFile + "\") failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
}
}
ERR_clear_error();
if (!_tlsOptions.isPeerVerifyDisabled())
{
if (_tlsOptions.isUsingSystemDefaults())
{
if (SSL_CTX_set_default_verify_paths(_ssl_context) == 0)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_default_verify_paths loading failed: "; errMsg = "OpenSSL failed - SSL_CTX_default_verify_paths loading failed: ";
errMsg += ERR_error_string(ssl_err, nullptr); errMsg += ERR_error_string(sslErr, nullptr);
}
}
else
{
const char* root_ca_file = _tlsOptions.caFile.c_str();
STACK_OF(X509_NAME) * rootCAs;
rootCAs = SSL_load_client_CA_file(root_ca_file);
if (rootCAs == NULL)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_load_client_CA_file('" + _tlsOptions.caFile +
"') failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
}
else
{
SSL_CTX_set_client_CA_list(_ssl_context, rootCAs);
if (SSL_CTX_load_verify_locations(_ssl_context, root_ca_file, NULL) != 1)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_load_verify_locations(\"" +
_tlsOptions.caFile + "\") failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
}
}
}
} }
_ssl_connection = SSL_new(_ssl_context); _ssl_connection = SSL_new(_ssl_context);
@ -294,7 +346,6 @@ namespace ix
X509_VERIFY_PARAM* param = SSL_get0_param(_ssl_connection); X509_VERIFY_PARAM* param = SSL_get0_param(_ssl_connection);
X509_VERIFY_PARAM_set1_host(param, host.c_str(), 0); X509_VERIFY_PARAM_set1_host(param, host.c_str(), 0);
#endif #endif
handshakeSuccessful = openSSLHandshake(host, errMsg); handshakeSuccessful = openSSLHandshake(host, errMsg);
} }
@ -342,13 +393,18 @@ namespace ix
ssize_t write_result = SSL_write(_ssl_connection, buf + sent, (int) nbyte); ssize_t write_result = SSL_write(_ssl_connection, buf + sent, (int) nbyte);
int reason = SSL_get_error(_ssl_connection, (int) write_result); int reason = SSL_get_error(_ssl_connection, (int) write_result);
if (reason == SSL_ERROR_NONE) { if (reason == SSL_ERROR_NONE)
{
nbyte -= write_result; nbyte -= write_result;
sent += write_result; sent += write_result;
} else if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE) { }
else if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE)
{
errno = EWOULDBLOCK; errno = EWOULDBLOCK;
return -1; return -1;
} else { }
else
{
return -1; return -1;
} }
} }
@ -389,4 +445,4 @@ namespace ix
} }
} }
} } // namespace ix

View File

@ -8,6 +8,7 @@
#include "IXCancellationRequest.h" #include "IXCancellationRequest.h"
#include "IXSocket.h" #include "IXSocket.h"
#include "IXSocketTLSOptions.h"
#include <mutex> #include <mutex>
#include <openssl/bio.h> #include <openssl/bio.h>
#include <openssl/conf.h> #include <openssl/conf.h>
@ -20,7 +21,7 @@ namespace ix
class SocketOpenSSL final : public Socket class SocketOpenSSL final : public Socket
{ {
public: public:
SocketOpenSSL(int fd = -1); SocketOpenSSL(const SocketTLSOptions& tlsOptions, int fd = -1);
~SocketOpenSSL(); ~SocketOpenSSL();
virtual bool connect(const std::string& host, virtual bool connect(const std::string& host,
@ -44,6 +45,8 @@ namespace ix
SSL* _ssl_connection; SSL* _ssl_connection;
SSL_CTX* _ssl_context; SSL_CTX* _ssl_context;
const SSL_METHOD* _ssl_method; const SSL_METHOD* _ssl_method;
SocketTLSOptions _tlsOptions;
mutable std::mutex _mutex; // OpenSSL routines are not thread-safe mutable std::mutex _mutex; // OpenSSL routines are not thread-safe
static std::once_flag _openSSLInitFlag; static std::once_flag _openSSLInitFlag;

View File

@ -0,0 +1,34 @@
/*
* IXSocketTLSOptions.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
*/
#include <assert.h>
#include "IXSocketTLSOptions.h"
namespace ix
{
SocketTLSOptions::SocketTLSOptions() {
#ifndef IXWEBSOCKET_USE_TLS
assert(false && "To use TLS features the library must be compiled with USE_TLS");
#endif
}
bool SocketTLSOptions::isUsingClientCert() const
{
return !certFile.empty() && !keyFile.empty();
}
bool SocketTLSOptions::isUsingSystemDefaults() const
{
return caFile == "SYSTEM";
}
bool SocketTLSOptions::isPeerVerifyDisabled() const
{
return caFile != "NONE";
}
} // namespace ix

View File

@ -0,0 +1,32 @@
/*
* IXSocketTLSOptions.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <string>
namespace ix
{
struct SocketTLSOptions
{
SocketTLSOptions();
// the certificate presented to peers
std::string certFile;
// the key used for signing/encryption
std::string keyFile;
// the ca certificate (or certificate bundle) file containing
// certificates to be trusted by peers; use 'SYSTEM' to
// leverage the system defaults, use 'NONE' to disable peer verification
std::string caFile = "SYSTEM";
bool isUsingClientCert() const;
bool isUsingSystemDefaults() const;
bool isPeerVerifyDisabled() const;
};
} // namespace ix

View File

@ -73,6 +73,12 @@ namespace ix
_perMessageDeflateOptions = perMessageDeflateOptions; _perMessageDeflateOptions = perMessageDeflateOptions;
} }
void WebSocket::setTLSOptions(const SocketTLSOptions& socketTLSOptions)
{
std::lock_guard<std::mutex> lock(_configMutex);
_socketTLSOptions = socketTLSOptions;
}
const WebSocketPerMessageDeflateOptions& WebSocket::getPerMessageDeflateOptions() const const WebSocketPerMessageDeflateOptions& WebSocket::getPerMessageDeflateOptions() const
{ {
std::lock_guard<std::mutex> lock(_configMutex); std::lock_guard<std::mutex> lock(_configMutex);
@ -173,6 +179,7 @@ namespace ix
{ {
std::lock_guard<std::mutex> lock(_configMutex); std::lock_guard<std::mutex> lock(_configMutex);
_ws.configure(_perMessageDeflateOptions, _ws.configure(_perMessageDeflateOptions,
_socketTLSOptions,
_enablePong, _enablePong,
_pingIntervalSecs, _pingIntervalSecs,
_pingTimeoutSecs); _pingTimeoutSecs);
@ -198,6 +205,7 @@ namespace ix
{ {
std::lock_guard<std::mutex> lock(_configMutex); std::lock_guard<std::mutex> lock(_configMutex);
_ws.configure(_perMessageDeflateOptions, _ws.configure(_perMessageDeflateOptions,
_socketTLSOptions,
_enablePong, _enablePong,
_pingIntervalSecs, _pingIntervalSecs,
_pingTimeoutSecs); _pingTimeoutSecs);

View File

@ -10,6 +10,9 @@
#pragma once #pragma once
#include "IXProgressCallback.h" #include "IXProgressCallback.h"
#ifdef IXWEBSOCKET_USE_TLS
#include "IXSocketTLSOptions.h"
#endif
#include "IXWebSocketCloseConstants.h" #include "IXWebSocketCloseConstants.h"
#include "IXWebSocketErrorInfo.h" #include "IXWebSocketErrorInfo.h"
#include "IXWebSocketHttpHeaders.h" #include "IXWebSocketHttpHeaders.h"
@ -49,6 +52,7 @@ namespace ix
void setExtraHeaders(const WebSocketHttpHeaders& headers); void setExtraHeaders(const WebSocketHttpHeaders& headers);
void setPerMessageDeflateOptions( void setPerMessageDeflateOptions(
const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions); const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
void setTLSOptions(const SocketTLSOptions& socketTLSOptions);
void setHeartBeatPeriod(int heartBeatPeriodSecs); void setHeartBeatPeriod(int heartBeatPeriodSecs);
void setPingInterval(int pingIntervalSecs); // alias of setHeartBeatPeriod void setPingInterval(int pingIntervalSecs); // alias of setHeartBeatPeriod
void setPingTimeout(int pingTimeoutSecs); void setPingTimeout(int pingTimeoutSecs);
@ -119,6 +123,9 @@ namespace ix
WebSocketHttpHeaders _extraHeaders; WebSocketHttpHeaders _extraHeaders;
WebSocketPerMessageDeflateOptions _perMessageDeflateOptions; WebSocketPerMessageDeflateOptions _perMessageDeflateOptions;
SocketTLSOptions _socketTLSOptions;
mutable std::mutex _configMutex; // protect all config variables access mutable std::mutex _configMutex; // protect all config variables access
OnMessageCallback _onMessageCallback; OnMessageCallback _onMessageCallback;

View File

@ -32,6 +32,7 @@
// Adapted from https://github.com/dhbaird/easywsclient // Adapted from https://github.com/dhbaird/easywsclient
// //
#include "IXSocketTLSOptions.h"
#include "IXWebSocketTransport.h" #include "IXWebSocketTransport.h"
#include "IXWebSocketHandshake.h" #include "IXWebSocketHandshake.h"
#include "IXWebSocketHttpHeaders.h" #include "IXWebSocketHttpHeaders.h"
@ -103,12 +104,14 @@ namespace ix
} }
void WebSocketTransport::configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions, void WebSocketTransport::configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
const SocketTLSOptions& socketTLSOptions,
bool enablePong, bool enablePong,
int pingIntervalSecs, int pingIntervalSecs,
int pingTimeoutSecs) int pingTimeoutSecs)
{ {
_perMessageDeflateOptions = perMessageDeflateOptions; _perMessageDeflateOptions = perMessageDeflateOptions;
_enablePerMessageDeflate = _perMessageDeflateOptions.enabled(); _enablePerMessageDeflate = _perMessageDeflateOptions.enabled();
_socketTLSOptions = socketTLSOptions;
_enablePong = enablePong; _enablePong = enablePong;
_pingIntervalSecs = pingIntervalSecs; _pingIntervalSecs = pingIntervalSecs;
_pingTimeoutSecs = pingTimeoutSecs; _pingTimeoutSecs = pingTimeoutSecs;
@ -147,7 +150,7 @@ namespace ix
std::string errorMsg; std::string errorMsg;
bool tls = protocol == "wss"; bool tls = protocol == "wss";
_socket = createSocket(tls, errorMsg); _socket = createSocket(tls, errorMsg, _socketTLSOptions);
if (!_socket) if (!_socket)
{ {

View File

@ -12,6 +12,7 @@
#include "IXCancellationRequest.h" #include "IXCancellationRequest.h"
#include "IXProgressCallback.h" #include "IXProgressCallback.h"
#include "IXSocketTLSOptions.h"
#include "IXWebSocketCloseConstants.h" #include "IXWebSocketCloseConstants.h"
#include "IXWebSocketHandshake.h" #include "IXWebSocketHandshake.h"
#include "IXWebSocketHttpHeaders.h" #include "IXWebSocketHttpHeaders.h"
@ -72,6 +73,7 @@ namespace ix
~WebSocketTransport(); ~WebSocketTransport();
void configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions, void configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
const SocketTLSOptions& socketTLSOptions,
bool enablePong, bool enablePong,
int pingIntervalSecs, int pingIntervalSecs,
int pingTimeoutSecs); int pingTimeoutSecs);
@ -181,6 +183,9 @@ namespace ix
WebSocketPerMessageDeflateOptions _perMessageDeflateOptions; WebSocketPerMessageDeflateOptions _perMessageDeflateOptions;
std::atomic<bool> _enablePerMessageDeflate; std::atomic<bool> _enablePerMessageDeflate;
// Used to control TLS connection behavior
SocketTLSOptions _socketTLSOptions;
// Used to cancel dns lookup + socket connect + http upgrade // Used to cancel dns lookup + socket connect + http upgrade
std::atomic<bool> _requestInitCancellation; std::atomic<bool> _requestInitCancellation;