diff options
author | Spencer Jackson <spencer.jackson@mongodb.com> | 2016-12-07 16:11:57 -0500 |
---|---|---|
committer | Spencer Jackson <spencer.jackson@mongodb.com> | 2017-07-11 16:20:07 -0400 |
commit | 16e83332ed20e4054324a1a7714506e74eed5180 (patch) | |
tree | b0c03f332f37dc2ca5530a0bc6aa592cc3fbcb40 /src/mongo/client | |
parent | 78800570b838dd7e2bb53939d9d1a752d71e5ae3 (diff) | |
download | mongo-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/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/client/authenticate.cpp | 6 | ||||
-rw-r--r-- | src/mongo/client/authenticate.h | 2 | ||||
-rw-r--r-- | src/mongo/client/authenticate_test.cpp | 8 | ||||
-rw-r--r-- | src/mongo/client/dbclient.cpp | 2 | ||||
-rw-r--r-- | src/mongo/client/native_sasl_client_session.cpp | 6 | ||||
-rw-r--r-- | src/mongo/client/sasl_client_authenticate.cpp | 2 | ||||
-rw-r--r-- | src/mongo/client/sasl_client_authenticate.h | 2 | ||||
-rw-r--r-- | src/mongo/client/sasl_client_authenticate_impl.cpp | 7 | ||||
-rw-r--r-- | src/mongo/client/sasl_client_session.h | 1 | ||||
-rw-r--r-- | src/mongo/client/sasl_scramsha1_client_conversation.cpp | 48 | ||||
-rw-r--r-- | src/mongo/client/sasl_scramsha1_client_conversation.h | 12 | ||||
-rw-r--r-- | src/mongo/client/scram_sha1_client_cache.cpp | 70 | ||||
-rw-r--r-- | src/mongo/client/scram_sha1_client_cache.h | 88 |
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 |