/* Copyright 2009 10gen 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 "mongo/base/init.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/config.h"
#include "mongo/platform/atomic_word.h"
#include "mongo/stdx/memory.h"
#include "mongo/util/concurrency/mutex.h"
#include "mongo/util/exit.h"
#include "mongo/util/debug_util.h"
#include "mongo/util/log.h"
#include "mongo/util/mongoutils/str.h"
#include "mongo/util/net/sock.h"
#include "mongo/util/net/ssl_expiration.h"
#include "mongo/util/net/ssl_options.h"
#include "mongo/util/scopeguard.h"
#ifdef MONGO_CONFIG_SSL
#include
#include
#endif
using std::endl;
namespace mongo {
SSLParams sslGlobalParams;
#ifdef MONGO_CONFIG_SSL
// 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
namespace {
/**
* Multithreaded Support for SSL.
*
* In order to allow OpenSSL to work in a multithreaded environment, you
* must provide some callbacks for it to use for locking. The following code
* sets up a vector of mutexes and uses thread-local storage to assign an id
* to each thread.
* The so-called SSLThreadInfo class encapsulates most of the logic required for
* OpenSSL multithreaded support.
*/
unsigned long _ssl_id_callback();
void _ssl_locking_callback(int mode, int type, const char *file, int line);
class SSLThreadInfo {
public:
SSLThreadInfo() {
_id = _next.fetchAndAdd(1);
}
~SSLThreadInfo() {
}
unsigned long id() const { return _id; }
void lock_callback( int mode, int type, const char *file, int line ) {
if ( mode & CRYPTO_LOCK ) {
_mutex[type]->lock();
}
else {
_mutex[type]->unlock();
}
}
static void init() {
while ( (int)_mutex.size() < CRYPTO_num_locks() )
_mutex.push_back( new boost::recursive_mutex );
}
static SSLThreadInfo* get() {
SSLThreadInfo* me = _thread.get();
if ( ! me ) {
me = new SSLThreadInfo();
_thread.reset( me );
}
return me;
}
private:
unsigned _id;
static AtomicUInt32 _next;
// 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;
static boost::thread_specific_ptr _thread;
};
unsigned long _ssl_id_callback() {
return SSLThreadInfo::get()->id();
}
void _ssl_locking_callback(int mode, int type, const char *file, int line) {
SSLThreadInfo::get()->lock_callback( mode , type , file , line );
}
AtomicUInt32 SSLThreadInfo::_next;
std::vector SSLThreadInfo::_mutex;
boost::thread_specific_ptr SSLThreadInfo::_thread;
////////////////////////////////////////////////////////////////
SimpleMutex sslManagerMtx("SSL Manager");
SSLManagerInterface* theSSLManager = NULL;
static const int BUFFER_SIZE = 8*1024;
static const int DATE_LEN = 128;
class SSLManager : public SSLManagerInterface {
public:
explicit SSLManager(const SSLParams& params, bool isServer);
virtual ~SSLManager();
virtual SSLConnection* connect(Socket* socket);
virtual SSLConnection* accept(Socket* socket, const char* initialBytes, int len);
virtual std::string parseAndValidatePeerCertificate(const SSLConnection* conn,
const std::string& remoteHost);
virtual void cleanupThreadLocals();
virtual const SSLConfiguration& getSSLConfiguration() const {
return _sslConfiguration;
}
virtual std::string getSSLErrorMessage(int code);
virtual int SSL_read(SSLConnection* conn, void* buf, int num);
virtual int SSL_write(SSLConnection* conn, const void* buf, int num);
virtual unsigned long ERR_get_error();
virtual char* ERR_error_string(unsigned long e, char* buf);
virtual int SSL_get_error(const SSLConnection* conn, int ret);
virtual int SSL_shutdown(SSLConnection* conn);
virtual void SSL_free(SSLConnection* conn);
private:
SSL_CTX* _serverContext; // SSL context for incoming connections
SSL_CTX* _clientContext; // SSL context for outgoing connections
std::string _password;
bool _weakValidation;
bool _allowInvalidCertificates;
bool _allowInvalidHostnames;
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 SocketException
*/
MONGO_COMPILER_NORETURN void _handleSSLError(int code, int ret);
/*
* Init the SSL context using parameters provided in params.
*/
bool _initSSLContext(SSL_CTX** context, const SSLParams& params);
/*
* 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,
std::string* subjectName,
Date_t* serverNotAfter);
/** @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
*/
bool _setupCA(SSL_CTX* context, const std::string& caFile);
/*
* Import a certificate revocation list into an SSL context
* for use with validating certificates
*/
bool _setupCRL(SSL_CTX* context, const std::string& crlFile);
/*
* Activate FIPS 140-2 mode, if the server started with a command line
* parameter.
*/
void _setupFIPS();
/*
* sub function for checking the result of an SSL operation
*/
bool _doneWithSSLOp(SSLConnection* conn, int status);
/*
* Send and receive network data
*/
void _flushNetworkBIO(SSLConnection* conn);
/*
* match a remote host name to an x.509 host name
*/
bool _hostNameMatch(const char* nameToMatch, const char* certHostName);
/**
* 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);
};
} // namespace
// Global variable indicating if this is a server or a client instance
bool isSSLServer = false;
MONGO_INITIALIZER(SSLManager)(InitializerContext* context) {
SimpleMutex::scoped_lock lck(sslManagerMtx);
if (sslGlobalParams.sslMode.load() != SSLParams::SSLMode_disabled) {
theSSLManager = new SSLManager(sslGlobalParams, isSSLServer);
}
return Status::OK();
}
SSLManagerInterface* getSSLManager() {
SimpleMutex::scoped_lock lck(sslManagerMtx);
if (theSSLManager)
return theSSLManager;
return NULL;
}
std::string getCertificateSubjectName(X509* cert) {
std::string result;
BIO* out = BIO_new(BIO_s_mem());
uassert(16884, "unable to allocate BIO memory", NULL != out);
ON_BLOCK_EXIT(BIO_free, out);
if (X509_NAME_print_ex(out,
X509_get_subject_name(cert),
0,
XN_FLAG_RFC2253) >= 0) {
if (BIO_number_written(out) > 0) {
result.resize(BIO_number_written(out));
BIO_read(out, &result[0], result.size());
}
}
else {
log() << "failed to convert subject name to RFC2253 format" << endl;
}
return result;
}
SSLConnection::SSLConnection(SSL_CTX* context,
Socket* sock,
const char* initialBytes,
int len) : socket(sock) {
// This just ensures that SSL multithreading support is set up for this thread,
// if it's not already.
SSLThreadInfo::get();
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";
throw SocketException(SocketException::RECV_ERROR , socket->remoteString());
}
}
}
SSLConnection::~SSLConnection() {
if (ssl) { // The internalBIO is automatically freed as part of SSL_free
SSL_free(ssl);
}
if (networkBIO) {
BIO_free(networkBIO);
}
}
BSONObj SSLConfiguration::getServerStatusBSON() const {
BSONObjBuilder security;
security.append("SSLServerSubjectName",
serverSubjectName);
security.appendBool("SSLServerHasCertificateAuthority",
hasCA);
security.appendDate("SSLServerCertificateExpirationDate",
serverCertificateExpirationDate);
return security.obj();
}
SSLManagerInterface::~SSLManagerInterface() {}
SSLManager::SSLManager(const SSLParams& params, bool isServer) :
_serverContext(NULL),
_clientContext(NULL),
_weakValidation(params.sslWeakCertificateValidation),
_allowInvalidCertificates(params.sslAllowInvalidCertificates),
_allowInvalidHostnames(params.sslAllowInvalidHostnames) {
SSL_library_init();
SSL_load_error_strings();
ERR_load_crypto_strings();
if (params.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
CRYPTO_set_id_callback(_ssl_id_callback);
CRYPTO_set_locking_callback(_ssl_locking_callback);
SSLThreadInfo::init();
SSLThreadInfo::get();
if (!_initSSLContext(&_clientContext, params)) {
uasserted(16768, "ssl initialization problem");
}
// pick the certificate for use in outgoing connections,
std::string clientPEM;
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;
}
else {
// We are a server with a cluster key, so use the cluster key file
clientPEM = params.sslClusterFile;
}
if (!clientPEM.empty()) {
if (!_parseAndValidateCertificate(clientPEM,
&_sslConfiguration.clientSubjectName, NULL)) {
uasserted(16941, "ssl initialization problem");
}
}
// SSL server specific initialization
if (isServer) {
if (!_initSSLContext(&_serverContext, params)) {
uasserted(16562, "ssl initialization problem");
}
if (!_parseAndValidateCertificate(params.sslPEMKeyFile,
&_sslConfiguration.serverSubjectName,
&_sslConfiguration.serverCertificateExpirationDate)) {
uasserted(16942, "ssl initialization problem");
}
static CertificateExpirationMonitor task =
CertificateExpirationMonitor(_sslConfiguration.serverCertificateExpirationDate);
}
}
SSLManager::~SSLManager() {
CRYPTO_set_id_callback(0);
ERR_free_strings();
EVP_cleanup();
if (NULL != _serverContext) {
SSL_CTX_free(_serverContext);
}
if (NULL != _clientContext) {
SSL_CTX_free(_clientContext);
}
}
int SSLManager::password_cb(char *buf,int num, int rwflag,void *userdata) {
// Unless OpenSSL misbehaves, num should always be positive
fassert(17314, num > 0);
SSLManager* sm = static_cast(userdata);
const size_t copied = sm->_password.copy(buf, num - 1);
buf[copied] = '\0';
return copied;
}
int SSLManager::verify_cb(int ok, X509_STORE_CTX *ctx) {
return 1; // always succeed; we will catch the error in our get_verify_result() call
}
int SSLManager::SSL_read(SSLConnection* conn, void* buf, int num) {
int status;
do {
status = ::SSL_read(conn->ssl, buf, num);
} while(!_doneWithSSLOp(conn, status));
if (status <= 0)
_handleSSLError(SSL_get_error(conn, status), status);
return status;
}
int SSLManager::SSL_write(SSLConnection* conn, const void* buf, int num) {
int status;
do {
status = ::SSL_write(conn->ssl, buf, num);
} while(!_doneWithSSLOp(conn, status));
if (status <= 0)
_handleSSLError(SSL_get_error(conn, status), status);
return status;
}
unsigned long SSLManager::ERR_get_error() {
return ::ERR_get_error();
}
char* SSLManager::ERR_error_string(unsigned long e, char* buf) {
return ::ERR_error_string(e, buf);
}
int SSLManager::SSL_get_error(const SSLConnection* conn, int ret) {
return ::SSL_get_error(conn->ssl, ret);
}
int SSLManager::SSL_shutdown(SSLConnection* conn) {
int status;
do {
status = ::SSL_shutdown(conn->ssl);
} while(!_doneWithSSLOp(conn, status));
if (status < 0)
_handleSSLError(SSL_get_error(conn, status), status);
return status;
}
void SSLManager::SSL_free(SSLConnection* conn) {
return ::SSL_free(conn->ssl);
}
void SSLManager::_setupFIPS() {
// Turn on FIPS mode if requested.
// OPENSSL_FIPS must be defined by the OpenSSL headers, plus MONGO_CONFIG_SSL_FIPS
// must be defined via a MongoDB build flag.
#if defined(OPENSSL_FIPS) && defined(MONGO_CONFIG_SSL_FIPS)
int status = FIPS_mode_set(1);
if (!status) {
severe() << "can't activate FIPS mode: " <<
getSSLErrorMessage(ERR_get_error()) << endl;
fassertFailedNoTrace(16703);
}
log() << "FIPS 140-2 mode activated" << endl;
#else
severe() << "this version of mongodb was not compiled with FIPS support";
fassertFailedNoTrace(17089);
#endif
}
bool SSLManager::_initSSLContext(SSL_CTX** context, const SSLParams& params) {
*context = SSL_CTX_new(SSLv23_method());
massert(15864,
mongoutils::str::stream() << "can't create SSL Context: " <<
getSSLErrorMessage(ERR_get_error()),
context);
// 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.
if (!params.sslDisabledProtocols.empty()) {
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;
}
massert(28615, mongoutils::str::stream() << "can't set supported cipher suites: " <<
getSSLErrorMessage(ERR_get_error()),
SSL_CTX_set_cipher_list(*context, cipherConfig.c_str()));
// 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(*context, SSL_MODE_AUTO_RETRY);
massert(28607,
mongoutils::str::stream() << "can't store ssl session id context: " <<
getSSLErrorMessage(ERR_get_error()),
SSL_CTX_set_session_id_context(
*context,
static_cast(static_cast(context)),
sizeof(*context)));
// Use the clusterfile for internal outgoing SSL connections if specified
if (context == &_clientContext && !params.sslClusterFile.empty()) {
EVP_set_pw_prompt("Enter cluster certificate passphrase");
if (!_setupPEM(*context, params.sslClusterFile, params.sslClusterPassword)) {
return false;
}
}
// Use the pemfile for everything else
else if (!params.sslPEMKeyFile.empty()) {
EVP_set_pw_prompt("Enter PEM passphrase");
if (!_setupPEM(*context, params.sslPEMKeyFile, params.sslPEMKeyPassword)) {
return false;
}
}
if (!params.sslCAFile.empty()) {
// Set up certificate validation with a certificate authority
if (!_setupCA(*context, params.sslCAFile)) {
return false;
}
}
if (!params.sslCRLFile.empty()) {
if (!_setupCRL(*context, params.sslCRLFile)) {
return false;
}
}
return true;
}
unsigned long long SSLManager::_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 SSLManager::_parseAndValidateCertificate(const std::string& keyFile,
std::string* subjectName,
Date_t* serverCertificateExpirationDate) {
BIO *inBIO = BIO_new(BIO_s_file_internal());
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;
}
X509* x509 = PEM_read_bio_X509(inBIO, NULL, &SSLManager::password_cb, this);
if (x509 == NULL) {
error() << "cannot retrieve certificate from keyfile: "
<< keyFile << ' ' << getSSLErrorMessage(ERR_get_error());
return false;
}
ON_BLOCK_EXIT(X509_free, x509);
*subjectName = getCertificateSubjectName(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 SSLManager::_setupPEM(SSL_CTX* context,
const std::string& keyFile,
const std::string& password) {
_password = password;
if ( SSL_CTX_use_certificate_chain_file( context , keyFile.c_str() ) != 1 ) {
error() << "cannot read certificate file: " << keyFile << ' ' <<
getSSLErrorMessage(ERR_get_error()) << endl;
return false;
}
// If password is empty, use default OpenSSL callback, which uses the terminal
// to securely request the password interactively from the user.
if (!password.empty()) {
SSL_CTX_set_default_passwd_cb_userdata( context , this );
SSL_CTX_set_default_passwd_cb( context, &SSLManager::password_cb );
}
if ( SSL_CTX_use_PrivateKey_file( context , keyFile.c_str() , SSL_FILETYPE_PEM ) != 1 ) {
error() << "cannot read PEM key file: " << keyFile << ' ' <<
getSSLErrorMessage(ERR_get_error()) << endl;
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())
<< endl;
return false;
}
return true;
}
bool SSLManager::_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) {
error() << "cannot read certificate authority file: " << caFile << " " <<
getSSLErrorMessage(ERR_get_error()) << endl;
return false;
}
SSL_CTX_set_client_CA_list(context, certNames);
// Load trusted CA
if (SSL_CTX_load_verify_locations(context, caFile.c_str(), NULL) != 1) {
error() << "cannot read certificate authority file: " << caFile << " " <<
getSSLErrorMessage(ERR_get_error()) << endl;
return false;
}
// Set SSL to require peer (client) certificate verification
// if a certificate is presented
SSL_CTX_set_verify(context, SSL_VERIFY_PEER, &SSLManager::verify_cb);
_sslConfiguration.hasCA = true;
return true;
}
bool SSLManager::_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()) << endl;
return false;
}
log() << "ssl imported " << status << " revoked certificate" <<
((status == 1) ? "" : "s") << " from the revocation list." <<
endl;
return true;
}
/*
* The interface layer between network and BIO-pair. The BIO-pair buffers
* the data to/from the TLS layer.
*/
void SSLManager::_flushNetworkBIO(SSLConnection* 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";
throw SocketException(SocketException::RECV_ERROR , conn->socket->remoteString());
}
}
}
bool SSLManager::_doneWithSSLOp(SSLConnection* conn, int status) {
int sslErr = SSL_get_error(conn, 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;
}
}
SSLConnection* SSLManager::connect(Socket* socket) {
std::unique_ptr sslConn = stdx::make_unique(_clientContext, socket, (const char*)NULL, 0);
int ret;
do {
ret = ::SSL_connect(sslConn->ssl);
} while(!_doneWithSSLOp(sslConn.get(), ret));
if (ret != 1)
_handleSSLError(SSL_get_error(sslConn.get(), ret), ret);
return sslConn.release();
}
SSLConnection* SSLManager::accept(Socket* socket, const char* initialBytes, int len) {
std::unique_ptr sslConn = stdx::make_unique(_serverContext, socket, initialBytes, len);
int ret;
do {
ret = ::SSL_accept(sslConn->ssl);
} while(!_doneWithSSLOp(sslConn.get(), ret));
if (ret != 1)
_handleSSLError(SSL_get_error(sslConn.get(), ret), ret);
return sslConn.release();
}
// TODO SERVER-11601 Use NFC Unicode canonicalization
bool SSLManager::_hostNameMatch(const char* nameToMatch,
const char* certHostName) {
if (strlen(certHostName) < 2) {
return false;
}
// match wildcard DNS names
if (certHostName[0] == '*' && certHostName[1] == '.') {
// allow name.example.com if the cert is *.example.com, '*' does not match '.'
const char* subName = strchr(nameToMatch, '.');
return subName && !strcasecmp(certHostName+1, subName);
}
else {
return !strcasecmp(nameToMatch, certHostName);
}
}
std::string SSLManager::parseAndValidatePeerCertificate(const SSLConnection* conn,
const std::string& remoteHost) {
// only set if a CA cert has been provided
if (!_sslConfiguration.hasCA) return "";
X509* peerCert = SSL_get_peer_certificate(conn->ssl);
if (NULL == peerCert) { // no certificate presented by peer
if (_weakValidation) {
warning() << "no SSL certificate provided by peer" << endl;
}
else {
error() << "no SSL certificate provided by peer; connection rejected" << endl;
throw SocketException(SocketException::CONNECT_ERROR, "");
}
return "";
}
ON_BLOCK_EXIT(X509_free, peerCert);
long result = SSL_get_verify_result(conn->ssl);
if (result != X509_V_OK) {
if (_allowInvalidCertificates) {
warning() << "SSL peer certificate validation failed:" <<
X509_verify_cert_error_string(result);
}
else {
error() << "SSL peer certificate validation failed:" <<
X509_verify_cert_error_string(result);
throw SocketException(SocketException::CONNECT_ERROR, "");
}
}
// TODO: check optional cipher restriction, using cert.
std::string peerSubjectName = getCertificateSubjectName(peerCert);
// If this is an SSL client context (on a MongoDB server or client)
// perform hostname validation of the remote server
if (remoteHost.empty()) {
return peerSubjectName;
}
// 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;
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);
for (int i = 0; i < sanNamesList; i++) {
const GENERAL_NAME* currentName = sk_GENERAL_NAME_value(sanNames, i);
if (currentName && currentName->type == GEN_DNS) {
char *dnsName =
reinterpret_cast(ASN1_STRING_data(currentName->d.dNSName));
if (_hostNameMatch(remoteHost.c_str(), dnsName)) {
sanMatch = true;
break;
}
}
}
sk_GENERAL_NAME_pop_free(sanNames, GENERAL_NAME_free);
}
else {
// If Subject Alternate Name (SAN) didn't exist, check Common Name (CN).
int cnBegin = peerSubjectName.find("CN=") + 3;
int cnEnd = peerSubjectName.find(",", cnBegin);
std::string commonName = peerSubjectName.substr(cnBegin, cnEnd-cnBegin);
if (_hostNameMatch(remoteHost.c_str(), commonName.c_str())) {
cnMatch = true;
}
}
if (!sanMatch && !cnMatch) {
if (_allowInvalidCertificates || _allowInvalidHostnames) {
warning() << "The server certificate does not match the host name " <<
remoteHost;
}
else {
error() << "The server certificate does not match the host name " <<
remoteHost;
throw SocketException(SocketException::CONNECT_ERROR, "");
}
}
return peerSubjectName;
}
void SSLManager::cleanupThreadLocals() {
ERR_remove_state(0);
}
std::string SSLManager::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 SSLManager::_handleSSLError(int code, int 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;
}
throw SocketException(SocketException::CONNECT_ERROR, "");
}
#endif // #ifdef MONGO_CONFIG_SSL
}