/**
* Copyright (C) 2018 MongoDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the GNU Affero General Public License in all respects
* for all of the code used other than as permitted herein. If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. If you do not
* wish to do so, delete this exception statement from your version. If you
* delete this exception statement from all source files in the program,
* then also delete it in the license file.
*/
#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kNetwork
#include "mongo/platform/basic.h"
#include "mongo/util/net/ssl_manager.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include "mongo/base/checked_cast.h"
#include "mongo/base/init.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/config.h"
#include "mongo/db/server_parameters.h"
#include "mongo/platform/atomic_word.h"
#include "mongo/stdx/memory.h"
#include "mongo/transport/session.h"
#include "mongo/util/concurrency/mutex.h"
#include "mongo/util/debug_util.h"
#include "mongo/util/exit.h"
#include "mongo/util/log.h"
#include "mongo/util/mongoutils/str.h"
#include "mongo/util/net/cidr.h"
#include "mongo/util/net/private/ssl_expiration.h"
#include "mongo/util/net/socket_exception.h"
#include "mongo/util/net/ssl_options.h"
#include "mongo/util/net/ssl_types.h"
#include "mongo/util/scopeguard.h"
#include "mongo/util/text.h"
#include
#include
#include
#include
#include
#include
#if defined(_WIN32)
#include
#elif defined(__APPLE__)
#include
#endif
namespace mongo {
namespace {
// Because the hostname having a slash is used by `mongo::SockAddr` to determine if a hostname is a
// Unix Domain Socket endpoint, this function uses the same logic. (See
// `mongo::SockAddr::Sockaddr(StringData, int, sa_family_t)`). A user explicitly specifying a Unix
// Domain Socket in the present working directory, through a code path which supplies `sa_family_t`
// as `AF_UNIX` will cause this code to lie. This will, in turn, cause the
// `SSLManagerInterface::parseAndValidatePeerCertificate` code to believe a socket is a host, which
// will then cause a connection failure if and only if that domain socket also has a certificate for
// SSL and the connection is an SSL connection.
bool isUnixDomainSocket(const std::string& hostname) {
return end(hostname) != std::find(begin(hostname), end(hostname), '/');
}
// If the underlying SSL supports auto-configuration of ECDH parameters, this function will select
// it, otherwise this function will do nothing.
void setECDHModeAuto(SSL_CTX* const ctx) {
#ifdef MONGO_CONFIG_HAVE_SSL_SET_ECDH_AUTO
::SSL_CTX_set_ecdh_auto(ctx, true);
#endif
std::ignore = ctx;
}
struct DHFreer {
void operator()(DH* const dh) noexcept {
if (dh) {
::DH_free(dh);
}
}
};
using UniqueDHParams = std::unique_ptr;
struct BIOFree {
void operator()(BIO* const p) noexcept {
// Assumes that BIO_free succeeds.
if (p) {
::BIO_free(p);
}
}
};
using UniqueBIO = std::unique_ptr;
UniqueBIO makeUniqueMemBio(std::vector& v) {
UniqueBIO rv(::BIO_new_mem_buf(v.data(), v.size()));
if (!rv) {
class ssl_bad_alloc : public std::bad_alloc {
private:
std::string message;
public:
explicit ssl_bad_alloc(std::string m) : message(std::move(m)) {}
const char* what() const noexcept override {
return message.c_str();
}
};
throw ssl_bad_alloc(str::stream()
<< "Error allocating SSL BIO: "
<< SSLManagerInterface::getSSLErrorMessage(ERR_get_error()));
}
return rv;
}
// Old copies of OpenSSL will not have constants to disable protocols they don't support.
// Define them to values we can OR together safely to generically disable these protocols across
// all versions of OpenSSL.
#ifndef SSL_OP_NO_TLSv1_1
#define SSL_OP_NO_TLSv1_1 0
#endif
#ifndef SSL_OP_NO_TLSv1_2
#define SSL_OP_NO_TLSv1_2 0
#endif
// clang-format off
#ifndef MONGO_CONFIG_HAVE_ASN1_ANY_DEFINITIONS
// Copies of OpenSSL before 1.0.0 do not have ASN1_SEQUENCE_ANY, ASN1_SET_ANY, or the helper
// functions which let us deserialize these objects. We must polyfill the definitions to interact
// with ASN1 objects so stored.
typedef STACK_OF(ASN1_TYPE) ASN1_SEQUENCE_ANY;
ASN1_ITEM_TEMPLATE(ASN1_SEQUENCE_ANY) =
ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_SEQUENCE_OF, 0, ASN1_SEQUENCE_ANY, ASN1_ANY)
ASN1_ITEM_TEMPLATE_END(ASN1_SEQUENCE_ANY)
ASN1_ITEM_TEMPLATE(ASN1_SET_ANY) =
ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_SET_OF, 0, ASN1_SET_ANY, ASN1_ANY)
ASN1_ITEM_TEMPLATE_END(ASN1_SET_ANY)
IMPLEMENT_ASN1_ENCODE_FUNCTIONS_const_fname(ASN1_SEQUENCE_ANY, ASN1_SEQUENCE_ANY,
ASN1_SEQUENCE_ANY)
IMPLEMENT_ASN1_ENCODE_FUNCTIONS_const_fname(ASN1_SEQUENCE_ANY, ASN1_SET_ANY, ASN1_SET_ANY)
; // clang format needs to see a semicolon or it will start formatting unrelated code
#endif // MONGO_CONFIG_NEEDS_ASN1_ANY_DEFINITIONS
// clang-format on
#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
(defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL)
// Copies of OpenSSL after 1.1.0 define new functions for interaction with
// X509 structure. We must polyfill used definitions to interact with older
// OpenSSL versions.
const STACK_OF(X509_EXTENSION) * X509_get0_extensions(const X509* peerCert) {
return peerCert->cert_info->extensions;
}
inline int X509_NAME_ENTRY_set(const X509_NAME_ENTRY* ne) {
return ne->set;
}
#endif
/**
* Multithreaded Support for SSL.
*
* In order to allow OpenSSL to work in a multithreaded environment, you
* may need to provide some callbacks for it to use for locking. The following code
* sets up a vector of mutexes and provides a thread unique ID number.
* The so-called SSLThreadInfo class encapsulates most of the logic required for
* OpenSSL multithreaded support.
*
* OpenSSL before version 1.1.0 requires applications provide a callback which emits a thread
* identifier. This ID is used to store thread specific ERR information. When a thread is
* terminated, it must call ERR_remove_state or ERR_remove_thread_state. These functions may
* themselves invoke the application provided callback. These IDs are stored in a hashtable with
* a questionable hash function. They must be uniformly distributed to prevent collisions.
*/
class SSLThreadInfo {
public:
static unsigned long getID() {
struct CallErrRemoveState {
explicit CallErrRemoveState(ThreadIDManager& manager, unsigned long id)
: _manager(manager), id(id) {}
~CallErrRemoveState() {
ERR_remove_state(0);
_manager.releaseID(id);
};
ThreadIDManager& _manager;
unsigned long id;
};
// NOTE: This logic is fully intentional. Because ERR_remove_state (called within
// the destructor of the kRemoveStateFromThread object) re-enters this function,
// we must have a two phase protection, otherwise we would access a thread local
// during its destruction.
static thread_local boost::optional threadLocalState;
if (!threadLocalState) {
threadLocalState.emplace(_idManager, _idManager.reserveID());
}
return threadLocalState->id;
}
static void lockingCallback(int mode, int type, const char* file, int line) {
if (mode & CRYPTO_LOCK) {
_mutex[type]->lock();
} else {
_mutex[type]->unlock();
}
}
static void init() {
CRYPTO_set_id_callback(&SSLThreadInfo::getID);
CRYPTO_set_locking_callback(&SSLThreadInfo::lockingCallback);
while ((int)_mutex.size() < CRYPTO_num_locks()) {
_mutex.emplace_back(stdx::make_unique());
}
}
private:
SSLThreadInfo() = delete;
// Note: see SERVER-8734 for why we are using a recursive mutex here.
// Once the deadlock fix in OpenSSL is incorporated into most distros of
// Linux, this can be changed back to a nonrecursive mutex.
static std::vector> _mutex;
class ThreadIDManager {
public:
unsigned long reserveID() {
stdx::unique_lock lock(_idMutex);
if (!_idLast.empty()) {
unsigned long ret = _idLast.top();
_idLast.pop();
return ret;
}
return ++_idNext;
}
void releaseID(unsigned long id) {
stdx::unique_lock lock(_idMutex);
_idLast.push(id);
}
private:
// Machinery for producing IDs that are unique for the life of a thread.
stdx::mutex _idMutex; // Protects _idNext and _idLast.
unsigned long _idNext = 0; // Stores the next thread ID to use, if none already allocated.
std::stack>
_idLast; // Stores old thread IDs, for reuse.
};
static ThreadIDManager _idManager;
};
std::vector> SSLThreadInfo::_mutex;
SSLThreadInfo::ThreadIDManager SSLThreadInfo::_idManager;
namespace {
// We only want to free SSL_CTX objects if they have been populated. OpenSSL seems to perform this
// check before freeing them, but because it does not document this, we should protect ourselves.
void free_ssl_context(SSL_CTX* ctx) {
if (ctx != nullptr) {
SSL_CTX_free(ctx);
}
}
} // namespace
class SSLConnectionOpenSSL : public SSLConnectionInterface {
public:
SSL* ssl;
BIO* networkBIO;
BIO* internalBIO;
Socket* socket;
SSLConnectionOpenSSL(SSL_CTX* ctx, Socket* sock, const char* initialBytes, int len);
~SSLConnectionOpenSSL();
std::string getSNIServerName() const final {
const char* name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
if (!name)
return "";
return name;
}
};
////////////////////////////////////////////////////////////////
SimpleMutex sslManagerMtx;
SSLManagerInterface* theSSLManager = NULL;
using UniqueSSLContext = std::unique_ptr;
static const int BUFFER_SIZE = 8 * 1024;
static const int DATE_LEN = 128;
struct UniqueX509Free {
void operator()(X509* ptr) const {
X509_free(ptr);
}
};
using UniqueX509 = std::unique_ptr;
class SSLManagerOpenSSL : public SSLManagerInterface {
public:
explicit SSLManagerOpenSSL(const SSLParams& params, bool isServer);
/**
* Initializes an OpenSSL context according to the provided settings. Only settings which are
* acceptable on non-blocking connections are set.
*/
Status initSSLContext(SSL_CTX* context,
const SSLParams& params,
ConnectionDirection direction) final;
SSLConnectionInterface* connect(Socket* socket) final;
SSLConnectionInterface* accept(Socket* socket, const char* initialBytes, int len) final;
SSLPeerInfo parseAndValidatePeerCertificateDeprecated(const SSLConnectionInterface* conn,
const std::string& remoteHost) final;
StatusWith> parseAndValidatePeerCertificate(
SSL* conn, const std::string& remoteHost) final;
const SSLConfiguration& getSSLConfiguration() const final {
return _sslConfiguration;
}
int SSL_read(SSLConnectionInterface* conn, void* buf, int num) final;
int SSL_write(SSLConnectionInterface* conn, const void* buf, int num) final;
int SSL_shutdown(SSLConnectionInterface* conn) final;
private:
const int _rolesNid = OBJ_create(mongodbRolesOID.identifier.c_str(),
mongodbRolesOID.shortDescription.c_str(),
mongodbRolesOID.longDescription.c_str());
UniqueSSLContext _serverContext; // SSL context for incoming connections
UniqueSSLContext _clientContext; // SSL context for outgoing connections
// On OSX, it's not safe to copy the system CA certificates after a fork(), which we need to
// do the deathtest unittests. So on __APPLE__ platforms, we copy the certificates into a vector
// of OpenSSL X509 objects once at startup in _getSystemCerts() and then append them into
// X509_STORE's when initializing SSL_CTX's.
//
// On other platforms it's safe to load the certificate each time we initialize an SSL_CTX.
#if defined(__APPLE__)
std::vector _systemCACertificates;
#endif
bool _weakValidation;
bool _allowInvalidCertificates;
bool _allowInvalidHostnames;
bool _suppressNoCertificateWarning;
SSLConfiguration _sslConfiguration;
/**
* creates an SSL object to be used for this file descriptor.
* caller must SSL_free it.
*/
SSL* _secure(SSL_CTX* context, int fd);
/**
* Given an error code from an SSL-type IO function, logs an
* appropriate message and throws a NetworkException.
*/
MONGO_COMPILER_NORETURN void _handleSSLError(SSLConnectionOpenSSL* conn, int ret);
/*
* Init the SSL context using parameters provided in params. This SSL context will
* be configured for blocking send/receive.
*/
bool _initSynchronousSSLContext(UniqueSSLContext* context,
const SSLParams& params,
ConnectionDirection direction);
/*
* Converts time from OpenSSL return value to unsigned long long
* representing the milliseconds since the epoch.
*/
unsigned long long _convertASN1ToMillis(ASN1_TIME* t);
/*
* Parse and store x509 subject name from the PEM keyfile.
* For server instances check that PEM certificate is not expired
* and extract server certificate notAfter date.
* @param keyFile referencing the PEM file to be read.
* @param subjectName as a pointer to the subject name variable being set.
* @param serverNotAfter a Date_t object pointer that is valued if the
* date is to be checked (as for a server certificate) and null otherwise.
* @return bool showing if the function was successful.
*/
bool _parseAndValidateCertificate(const std::string& keyFile,
const std::string& keyPassword,
SSLX509Name* subjectName,
Date_t* serverNotAfter);
StatusWith> _parsePeerRoles(X509* peerCert) const;
/** @return true if was successful, otherwise false */
bool _setupPEM(SSL_CTX* context, const std::string& keyFile, const std::string& password);
/*
* Set up an SSL context for certificate validation by loading a CA
*/
Status _setupCA(SSL_CTX* context, const std::string& caFile);
/*
* Set up an SSL context for certificate validation by loading the system's CA store
*/
Status _setupSystemCA(SSL_CTX* context);
#if defined(__APPLE__)
/*
* Loads the system-trusted CA certificates from a native source into a vector of X509 objects
*/
std::vector _getSystemCerts();
#endif
/*
* Import a certificate revocation list into an SSL context
* for use with validating certificates
*/
bool _setupCRL(SSL_CTX* context, const std::string& crlFile);
/*
* sub function for checking the result of an SSL operation
*/
bool _doneWithSSLOp(SSLConnectionOpenSSL* conn, int status);
/*
* Send and receive network data
*/
void _flushNetworkBIO(SSLConnectionOpenSSL* conn);
/**
* Callbacks for SSL functions.
*/
static int password_cb(char* buf, int num, int rwflag, void* userdata);
static int verify_cb(int ok, X509_STORE_CTX* ctx);
};
void setupFIPS() {
// Turn on FIPS mode if requested, OPENSSL_FIPS must be defined by the OpenSSL headers
#if defined(MONGO_CONFIG_HAVE_FIPS_MODE_SET)
int status = FIPS_mode_set(1);
if (!status) {
severe() << "can't activate FIPS mode: "
<< SSLManagerInterface::getSSLErrorMessage(ERR_get_error());
fassertFailedNoTrace(16703);
}
log() << "FIPS 140-2 mode activated";
#else
severe() << "this version of mongodb was not compiled with FIPS support";
fassertFailedNoTrace(17089);
#endif
}
} // namespace
// Global variable indicating if this is a server or a client instance
bool isSSLServer = false;
MONGO_INITIALIZER(SetupOpenSSL)(InitializerContext*) {
SSL_library_init();
SSL_load_error_strings();
ERR_load_crypto_strings();
if (sslGlobalParams.sslFIPSMode) {
setupFIPS();
}
// Add all digests and ciphers to OpenSSL's internal table
// so that encryption/decryption is backwards compatible
OpenSSL_add_all_algorithms();
// Setup OpenSSL multithreading callbacks and mutexes
SSLThreadInfo::init();
return Status::OK();
}
MONGO_INITIALIZER_WITH_PREREQUISITES(SSLManager, ("SetupOpenSSL"))(InitializerContext*) {
stdx::lock_guard lck(sslManagerMtx);
if (!isSSLServer || (sslGlobalParams.sslMode.load() != SSLParams::SSLMode_disabled)) {
theSSLManager = new SSLManagerOpenSSL(sslGlobalParams, isSSLServer);
}
return Status::OK();
}
std::unique_ptr SSLManagerInterface::create(const SSLParams& params,
bool isServer) {
return stdx::make_unique(params, isServer);
}
SSLManagerInterface* getSSLManager() {
stdx::lock_guard lck(sslManagerMtx);
if (theSSLManager)
return theSSLManager;
return NULL;
}
SSLX509Name getCertificateSubjectX509Name(X509* cert) {
std::vector> entries;
auto name = X509_get_subject_name(cert);
int count = X509_NAME_entry_count(name);
int prevSet = -1;
std::vector rdn;
for (int i = count - 1; i >= 0; --i) {
auto* entry = X509_NAME_get_entry(name, i);
const auto currentSet = X509_NAME_ENTRY_set(entry);
if (currentSet != prevSet) {
if (!rdn.empty()) {
entries.push_back(std::move(rdn));
rdn = std::vector();
}
prevSet = currentSet;
}
char buffer[128];
// OBJ_obj2txt can only fail if we pass a nullptr from get_object,
// or if OpenSSL's BN library falls over.
// In either case, just panic.
uassert(ErrorCodes::InvalidSSLConfiguration,
"Unable to parse certiciate subject name",
OBJ_obj2txt(buffer, sizeof(buffer), X509_NAME_ENTRY_get_object(entry), 1) > 0);
const auto* str = X509_NAME_ENTRY_get_data(entry);
rdn.emplace_back(
buffer, str->type, std::string(reinterpret_cast(str->data), str->length));
}
if (!rdn.empty()) {
entries.push_back(std::move(rdn));
}
return SSLX509Name(std::move(entries));
}
SSLConnectionOpenSSL::SSLConnectionOpenSSL(SSL_CTX* context,
Socket* sock,
const char* initialBytes,
int len)
: socket(sock) {
ssl = SSL_new(context);
std::string sslErr =
NULL != getSSLManager() ? getSSLManager()->getSSLErrorMessage(ERR_get_error()) : "";
massert(15861, "Error creating new SSL object " + sslErr, ssl);
BIO_new_bio_pair(&internalBIO, BUFFER_SIZE, &networkBIO, BUFFER_SIZE);
SSL_set_bio(ssl, internalBIO, internalBIO);
if (len > 0) {
int toBIO = BIO_write(networkBIO, initialBytes, len);
if (toBIO != len) {
LOG(3) << "Failed to write initial network data to the SSL BIO layer";
throwSocketError(SocketErrorKind::RECV_ERROR, socket->remoteString());
}
}
}
SSLConnectionOpenSSL::~SSLConnectionOpenSSL() {
if (ssl) { // The internalBIO is automatically freed as part of SSL_free
SSL_free(ssl);
}
if (networkBIO) {
BIO_free(networkBIO);
}
}
SSLManagerOpenSSL::SSLManagerOpenSSL(const SSLParams& params, bool isServer)
: _serverContext(nullptr, free_ssl_context),
_clientContext(nullptr, free_ssl_context),
#if defined(__APPLE__)
_systemCACertificates(_getSystemCerts()),
#endif
_weakValidation(params.sslWeakCertificateValidation),
_allowInvalidCertificates(params.sslAllowInvalidCertificates),
_allowInvalidHostnames(params.sslAllowInvalidHostnames),
_suppressNoCertificateWarning(params.suppressNoTLSPeerCertificateWarning) {
if (!_initSynchronousSSLContext(&_clientContext, params, ConnectionDirection::kOutgoing)) {
uasserted(16768, "ssl initialization problem");
}
// pick the certificate for use in outgoing connections,
std::string clientPEM, clientPassword;
if (!isServer || params.sslClusterFile.empty()) {
// We are either a client, or a server without a cluster key,
// so use the PEM key file, if specified
clientPEM = params.sslPEMKeyFile;
clientPassword = params.sslPEMKeyPassword;
} else {
// We are a server with a cluster key, so use the cluster key file
clientPEM = params.sslClusterFile;
clientPassword = params.sslClusterPassword;
}
if (!clientPEM.empty()) {
if (!_parseAndValidateCertificate(
clientPEM, clientPassword, &_sslConfiguration.clientSubjectName, NULL)) {
uasserted(16941, "ssl initialization problem");
}
}
// SSL server specific initialization
if (isServer) {
if (!_initSynchronousSSLContext(&_serverContext, params, ConnectionDirection::kIncoming)) {
uasserted(16562, "ssl initialization problem");
}
if (!_parseAndValidateCertificate(params.sslPEMKeyFile,
params.sslPEMKeyPassword,
&_sslConfiguration.serverSubjectName,
&_sslConfiguration.serverCertificateExpirationDate)) {
uasserted(16942, "ssl initialization problem");
}
static CertificateExpirationMonitor task =
CertificateExpirationMonitor(_sslConfiguration.serverCertificateExpirationDate);
}
}
int SSLManagerOpenSSL::password_cb(char* buf, int num, int rwflag, void* userdata) {
// Unless OpenSSL misbehaves, num should always be positive
fassert(17314, num > 0);
invariant(userdata);
auto pw = static_cast(userdata);
const size_t copied = pw->copy(buf, num - 1);
buf[copied] = '\0';
return copied;
}
int SSLManagerOpenSSL::verify_cb(int ok, X509_STORE_CTX* ctx) {
return 1; // always succeed; we will catch the error in our get_verify_result() call
}
int SSLManagerOpenSSL::SSL_read(SSLConnectionInterface* connInterface, void* buf, int num) {
int status;
SSLConnectionOpenSSL* conn = checked_cast(connInterface);
do {
status = ::SSL_read(conn->ssl, buf, num);
} while (!_doneWithSSLOp(conn, status));
if (status <= 0)
_handleSSLError(conn, status);
return status;
}
int SSLManagerOpenSSL::SSL_write(SSLConnectionInterface* connInterface, const void* buf, int num) {
int status;
SSLConnectionOpenSSL* conn = checked_cast(connInterface);
do {
status = ::SSL_write(conn->ssl, buf, num);
} while (!_doneWithSSLOp(conn, status));
if (status <= 0)
_handleSSLError(conn, status);
return status;
}
int SSLManagerOpenSSL::SSL_shutdown(SSLConnectionInterface* connInterface) {
int status;
SSLConnectionOpenSSL* conn = checked_cast(connInterface);
do {
status = ::SSL_shutdown(conn->ssl);
} while (!_doneWithSSLOp(conn, status));
if (status < 0)
_handleSSLError(conn, status);
return status;
}
Status SSLManagerOpenSSL::initSSLContext(SSL_CTX* context,
const SSLParams& params,
ConnectionDirection direction) {
// SSL_OP_ALL - Activate all bug workaround options, to support buggy client SSL's.
// SSL_OP_NO_SSLv2 - Disable SSL v2 support
// SSL_OP_NO_SSLv3 - Disable SSL v3 support
long supportedProtocols = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
// Set the supported TLS protocols. Allow --sslDisabledProtocols to disable selected
// ciphers.
for (const SSLParams::Protocols& protocol : params.sslDisabledProtocols) {
if (protocol == SSLParams::Protocols::TLS1_0) {
supportedProtocols |= SSL_OP_NO_TLSv1;
} else if (protocol == SSLParams::Protocols::TLS1_1) {
supportedProtocols |= SSL_OP_NO_TLSv1_1;
} else if (protocol == SSLParams::Protocols::TLS1_2) {
supportedProtocols |= SSL_OP_NO_TLSv1_2;
}
}
::SSL_CTX_set_options(context, supportedProtocols);
// HIGH - Enable strong ciphers
// !EXPORT - Disable export ciphers (40/56 bit)
// !aNULL - Disable anonymous auth ciphers
// @STRENGTH - Sort ciphers based on strength
std::string cipherConfig = "HIGH:!EXPORT:!aNULL@STRENGTH";
// Allow the cipher configuration string to be overriden by --sslCipherConfig
if (!params.sslCipherConfig.empty()) {
cipherConfig = params.sslCipherConfig;
}
if (0 == ::SSL_CTX_set_cipher_list(context, cipherConfig.c_str())) {
return Status(ErrorCodes::InvalidSSLConfiguration,
str::stream() << "Can not set supported cipher suites: "
<< getSSLErrorMessage(ERR_get_error()));
}
// We use the address of the context as the session id context.
if (0 == ::SSL_CTX_set_session_id_context(
context, reinterpret_cast(&context), sizeof(context))) {
return Status(ErrorCodes::InvalidSSLConfiguration,
str::stream() << "Can not store ssl session id context: "
<< getSSLErrorMessage(ERR_get_error()));
}
if (direction == ConnectionDirection::kOutgoing && !params.sslClusterFile.empty()) {
::EVP_set_pw_prompt("Enter cluster certificate passphrase");
if (!_setupPEM(context, params.sslClusterFile, params.sslClusterPassword)) {
return Status(ErrorCodes::InvalidSSLConfiguration, "Can not set up ssl clusterFile.");
}
} else if (!params.sslPEMKeyFile.empty()) {
// Use the pemfile for everything else
::EVP_set_pw_prompt("Enter PEM passphrase");
if (!_setupPEM(context, params.sslPEMKeyFile, params.sslPEMKeyPassword)) {
return Status(ErrorCodes::InvalidSSLConfiguration, "Can not set up PEM key file.");
}
}
std::string cafile = params.sslCAFile;
if (direction == ConnectionDirection::kIncoming && !params.sslClusterCAFile.empty()) {
cafile = params.sslClusterCAFile;
}
const auto status = cafile.empty() ? _setupSystemCA(context) : _setupCA(context, cafile);
if (!status.isOK()) {
return status;
}
if (!params.sslCRLFile.empty()) {
if (!_setupCRL(context, params.sslCRLFile)) {
return Status(ErrorCodes::InvalidSSLConfiguration, "Can not set up CRL file.");
}
}
if (!params.sslPEMTempDHParam.empty()) {
try {
std::ifstream dhparamPemFile(params.sslPEMTempDHParam, std::ios_base::binary);
if (dhparamPemFile.fail() || dhparamPemFile.bad()) {
return Status(ErrorCodes::InvalidSSLConfiguration,
str::stream() << "Cannot open PEM DHParams file.");
}
std::vector paramData{std::istreambuf_iterator(dhparamPemFile),
std::istreambuf_iterator()};
auto bio = makeUniqueMemBio(paramData);
UniqueDHParams dhparams(::PEM_read_bio_DHparams(bio.get(), nullptr, nullptr, nullptr));
if (!dhparams) {
return Status(ErrorCodes::InvalidSSLConfiguration,
str::stream() << "Error reading DHParams file."
<< getSSLErrorMessage(ERR_get_error()));
}
if (::SSL_CTX_set_tmp_dh(context, dhparams.get()) != 1) {
return Status(ErrorCodes::InvalidSSLConfiguration,
str::stream() << "Failure to set PFS DH parameters: "
<< getSSLErrorMessage(ERR_get_error()));
}
} catch (const std::exception& ex) {
return Status(ErrorCodes::InvalidSSLConfiguration, ex.what());
}
}
// We always set ECDH mode anyhow, if available.
setECDHModeAuto(context);
return Status::OK();
}
bool SSLManagerOpenSSL::_initSynchronousSSLContext(UniqueSSLContext* contextPtr,
const SSLParams& params,
ConnectionDirection direction) {
*contextPtr = UniqueSSLContext(SSL_CTX_new(SSLv23_method()), free_ssl_context);
uassertStatusOK(initSSLContext(contextPtr->get(), params, direction));
// If renegotiation is needed, don't return from recv() or send() until it's successful.
// Note: this is for blocking sockets only.
SSL_CTX_set_mode(contextPtr->get(), SSL_MODE_AUTO_RETRY);
return true;
}
unsigned long long SSLManagerOpenSSL::_convertASN1ToMillis(ASN1_TIME* asn1time) {
BIO* outBIO = BIO_new(BIO_s_mem());
int timeError = ASN1_TIME_print(outBIO, asn1time);
ON_BLOCK_EXIT(BIO_free, outBIO);
if (timeError <= 0) {
error() << "ASN1_TIME_print failed or wrote no data.";
return 0;
}
char dateChar[DATE_LEN];
timeError = BIO_gets(outBIO, dateChar, DATE_LEN);
if (timeError <= 0) {
error() << "BIO_gets call failed to transfer contents to buf";
return 0;
}
// Ensure that day format is two digits for parsing.
// Jun 8 17:00:03 2014 becomes Jun 08 17:00:03 2014.
if (dateChar[4] == ' ') {
dateChar[4] = '0';
}
std::istringstream inStringStream((std::string(dateChar, 20)));
boost::posix_time::time_input_facet* inputFacet =
new boost::posix_time::time_input_facet("%b %d %H:%M:%S %Y");
inStringStream.imbue(std::locale(std::cout.getloc(), inputFacet));
boost::posix_time::ptime posixTime;
inStringStream >> posixTime;
const boost::gregorian::date epoch = boost::gregorian::date(1970, boost::gregorian::Jan, 1);
return (posixTime - boost::posix_time::ptime(epoch)).total_milliseconds();
}
bool SSLManagerOpenSSL::_parseAndValidateCertificate(const std::string& keyFile,
const std::string& keyPassword,
SSLX509Name* subjectName,
Date_t* serverCertificateExpirationDate) {
BIO* inBIO = BIO_new(BIO_s_file());
if (inBIO == NULL) {
error() << "failed to allocate BIO object: " << getSSLErrorMessage(ERR_get_error());
return false;
}
ON_BLOCK_EXIT(BIO_free, inBIO);
if (BIO_read_filename(inBIO, keyFile.c_str()) <= 0) {
error() << "cannot read key file when setting subject name: " << keyFile << ' '
<< getSSLErrorMessage(ERR_get_error());
return false;
}
// Callback will not manipulate the password, so const_cast is safe.
X509* x509 = PEM_read_bio_X509(inBIO,
NULL,
&SSLManagerOpenSSL::password_cb,
const_cast(static_cast(&keyPassword)));
if (x509 == NULL) {
error() << "cannot retrieve certificate from keyfile: " << keyFile << ' '
<< getSSLErrorMessage(ERR_get_error());
return false;
}
ON_BLOCK_EXIT(X509_free, x509);
*subjectName = getCertificateSubjectX509Name(x509);
if (serverCertificateExpirationDate != NULL) {
unsigned long long notBeforeMillis = _convertASN1ToMillis(X509_get_notBefore(x509));
if (notBeforeMillis == 0) {
error() << "date conversion failed";
return false;
}
unsigned long long notAfterMillis = _convertASN1ToMillis(X509_get_notAfter(x509));
if (notAfterMillis == 0) {
error() << "date conversion failed";
return false;
}
if ((notBeforeMillis > curTimeMillis64()) || (curTimeMillis64() > notAfterMillis)) {
severe() << "The provided SSL certificate is expired or not yet valid.";
fassertFailedNoTrace(28652);
}
*serverCertificateExpirationDate = Date_t::fromMillisSinceEpoch(notAfterMillis);
}
return true;
}
bool SSLManagerOpenSSL::_setupPEM(SSL_CTX* context,
const std::string& keyFile,
const std::string& password) {
if (SSL_CTX_use_certificate_chain_file(context, keyFile.c_str()) != 1) {
error() << "cannot read certificate file: " << keyFile << ' '
<< getSSLErrorMessage(ERR_get_error());
return false;
}
BIO* inBio = BIO_new(BIO_s_file());
if (!inBio) {
error() << "failed to allocate BIO object: " << getSSLErrorMessage(ERR_get_error());
return false;
}
const auto bioGuard = MakeGuard([&inBio]() { BIO_free(inBio); });
if (BIO_read_filename(inBio, keyFile.c_str()) <= 0) {
error() << "cannot read PEM key file: " << keyFile << ' '
<< getSSLErrorMessage(ERR_get_error());
return false;
}
// If password is empty, use default OpenSSL callback, which uses the terminal
// to securely request the password interactively from the user.
decltype(&SSLManagerOpenSSL::password_cb) password_cb = nullptr;
void* userdata = nullptr;
if (!password.empty()) {
password_cb = &SSLManagerOpenSSL::password_cb;
// SSLManagerOpenSSL::password_cb will not manipulate the password, so const_cast is safe.
userdata = const_cast(static_cast(&password));
}
EVP_PKEY* privateKey = PEM_read_bio_PrivateKey(inBio, nullptr, password_cb, userdata);
if (!privateKey) {
error() << "cannot read PEM key file: " << keyFile << ' '
<< getSSLErrorMessage(ERR_get_error());
return false;
}
const auto privateKeyGuard = MakeGuard([&privateKey]() { EVP_PKEY_free(privateKey); });
if (SSL_CTX_use_PrivateKey(context, privateKey) != 1) {
error() << "cannot use PEM key file: " << keyFile << ' '
<< getSSLErrorMessage(ERR_get_error());
return false;
}
// Verify that the certificate and the key go together.
if (SSL_CTX_check_private_key(context) != 1) {
error() << "SSL certificate validation: " << getSSLErrorMessage(ERR_get_error());
return false;
}
return true;
}
Status SSLManagerOpenSSL::_setupCA(SSL_CTX* context, const std::string& caFile) {
// Set the list of CAs sent to clients
STACK_OF(X509_NAME)* certNames = SSL_load_client_CA_file(caFile.c_str());
if (certNames == NULL) {
return Status(ErrorCodes::InvalidSSLConfiguration,
str::stream() << "cannot read certificate authority file: " << caFile << " "
<< getSSLErrorMessage(ERR_get_error()));
}
SSL_CTX_set_client_CA_list(context, certNames);
// Load trusted CA
if (SSL_CTX_load_verify_locations(context, caFile.c_str(), NULL) != 1) {
return Status(ErrorCodes::InvalidSSLConfiguration,
str::stream() << "cannot read certificate authority file: " << caFile << " "
<< getSSLErrorMessage(ERR_get_error()));
}
// Set SSL to require peer (client) certificate verification
// if a certificate is presented
SSL_CTX_set_verify(context, SSL_VERIFY_PEER, &SSLManagerOpenSSL::verify_cb);
_sslConfiguration.hasCA = true;
return Status::OK();
}
inline Status checkX509_STORE_error() {
const auto errCode = ERR_peek_last_error();
if (ERR_GET_LIB(errCode) != ERR_LIB_X509 ||
ERR_GET_REASON(errCode) != X509_R_CERT_ALREADY_IN_HASH_TABLE) {
return {ErrorCodes::InvalidSSLConfiguration,
str::stream() << "Error adding certificate to X509 store: "
<< ERR_reason_error_string(errCode)};
}
return Status::OK();
}
#if defined(_WIN32)
// This imports the certificates in a given Windows certificate store into an X509_STORE for
// openssl to use during certificate validation.
Status importCertStoreToX509_STORE(const wchar_t* storeName,
DWORD storeLocation,
X509_STORE* verifyStore) {
HCERTSTORE systemStore = CertOpenStore(CERT_STORE_PROV_SYSTEM_W,
0,
NULL,
storeLocation | CERT_STORE_READONLY_FLAG,
const_cast(storeName));
if (systemStore == NULL) {
return {ErrorCodes::InvalidSSLConfiguration,
str::stream() << "error opening system CA store: " << errnoWithDescription()};
}
auto systemStoreGuard = MakeGuard([systemStore]() { CertCloseStore(systemStore, 0); });
PCCERT_CONTEXT certCtx = NULL;
while ((certCtx = CertEnumCertificatesInStore(systemStore, certCtx)) != NULL) {
auto certBytes = static_cast(certCtx->pbCertEncoded);
X509* x509Obj = d2i_X509(NULL, &certBytes, certCtx->cbCertEncoded);
if (x509Obj == NULL) {
return {ErrorCodes::InvalidSSLConfiguration,
str::stream() << "Error parsing X509 object from Windows certificate store"
<< SSLManagerInterface::getSSLErrorMessage(ERR_get_error())};
}
const auto x509ObjGuard = MakeGuard([&x509Obj]() { X509_free(x509Obj); });
if (X509_STORE_add_cert(verifyStore, x509Obj) != 1) {
auto status = checkX509_STORE_error();
if (!status.isOK())
return status;
}
}
int lastError = GetLastError();
if (lastError != CRYPT_E_NOT_FOUND) {
return {ErrorCodes::InvalidSSLConfiguration,
str::stream() << "Error enumerating certificates: "
<< errnoWithDescription(lastError)};
}
return Status::OK();
}
#elif defined(__APPLE__)
template
class CFTypeRefHolder {
public:
explicit CFTypeRefHolder(T ptr) : ref(static_cast(ptr)) {}
~CFTypeRefHolder() {
CFRelease(ref);
}
operator T() {
return static_cast(ref);
}
private:
CFTypeRef ref = nullptr;
};
template
CFTypeRefHolder makeCFTypeRefHolder(T ptr) {
return CFTypeRefHolder(ptr);
}
std::string OSStatusToString(OSStatus status) {
auto errMsg = makeCFTypeRefHolder(SecCopyErrorMessageString(status, NULL));
return std::string{CFStringGetCStringPtr(errMsg, kCFStringEncodingUTF8)};
}
std::vector SSLManagerOpenSSL::_getSystemCerts() {
// Copy the system CA certs into a CFArray for us to iterate and convert into X509*
auto anchorCerts = makeCFTypeRefHolder([] {
CFArrayRef ret;
auto status = SecTrustCopyAnchorCertificates(&ret);
uassert(50928,
str::stream() << "Error enumerating certificates: " << OSStatusToString(status),
status == ::errSecSuccess);
return ret;
}());
std::vector ret;
for (CFIndex i = 0; i < CFArrayGetCount(anchorCerts); i++) {
SecCertificateRef cert = static_cast(
const_cast(CFArrayGetValueAtIndex(anchorCerts, i)));
uassert(50929,
"Certificate array had something other than a certificate in it",
::CFGetTypeID(cert) == ::SecCertificateGetTypeID());
// Get the raw X509 bytes out of the certificate
auto rawData = makeCFTypeRefHolder(SecCertificateCopyData(cert));
uassert(50930, str::stream() << "Error converting certificate to raw bytes", rawData);
const uint8_t* rawDataPtr = CFDataGetBytePtr(rawData);
// Parse an openssl X509 object from each returned certificate
UniqueX509 x509Cert(d2i_X509(nullptr, &rawDataPtr, CFDataGetLength(rawData)));
uassert(50931,
str::stream() << "Error parsing X509 certificate from system keychain: "
<< ERR_reason_error_string(ERR_peek_last_error()),
x509Cert);
ret.push_back(std::move(x509Cert));
}
return ret;
}
#endif
Status SSLManagerOpenSSL::_setupSystemCA(SSL_CTX* context) {
#if !defined(_WIN32) && !defined(__APPLE__)
// On non-Windows/non-Apple platforms, the OpenSSL libraries should have been configured
// with default locations for CA certificates.
if (SSL_CTX_set_default_verify_paths(context) != 1) {
return {ErrorCodes::InvalidSSLConfiguration,
str::stream() << "error loading system CA certificates "
<< "(default certificate file: "
<< X509_get_default_cert_file()
<< ", "
<< "default certificate path: "
<< X509_get_default_cert_dir()
<< ")"};
}
#else
X509_STORE* verifyStore = SSL_CTX_get_cert_store(context);
if (!verifyStore) {
return {ErrorCodes::InvalidSSLConfiguration,
"no X509 store found for SSL context while loading system certificates"};
}
#if defined(_WIN32)
auto status = importCertStoreToX509_STORE(L"root", CERT_SYSTEM_STORE_CURRENT_USER, verifyStore);
if (!status.isOK())
return status;
return importCertStoreToX509_STORE(L"CA", CERT_SYSTEM_STORE_CURRENT_USER, verifyStore);
#elif defined(__APPLE__)
for (const auto& cert : _systemCACertificates) {
X509_STORE_add_cert(verifyStore, cert.get());
}
#endif
#endif
return Status::OK();
}
bool SSLManagerOpenSSL::_setupCRL(SSL_CTX* context, const std::string& crlFile) {
X509_STORE* store = SSL_CTX_get_cert_store(context);
fassert(16583, store);
X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK);
X509_LOOKUP* lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
fassert(16584, lookup);
int status = X509_load_crl_file(lookup, crlFile.c_str(), X509_FILETYPE_PEM);
if (status == 0) {
error() << "cannot read CRL file: " << crlFile << ' '
<< getSSLErrorMessage(ERR_get_error());
return false;
}
log() << "ssl imported " << status << " revoked certificate" << ((status == 1) ? "" : "s")
<< " from the revocation list.";
return true;
}
/*
* The interface layer between network and BIO-pair. The BIO-pair buffers
* the data to/from the TLS layer.
*/
void SSLManagerOpenSSL::_flushNetworkBIO(SSLConnectionOpenSSL* conn) {
char buffer[BUFFER_SIZE];
int wantWrite;
/*
* Write the complete contents of the buffer. Leaving the buffer
* unflushed could cause a deadlock.
*/
while ((wantWrite = BIO_ctrl_pending(conn->networkBIO)) > 0) {
if (wantWrite > BUFFER_SIZE) {
wantWrite = BUFFER_SIZE;
}
int fromBIO = BIO_read(conn->networkBIO, buffer, wantWrite);
int writePos = 0;
do {
int numWrite = fromBIO - writePos;
numWrite = send(conn->socket->rawFD(), buffer + writePos, numWrite, portSendFlags);
if (numWrite < 0) {
conn->socket->handleSendError(numWrite, "");
}
writePos += numWrite;
} while (writePos < fromBIO);
}
int wantRead;
while ((wantRead = BIO_ctrl_get_read_request(conn->networkBIO)) > 0) {
if (wantRead > BUFFER_SIZE) {
wantRead = BUFFER_SIZE;
}
int numRead = recv(conn->socket->rawFD(), buffer, wantRead, portRecvFlags);
if (numRead <= 0) {
conn->socket->handleRecvError(numRead, wantRead);
continue;
}
int toBIO = BIO_write(conn->networkBIO, buffer, numRead);
if (toBIO != numRead) {
LOG(3) << "Failed to write network data to the SSL BIO layer";
throwSocketError(SocketErrorKind::RECV_ERROR, conn->socket->remoteString());
}
}
}
bool SSLManagerOpenSSL::_doneWithSSLOp(SSLConnectionOpenSSL* conn, int status) {
int sslErr = SSL_get_error(conn->ssl, status);
switch (sslErr) {
case SSL_ERROR_NONE:
_flushNetworkBIO(conn); // success, flush network BIO before leaving
return true;
case SSL_ERROR_WANT_WRITE:
case SSL_ERROR_WANT_READ:
_flushNetworkBIO(conn); // not ready, flush network BIO and try again
return false;
default:
return true;
}
}
SSLConnectionInterface* SSLManagerOpenSSL::connect(Socket* socket) {
std::unique_ptr sslConn =
stdx::make_unique(_clientContext.get(), socket, (const char*)NULL, 0);
const auto undotted = removeFQDNRoot(socket->remoteAddr().hostOrIp());
int ret = ::SSL_set_tlsext_host_name(sslConn->ssl, undotted.c_str());
if (ret != 1)
_handleSSLError(sslConn.get(), ret);
do {
ret = ::SSL_connect(sslConn->ssl);
} while (!_doneWithSSLOp(sslConn.get(), ret));
if (ret != 1)
_handleSSLError(sslConn.get(), ret);
return sslConn.release();
}
SSLConnectionInterface* SSLManagerOpenSSL::accept(Socket* socket,
const char* initialBytes,
int len) {
std::unique_ptr sslConn =
stdx::make_unique(_serverContext.get(), socket, initialBytes, len);
int ret;
do {
ret = ::SSL_accept(sslConn->ssl);
} while (!_doneWithSSLOp(sslConn.get(), ret));
if (ret != 1)
_handleSSLError(sslConn.get(), ret);
return sslConn.release();
}
void recordTLSVersion(const SSL* conn) {
int protocol = SSL_version(conn);
auto& counts = mongo::TLSVersionCounts::get(getGlobalServiceContext());
switch (protocol) {
case TLS1_VERSION:
counts.tls10.addAndFetch(1);
break;
case TLS1_1_VERSION:
counts.tls11.addAndFetch(1);
break;
case TLS1_2_VERSION:
counts.tls12.addAndFetch(1);
break;
#ifdef TLS1_3_VERSION
case TLS1_3_VERSION:
counts.tls13.addAndFetch(1);
break;
#endif
default:
// Do nothing
break;
}
}
StatusWith> SSLManagerOpenSSL::parseAndValidatePeerCertificate(
SSL* conn, const std::string& remoteHost) {
recordTLSVersion(conn);
if (!_sslConfiguration.hasCA && isSSLServer)
return {boost::none};
X509* peerCert = SSL_get_peer_certificate(conn);
if (NULL == peerCert) { // no certificate presented by peer
if (_weakValidation) {
// do not give warning if certificate warnings are suppressed
if (!_suppressNoCertificateWarning) {
warning() << "no SSL certificate provided by peer";
}
return {boost::none};
} else {
auto msg = "no SSL certificate provided by peer; connection rejected";
error() << msg;
return Status(ErrorCodes::SSLHandshakeFailed, msg);
}
}
ON_BLOCK_EXIT(X509_free, peerCert);
long result = SSL_get_verify_result(conn);
if (result != X509_V_OK) {
if (_allowInvalidCertificates) {
warning() << "SSL peer certificate validation failed: "
<< X509_verify_cert_error_string(result);
return {boost::none};
} else {
str::stream msg;
msg << "SSL peer certificate validation failed: "
<< X509_verify_cert_error_string(result);
error() << msg.ss.str();
return Status(ErrorCodes::SSLHandshakeFailed, msg);
}
}
// TODO: check optional cipher restriction, using cert.
auto peerSubject = getCertificateSubjectX509Name(peerCert);
LOG(2) << "Accepted TLS connection from peer: " << peerSubject;
StatusWith> swPeerCertificateRoles = _parsePeerRoles(peerCert);
if (!swPeerCertificateRoles.isOK()) {
return swPeerCertificateRoles.getStatus();
}
// If this is an SSL client context (on a MongoDB server or client)
// perform hostname validation of the remote server
if (remoteHost.empty()) {
return boost::make_optional(
SSLPeerInfo(peerSubject, std::move(swPeerCertificateRoles.getValue())));
}
// This is to standardize the IPAddress format for comparison.
auto remoteHostName = remoteHost;
auto swCIDRRemoteHost = CIDR::parse(remoteHost);
if (swCIDRRemoteHost.isOK()) {
remoteHostName = swCIDRRemoteHost.getValue().toString();
}
// Try to match using the Subject Alternate Name, if it exists.
// RFC-2818 requires the Subject Alternate Name to be used if present.
// Otherwise, the most specific Common Name field in the subject field
// must be used.
bool sanMatch = false;
bool cnMatch = false;
StringBuilder certificateNames;
STACK_OF(GENERAL_NAME)* sanNames = static_cast(
X509_get_ext_d2i(peerCert, NID_subject_alt_name, NULL, NULL));
if (sanNames != NULL) {
int sanNamesList = sk_GENERAL_NAME_num(sanNames);
certificateNames << "SAN(s): ";
for (int i = 0; i < sanNamesList; i++) {
const GENERAL_NAME* currentName = sk_GENERAL_NAME_value(sanNames, i);
if (currentName && currentName->type == GEN_DNS) {
std::string dnsName (reinterpret_cast(ASN1_STRING_data(currentName->d.dNSName)));
auto swCIDRDNSName = CIDR::parse(dnsName);
if (swCIDRDNSName.isOK()) {
dnsName = swCIDRDNSName.getValue().toString();
warning() << "You have an IP Address in the DNS Name field on your certificate. We will not allow this in MongoDB version 4.2.";
}
if (hostNameMatchForX509Certificates(remoteHostName, dnsName)) {
sanMatch = true;
break;
}
certificateNames << std::string(dnsName) << ", ";
} else if (currentName && currentName -> type == GEN_IPADD) {
std::string ipAddress (reinterpret_cast(ASN1_STRING_data(currentName->d.iPAddress)));
auto swCIDRIPAddress = CIDR::parse(ipAddress);
if (swCIDRIPAddress.isOK()) {
ipAddress = swCIDRIPAddress.getValue().toString();
}
if (hostNameMatchForX509Certificates(remoteHostName, ipAddress)) {
sanMatch = true;
break;
}
certificateNames << std::string(ipAddress) << ", ";
}
}
sk_GENERAL_NAME_pop_free(sanNames, GENERAL_NAME_free);
} else {
// If Subject Alternate Name (SAN) doesn't exist and Common Name (CN) does,
// check Common Name.
auto swCN = peerSubject.getOID(kOID_CommonName);
if (swCN.isOK()) {
auto commonName = std::move(swCN.getValue());
if (hostNameMatchForX509Certificates(remoteHostName, commonName)) {
cnMatch = true;
}
certificateNames << "CN: " << commonName;
} else {
certificateNames << "No Common Name (CN) or Subject Alternate Names (SAN) found";
}
}
if (!sanMatch && !cnMatch) {
StringBuilder msgBuilder;
msgBuilder << "The server certificate does not match the host name. Hostname: "
<< remoteHost << " does not match " << certificateNames.str();
std::string msg = msgBuilder.str();
if (_allowInvalidCertificates || _allowInvalidHostnames || isUnixDomainSocket(remoteHost)) {
warning() << msg;
} else {
error() << msg;
return Status(ErrorCodes::SSLHandshakeFailed, msg);
}
}
return boost::make_optional(SSLPeerInfo(peerSubject, stdx::unordered_set()));
}
SSLPeerInfo SSLManagerOpenSSL::parseAndValidatePeerCertificateDeprecated(
const SSLConnectionInterface* connInterface, const std::string& remoteHost) {
const SSLConnectionOpenSSL* conn = checked_cast(connInterface);
auto swPeerSubjectName = parseAndValidatePeerCertificate(conn->ssl, remoteHost);
// We can't use uassertStatusOK here because we need to throw a NetworkException.
if (!swPeerSubjectName.isOK()) {
throwSocketError(SocketErrorKind::CONNECT_ERROR, swPeerSubjectName.getStatus().reason());
}
return swPeerSubjectName.getValue().get_value_or(SSLPeerInfo());
}
StatusWith> SSLManagerOpenSSL::_parsePeerRoles(X509* peerCert) const {
// exts is owned by the peerCert
const STACK_OF(X509_EXTENSION)* exts = X509_get0_extensions(peerCert);
int extCount = 0;
if (exts) {
extCount = sk_X509_EXTENSION_num(exts);
}
ASN1_OBJECT* rolesObj = OBJ_nid2obj(_rolesNid);
// Search all certificate extensions for our own
stdx::unordered_set roles;
for (int i = 0; i < extCount; i++) {
X509_EXTENSION* ex = sk_X509_EXTENSION_value(exts, i);
ASN1_OBJECT* obj = X509_EXTENSION_get_object(ex);
if (!OBJ_cmp(obj, rolesObj)) {
// We've found an extension which has our roles OID
ASN1_OCTET_STRING* data = X509_EXTENSION_get_data(ex);
return parsePeerRoles(
ConstDataRange(reinterpret_cast(data->data),
reinterpret_cast(data->data) + data->length));
}
}
return roles;
}
std::string SSLManagerInterface::getSSLErrorMessage(int code) {
// 120 from the SSL documentation for ERR_error_string
static const size_t msglen = 120;
char msg[msglen];
ERR_error_string_n(code, msg, msglen);
return msg;
}
void SSLManagerOpenSSL::_handleSSLError(SSLConnectionOpenSSL* conn, int ret) {
int code = SSL_get_error(conn->ssl, ret);
int err = ERR_get_error();
switch (code) {
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
// should not happen because we turned on AUTO_RETRY
// However, it turns out this CAN happen during a connect, if the other side
// accepts the socket connection but fails to do the SSL handshake in a timely
// manner.
error() << "SSL: " << code << ", possibly timed out during connect";
break;
case SSL_ERROR_ZERO_RETURN:
// TODO: Check if we can avoid throwing an exception for this condition
LOG(3) << "SSL network connection closed";
break;
case SSL_ERROR_SYSCALL:
// If ERR_get_error returned 0, the error queue is empty
// check the return value of the actual SSL operation
if (err != 0) {
error() << "SSL: " << getSSLErrorMessage(err);
} else if (ret == 0) {
error() << "Unexpected EOF encountered during SSL communication";
} else {
error() << "The SSL BIO reported an I/O error " << errnoWithDescription();
}
break;
case SSL_ERROR_SSL: {
error() << "SSL: " << getSSLErrorMessage(err);
break;
}
default:
error() << "unrecognized SSL error";
break;
}
_flushNetworkBIO(conn);
throwSocketError(SocketErrorKind::CONNECT_ERROR, "");
}
} // namespace mongo