2022-01-01 23:50:23 +01:00

762 lines
24 KiB
C++

/*
* IXSocketOpenSSL.cpp
* Author: Benjamin Sergeant, Matt DeBoer, Max Weisel
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
*
* Adapted from Satori SDK OpenSSL code.
*/
#ifdef IXWEBSOCKET_USE_OPEN_SSL
#include "IXSocketOpenSSL.h"
#include "IXSocketConnect.h"
#include "IXUniquePtr.h"
#include <cassert>
#include <errno.h>
#include <vector>
#ifdef _WIN32
#include <Shlwapi.h>
#else
#include <fnmatch.h>
#endif
#if OPENSSL_VERSION_NUMBER < 0x10100000L
#include <openssl/x509v3.h>
#endif
#define socketerrno errno
#ifdef _WIN32
// For manipulating the certificate store
#include <wincrypt.h>
#endif
#ifdef _WIN32
namespace
{
bool loadWindowsSystemCertificates(SSL_CTX *ssl, std::string &errorMsg)
{
DWORD flags = CERT_STORE_READONLY_FLAG | CERT_STORE_OPEN_EXISTING_FLAG |
CERT_SYSTEM_STORE_CURRENT_USER;
HCERTSTORE systemStore =
CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, flags, L"Root");
if (!systemStore) {
errorMsg = "CertOpenStore failed with ";
errorMsg += std::to_string(GetLastError());
return false;
}
PCCERT_CONTEXT certificateIterator = NULL;
X509_STORE *opensslStore = SSL_CTX_get_cert_store(ssl);
int certificateCount = 0;
while (certificateIterator =
CertEnumCertificatesInStore(systemStore, certificateIterator)) {
X509 *x509 = d2i_X509(
NULL,
(const unsigned char **)&certificateIterator->pbCertEncoded,
certificateIterator->cbCertEncoded);
if (x509) {
if (X509_STORE_add_cert(opensslStore, x509) == 1) {
++certificateCount;
}
X509_free(x509);
}
}
CertFreeCertificateContext(certificateIterator);
CertCloseStore(systemStore, 0);
if (certificateCount == 0) {
errorMsg = "No certificates found";
return false;
}
return true;
}
} // namespace
#endif
namespace ix
{
const std::string kDefaultCiphers =
"ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 "
"ECDHE-ECDSA-AES128-SHA "
"ECDHE-ECDSA-AES256-SHA ECDHE-ECDSA-AES128-SHA256 "
"ECDHE-ECDSA-AES256-SHA384 "
"ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES256-GCM-SHA384 "
"ECDHE-RSA-AES128-SHA "
"ECDHE-RSA-AES256-SHA ECDHE-RSA-AES128-SHA256 ECDHE-RSA-AES256-SHA384 "
"DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES128-SHA "
"DHE-RSA-AES256-SHA DHE-RSA-AES128-SHA256 DHE-RSA-AES256-SHA256 AES128-SHA";
std::atomic<bool> SocketOpenSSL::_openSSLInitializationSuccessful(false);
std::once_flag SocketOpenSSL::_openSSLInitFlag;
std::vector<std::unique_ptr<std::mutex>> openSSLMutexes;
SocketOpenSSL::SocketOpenSSL(const SocketTLSOptions &tlsOptions, int fd)
: Socket(fd), _ssl_connection(nullptr), _ssl_context(nullptr),
_tlsOptions(tlsOptions)
{
std::call_once(_openSSLInitFlag, &SocketOpenSSL::openSSLInitialize, this);
}
SocketOpenSSL::~SocketOpenSSL() { SocketOpenSSL::close(); }
void SocketOpenSSL::openSSLInitialize()
{
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
if (!OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, nullptr))
return;
#else
(void)OPENSSL_config(nullptr);
if (CRYPTO_get_locking_callback() == nullptr) {
openSSLMutexes.clear();
for (int i = 0; i < CRYPTO_num_locks(); ++i) {
openSSLMutexes.push_back(ix::make_unique<std::mutex>());
}
CRYPTO_set_locking_callback(SocketOpenSSL::openSSLLockingCallback);
}
#endif
(void)OpenSSL_add_ssl_algorithms();
(void)SSL_load_error_strings();
_openSSLInitializationSuccessful = true;
}
void SocketOpenSSL::openSSLLockingCallback(int mode,
int type,
const char * /*file*/,
int /*line*/)
{
if (mode & CRYPTO_LOCK) {
openSSLMutexes[type]->lock();
} else {
openSSLMutexes[type]->unlock();
}
}
std::string SocketOpenSSL::getSSLError(int ret)
{
unsigned long e;
int err = SSL_get_error(_ssl_connection, ret);
if (err == SSL_ERROR_WANT_CONNECT || err == SSL_ERROR_WANT_ACCEPT) {
return "OpenSSL failed - connection failure";
} else if (err == SSL_ERROR_WANT_X509_LOOKUP) {
return "OpenSSL failed - x509 error";
} else if (err == SSL_ERROR_SYSCALL) {
e = ERR_get_error();
if (e > 0) {
std::string errMsg("OpenSSL failed - ");
errMsg += ERR_error_string(e, nullptr);
return errMsg;
} else if (e == 0 && ret == 0) {
return "OpenSSL failed - received early EOF";
} else {
return "OpenSSL failed - underlying BIO reported an I/O error";
}
} else if (err == SSL_ERROR_SSL) {
e = ERR_get_error();
std::string errMsg("OpenSSL failed - ");
errMsg += ERR_error_string(e, nullptr);
return errMsg;
} else if (err == SSL_ERROR_NONE) {
return "OpenSSL failed - err none";
} else if (err == SSL_ERROR_ZERO_RETURN) {
return "OpenSSL failed - err zero return";
} else {
return "OpenSSL failed - unknown error";
}
}
SSL_CTX *SocketOpenSSL::openSSLCreateContext(std::string &errMsg)
{
const SSL_METHOD *method = SSLv23_client_method();
if (method == nullptr) {
errMsg = "SSLv23_client_method failure";
return nullptr;
}
_ssl_method = method;
SSL_CTX *ctx = SSL_CTX_new(_ssl_method);
if (ctx) {
SSL_CTX_set_mode(ctx,
SSL_MODE_ENABLE_PARTIAL_WRITE |
SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
int options =
SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_CIPHER_SERVER_PREFERENCE;
#ifdef SSL_OP_NO_TLSv1_3
// (partially?) work around hang in openssl 1.1.1b, by disabling TLS
// V1.3 https://github.com/openssl/openssl/issues/7967
options |= SSL_OP_NO_TLSv1_3;
#endif
SSL_CTX_set_options(ctx, options);
}
return ctx;
}
bool SocketOpenSSL::openSSLAddCARootsFromString(const std::string roots)
{
// Create certificate store
X509_STORE *certificate_store = SSL_CTX_get_cert_store(_ssl_context);
if (certificate_store == nullptr)
return false;
// Configure to allow intermediate certs
X509_STORE_set_flags(certificate_store,
X509_V_FLAG_TRUSTED_FIRST | X509_V_FLAG_PARTIAL_CHAIN);
// Create a new buffer and populate it with the roots
BIO *buffer = BIO_new_mem_buf((void *)roots.c_str(),
static_cast<int>(roots.length()));
if (buffer == nullptr)
return false;
// Read each root in the buffer and add to the certificate store
bool success = true;
size_t number_of_roots = 0;
while (true) {
// Read the next root in the buffer
X509 *root =
PEM_read_bio_X509_AUX(buffer, nullptr, nullptr, (void *)"");
if (root == nullptr) {
// No more certs left in the buffer, we're done.
ERR_clear_error();
break;
}
// Try adding the root to the certificate store
ERR_clear_error();
if (!X509_STORE_add_cert(certificate_store, root)) {
// Failed to add. If the error is unrelated to the x509 lib or the
// cert already exists, we're safe to continue.
unsigned long error = ERR_get_error();
if (ERR_GET_LIB(error) != ERR_LIB_X509 ||
ERR_GET_REASON(error) != X509_R_CERT_ALREADY_IN_HASH_TABLE) {
// Failed. Clean up and bail.
success = false;
X509_free(root);
break;
}
}
// Clean up and loop
X509_free(root);
number_of_roots++;
}
// Clean up buffer
BIO_free(buffer);
// Make sure we loaded at least one certificate.
if (number_of_roots == 0)
success = false;
return success;
}
/**
* Check whether a hostname matches a pattern
*/
bool SocketOpenSSL::checkHost(const std::string &host, const char *pattern)
{
#ifdef _WIN32
return PathMatchSpecA(host.c_str(), pattern);
#else
return fnmatch(pattern, host.c_str(), 0) != FNM_NOMATCH;
#endif
}
bool SocketOpenSSL::openSSLCheckServerCert(SSL *ssl,
const std::string &hostname,
std::string &errMsg)
{
X509 *server_cert = SSL_get_peer_certificate(ssl);
if (server_cert == nullptr) {
errMsg = "OpenSSL failed - peer didn't present a X509 certificate.";
return false;
}
#if OPENSSL_VERSION_NUMBER < 0x10100000L
// Check server name
bool hostname_verifies_ok = false;
STACK_OF(GENERAL_NAME) *san_names = (STACK_OF(GENERAL_NAME) *)
X509_get_ext_d2i((X509 *)server_cert, NID_subject_alt_name, NULL, NULL);
if (san_names) {
for (int i = 0; i < sk_GENERAL_NAME_num(san_names); i++) {
const GENERAL_NAME *sk_name = sk_GENERAL_NAME_value(san_names, i);
if (sk_name->type == GEN_DNS) {
char *name = (char *)ASN1_STRING_data(sk_name->d.dNSName);
if ((size_t)ASN1_STRING_length(sk_name->d.dNSName) ==
strlen(name) &&
checkHost(hostname, name)) {
hostname_verifies_ok = true;
break;
}
}
}
}
sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);
if (!hostname_verifies_ok) {
int cn_pos = X509_NAME_get_index_by_NID(
X509_get_subject_name((X509 *)server_cert),
NID_commonName,
-1);
if (cn_pos) {
X509_NAME_ENTRY *cn_entry =
X509_NAME_get_entry(X509_get_subject_name((X509 *)server_cert),
cn_pos);
if (cn_entry) {
ASN1_STRING *cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry);
char *cn = (char *)ASN1_STRING_data(cn_asn1);
if ((size_t)ASN1_STRING_length(cn_asn1) == strlen(cn) &&
checkHost(hostname, cn)) {
hostname_verifies_ok = true;
}
}
}
}
if (!hostname_verifies_ok) {
errMsg =
"OpenSSL failed - certificate was issued for a different domain.";
return false;
}
#endif
X509_free(server_cert);
return true;
}
bool SocketOpenSSL::openSSLClientHandshake(
const std::string &host,
std::string &errMsg,
const CancellationRequest &isCancellationRequested)
{
while (true) {
if (_ssl_connection == nullptr || _ssl_context == nullptr) {
return false;
}
if (isCancellationRequested()) {
errMsg = "Cancellation requested";
return false;
}
ERR_clear_error();
int connect_result = SSL_connect(_ssl_connection);
if (connect_result == 1) {
return openSSLCheckServerCert(_ssl_connection, host, errMsg);
}
int reason = SSL_get_error(_ssl_connection, connect_result);
bool rc = false;
if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE) {
rc = true;
} else {
errMsg = getSSLError(connect_result);
rc = false;
}
if (!rc) {
return false;
}
}
}
bool SocketOpenSSL::openSSLServerHandshake(std::string &errMsg)
{
while (true) {
if (_ssl_connection == nullptr || _ssl_context == nullptr) {
return false;
}
ERR_clear_error();
int accept_result = SSL_accept(_ssl_connection);
if (accept_result == 1) {
return true;
}
int reason = SSL_get_error(_ssl_connection, accept_result);
bool rc = false;
if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE) {
rc = true;
} else {
errMsg = getSSLError(accept_result);
rc = false;
}
if (!rc) {
return false;
}
}
}
bool SocketOpenSSL::handleTLSOptions(std::string &errMsg)
{
ERR_clear_error();
if (_tlsOptions.hasCertAndKey()) {
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);
} else if (!SSL_CTX_check_private_key(_ssl_context)) {
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - cert/key mismatch(\"" +
_tlsOptions.certFile + ", " + _tlsOptions.keyFile + "\")";
errMsg += ERR_error_string(sslErr, nullptr);
}
}
ERR_clear_error();
if (!_tlsOptions.isPeerVerifyDisabled()) {
if (_tlsOptions.isUsingSystemDefaults()) {
#ifdef _WIN32
if (!loadWindowsSystemCertificates(_ssl_context, errMsg)) {
return false;
}
#else
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 += ERR_error_string(sslErr, nullptr);
return false;
}
#endif
} else {
if (_tlsOptions.isUsingInMemoryCAs()) {
// Load from memory
openSSLAddCARootsFromString(_tlsOptions.caFile);
} else {
if (SSL_CTX_load_verify_locations(_ssl_context,
_tlsOptions.caFile.c_str(),
NULL) != 1) {
auto sslErr = ERR_get_error();
errMsg =
"OpenSSL failed - SSL_CTX_load_verify_locations(\"" +
_tlsOptions.caFile + "\") failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
return false;
}
}
}
SSL_CTX_set_verify(
_ssl_context,
SSL_VERIFY_PEER,
[](int preverify, X509_STORE_CTX *) -> int { return preverify; });
SSL_CTX_set_verify_depth(_ssl_context, 4);
} else {
SSL_CTX_set_verify(_ssl_context, SSL_VERIFY_NONE, nullptr);
}
if (_tlsOptions.isUsingDefaultCiphers()) {
if (SSL_CTX_set_cipher_list(_ssl_context, kDefaultCiphers.c_str()) !=
1) {
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_set_cipher_list(\"" +
kDefaultCiphers + "\") failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
return false;
}
} else if (SSL_CTX_set_cipher_list(_ssl_context,
_tlsOptions.ciphers.c_str()) != 1) {
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_set_cipher_list(\"" +
_tlsOptions.ciphers + "\") failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
return false;
}
return true;
}
bool SocketOpenSSL::accept(std::string &errMsg)
{
bool handshakeSuccessful = false;
{
std::lock_guard<std::mutex> lock(_mutex);
if (!_openSSLInitializationSuccessful) {
errMsg = "OPENSSL_init_ssl failure";
return false;
}
if (_sockfd == -1) {
return false;
}
{
const SSL_METHOD *method = SSLv23_server_method();
if (method == nullptr) {
errMsg = "SSLv23_server_method failure";
_ssl_context = nullptr;
} else {
_ssl_method = method;
_ssl_context = SSL_CTX_new(_ssl_method);
if (_ssl_context) {
SSL_CTX_set_mode(_ssl_context,
SSL_MODE_ENABLE_PARTIAL_WRITE);
SSL_CTX_set_mode(_ssl_context,
SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
SSL_CTX_set_options(_ssl_context,
SSL_OP_ALL | SSL_OP_NO_SSLv2 |
SSL_OP_NO_SSLv3);
}
}
}
if (_ssl_context == nullptr) {
return false;
}
ERR_clear_error();
if (_tlsOptions.hasCertAndKey()) {
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 += ERR_error_string(sslErr, nullptr);
}
} else {
if (_tlsOptions.isUsingInMemoryCAs()) {
// Load from memory
openSSLAddCARootsFromString(_tlsOptions.caFile);
} 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,
nullptr) != 1) {
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - "
"SSL_CTX_load_verify_locations(\"" +
_tlsOptions.caFile + "\") failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
}
}
}
}
SSL_CTX_set_verify(_ssl_context,
SSL_VERIFY_PEER |
SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
nullptr);
SSL_CTX_set_verify_depth(_ssl_context, 4);
} else {
SSL_CTX_set_verify(_ssl_context, SSL_VERIFY_NONE, nullptr);
}
if (_tlsOptions.isUsingDefaultCiphers()) {
if (SSL_CTX_set_cipher_list(_ssl_context,
kDefaultCiphers.c_str()) != 1) {
return false;
}
} else if (SSL_CTX_set_cipher_list(_ssl_context,
_tlsOptions.ciphers.c_str()) != 1) {
return false;
}
_ssl_connection = SSL_new(_ssl_context);
if (_ssl_connection == nullptr) {
errMsg = "OpenSSL failed to connect";
SSL_CTX_free(_ssl_context);
_ssl_context = nullptr;
return false;
}
SSL_set_ecdh_auto(_ssl_connection, 1);
SSL_set_fd(_ssl_connection, _sockfd);
handshakeSuccessful = openSSLServerHandshake(errMsg);
}
if (!handshakeSuccessful) {
close();
return false;
}
return true;
}
bool SocketOpenSSL::connect(const std::string &host,
int port,
std::string &errMsg,
const CancellationRequest &isCancellationRequested)
{
bool handshakeSuccessful = false;
{
std::lock_guard<std::mutex> lock(_mutex);
if (!_openSSLInitializationSuccessful) {
errMsg = "OPENSSL_init_ssl failure";
return false;
}
_sockfd =
SocketConnect::connect(host, port, errMsg, isCancellationRequested);
if (_sockfd == -1)
return false;
_ssl_context = openSSLCreateContext(errMsg);
if (_ssl_context == nullptr) {
return false;
}
if (!handleTLSOptions(errMsg)) {
return false;
}
_ssl_connection = SSL_new(_ssl_context);
if (_ssl_connection == nullptr) {
errMsg = "OpenSSL failed to connect";
SSL_CTX_free(_ssl_context);
_ssl_context = nullptr;
return false;
}
SSL_set_fd(_ssl_connection, _sockfd);
// SNI support
SSL_set_tlsext_host_name(_ssl_connection, host.c_str());
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
// Support for server name verification
// (The docs say that this should work from 1.0.2, and is the default
// from 1.1.0, but it does not. To be on the safe side, the manual test
// below is enabled for all versions prior to 1.1.0.)
X509_VERIFY_PARAM *param = SSL_get0_param(_ssl_connection);
X509_VERIFY_PARAM_set1_host(param, host.c_str(), 0);
#endif
handshakeSuccessful =
openSSLClientHandshake(host, errMsg, isCancellationRequested);
}
if (!handshakeSuccessful) {
close();
return false;
}
return true;
}
void SocketOpenSSL::close()
{
std::lock_guard<std::mutex> lock(_mutex);
if (_ssl_connection != nullptr) {
SSL_free(_ssl_connection);
_ssl_connection = nullptr;
}
if (_ssl_context != nullptr) {
SSL_CTX_free(_ssl_context);
_ssl_context = nullptr;
}
Socket::close();
}
ssize_t SocketOpenSSL::send(char *buf, size_t nbyte)
{
std::lock_guard<std::mutex> lock(_mutex);
if (_ssl_connection == nullptr || _ssl_context == nullptr) {
return 0;
}
ERR_clear_error();
ssize_t write_result = SSL_write(_ssl_connection, buf, (int)nbyte);
int reason = SSL_get_error(_ssl_connection, (int)write_result);
if (reason == SSL_ERROR_NONE) {
return write_result;
} else if (reason == SSL_ERROR_WANT_READ ||
reason == SSL_ERROR_WANT_WRITE) {
errno = EWOULDBLOCK;
return -1;
} else {
return -1;
}
}
ssize_t SocketOpenSSL::recv(void *buf, size_t nbyte)
{
while (true) {
std::lock_guard<std::mutex> lock(_mutex);
if (_ssl_connection == nullptr || _ssl_context == nullptr) {
return 0;
}
ERR_clear_error();
ssize_t read_result = SSL_read(_ssl_connection, buf, (int)nbyte);
if (read_result > 0) {
return read_result;
}
int reason = SSL_get_error(_ssl_connection, (int)read_result);
if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE) {
errno = EWOULDBLOCK;
}
return -1;
}
}
} // namespace ix
#endif // IXWEBSOCKET_USE_OPEN_SSL