summaryrefslogtreecommitdiff
path: root/src/mongo/client
diff options
context:
space:
mode:
authorSpencer Jackson <spencer.jackson@mongodb.com>2016-12-07 16:11:57 -0500
committerSpencer Jackson <spencer.jackson@mongodb.com>2017-07-11 16:20:07 -0400
commit16e83332ed20e4054324a1a7714506e74eed5180 (patch)
treeb0c03f332f37dc2ca5530a0bc6aa592cc3fbcb40 /src/mongo/client
parent78800570b838dd7e2bb53939d9d1a752d71e5ae3 (diff)
downloadmongo-16e83332ed20e4054324a1a7714506e74eed5180.tar.gz
SERVER-26952: Cache SCRAM-SHA-1 ClientKey
(cherry picked from commit 47da0b53f9cd27aeec1d2822780784866269a47d)
Diffstat (limited to 'src/mongo/client')
-rw-r--r--src/mongo/client/SConscript1
-rw-r--r--src/mongo/client/authenticate.cpp6
-rw-r--r--src/mongo/client/authenticate.h2
-rw-r--r--src/mongo/client/authenticate_test.cpp8
-rw-r--r--src/mongo/client/dbclient.cpp2
-rw-r--r--src/mongo/client/native_sasl_client_session.cpp6
-rw-r--r--src/mongo/client/sasl_client_authenticate.cpp2
-rw-r--r--src/mongo/client/sasl_client_authenticate.h2
-rw-r--r--src/mongo/client/sasl_client_authenticate_impl.cpp7
-rw-r--r--src/mongo/client/sasl_client_session.h1
-rw-r--r--src/mongo/client/sasl_scramsha1_client_conversation.cpp48
-rw-r--r--src/mongo/client/sasl_scramsha1_client_conversation.h12
-rw-r--r--src/mongo/client/scram_sha1_client_cache.cpp70
-rw-r--r--src/mongo/client/scram_sha1_client_cache.h88
14 files changed, 220 insertions, 35 deletions
diff --git a/src/mongo/client/SConscript b/src/mongo/client/SConscript
index d112f34fe14..a8a5367e2fe 100644
--- a/src/mongo/client/SConscript
+++ b/src/mongo/client/SConscript
@@ -60,6 +60,7 @@ saslClientSource = [
'sasl_client_session.cpp',
'sasl_plain_client_conversation.cpp',
'sasl_scramsha1_client_conversation.cpp',
+ 'scram_sha1_client_cache.cpp',
]
# Add in actual sasl dependencies if sasl is enabled, otherwise
diff --git a/src/mongo/client/authenticate.cpp b/src/mongo/client/authenticate.cpp
index 58002af5607..6db235f552b 100644
--- a/src/mongo/client/authenticate.cpp
+++ b/src/mongo/client/authenticate.cpp
@@ -237,7 +237,7 @@ void authX509(RunCommandHook runCommand,
void auth(RunCommandHook runCommand,
const BSONObj& params,
- StringData hostname,
+ const HostAndPort& hostname,
StringData clientName,
AuthCompletionHandler handler) {
std::string mechanism;
@@ -273,7 +273,7 @@ bool needsFallback(const AuthResponse& response) {
void asyncAuth(RunCommandHook runCommand,
const BSONObj& params,
- StringData hostname,
+ const HostAndPort& hostname,
StringData clientName,
AuthCompletionHandler handler) {
auth(runCommand,
@@ -297,7 +297,7 @@ void asyncAuth(RunCommandHook runCommand,
} // namespace
void authenticateClient(const BSONObj& params,
- StringData hostname,
+ const HostAndPort& hostname,
StringData clientName,
RunCommandHook runCommand,
AuthCompletionHandler handler) {
diff --git a/src/mongo/client/authenticate.h b/src/mongo/client/authenticate.h
index 65c649c49c2..f8d7434f30a 100644
--- a/src/mongo/client/authenticate.h
+++ b/src/mongo/client/authenticate.h
@@ -92,7 +92,7 @@ extern const char* const kMechanismScramSha1;
* tantamount to authentication failure, but may also indicate more serious problems.
*/
void authenticateClient(const BSONObj& params,
- StringData hostname,
+ const HostAndPort& hostname,
StringData clientSubjectName,
RunCommandHook runCommand,
AuthCompletionHandler handler = AuthCompletionHandler());
diff --git a/src/mongo/client/authenticate_test.cpp b/src/mongo/client/authenticate_test.cpp
index 0de2a1e9670..6a6596d52d8 100644
--- a/src/mongo/client/authenticate_test.cpp
+++ b/src/mongo/client/authenticate_test.cpp
@@ -173,13 +173,13 @@ public:
TEST_F(AuthClientTest, MongoCR) {
auto params = loadMongoCRConversation();
- auth::authenticateClient(std::move(params), "", "", _runCommandCallback);
+ auth::authenticateClient(std::move(params), HostAndPort(), "", _runCommandCallback);
}
TEST_F(AuthClientTest, asyncMongoCR) {
auto params = loadMongoCRConversation();
auth::authenticateClient(std::move(params),
- "",
+ HostAndPort(),
"",
_runCommandCallback,
[this](auth::AuthResponse response) { ASSERT(response.isOK()); });
@@ -188,13 +188,13 @@ TEST_F(AuthClientTest, asyncMongoCR) {
#ifdef MONGO_CONFIG_SSL
TEST_F(AuthClientTest, X509) {
auto params = loadX509Conversation();
- auth::authenticateClient(std::move(params), "", _username, _runCommandCallback);
+ auth::authenticateClient(std::move(params), HostAndPort(), _username, _runCommandCallback);
}
TEST_F(AuthClientTest, asyncX509) {
auto params = loadX509Conversation();
auth::authenticateClient(std::move(params),
- "",
+ HostAndPort(),
_username,
_runCommandCallback,
[this](auth::AuthResponse response) { ASSERT(response.isOK()); });
diff --git a/src/mongo/client/dbclient.cpp b/src/mongo/client/dbclient.cpp
index ab4ba058d00..6fd1c849c8d 100644
--- a/src/mongo/client/dbclient.cpp
+++ b/src/mongo/client/dbclient.cpp
@@ -536,7 +536,7 @@ void DBClientWithCommands::_auth(const BSONObj& params) {
auth::authenticateClient(
params,
- HostAndPort(getServerAddress()).host(),
+ HostAndPort(getServerAddress()),
clientName,
[this](RemoteCommandRequest request, auth::AuthCompletionHandler handler) {
BSONObj info;
diff --git a/src/mongo/client/native_sasl_client_session.cpp b/src/mongo/client/native_sasl_client_session.cpp
index 7976c09e413..8f8d4fe91af 100644
--- a/src/mongo/client/native_sasl_client_session.cpp
+++ b/src/mongo/client/native_sasl_client_session.cpp
@@ -33,6 +33,7 @@
#include "mongo/client/sasl_client_conversation.h"
#include "mongo/client/sasl_plain_client_conversation.h"
#include "mongo/client/sasl_scramsha1_client_conversation.h"
+#include "mongo/client/scram_sha1_client_cache.h"
#include "mongo/util/mongoutils/str.h"
namespace mongo {
@@ -47,6 +48,9 @@ MONGO_INITIALIZER(NativeSaslClientContext)(InitializerContext* context) {
return Status::OK();
}
+// Global cache for SCRAM-SHA-1 credentials
+SCRAMSHA1ClientCache* scramsha1ClientCache = new SCRAMSHA1ClientCache;
+
} // namespace
NativeSaslClientSession::NativeSaslClientSession()
@@ -63,7 +67,7 @@ Status NativeSaslClientSession::initialize() {
if (mechanism == "PLAIN") {
_saslConversation.reset(new SaslPLAINClientConversation(this));
} else if (mechanism == "SCRAM-SHA-1") {
- _saslConversation.reset(new SaslSCRAMSHA1ClientConversation(this));
+ _saslConversation.reset(new SaslSCRAMSHA1ClientConversation(this, scramsha1ClientCache));
} else {
return Status(ErrorCodes::BadValue,
mongoutils::str::stream() << "SASL mechanism " << mechanism
diff --git a/src/mongo/client/sasl_client_authenticate.cpp b/src/mongo/client/sasl_client_authenticate.cpp
index ecb7dfd7d1e..1d305d44d05 100644
--- a/src/mongo/client/sasl_client_authenticate.cpp
+++ b/src/mongo/client/sasl_client_authenticate.cpp
@@ -39,7 +39,7 @@ namespace mongo {
using namespace mongoutils;
void (*saslClientAuthenticate)(auth::RunCommandHook runCommand,
- StringData hostname,
+ const HostAndPort& hostname,
const BSONObj& saslParameters,
auth::AuthCompletionHandler handler) = nullptr;
diff --git a/src/mongo/client/sasl_client_authenticate.h b/src/mongo/client/sasl_client_authenticate.h
index 8e52bee407d..cde16cd3afe 100644
--- a/src/mongo/client/sasl_client_authenticate.h
+++ b/src/mongo/client/sasl_client_authenticate.h
@@ -68,7 +68,7 @@ class BSONObj;
* returned.
*/
extern void (*saslClientAuthenticate)(auth::RunCommandHook runCommand,
- StringData hostname,
+ const HostAndPort& hostname,
const BSONObj& saslParameters,
auth::AuthCompletionHandler handler);
diff --git a/src/mongo/client/sasl_client_authenticate_impl.cpp b/src/mongo/client/sasl_client_authenticate_impl.cpp
index aa4d7e64838..35b575e545a 100644
--- a/src/mongo/client/sasl_client_authenticate_impl.cpp
+++ b/src/mongo/client/sasl_client_authenticate_impl.cpp
@@ -116,7 +116,7 @@ Status extractPassword(const BSONObj& saslParameters,
* Returns Status::OK() on success.
*/
Status configureSession(SaslClientSession* session,
- StringData hostname,
+ const HostAndPort& hostname,
StringData targetDatabase,
const BSONObj& saslParameters) {
std::string mechanism;
@@ -134,10 +134,11 @@ Status configureSession(SaslClientSession* session,
session->setParameter(SaslClientSession::parameterServiceName, value);
status = bsonExtractStringFieldWithDefault(
- saslParameters, saslCommandServiceHostnameFieldName, hostname, &value);
+ saslParameters, saslCommandServiceHostnameFieldName, hostname.host(), &value);
if (!status.isOK())
return status;
session->setParameter(SaslClientSession::parameterServiceHostname, value);
+ session->setParameter(SaslClientSession::parameterServiceHostAndPort, hostname.toString());
status = bsonExtractStringField(saslParameters, saslCommandUserFieldName, &value);
if (!status.isOK())
@@ -247,7 +248,7 @@ void asyncSaslConversation(auth::RunCommandHook runCommand,
* "client".
*/
void saslClientAuthenticateImpl(auth::RunCommandHook runCommand,
- StringData hostname,
+ const HostAndPort& hostname,
const BSONObj& saslParameters,
auth::AuthCompletionHandler handler) {
int saslLogLevel = getSaslClientLogLevel(saslParameters);
diff --git a/src/mongo/client/sasl_client_session.h b/src/mongo/client/sasl_client_session.h
index c93a843c9d7..3cda8fba89c 100644
--- a/src/mongo/client/sasl_client_session.h
+++ b/src/mongo/client/sasl_client_session.h
@@ -63,6 +63,7 @@ public:
enum Parameter {
parameterServiceName = 0,
parameterServiceHostname,
+ parameterServiceHostAndPort,
parameterMechanism,
parameterUser,
parameterPassword,
diff --git a/src/mongo/client/sasl_scramsha1_client_conversation.cpp b/src/mongo/client/sasl_scramsha1_client_conversation.cpp
index e7d8643668a..87d51d27d2a 100644
--- a/src/mongo/client/sasl_scramsha1_client_conversation.cpp
+++ b/src/mongo/client/sasl_scramsha1_client_conversation.cpp
@@ -34,6 +34,7 @@
#include "mongo/base/parse_number.h"
#include "mongo/client/sasl_client_session.h"
+#include "mongo/client/scram_sha1_client_cache.h"
#include "mongo/platform/random.h"
#include "mongo/util/base64.h"
#include "mongo/util/mongoutils/str.h"
@@ -46,13 +47,12 @@ using std::unique_ptr;
using std::string;
SaslSCRAMSHA1ClientConversation::SaslSCRAMSHA1ClientConversation(
- SaslClientSession* saslClientSession)
- : SaslClientConversation(saslClientSession), _step(0), _authMessage(""), _clientNonce("") {}
-
-SaslSCRAMSHA1ClientConversation::~SaslSCRAMSHA1ClientConversation() {
- // clear the _saltedPassword memory
- memset(_saltedPassword, 0, scram::hashSize);
-}
+ SaslClientSession* saslClientSession, SCRAMSHA1ClientCache* clientCache)
+ : SaslClientConversation(saslClientSession),
+ _step(0),
+ _authMessage(),
+ _clientCache(clientCache),
+ _clientNonce() {}
StatusWith<bool> SaslSCRAMSHA1ClientConversation::step(StringData inputData,
std::string* outputData) {
@@ -159,7 +159,7 @@ StatusWith<bool> SaslSCRAMSHA1ClientConversation::_secondStep(const std::vector<
}
std::string salt = input[1].substr(2);
- int iterationCount;
+ size_t iterationCount;
Status status = parseNumberFromStringWithBase(input[2].substr(2), 10, &iterationCount);
if (status != Status::OK()) {
@@ -178,14 +178,30 @@ StatusWith<bool> SaslSCRAMSHA1ClientConversation::_secondStep(const std::vector<
return StatusWith<bool>(ex.toStatus());
}
- scram::generateSaltedPassword(
- _saslClientSession->getParameter(SaslClientSession::parameterPassword),
- reinterpret_cast<const unsigned char*>(decodedSalt.c_str()),
- decodedSalt.size(),
- iterationCount,
- _saltedPassword);
+ scram::SCRAMPresecrets presecrets(
+ _saslClientSession->getParameter(SaslClientSession::parameterPassword).toString(),
+ std::vector<std::uint8_t>(decodedSalt.begin(), decodedSalt.end()),
+ iterationCount);
+
+ StatusWith<HostAndPort> targetHost = HostAndPort::parse(
+ _saslClientSession->getParameter(SaslClientSession::parameterServiceHostAndPort));
+
+ if (targetHost.isOK()) {
+ auto cachedSecrets = _clientCache->getCachedSecrets(targetHost.getValue(), presecrets);
+
+ if (cachedSecrets) {
+ _credentials = *cachedSecrets;
+ } else {
+ _credentials = scram::generateSecrets(presecrets);
+
+ _clientCache->setCachedSecrets(
+ std::move(targetHost.getValue()), std::move(presecrets), _credentials);
+ }
+ } else {
+ _credentials = scram::generateSecrets(presecrets);
+ }
- std::string clientProof = scram::generateClientProof(_saltedPassword, _authMessage);
+ std::string clientProof = scram::generateClientProof(_credentials, _authMessage);
StringBuilder sb;
sb << "c=biws,r=" << nonce << ",p=" << clientProof;
@@ -224,7 +240,7 @@ StatusWith<bool> SaslSCRAMSHA1ClientConversation::_thirdStep(const std::vector<s
}
bool validServerSignature =
- scram::verifyServerSignature(_saltedPassword, _authMessage, input[0].substr(2));
+ scram::verifyServerSignature(_credentials, _authMessage, input[0].substr(2));
if (!validServerSignature) {
*outputData = "e=Invalid server signature";
diff --git a/src/mongo/client/sasl_scramsha1_client_conversation.h b/src/mongo/client/sasl_scramsha1_client_conversation.h
index ccc89eb2243..dcf8a5686e4 100644
--- a/src/mongo/client/sasl_scramsha1_client_conversation.h
+++ b/src/mongo/client/sasl_scramsha1_client_conversation.h
@@ -38,6 +38,8 @@
#include "mongo/crypto/mechanism_scram.h"
namespace mongo {
+
+class SCRAMSHA1ClientCache;
/**
* Client side authentication session for SASL PLAIN.
*/
@@ -48,9 +50,8 @@ public:
/**
* Implements the client side of a SASL PLAIN mechanism session.
**/
- explicit SaslSCRAMSHA1ClientConversation(SaslClientSession* saslClientSession);
-
- virtual ~SaslSCRAMSHA1ClientConversation();
+ SaslSCRAMSHA1ClientConversation(SaslClientSession* saslClientSession,
+ SCRAMSHA1ClientCache* clientCache);
/**
* Takes one step in a SCRAM-SHA-1 conversation.
@@ -79,7 +80,10 @@ private:
int _step;
std::string _authMessage;
- unsigned char _saltedPassword[scram::hashSize];
+
+ // Secrets and secrets cache
+ scram::SCRAMSecrets _credentials;
+ SCRAMSHA1ClientCache* const _clientCache;
// client and server nonce concatenated
std::string _clientNonce;
diff --git a/src/mongo/client/scram_sha1_client_cache.cpp b/src/mongo/client/scram_sha1_client_cache.cpp
new file mode 100644
index 00000000000..a087f27ca0a
--- /dev/null
+++ b/src/mongo/client/scram_sha1_client_cache.cpp
@@ -0,0 +1,70 @@
+/**
+ * Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
+ *
+ * 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/client/scram_sha1_client_cache.h"
+
+namespace mongo {
+
+boost::optional<scram::SCRAMSecrets> SCRAMSHA1ClientCache::getCachedSecrets(
+ const HostAndPort& target, const scram::SCRAMPresecrets& presecrets) const {
+ const stdx::lock_guard<stdx::mutex> lock(_hostToSecretsMutex);
+
+ // Search the cache for a record associated with the host we're trying to connect to.
+ auto foundSecret = _hostToSecrets.find(target);
+ if (foundSecret != _hostToSecrets.end()) {
+ // Presecrets contain parameters provided by the server, which may change. If the
+ // cached presecrets don't match the presecrets we have on hand, we must not return the
+ // stale cached secrets. We'll need to rerun the SCRAM computation.
+ const scram::SCRAMPresecrets& foundPresecrets = foundSecret->second.first;
+ if (foundPresecrets == presecrets) {
+ return foundSecret->second.second;
+ }
+ }
+ return {};
+}
+
+void SCRAMSHA1ClientCache::setCachedSecrets(HostAndPort target,
+ scram::SCRAMPresecrets presecrets,
+ scram::SCRAMSecrets secrets) {
+ const stdx::lock_guard<stdx::mutex> lock(_hostToSecretsMutex);
+
+ decltype(_hostToSecrets)::iterator it;
+ bool insertionSuccessful;
+ auto cacheRecord = std::make_pair(std::move(presecrets), std::move(secrets));
+ // Insert the presecrets, and the secrets we computed for them into the cache
+ std::tie(it, insertionSuccessful) = _hostToSecrets.emplace(std::move(target), cacheRecord);
+ // If there was already a cache entry for the target HostAndPort, we should overwrite it.
+ // We have fresher presecrets and secrets.
+ if (!insertionSuccessful) {
+ it->second = std::move(cacheRecord);
+ }
+}
+
+} // namespace mongo
diff --git a/src/mongo/client/scram_sha1_client_cache.h b/src/mongo/client/scram_sha1_client_cache.h
new file mode 100644
index 00000000000..b3fba8734e2
--- /dev/null
+++ b/src/mongo/client/scram_sha1_client_cache.h
@@ -0,0 +1,88 @@
+/**
+ * Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
+ *
+ * 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.
+ */
+#pragma once
+
+#include <boost/optional.hpp>
+#include <string>
+
+#include "mongo/crypto/mechanism_scram.h"
+#include "mongo/platform/unordered_map.h"
+#include "mongo/stdx/mutex.h"
+#include "mongo/util/net/hostandport.h"
+
+namespace mongo {
+
+/**
+ * A cache for the intermediate steps of the SCRAM-SHA-1 computation.
+ *
+ * Clients wishing to authenticate to a server using SCRAM-SHA-1
+ * must produce a set of credential objects from their password,
+ * a salt, and an iteration count. The computation to generate these
+ * is very expensive, proportional to the iteration count. The high
+ * cost of this computation prevents brute force attacks on
+ * intercepted SCRAM authentication data, or a stolen password
+ * database. The inputs to the function are unlikely to frequently
+ * change. Caching the relationship between the inputs and the
+ * resulting output should make repeated authentication attempts
+ * to a single server much faster.
+ *
+ * This is explicitly permitted by RFC5802, section 5.1:
+ *
+ * "Note that a client implementation MAY cache
+ * ClientKey&ServerKey (or just SaltedPassword) for later
+ * reauthentication to the same service, as it is likely that the
+ * server is going to advertise the same salt value upon
+ * reauthentication. This might be useful for mobile clients where
+ * CPU usage is a concern."
+ */
+class SCRAMSHA1ClientCache {
+public:
+ /**
+ * Returns precomputed SCRAMSecrets, if one has already been
+ * stored for the specified hostname and the provided presecrets
+ * match those recorded for the hostname. Otherwise, no secrets
+ * are returned.
+ */
+ boost::optional<scram::SCRAMSecrets> getCachedSecrets(
+ const HostAndPort& target, const scram::SCRAMPresecrets& presecrets) const;
+
+ /**
+ * Records a set of precomputed SCRAMSecrets for the specified
+ * host, along with the presecrets used to generate them.
+ */
+ void setCachedSecrets(HostAndPort target,
+ scram::SCRAMPresecrets presecrets,
+ scram::SCRAMSecrets secrets);
+
+private:
+ mutable stdx::mutex _hostToSecretsMutex;
+ std::unordered_map<HostAndPort, std::pair<scram::SCRAMPresecrets, scram::SCRAMSecrets>>
+ _hostToSecrets;
+};
+
+} // namespace mongo