summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorAndreas Nilsson <andreas.nilsson@10gen.com>2013-11-12 19:02:22 +0000
committerAndreas Nilsson <andreas.nilsson@10gen.com>2013-11-13 08:50:07 +0000
commitb5d36ec05cd4f22e02a8b4143954980946710648 (patch)
tree81d08173ff86c94d34ccb2fd4ddcbc86a98d2cce /src/mongo
parent45155a897e1060cffc83ac9f49d0d17e062d3d24 (diff)
downloadmongo-b5d36ec05cd4f22e02a8b4143954980946710648.tar.gz
SERVER-10330 SERVER-11195 SSL server hostname validation
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/client/dbclient.cpp2
-rwxr-xr-xsrc/mongo/shell/servers.js3
-rw-r--r--src/mongo/shell/servers_misc.js2
-rw-r--r--src/mongo/util/net/httpclient.cpp2
-rw-r--r--src/mongo/util/net/message_port.h6
-rw-r--r--src/mongo/util/net/sock.cpp6
-rw-r--r--src/mongo/util/net/sock.h7
-rw-r--r--src/mongo/util/net/ssl_manager.cpp103
-rw-r--r--src/mongo/util/net/ssl_manager.h3
-rw-r--r--src/mongo/util/net/ssl_options.cpp18
-rw-r--r--src/mongo/util/net/ssl_options.h1
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);