summaryrefslogtreecommitdiff
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
parent78800570b838dd7e2bb53939d9d1a752d71e5ae3 (diff)
downloadmongo-16e83332ed20e4054324a1a7714506e74eed5180.tar.gz
SERVER-26952: Cache SCRAM-SHA-1 ClientKey
(cherry picked from commit 47da0b53f9cd27aeec1d2822780784866269a47d)
-rw-r--r--buildscripts/resmokeconfig/suites/multiversion_auth.yml37
-rw-r--r--etc/evergreen.yml14
-rw-r--r--jstests/libs/test_background_ops.js4
-rw-r--r--jstests/multiVersion/libs/multi_rs.js1
-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
-rw-r--r--src/mongo/crypto/SConscript21
-rw-r--r--src/mongo/crypto/crypto.h16
-rw-r--r--src/mongo/crypto/crypto_openssl.cpp33
-rw-r--r--src/mongo/crypto/crypto_test.cpp30
-rw-r--r--src/mongo/crypto/crypto_tom.cpp47
-rw-r--r--src/mongo/crypto/mechanism_scram.cpp290
-rw-r--r--src/mongo/crypto/mechanism_scram.h70
-rw-r--r--src/mongo/db/auth/sasl_plain_server_conversation.cpp18
-rw-r--r--src/mongo/db/auth/sasl_scramsha1_server_conversation.cpp53
-rw-r--r--src/mongo/executor/network_interface_asio_auth.cpp3
-rw-r--r--src/mongo/shell/utils.js10
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;
}