diff options
29 files changed, 570 insertions, 332 deletions
diff --git a/buildscripts/resmokeconfig/suites/multiversion_auth.yml b/buildscripts/resmokeconfig/suites/multiversion_auth.yml new file mode 100644 index 00000000000..4db3f87f7af --- /dev/null +++ b/buildscripts/resmokeconfig/suites/multiversion_auth.yml @@ -0,0 +1,37 @@ +# Section that is ignored by resmoke.py. +config_variables: +- &keyFile jstests/libs/authTestsKey +- &keyFileData Thiskeyisonlyforrunningthesuitewithauthenticationdontuseitinanytestsdirectly + +selector: + js_test: + roots: + - jstests/multiVersion/*.js + exclude_files: + # Needs extra work to support MONGODB-CR + - jstests/multiVersion/upgrade_cluster_v5_to_v6.js + # Uses threads which don't propagate jsTest() information + - jstests/multiVersion/mixed_storage_version_replication.js + # Uses ToolTest, which doesn't start servers with keyFile + - jstests/multiVersion/transitioning_to_and_from_WT.js + # TODO: SERVER-21578 + - jstests/multiVersion/balancer_multiVersion_detect.js + +# Multiversion tests start their own mongod's. +executor: + js_test: + config: + shell_options: + global_vars: + TestData: + auth: true + authMechanism: SCRAM-SHA-1 + keyFile: *keyFile + keyFileData: *keyFileData + authenticationDatabase: local + authenticationMechanism: SCRAM-SHA-1 + password: *keyFileData + username: __system + nodb: '' + readMode: legacy + writeMode: legacy diff --git a/etc/evergreen.yml b/etc/evergreen.yml index 485a05f2c33..9bef88f9a0e 100644 --- a/etc/evergreen.yml +++ b/etc/evergreen.yml @@ -1363,6 +1363,17 @@ tasks: run_multiple_jobs: true - <<: *task_template + name: multiversion_auth + commands: + - func: "do setup" + - func: "do multiversion setup" + - func: "run tests" + vars: + path_prefix: PATH=$PATH:/data/multiversion + resmoke_args: --suites=multiversion_auth + run_multiple_jobs: true + +- <<: *task_template name: noPassthrough commands: - func: "do setup" @@ -3523,6 +3534,7 @@ buildvariants: - name: mmap - name: mongosTest - name: multiversion + - name: multiversion_auth - name: noPassthrough - name: noPassthrough_WT - name: noPassthroughWithMongod @@ -4306,6 +4318,7 @@ buildvariants: - name: mmap - name: mongosTest - name: multiversion + - name: multiversion_auth - name: noPassthrough - name: noPassthroughWithMongod - name: noPassthroughWithMongod_WT @@ -6114,6 +6127,7 @@ buildvariants: - name: mmap - name: mongosTest - name: multiversion + - name: multiversion_auth - name: noPassthrough - name: noPassthroughWithMongod - name: noPassthroughWithMongod_WT diff --git a/jstests/libs/test_background_ops.js b/jstests/libs/test_background_ops.js index ce21a636ff6..f30314234da 100644 --- a/jstests/libs/test_background_ops.js +++ b/jstests/libs/test_background_ops.js @@ -84,6 +84,10 @@ var getResult = function(mongo, name) { * Overrides the parallel shell code in mongo */ function startParallelShell(jsCode, port) { + if (TestData) { + jsCode = "TestData = " + tojson(TestData) + ";" + jsCode; + } + var x; if (port) { x = startMongoProgramNoConnect("mongo", "--port", port, "--eval", jsCode); diff --git a/jstests/multiVersion/libs/multi_rs.js b/jstests/multiVersion/libs/multi_rs.js index 109db580453..d13eca205aa 100644 --- a/jstests/multiVersion/libs/multi_rs.js +++ b/jstests/multiVersion/libs/multi_rs.js @@ -45,6 +45,7 @@ ReplSetTest.prototype.upgradeNode = function(node, opts, user, pwd) { if (user != undefined) { assert.eq(1, node.getDB("admin").auth(user, pwd)); } + jsTest.authenticate(node); var isMaster = node.getDB('admin').runCommand({isMaster: 1}); 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 diff --git a/src/mongo/crypto/SConscript b/src/mongo/crypto/SConscript index f66e88be2b3..9013c70b207 100644 --- a/src/mongo/crypto/SConscript +++ b/src/mongo/crypto/SConscript @@ -8,16 +8,29 @@ env.SConscript( ], ) -env.Library('crypto_tom', - ['crypto_tom.cpp'], - LIBDEPS=['tom/tomcrypt']) +env.Library( + target='crypto_tom', + source=[ + 'crypto_tom.cpp' + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/base', + 'tom/tomcrypt' + ]) env.Library('crypto_openssl', - ['crypto_openssl.cpp']) + source=[ + 'crypto_openssl.cpp' + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/base', + ]) env.Library('scramauth', ['mechanism_scram.cpp'], LIBDEPS=['$BUILD_DIR/mongo/base', + '$BUILD_DIR/mongo/base/secure_allocator', + '$BUILD_DIR/mongo/util/secure_zero_memory', 'crypto_${MONGO_CRYPTO}']) env.CppUnitTest('crypto_test', diff --git a/src/mongo/crypto/crypto.h b/src/mongo/crypto/crypto.h index 7b08e371a84..1f010980078 100644 --- a/src/mongo/crypto/crypto.h +++ b/src/mongo/crypto/crypto.h @@ -28,24 +28,26 @@ #pragma once +#include <array> #include <cstddef> namespace mongo { +// Storage container for a SHA1 hash +using SHA1Hash = std::array<std::uint8_t, 20>; + namespace crypto { /* * Computes a SHA-1 hash of 'input'. */ -bool sha1(const unsigned char* input, const size_t inputLen, unsigned char* output); +SHA1Hash sha1(const unsigned char* input, const size_t inputLen); /* * Computes a HMAC SHA-1 keyed hash of 'input' using the key 'key' */ -bool hmacSha1(const unsigned char* key, - const size_t keyLen, - const unsigned char* input, - const size_t inputLen, - unsigned char* output, - unsigned int* outputLen); +SHA1Hash hmacSha1(const unsigned char* key, + const size_t keyLen, + const unsigned char* input, + const size_t inputLen); } // namespace crypto } // namespace mongo diff --git a/src/mongo/crypto/crypto_openssl.cpp b/src/mongo/crypto/crypto_openssl.cpp index 89e9cbb468f..d7e310e0d46 100644 --- a/src/mongo/crypto/crypto_openssl.cpp +++ b/src/mongo/crypto/crypto_openssl.cpp @@ -30,8 +30,8 @@ #include "mongo/config.h" #include "mongo/crypto/crypto.h" -#include "mongo/stdx/memory.h" #include "mongo/util/assert_util.h" +#include "mongo/util/scopeguard.h" #ifndef MONGO_CONFIG_SSL #error This file should only be included in SSL-enabled builds @@ -66,25 +66,30 @@ namespace crypto { /* * Computes a SHA-1 hash of 'input'. */ -bool sha1(const unsigned char* input, const size_t inputLen, unsigned char* output) { - std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> digestCtx(EVP_MD_CTX_new(), - EVP_MD_CTX_free); +SHA1Hash sha1(const unsigned char* input, const size_t inputLen) { + SHA1Hash output; - return (EVP_DigestInit_ex(digestCtx.get(), EVP_sha1(), NULL) == 1 && - EVP_DigestUpdate(digestCtx.get(), input, inputLen) == 1 && - EVP_DigestFinal_ex(digestCtx.get(), output, NULL) == 1); + EVP_MD_CTX digestCtx; + EVP_MD_CTX_init(&digestCtx); + ON_BLOCK_EXIT(EVP_MD_CTX_cleanup, &digestCtx); + + fassert(40379, + EVP_DigestInit_ex(&digestCtx, EVP_sha1(), NULL) == 1 && + EVP_DigestUpdate(&digestCtx, input, inputLen) == 1 && + EVP_DigestFinal_ex(&digestCtx, output.data(), NULL) == 1); + return output; } /* * Computes a HMAC SHA-1 keyed hash of 'input' using the key 'key' */ -bool hmacSha1(const unsigned char* key, - const size_t keyLen, - const unsigned char* input, - const size_t inputLen, - unsigned char* output, - unsigned int* outputLen) { - return HMAC(EVP_sha1(), key, keyLen, input, inputLen, output, outputLen); +SHA1Hash hmacSha1(const unsigned char* key, + const size_t keyLen, + const unsigned char* input, + const size_t inputLen) { + SHA1Hash output; + fassert(40380, HMAC(EVP_sha1(), key, keyLen, input, inputLen, output.data(), NULL) != NULL); + return output; } } // namespace crypto diff --git a/src/mongo/crypto/crypto_test.cpp b/src/mongo/crypto/crypto_test.cpp index 52d488b92cf..8c8d3928c75 100644 --- a/src/mongo/crypto/crypto_test.cpp +++ b/src/mongo/crypto/crypto_test.cpp @@ -31,12 +31,11 @@ namespace mongo { namespace { -const int digestLen = 20; // SHA-1 test vectors from http://csrc.nist.gov/groups/ST/toolkit/documents/Examples/SHA_All.pdf const struct { const char* msg; - unsigned char hash[digestLen]; + SHA1Hash hash; } sha1Tests[] = {{"abc", {0xa9, 0x99, @@ -82,15 +81,12 @@ const struct { 0xF1}}}; TEST(CryptoVectors, SHA1) { - unsigned char sha1Result[digestLen]; size_t numTests = sizeof(sha1Tests) / sizeof(sha1Tests[0]); for (size_t i = 0; i < numTests; i++) { - ASSERT(crypto::sha1(reinterpret_cast<const unsigned char*>(sha1Tests[i].msg), - strlen(sha1Tests[i].msg), - sha1Result)) + SHA1Hash result = crypto::sha1(reinterpret_cast<const unsigned char*>(sha1Tests[i].msg), + strlen(sha1Tests[i].msg)); + ASSERT(0 == memcmp(sha1Tests[i].hash.data(), result.data(), result.size())) << "Failed SHA1 iteration " << i; - ASSERT(0 == memcmp(sha1Tests[i].hash, sha1Result, digestLen)) << "Failed SHA1 iteration " - << i; } } @@ -102,7 +98,7 @@ const struct { int keyLen; unsigned char data[maxDataSize]; int dataLen; - unsigned char hash[digestLen]; + SHA1Hash hash; } hmacSha1Tests[] = { // RFC test case 1 {{0x0b, @@ -501,19 +497,13 @@ const struct { 0x12}}}; TEST(CryptoVectors, HMACSHA1) { - unsigned char hmacSha1Result[digestLen]; - unsigned int hashLen = digestLen; - size_t numTests = sizeof(hmacSha1Tests) / sizeof(hmacSha1Tests[0]); for (size_t i = 0; i < numTests; i++) { - ASSERT(crypto::hmacSha1(hmacSha1Tests[i].key, - hmacSha1Tests[i].keyLen, - hmacSha1Tests[i].data, - hmacSha1Tests[i].dataLen, - hmacSha1Result, - &hashLen)) - << "Failed HMAC-SHA1 iteration " << i; - ASSERT(0 == memcmp(hmacSha1Tests[i].hash, hmacSha1Result, digestLen)) + SHA1Hash result = crypto::hmacSha1(hmacSha1Tests[i].key, + hmacSha1Tests[i].keyLen, + hmacSha1Tests[i].data, + hmacSha1Tests[i].dataLen); + ASSERT(0 == memcmp(hmacSha1Tests[i].hash.data(), result.data(), result.size())) << "Failed HMAC-SHA1 iteration " << i; } } diff --git a/src/mongo/crypto/crypto_tom.cpp b/src/mongo/crypto/crypto_tom.cpp index a629dff945d..904eb5925e3 100644 --- a/src/mongo/crypto/crypto_tom.cpp +++ b/src/mongo/crypto/crypto_tom.cpp @@ -29,6 +29,8 @@ #include "mongo/platform/basic.h" #include "mongo/config.h" +#include "mongo/crypto/crypto.h" +#include "mongo/util/assert_util.h" #ifdef MONGO_CONFIG_SSL #error This file should not be included if compiling with SSL support @@ -41,33 +43,26 @@ namespace crypto { /* * Computes a SHA-1 hash of 'input'. */ -bool sha1(const unsigned char* input, const size_t inputLen, unsigned char* output) { - hash_state hashState; - if (sha1_init(&hashState) != CRYPT_OK) { - return false; - } - if (sha1_process(&hashState, input, inputLen) != CRYPT_OK) { - return false; - } - if (sha1_done(&hashState, output) != CRYPT_OK) { - return false; - } +SHA1Hash sha1(const unsigned char* input, const size_t inputLen) { + SHA1Hash output; - return true; + hash_state hashState; + fassert(40381, + sha1_init(&hashState) == CRYPT_OK && + sha1_process(&hashState, input, inputLen) == CRYPT_OK && + sha1_done(&hashState, output.data()) == CRYPT_OK); + return output; } /* * Computes a HMAC SHA-1 keyed hash of 'input' using the key 'key' */ -bool hmacSha1(const unsigned char* key, - const size_t keyLen, - const unsigned char* input, - const size_t inputLen, - unsigned char* output, - unsigned int* outputLen) { - if (!key || !input || !output) { - return false; - } +SHA1Hash hmacSha1(const unsigned char* key, + const size_t keyLen, + const unsigned char* input, + const size_t inputLen) { + invariant(key && input); + SHA1Hash output; static int hashId = -1; if (hashId == -1) { @@ -76,12 +71,10 @@ bool hmacSha1(const unsigned char* key, } unsigned long sha1HashLen = 20; - if (hmac_memory(hashId, key, keyLen, input, inputLen, output, &sha1HashLen) != CRYPT_OK) { - return false; - } - - *outputLen = sha1HashLen; - return true; + fassert(40382, + hmac_memory(hashId, key, keyLen, input, inputLen, output.data(), &sha1HashLen) == + CRYPT_OK); + return output; } } // namespace crypto diff --git a/src/mongo/crypto/mechanism_scram.cpp b/src/mongo/crypto/mechanism_scram.cpp index 086e9943c4c..5d1cb784011 100644 --- a/src/mongo/crypto/mechanism_scram.cpp +++ b/src/mongo/crypto/mechanism_scram.cpp @@ -35,26 +35,56 @@ #include "mongo/crypto/crypto.h" #include "mongo/platform/random.h" #include "mongo/util/base64.h" +#include "mongo/util/secure_zero_memory.h" namespace mongo { namespace scram { using std::unique_ptr; +namespace { +/** + * Compare two arrays of bytes for equality in constant time. + * + * This means that the function runs for the same amount of time even if they differ. Unlike memcmp, + * this function does not exit on the first difference. + * + * Returns true if the two arrays are equal. + * + * TODO: evaluate if LTO inlines or changes the code flow of this function. + */ +NOINLINE_DECL +bool consttimeMemEqual(volatile const unsigned char* s1, // NOLINT - using volatile to + volatile const unsigned char* s2, // NOLINT - disable compiler optimizations + size_t length) { + unsigned int ret = 0; + + for (size_t i = 0; i < length; ++i) { + ret |= s1[i] ^ s2[i]; + } + + return (1 & ((ret - 1) >> 8)); +} +} // namespace + +std::string hashToBase64(const SecureHandle<SHA1Hash>& hash) { + return base64::encode(reinterpret_cast<const char*>(hash->data()), hash->size()); +} + // Compute the SCRAM step Hi() as defined in RFC5802 -static void HMACIteration(const unsigned char input[], - size_t inputLen, - const unsigned char salt[], - size_t saltLen, - unsigned int iterationCount, - unsigned char output[]) { - unsigned char intermediateDigest[hashSize]; - unsigned char startKey[hashSize]; - // Placeholder for HMAC return size, will always be scram::hashSize for HMAC SHA-1 - unsigned int hashLen = 0; - - uassert(17450, "invalid salt length provided", saltLen + 4 == hashSize); - memcpy(startKey, salt, saltLen); +static SHA1Hash HMACIteration(const unsigned char input[], + size_t inputLen, + const unsigned char salt[], + size_t saltLen, + unsigned int iterationCount) { + SHA1Hash output; + SHA1Hash intermediateDigest; + // Reserve a 20 byte block for the initial key. We use 16 byte salts, and must reserve an extra + // 4 bytes for a suffix mandated by RFC5802. + std::array<std::uint8_t, 20> startKey; + + uassert(17450, "invalid salt length provided", saltLen + 4 == startKey.size()); + std::copy(salt, salt + saltLen, startKey.begin()); startKey[saltLen] = 0; startKey[saltLen + 1] = 0; @@ -62,72 +92,64 @@ static void HMACIteration(const unsigned char input[], startKey[saltLen + 3] = 1; // U1 = HMAC(input, salt + 0001) - fassert(17494, crypto::hmacSha1(input, inputLen, startKey, saltLen + 4, output, &hashLen)); - - memcpy(intermediateDigest, output, hashSize); + output = crypto::hmacSha1(input, inputLen, startKey.data(), startKey.size()); + intermediateDigest = output; // intermediateDigest contains Ui and output contains the accumulated XOR:ed result for (size_t i = 2; i <= iterationCount; i++) { - unsigned char intermediateOutput[hashSize]; - fassert(17495, - crypto::hmacSha1( - input, inputLen, intermediateDigest, hashSize, intermediateOutput, &hashLen)); - memcpy(intermediateDigest, intermediateOutput, hashSize); - for (size_t k = 0; k < hashSize; k++) { + intermediateDigest = + crypto::hmacSha1(input, inputLen, intermediateDigest.data(), intermediateDigest.size()); + for (size_t k = 0; k < output.size(); k++) { output[k] ^= intermediateDigest[k]; } } + + return output; } // Iterate the hash function to generate SaltedPassword -void generateSaltedPassword(StringData hashedPassword, - const unsigned char* salt, - const int saltLen, - const int iterationCount, - unsigned char saltedPassword[hashSize]) { +SHA1Hash generateSaltedPassword(const SCRAMPresecrets& presecrets) { // saltedPassword = Hi(hashedPassword, salt) - HMACIteration(reinterpret_cast<const unsigned char*>(hashedPassword.rawData()), - hashedPassword.size(), - salt, - saltLen, - iterationCount, - saltedPassword); + SHA1Hash saltedPassword = + HMACIteration(reinterpret_cast<const unsigned char*>(presecrets.hashedPassword.c_str()), + presecrets.hashedPassword.size(), + presecrets.salt.data(), + presecrets.salt.size(), + presecrets.iterationCount); + + return saltedPassword; +} + +SCRAMSecrets generateSecrets(const SCRAMPresecrets& presecrets) { + SHA1Hash saltedPassword = generateSaltedPassword(presecrets); + return generateSecrets(saltedPassword); } -void generateSecrets(const std::string& hashedPassword, - const unsigned char salt[], - size_t saltLen, - size_t iterationCount, - unsigned char storedKey[hashSize], - unsigned char serverKey[hashSize]) { - unsigned char saltedPassword[hashSize]; - unsigned char clientKey[hashSize]; - unsigned int hashLen = 0; - - generateSaltedPassword(hashedPassword, salt, saltLen, iterationCount, saltedPassword); - - // clientKey = HMAC(saltedPassword, "Client Key") - fassert(17498, - crypto::hmacSha1(saltedPassword, - hashSize, - reinterpret_cast<const unsigned char*>(clientKeyConst.data()), - clientKeyConst.size(), - clientKey, - &hashLen)); - - // storedKey = H(clientKey) - fassert(17499, crypto::sha1(clientKey, hashSize, storedKey)); - - // serverKey = HMAC(saltedPassword, "Server Key") - fassert(17500, - crypto::hmacSha1(saltedPassword, - hashSize, - reinterpret_cast<const unsigned char*>(serverKeyConst.data()), - serverKeyConst.size(), - serverKey, - &hashLen)); +SCRAMSecrets generateSecrets(const SHA1Hash& saltedPassword) { + SCRAMSecrets credentials; + + // ClientKey := HMAC(saltedPassword, "Client Key") + credentials.clientKey = + crypto::hmacSha1(saltedPassword.data(), + saltedPassword.size(), + reinterpret_cast<const unsigned char*>(clientKeyConst.data()), + clientKeyConst.size()); + + // StoredKey := H(clientKey) + credentials.storedKey = + crypto::sha1(credentials.clientKey->data(), credentials.clientKey->size()); + + // ServerKey := HMAC(SaltedPassword, "Server Key") + credentials.serverKey = + crypto::hmacSha1(saltedPassword.data(), + saltedPassword.size(), + reinterpret_cast<const unsigned char*>(serverKeyConst.data()), + serverKeyConst.size()); + + return credentials; } + BSONObj generateCredentials(const std::string& hashedPassword, int iterationCount) { const int saltLenQWords = 2; @@ -142,115 +164,81 @@ BSONObj generateCredentials(const std::string& hashedPassword, int iterationCoun base64::encode(reinterpret_cast<char*>(userSalt), sizeof(userSalt)); // Compute SCRAM secrets serverKey and storedKey - unsigned char storedKey[hashSize]; - unsigned char serverKey[hashSize]; + auto secrets = generateSecrets( + SCRAMPresecrets(hashedPassword, + std::vector<std::uint8_t>(reinterpret_cast<std::uint8_t*>(userSalt), + reinterpret_cast<std::uint8_t*>(userSalt) + + saltLenQWords * sizeof(uint64_t)), + iterationCount)); - generateSecrets(hashedPassword, - reinterpret_cast<unsigned char*>(userSalt), - saltLenQWords * sizeof(uint64_t), - iterationCount, - storedKey, - serverKey); - - std::string encodedStoredKey = base64::encode(reinterpret_cast<char*>(storedKey), hashSize); - std::string encodedServerKey = base64::encode(reinterpret_cast<char*>(serverKey), hashSize); + std::string encodedStoredKey = hashToBase64(secrets.storedKey); + std::string encodedServerKey = hashToBase64(secrets.serverKey); return BSON(iterationCountFieldName << iterationCount << saltFieldName << encodedUserSalt << storedKeyFieldName << encodedStoredKey << serverKeyFieldName << encodedServerKey); } -std::string generateClientProof(const unsigned char saltedPassword[hashSize], +std::string generateClientProof(const SCRAMSecrets& clientCredentials, const std::string& authMessage) { - // ClientKey := HMAC(saltedPassword, "Client Key") - unsigned char clientKey[hashSize]; - unsigned int hashLen = 0; - fassert(18689, - crypto::hmacSha1(saltedPassword, - hashSize, - reinterpret_cast<const unsigned char*>(clientKeyConst.data()), - clientKeyConst.size(), - clientKey, - &hashLen)); - - // StoredKey := H(clientKey) - unsigned char storedKey[hashSize]; - fassert(18701, crypto::sha1(clientKey, hashSize, storedKey)); - // ClientSignature := HMAC(StoredKey, AuthMessage) - unsigned char clientSignature[hashSize]; - fassert(18702, - crypto::hmacSha1(storedKey, - hashSize, - reinterpret_cast<const unsigned char*>(authMessage.c_str()), - authMessage.size(), - clientSignature, - &hashLen)); + SHA1Hash clientSignature = + crypto::hmacSha1(clientCredentials.storedKey->data(), + clientCredentials.storedKey->size(), + reinterpret_cast<const unsigned char*>(authMessage.c_str()), + authMessage.size()); // ClientProof := ClientKey XOR ClientSignature - unsigned char clientProof[hashSize]; - for (size_t i = 0; i < hashSize; i++) { - clientProof[i] = clientKey[i] ^ clientSignature[i]; - } - - return base64::encode(reinterpret_cast<char*>(clientProof), hashSize); -} - -/** - * Compare two arrays of bytes for equality in constant time. - * - * This means that the function runs for the same amount of time even if they differ. Unlike memcmp, - * this function does not exit on the first difference. - * - * Returns true if the two arrays are equal. - * - * TODO: evaluate if LTO inlines or changes the code flow of this function. - */ -NOINLINE_DECL -bool memequal(volatile const unsigned char* s1, volatile const unsigned char* s2, size_t length) { - unsigned char ret = 0; - - for (size_t i = 0; i < length; ++i) { - ret |= s1[i] ^ s2[i]; + SHA1Hash clientProof; + for (size_t i = 0; i < clientCredentials.clientKey->size(); i++) { + clientProof[i] = (*clientCredentials.clientKey)[i] ^ clientSignature[i]; } - return ret == 0; + return hashToBase64(clientProof); } -bool verifyServerSignature(const unsigned char saltedPassword[hashSize], +bool verifyServerSignature(const SCRAMSecrets& clientCredentials, const std::string& authMessage, const std::string& receivedServerSignature) { - // ServerKey := HMAC(SaltedPassword, "Server Key") - unsigned int hashLen; - unsigned char serverKey[hashSize]; - fassert(18703, - crypto::hmacSha1(saltedPassword, - hashSize, - reinterpret_cast<const unsigned char*>(serverKeyConst.data()), - serverKeyConst.size(), - serverKey, - &hashLen)); - // ServerSignature := HMAC(ServerKey, AuthMessage) - unsigned char serverSignature[hashSize]; - fassert(18704, - crypto::hmacSha1(serverKey, - hashSize, - reinterpret_cast<const unsigned char*>(authMessage.c_str()), - authMessage.size(), - serverSignature, - &hashLen)); - - std::string encodedServerSignature = - base64::encode(reinterpret_cast<char*>(serverSignature), sizeof(serverSignature)); + SHA1Hash serverSignature = + crypto::hmacSha1(clientCredentials.serverKey->data(), + clientCredentials.serverKey->size(), + reinterpret_cast<const unsigned char*>(authMessage.c_str()), + authMessage.size()); + + std::string encodedServerSignature = hashToBase64(serverSignature); if (encodedServerSignature.size() != receivedServerSignature.size()) { return false; } - return memequal(reinterpret_cast<const unsigned char*>(encodedServerSignature.c_str()), - reinterpret_cast<const unsigned char*>(receivedServerSignature.c_str()), - encodedServerSignature.size()); + return consttimeMemEqual( + reinterpret_cast<const unsigned char*>(encodedServerSignature.c_str()), + reinterpret_cast<const unsigned char*>(receivedServerSignature.c_str()), + encodedServerSignature.size()); +} + +bool verifyClientProof(StringData clientProof, StringData storedKey, StringData authMessage) { + // ClientSignature := HMAC(StoredKey, AuthMessage) + SHA1Hash clientSignature = + crypto::hmacSha1(reinterpret_cast<const unsigned char*>(storedKey.rawData()), + storedKey.size(), + reinterpret_cast<const unsigned char*>(authMessage.rawData()), + authMessage.size()); + + // ClientKey := ClientSignature XOR ClientProof + SHA1Hash clientKey; + for (size_t i = 0; i < clientKey.size(); i++) { + clientKey[i] = clientSignature[i] ^ clientProof.rawData()[i]; + } + + // StoredKey := H(ClientKey) + SHA1Hash computedStoredKey = crypto::sha1(clientKey.data(), clientKey.size()); + + return consttimeMemEqual(reinterpret_cast<const unsigned char*>(storedKey.rawData()), + computedStoredKey.data(), + computedStoredKey.size()); } } // namespace scram diff --git a/src/mongo/crypto/mechanism_scram.h b/src/mongo/crypto/mechanism_scram.h index 65e77b38f74..1d3b86f2fdd 100644 --- a/src/mongo/crypto/mechanism_scram.h +++ b/src/mongo/crypto/mechanism_scram.h @@ -30,12 +30,15 @@ #include <string> +#include "mongo/base/secure_allocator.h" #include "mongo/base/status.h" +#include "mongo/crypto/crypto.h" #include "mongo/db/jsobj.h" namespace mongo { namespace scram { -const unsigned int hashSize = 20; +// Convert a SHA1Hash into a base64 encoded string. +std::string hashToBase64(const SecureHandle<SHA1Hash>& hash); const std::string serverKeyConst = "Server Key"; const std::string clientKeyConst = "Client Key"; @@ -46,24 +49,53 @@ const std::string storedKeyFieldName = "storedKey"; const std::string serverKeyFieldName = "serverKey"; /* + * The precursors necessary to perform the computation which produces SCRAMSecrets. + * These are the original password, its salt, and the number of times it must be + * hashed to produce the SaltedPassword used to generate the rest of the SCRAMSecrets. + */ +struct SCRAMPresecrets { + SCRAMPresecrets(std::string hashedPassword, + std::vector<std::uint8_t> salt, + size_t iterationCount) + : hashedPassword(std::move(hashedPassword)), + salt(std::move(salt)), + iterationCount(iterationCount) {} + + std::string hashedPassword; + std::vector<std::uint8_t> salt; + size_t iterationCount; +}; + +inline bool operator==(const SCRAMPresecrets& lhs, const SCRAMPresecrets& rhs) { + return lhs.hashedPassword == rhs.hashedPassword && lhs.salt == rhs.salt && + lhs.iterationCount == rhs.iterationCount; +} + +/* * Computes the SaltedPassword from password, salt and iterationCount. */ -void generateSaltedPassword(StringData hashedPassword, - const unsigned char* salt, - const int saltLen, - const int iterationCount, - unsigned char saltedPassword[hashSize]); +SHA1Hash generateSaltedPassword(const SCRAMPresecrets& presecrets); + +/* + * Stores all of the keys, generated from a password, needed for a client or server to perform a + * SCRAM handshake. This structure will secureZeroMemory itself on destruction. + */ +struct SCRAMSecrets { + SecureHandle<SHA1Hash> clientKey; + SecureHandle<SHA1Hash> storedKey; + SecureHandle<SHA1Hash> serverKey; +}; /* - * Computes the SCRAM secrets storedKey and serverKey using the salt 'salt' + * Computes the SCRAM secrets clientKey, storedKey, and serverKey using the salt 'salt' * and iteration count 'iterationCount' as defined in RFC5802 (server side). */ -void generateSecrets(const std::string& hashedPassword, - const unsigned char salt[], - size_t saltLen, - size_t iterationCount, - unsigned char storedKey[hashSize], - unsigned char serverKey[hashSize]); +SCRAMSecrets generateSecrets(const SCRAMPresecrets& presecrets); + +/* + * Computes the ClientKey and StoredKey from SaltedPassword (client side). + */ +SCRAMSecrets generateSecrets(const SHA1Hash& saltedPassword); /* * Generates the user salt and the SCRAM secrets storedKey and serverKey as @@ -72,9 +104,9 @@ void generateSecrets(const std::string& hashedPassword, BSONObj generateCredentials(const std::string& hashedPassword, int iterationCount); /* - * Computes the ClientProof from SaltedPassword and authMessage (client side). + * Computes the ClientProof from ClientKey, StoredKey, and authMessage (client side). */ -std::string generateClientProof(const unsigned char saltedPassword[hashSize], +std::string generateClientProof(const SCRAMSecrets& clientCredentials, const std::string& authMessage); /* @@ -89,8 +121,14 @@ bool validatePassword(const std::string& hashedPassword, /* * Verifies ServerSignature (client side). */ -bool verifyServerSignature(const unsigned char saltedPassword[hashSize], +bool verifyServerSignature(const SCRAMSecrets& clientCredentials, const std::string& authMessage, const std::string& serverSignature); + +/* + * Verifies ClientProof (server side). + */ +bool verifyClientProof(StringData clientProof, StringData storedKey, StringData authMessage); + } // namespace scram } // namespace mongo diff --git a/src/mongo/db/auth/sasl_plain_server_conversation.cpp b/src/mongo/db/auth/sasl_plain_server_conversation.cpp index b5f0b9e3c8f..d68a1ab92ad 100644 --- a/src/mongo/db/auth/sasl_plain_server_conversation.cpp +++ b/src/mongo/db/auth/sasl_plain_server_conversation.cpp @@ -80,18 +80,16 @@ StatusWith<bool> SaslPLAINServerConversation::step(StringData inputData, std::st } } else { // Handle schemaVersion28SCRAM (SCRAM only mode) - unsigned char storedKey[scram::hashSize]; - unsigned char serverKey[scram::hashSize]; - - scram::generateSecrets( + std::string decodedSalt = base64::decode(creds.scram.salt); + scram::SCRAMSecrets secrets = scram::generateSecrets(scram::SCRAMPresecrets( authDigest, - reinterpret_cast<const unsigned char*>(base64::decode(creds.scram.salt).c_str()), - 16, - creds.scram.iterationCount, - storedKey, - serverKey); + std::vector<std::uint8_t>(reinterpret_cast<const std::uint8_t*>(decodedSalt.c_str()), + reinterpret_cast<const std::uint8_t*>(decodedSalt.c_str()) + + 16), + creds.scram.iterationCount)); if (creds.scram.storedKey != - base64::encode(reinterpret_cast<const char*>(storedKey), scram::hashSize)) { + base64::encode(reinterpret_cast<const char*>(secrets.storedKey->data()), + secrets.storedKey->size())) { return StatusWith<bool>(ErrorCodes::AuthenticationFailed, mongoutils::str::stream() << "Incorrect user name or password"); } diff --git a/src/mongo/db/auth/sasl_scramsha1_server_conversation.cpp b/src/mongo/db/auth/sasl_scramsha1_server_conversation.cpp index 9fd8496b7bc..11848fb5ba9 100644 --- a/src/mongo/db/auth/sasl_scramsha1_server_conversation.cpp +++ b/src/mongo/db/auth/sasl_scramsha1_server_conversation.cpp @@ -275,60 +275,23 @@ StatusWith<bool> SaslSCRAMSHA1ServerConversation::_secondStep(const std::vector< // ClientKey := ClientSignature XOR ClientProof // ServerSignature := HMAC(ServerKey, AuthMessage) - unsigned int hashLen = 0; - unsigned char clientSignature[scram::hashSize]; - - std::string decodedStoredKey = base64::decode(_creds.scram.storedKey); - // ClientSignature := HMAC(StoredKey, AuthMessage) - fassert(18662, - crypto::hmacSha1(reinterpret_cast<const unsigned char*>(decodedStoredKey.c_str()), - scram::hashSize, - reinterpret_cast<const unsigned char*>(_authMessage.c_str()), - _authMessage.size(), - clientSignature, - &hashLen)); - - fassert(18658, hashLen == scram::hashSize); - - try { - clientProof = base64::decode(clientProof); - } catch (const DBException& ex) { - return StatusWith<bool>(ex.toStatus()); - } - const unsigned char* decodedClientProof = - reinterpret_cast<const unsigned char*>(clientProof.c_str()); - - // ClientKey := ClientSignature XOR ClientProof - unsigned char clientKey[scram::hashSize]; - for (size_t i = 0; i < scram::hashSize; i++) { - clientKey[i] = clientSignature[i] ^ decodedClientProof[i]; - } - - // StoredKey := H(ClientKey) - unsigned char computedStoredKey[scram::hashSize]; - fassert(18659, crypto::sha1(clientKey, scram::hashSize, computedStoredKey)); - - if (memcmp(decodedStoredKey.c_str(), computedStoredKey, scram::hashSize) != 0) { + if (!scram::verifyClientProof( + base64::decode(clientProof), base64::decode(_creds.scram.storedKey), _authMessage)) { return StatusWith<bool>(ErrorCodes::AuthenticationFailed, mongoutils::str::stream() << "SCRAM-SHA-1 authentication failed, storedKey mismatch"); } // ServerSignature := HMAC(ServerKey, AuthMessage) - unsigned char serverSignature[scram::hashSize]; std::string decodedServerKey = base64::decode(_creds.scram.serverKey); - fassert(18660, - crypto::hmacSha1(reinterpret_cast<const unsigned char*>(decodedServerKey.c_str()), - scram::hashSize, - reinterpret_cast<const unsigned char*>(_authMessage.c_str()), - _authMessage.size(), - serverSignature, - &hashLen)); - - fassert(18661, hashLen == scram::hashSize); + SHA1Hash serverSignature = + crypto::hmacSha1(reinterpret_cast<const unsigned char*>(decodedServerKey.c_str()), + decodedServerKey.size(), + reinterpret_cast<const unsigned char*>(_authMessage.c_str()), + _authMessage.size()); StringBuilder sb; - sb << "v=" << base64::encode(reinterpret_cast<char*>(serverSignature), scram::hashSize); + sb << "v=" << scram::hashToBase64(serverSignature); *outputData = sb.str(); return StatusWith<bool>(false); diff --git a/src/mongo/executor/network_interface_asio_auth.cpp b/src/mongo/executor/network_interface_asio_auth.cpp index 22a35ce8f8f..dcf8dc8a5ce 100644 --- a/src/mongo/executor/network_interface_asio_auth.cpp +++ b/src/mongo/executor/network_interface_asio_auth.cpp @@ -182,8 +182,7 @@ void NetworkInterfaceASIO::_authenticate(AsyncOp* op) { }; auto params = getInternalUserAuthParamsWithFallback(); - auth::authenticateClient( - params, op->request().target.host(), clientName, runCommandHook, authHook); + auth::authenticateClient(params, op->request().target, clientName, runCommandHook, authHook); } } // namespace executor diff --git a/src/mongo/shell/utils.js b/src/mongo/shell/utils.js index 43925df86a4..2d86a1f6ac3 100644 --- a/src/mongo/shell/utils.js +++ b/src/mongo/shell/utils.js @@ -283,7 +283,15 @@ jsTest.authenticateNodes = function(nodes) { assert.soonNoExcept(function() { for (var i = 0; i < nodes.length; i++) { // Don't try to authenticate to arbiters - res = nodes[i].getDB("admin").runCommand({replSetGetStatus: 1}); + try { + res = nodes[i].getDB("admin").runCommand({replSetGetStatus: 1}); + } catch (e) { + // ReplicaSet tests which don't use auth are allowed to have nodes crash during + // startup. To allow tests which use to behavior to work with auth, + // attempting authentication against a dead node should be non-fatal. + print("Caught exception getting replSetStatus while authenticating: " + e); + continue; + } if (res.myState == 7) { continue; } |