diff options
author | Andreas Nilsson <andreas.nilsson@10gen.com> | 2013-11-12 19:02:22 +0000 |
---|---|---|
committer | Andreas Nilsson <andreas.nilsson@10gen.com> | 2013-11-13 08:50:07 +0000 |
commit | b5d36ec05cd4f22e02a8b4143954980946710648 (patch) | |
tree | 81d08173ff86c94d34ccb2fd4ddcbc86a98d2cce /src/mongo | |
parent | 45155a897e1060cffc83ac9f49d0d17e062d3d24 (diff) | |
download | mongo-b5d36ec05cd4f22e02a8b4143954980946710648.tar.gz |
SERVER-10330 SERVER-11195 SSL server hostname validation
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/client/dbclient.cpp | 2 | ||||
-rwxr-xr-x | src/mongo/shell/servers.js | 3 | ||||
-rw-r--r-- | src/mongo/shell/servers_misc.js | 2 | ||||
-rw-r--r-- | src/mongo/util/net/httpclient.cpp | 2 | ||||
-rw-r--r-- | src/mongo/util/net/message_port.h | 6 | ||||
-rw-r--r-- | src/mongo/util/net/sock.cpp | 6 | ||||
-rw-r--r-- | src/mongo/util/net/sock.h | 7 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_manager.cpp | 103 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_manager.h | 3 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_options.cpp | 18 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_options.h | 1 |
11 files changed, 134 insertions, 19 deletions
diff --git a/src/mongo/client/dbclient.cpp b/src/mongo/client/dbclient.cpp index 8eef6b0eb35..cce94871f39 100644 --- a/src/mongo/client/dbclient.cpp +++ b/src/mongo/client/dbclient.cpp @@ -911,7 +911,7 @@ namespace mongo { int sslModeVal = sslGlobalParams.sslMode.load(); if (sslModeVal == SSLGlobalParams::SSLMode_sendAcceptSSL || sslModeVal == SSLGlobalParams::SSLMode_sslOnly) { - return p->secure( sslManager() ); + return p->secure( sslManager(), _server.host() ); } #endif diff --git a/src/mongo/shell/servers.js b/src/mongo/shell/servers.js index 890cb2a5ad8..5c655538a73 100755 --- a/src/mongo/shell/servers.js +++ b/src/mongo/shell/servers.js @@ -422,6 +422,7 @@ MongoRunner.mongoOptions = function( opts ){ if (!opts.sslPEMKeyFile) opts.sslPEMKeyFile = "jstests/libs/server.pem"; if (!opts.sslCAFile) opts.sslCAFile = "jstests/libs/ca.pem"; opts.sslWeakCertificateValidation = ""; + opts.sslAllowInvalidCertificates = ""; } if ( jsTestOptions().useX509 && !opts.clusterAuthMode ) { @@ -486,6 +487,7 @@ MongoRunner.mongodOptions = function( opts ){ if (!opts.sslPEMKeyFile) opts.sslPEMKeyFile = "jstests/libs/server.pem"; if (!opts.sslCAFile) opts.sslCAFile = "jstests/libs/ca.pem"; opts.sslWeakCertificateValidation = ""; + opts.sslAllowInvalidCertificates = ""; } if ( jsTestOptions().useX509 && !opts.clusterAuthMode ) { @@ -720,6 +722,7 @@ startMongodTest = function (port, dirname, restart, extraOptions ) { if (!options["sslPEMKeyFile"]) options["sslPEMKeyFile"] = "jstests/libs/server.pem"; if (!options["sslCAFile"]) options["sslCAFile"] = "jstests/libs/ca.pem"; options["sslWeakCertificateValidation"] = ""; + options["sslAllowInvalidCertificates"] = ""; } if ( jsTestOptions().useX509 && !options["clusterAuthMode"] ) { diff --git a/src/mongo/shell/servers_misc.js b/src/mongo/shell/servers_misc.js index 5a09856896a..72a1c3eed4e 100644 --- a/src/mongo/shell/servers_misc.js +++ b/src/mongo/shell/servers_misc.js @@ -177,6 +177,7 @@ ReplTest.prototype.getOptions = function( master , extra , putBinaryFirst, norep a.push( "jstests/libs/ca.pem" ) } a.push( "--sslWeakCertificateValidation" ) + a.push( "--sslAllowInvalidCertificates" ) } if( jsTestOptions().useX509 && !a.contains("--clusterAuthMode")) { a.push( "--clusterAuthMode" ) @@ -315,6 +316,7 @@ function startParallelShell( jsCode, port ){ args.push( "jstests/libs/client.pem" ) args.push( "--sslCAFile" ) args.push( "jstests/libs/ca.pem" ) + args.push( "--sslAllowInvalidCertificates" ) } x = startMongoProgramNoConnect.apply(null, args); diff --git a/src/mongo/util/net/httpclient.cpp b/src/mongo/util/net/httpclient.cpp index 2fc41039936..77b6ca09659 100644 --- a/src/mongo/util/net/httpclient.cpp +++ b/src/mongo/util/net/httpclient.cpp @@ -108,7 +108,7 @@ namespace mongo { // pointer to global singleton instance SSLManagerInterface* mgr = getSSLManager(); - sock.secure(mgr); + sock.secure(mgr, ""); #else uasserted( 15862 , "no ssl support" ); #endif diff --git a/src/mongo/util/net/message_port.h b/src/mongo/util/net/message_port.h index 8949f8081fb..49d6eb819ba 100644 --- a/src/mongo/util/net/message_port.h +++ b/src/mongo/util/net/message_port.h @@ -125,9 +125,11 @@ namespace mongo { * Initiates the TLS/SSL handshake on this MessagingPort. * When this function returns, further communication on this * MessagingPort will be encrypted. + * ssl - Pointer to the global SSLManager. + * remoteHost - The hostname of the remote server. */ - bool secure( SSLManagerInterface* ssl ) { - return psock->secure( ssl ); + bool secure( SSLManagerInterface* ssl, const std::string& remoteHost ) { + return psock->secure( ssl, remoteHost ); } #endif diff --git a/src/mongo/util/net/sock.cpp b/src/mongo/util/net/sock.cpp index 3c182be9110..95c27c594e2 100644 --- a/src/mongo/util/net/sock.cpp +++ b/src/mongo/util/net/sock.cpp @@ -458,14 +458,14 @@ namespace mongo { } #ifdef MONGO_SSL - bool Socket::secure(SSLManagerInterface* mgr) { + bool Socket::secure(SSLManagerInterface* mgr, const std::string& remoteHost) { fassert(16503, mgr); if ( _fd < 0 ) { return false; } _sslManager = mgr; _sslConnection.reset(_sslManager->connect(this)); - mgr->validatePeerCertificate(_sslConnection.get()); + mgr->parseAndValidatePeerCertificate(_sslConnection.get(), remoteHost); return true; } @@ -482,7 +482,7 @@ namespace mongo { remoteString()); } _sslConnection.reset(_sslManager->accept(this, firstBytes, len)); - return _sslManager->validatePeerCertificate(_sslConnection.get()); + return _sslManager->parseAndValidatePeerCertificate(_sslConnection.get(), ""); } #endif diff --git a/src/mongo/util/net/sock.h b/src/mongo/util/net/sock.h index 123ad44f9d4..252b615b2ac 100644 --- a/src/mongo/util/net/sock.h +++ b/src/mongo/util/net/sock.h @@ -237,8 +237,11 @@ namespace mongo { } #ifdef MONGO_SSL - /** secures inline */ - bool secure( SSLManagerInterface* ssl ); + /** secures inline + * ssl - Pointer to the global SSLManager. + * remoteHost - The hostname of the remote server. + */ + bool secure( SSLManagerInterface* ssl, const std::string& remoteHost); void secureAccepted( SSLManagerInterface* ssl ); #endif diff --git a/src/mongo/util/net/ssl_manager.cpp b/src/mongo/util/net/ssl_manager.cpp index 306213db045..cdc883fe39a 100644 --- a/src/mongo/util/net/ssl_manager.cpp +++ b/src/mongo/util/net/ssl_manager.cpp @@ -32,6 +32,7 @@ #ifdef MONGO_SSL #include <openssl/evp.h> +#include <openssl/x509v3.h> #endif namespace mongo { @@ -134,6 +135,7 @@ namespace mongo { const std::string& cafile = "", const std::string& crlfile = "", bool weakCertificateValidation = false, + bool allowInvalidCertificates = false, bool fipsMode = false) : pemfile(pemfile), pempwd(pempwd), @@ -142,6 +144,7 @@ namespace mongo { cafile(cafile), crlfile(crlfile), weakCertificateValidation(weakCertificateValidation), + allowInvalidCertificates(allowInvalidCertificates), fipsMode(fipsMode) {}; std::string pemfile; @@ -151,6 +154,7 @@ namespace mongo { std::string cafile; std::string crlfile; bool weakCertificateValidation; + bool allowInvalidCertificates; bool fipsMode; }; @@ -164,7 +168,8 @@ namespace mongo { virtual SSLConnection* accept(Socket* socket, const char* initialBytes, int len); - virtual std::string validatePeerCertificate(const SSLConnection* conn); + virtual std::string parseAndValidatePeerCertificate(const SSLConnection* conn, + const std::string& remoteHost); virtual void cleanupThreadLocals(); @@ -198,6 +203,7 @@ namespace mongo { std::string _password; bool _validateCertificates; bool _weakValidation; + bool _allowInvalidCertificates; std::string _serverSubjectName; std::string _clientSubjectName; @@ -255,6 +261,11 @@ namespace mongo { */ 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 */ @@ -279,6 +290,7 @@ namespace mongo { sslGlobalParams.sslCAFile, sslGlobalParams.sslCRLFile, sslGlobalParams.sslWeakCertificateValidation, + sslGlobalParams.sslAllowInvalidCertificates, sslGlobalParams.sslFIPSMode); theSSLManager = new SSLManager(params, isSSLServer); } @@ -354,7 +366,8 @@ namespace mongo { SSLManager::SSLManager(const Params& params, bool isServer) : _validateCertificates(false), - _weakValidation(params.weakCertificateValidation) { + _weakValidation(params.weakCertificateValidation), + _allowInvalidCertificates(params.allowInvalidCertificates) { SSL_library_init(); SSL_load_error_strings(); @@ -753,7 +766,27 @@ namespace mongo { return sslConn; } - std::string SSLManager::validatePeerCertificate(const SSLConnection* conn) { + // 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 '.' + 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 (!_validateCertificates) return ""; X509* peerCert = SSL_get_peer_certificate(conn->ssl); @@ -773,13 +806,69 @@ namespace mongo { long result = SSL_get_verify_result(conn->ssl); if (result != X509_V_OK) { - error() << "SSL peer certificate validation failed:" << - X509_verify_cert_error_string(result) << endl; - throw SocketException(SocketException::CONNECT_ERROR, ""); + 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. - return getCertificateSubjectName(peerCert); + 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; + } + + 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())) { + return peerSubjectName; + } + + // If Common Name (CN) didn't match, check Subject Alternate Name (SAN) + stack_st_GENERAL_NAME *sanNames = static_cast<stack_st_GENERAL_NAME*> + (X509_get_ext_d2i(peerCert, NID_subject_alt_name, NULL, NULL)); + + bool sanMatch = false; + 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<char *>(ASN1_STRING_data(currentName->d.dNSName)); + if (_hostNameMatch(remoteHost.c_str(), dnsName)) { + sanMatch = true; + break; + } + } + } + } + sk_GENERAL_NAME_pop_free(sanNames, GENERAL_NAME_free); + + if (!sanMatch) { + if (_allowInvalidCertificates) { + 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() { diff --git a/src/mongo/util/net/ssl_manager.h b/src/mongo/util/net/ssl_manager.h index d39ca78512b..a4e94bc5f23 100644 --- a/src/mongo/util/net/ssl_manager.h +++ b/src/mongo/util/net/ssl_manager.h @@ -71,7 +71,8 @@ namespace mongo { * Throws SocketException on failure * @return a std::string containing the certificate's subject name. */ - virtual std::string validatePeerCertificate(const SSLConnection* conn) = 0; + virtual std::string parseAndValidatePeerCertificate(const SSLConnection* conn, + const std::string& remoteHost) = 0; /** * Cleans up SSL thread local memory; use at thread exit diff --git a/src/mongo/util/net/ssl_options.cpp b/src/mongo/util/net/ssl_options.cpp index 47994dc6511..a52768a874b 100644 --- a/src/mongo/util/net/ssl_options.cpp +++ b/src/mongo/util/net/ssl_options.cpp @@ -53,10 +53,12 @@ namespace mongo { options->addOptionChaining("ssl.weakCertificateValidation", "sslWeakCertificateValidation", moe::Switch, "allow client to connect without presenting a certificate"); + options->addOptionChaining("ssl.allowInvalidCertificates", "sslAllowInvalidCertificates", + moe::Switch, "allow connections to servers with invalid certificates"); + options->addOptionChaining("ssl.FIPSMode", "sslFIPSMode", moe::Switch, "activate FIPS 140-2 mode at startup"); - return Status::OK(); } @@ -75,10 +77,12 @@ namespace mongo { options->addOptionChaining("ssl.CRLFile", "sslCRLFile", moe::String, "Certificate Revocation List file for SSL"); + options->addOptionChaining("ssl.allowInvalidCertificates", "sslAllowInvalidCertificates", + moe::Switch, "allow connections to servers with invalid certificates"); + options->addOptionChaining("ssl.FIPSMode", "sslFIPSMode", moe::Switch, "activate FIPS 140-2 mode at startup"); - return Status::OK(); } @@ -135,6 +139,9 @@ namespace mongo { if (params.count("ssl.weakCertificateValidation")) { sslGlobalParams.sslWeakCertificateValidation = true; } + if (params.count("ssl.allowInvalidCertificates")) { + sslGlobalParams.sslAllowInvalidCertificates = true; + } if (params.count("ssl.sslOnNormalPorts")) { if (params.count("ssl.mode")) { return Status(ErrorCodes::BadValue, @@ -162,6 +169,10 @@ namespace mongo { if (params.count("ssl.FIPSMode")) { sslGlobalParams.sslFIPSMode = true; } + if (sslGlobalParams.sslCAFile.empty()) { + warning() << "No SSL certificate validation can be performed since no CA file " + "has been provided; please specify an sslCAFile parameter"; + } } else if (sslGlobalParams.sslPEMKeyFile.size() || sslGlobalParams.sslPEMKeyPassword.size() || @@ -208,6 +219,9 @@ namespace mongo { if (params.count("ssl.CRLFile")) { sslGlobalParams.sslCRLFile = params["ssl.CRLFile"].as<std::string>(); } + if (params.count("ssl.allowInvalidCertificates")) { + sslGlobalParams.sslAllowInvalidCertificates = true; + } if (params.count("ssl.FIPSMode")) { sslGlobalParams.sslFIPSMode = true; } diff --git a/src/mongo/util/net/ssl_options.h b/src/mongo/util/net/ssl_options.h index 9bdbc9cd4ca..e4a46f27c9f 100644 --- a/src/mongo/util/net/ssl_options.h +++ b/src/mongo/util/net/ssl_options.h @@ -38,6 +38,7 @@ namespace mongo { std::string sslCRLFile; // --sslCRLFile bool sslWeakCertificateValidation; // --sslWeakCertificateValidation bool sslFIPSMode; // --sslFIPSMode + bool sslAllowInvalidCertificates; // --sslIgnoreCertificateValidation SSLGlobalParams() { sslMode.store(SSLMode_noSSL); |