From 298f7758a070630917fe6c67603a2951cab29322 Mon Sep 17 00:00:00 2001 From: Sara Golemon Date: Thu, 11 Jan 2018 14:27:11 -0500 Subject: SERVER-32836 Refactor SCRAM mechanism to be block independent --- src/mongo/client/SConscript | 2 +- .../client/sasl_scramsha1_client_conversation.cpp | 10 +- .../client/sasl_scramsha1_client_conversation.h | 2 +- src/mongo/client/scram_sha1_client_cache.cpp | 10 +- src/mongo/client/scram_sha1_client_cache.h | 10 +- src/mongo/crypto/SConscript | 16 +- src/mongo/crypto/mechanism_scram.cpp | 215 -------------- src/mongo/crypto/mechanism_scram.h | 311 ++++++++++++++------- src/mongo/crypto/mechanism_scram_test.cpp | 84 ++++++ src/mongo/db/auth/SConscript | 3 +- src/mongo/db/auth/authorization_manager_test.cpp | 2 +- src/mongo/db/auth/authorization_session_test.cpp | 2 +- .../db/auth/sasl_authentication_session_test.cpp | 2 +- .../db/auth/sasl_plain_server_conversation.cpp | 6 +- .../db/auth/sasl_scramsha1_server_conversation.cpp | 14 +- src/mongo/db/auth/sasl_scramsha1_test.cpp | 46 +-- src/mongo/db/auth/security_key.cpp | 12 +- src/mongo/db/auth/user_document_parser_test.cpp | 2 +- src/mongo/db/commands/user_management_commands.cpp | 4 +- src/mongo/db/logical_session_id_test.cpp | 4 +- 20 files changed, 367 insertions(+), 390 deletions(-) delete mode 100644 src/mongo/crypto/mechanism_scram.cpp create mode 100644 src/mongo/crypto/mechanism_scram_test.cpp diff --git a/src/mongo/client/SConscript b/src/mongo/client/SConscript index c1f90812b70..8b92c437ea5 100644 --- a/src/mongo/client/SConscript +++ b/src/mongo/client/SConscript @@ -106,8 +106,8 @@ saslClientEnv.Library( target='sasl_client', source=saslClientSource, LIBDEPS=[ + '$BUILD_DIR/mongo/base/secure_allocator', '$BUILD_DIR/mongo/bson/util/bson_extract', - '$BUILD_DIR/mongo/crypto/scramauth', '$BUILD_DIR/mongo/executor/remote_command', '$BUILD_DIR/mongo/rpc/command_status', '$BUILD_DIR/mongo/rpc/metadata', diff --git a/src/mongo/client/sasl_scramsha1_client_conversation.cpp b/src/mongo/client/sasl_scramsha1_client_conversation.cpp index 1c04d395708..0dc84fc0818 100644 --- a/src/mongo/client/sasl_scramsha1_client_conversation.cpp +++ b/src/mongo/client/sasl_scramsha1_client_conversation.cpp @@ -179,7 +179,7 @@ StatusWith SaslSCRAMSHA1ClientConversation::_secondStep(const std::vector< return StatusWith(ex.toStatus()); } - scram::SCRAMPresecrets presecrets( + scram::SHA1Presecrets presecrets( _saslClientSession->getParameter(SaslClientSession::parameterPassword).toString(), std::vector(decodedSalt.begin(), decodedSalt.end()), iterationCount); @@ -191,16 +191,16 @@ StatusWith SaslSCRAMSHA1ClientConversation::_secondStep(const std::vector< _credentials = _clientCache->getCachedSecrets(targetHost.getValue(), presecrets); if (!_credentials) { - _credentials = scram::generateSecrets(presecrets); + _credentials = presecrets; _clientCache->setCachedSecrets( std::move(targetHost.getValue()), std::move(presecrets), _credentials); } } else { - _credentials = scram::generateSecrets(presecrets); + _credentials = presecrets; } - std::string clientProof = scram::generateClientProof(_credentials, _authMessage); + std::string clientProof = _credentials.generateClientProof(_authMessage); StringBuilder sb; sb << "c=biws,r=" << nonce << ",p=" << clientProof; @@ -241,7 +241,7 @@ StatusWith SaslSCRAMSHA1ClientConversation::_thirdStep(const std::vector lock(_hostToSecretsMutex); // Search the cache for a record associated with the host we're trying to connect to. @@ -42,7 +42,7 @@ scram::SCRAMSecrets SCRAMSHA1ClientCache::getCachedSecrets( // 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; + const scram::SHA1Presecrets& foundPresecrets = foundSecret->second.first; if (foundPresecrets == presecrets) { return foundSecret->second.second; } @@ -51,8 +51,8 @@ scram::SCRAMSecrets SCRAMSHA1ClientCache::getCachedSecrets( } void SCRAMSHA1ClientCache::setCachedSecrets(HostAndPort target, - scram::SCRAMPresecrets presecrets, - scram::SCRAMSecrets secrets) { + scram::SHA1Presecrets presecrets, + scram::SHA1Secrets secrets) { const stdx::lock_guard lock(_hostToSecretsMutex); decltype(_hostToSecrets)::iterator it; diff --git a/src/mongo/client/scram_sha1_client_cache.h b/src/mongo/client/scram_sha1_client_cache.h index ac6a139af2c..84f03397cfd 100644 --- a/src/mongo/client/scram_sha1_client_cache.h +++ b/src/mongo/client/scram_sha1_client_cache.h @@ -68,20 +68,20 @@ public: * match those recorded for the hostname. Otherwise, no secrets * are returned. */ - scram::SCRAMSecrets getCachedSecrets(const HostAndPort& target, - const scram::SCRAMPresecrets& presecrets) const; + scram::SHA1Secrets getCachedSecrets(const HostAndPort& target, + const scram::SHA1Presecrets& 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); + scram::SHA1Presecrets presecrets, + scram::SHA1Secrets secrets); private: mutable stdx::mutex _hostToSecretsMutex; - stdx::unordered_map> + stdx::unordered_map> _hostToSecrets; }; diff --git a/src/mongo/crypto/SConscript b/src/mongo/crypto/SConscript index 0846f31dd22..381405abd94 100644 --- a/src/mongo/crypto/SConscript +++ b/src/mongo/crypto/SConscript @@ -67,14 +67,6 @@ if env.TargetOSIs('darwin', 'macOS'): 'sha256_block', ]) -env.Library('scramauth', - ['mechanism_scram.cpp'], - LIBDEPS=['$BUILD_DIR/mongo/base', - '$BUILD_DIR/mongo/base/secure_allocator', - '$BUILD_DIR/mongo/util/secure_compare_memory', - '$BUILD_DIR/mongo/util/secure_zero_memory', - 'sha_block_${MONGO_CRYPTO}']) - env.CppUnitTest('sha1_block_test', ['sha1_block_test.cpp'], LIBDEPS=['sha_block_${MONGO_CRYPTO}']) @@ -82,3 +74,11 @@ env.CppUnitTest('sha1_block_test', env.CppUnitTest('sha256_block_test', ['sha256_block_test.cpp'], LIBDEPS=['sha_block_${MONGO_CRYPTO}']) + +env.CppUnitTest('mechanism_scram_test', + ['mechanism_scram_test.cpp'], + LIBDEPS_PRIVATE=[ + '$BUILD_DIR/mongo/base', + '$BUILD_DIR/mongo/base/secure_allocator', + 'sha_block_${MONGO_CRYPTO}', + ]) diff --git a/src/mongo/crypto/mechanism_scram.cpp b/src/mongo/crypto/mechanism_scram.cpp deleted file mode 100644 index d0dc5965c99..00000000000 --- a/src/mongo/crypto/mechanism_scram.cpp +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright (C) 2014 10gen 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 . - * - * 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/crypto/mechanism_scram.h" - -#include - -#include "mongo/platform/random.h" -#include "mongo/util/base64.h" -#include "mongo/util/secure_compare_memory.h" -#include "mongo/util/secure_zero_memory.h" - -namespace mongo { -namespace scram { - -using std::unique_ptr; - -// Compute the SCRAM step Hi() as defined in RFC5802 -static SHA1Block HMACIteration(const unsigned char input[], - size_t inputLen, - const unsigned char salt[], - size_t saltLen, - unsigned int iterationCount) { - SHA1Block output; - SHA1Block 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 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; - startKey[saltLen + 2] = 0; - startKey[saltLen + 3] = 1; - - // U1 = HMAC(input, salt + 0001) - output = SHA1Block::computeHmac(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++) { - intermediateDigest = SHA1Block::computeHmac( - input, inputLen, intermediateDigest.data(), intermediateDigest.size()); - output.xorInline(intermediateDigest); - } - - return output; -} - -// Iterate the hash function to generate SaltedPassword -SHA1Block generateSaltedPassword(const SCRAMPresecrets& presecrets) { - // saltedPassword = Hi(hashedPassword, salt) - SHA1Block saltedPassword = - HMACIteration(reinterpret_cast(presecrets.hashedPassword.c_str()), - presecrets.hashedPassword.size(), - presecrets.salt.data(), - presecrets.salt.size(), - presecrets.iterationCount); - - return saltedPassword; -} - -SCRAMSecrets generateSecrets(const SCRAMPresecrets& presecrets) { - SHA1Block saltedPassword = generateSaltedPassword(presecrets); - return generateSecrets(saltedPassword); -} - -SCRAMSecrets generateSecrets(const SHA1Block& saltedPassword) { - auto generateAndStoreSecrets = [&saltedPassword]( - SHA1Block& clientKey, SHA1Block& storedKey, SHA1Block& serverKey) { - - // ClientKey := HMAC(saltedPassword, "Client Key") - clientKey = - SHA1Block::computeHmac(saltedPassword.data(), - saltedPassword.size(), - reinterpret_cast(clientKeyConst.data()), - clientKeyConst.size()); - - // StoredKey := H(clientKey) - storedKey = SHA1Block::computeHash(clientKey.data(), clientKey.size()); - - // ServerKey := HMAC(SaltedPassword, "Server Key") - serverKey = - SHA1Block::computeHmac(saltedPassword.data(), - saltedPassword.size(), - reinterpret_cast(serverKeyConst.data()), - serverKeyConst.size()); - }; - return SCRAMSecrets(std::move(generateAndStoreSecrets)); -} - - -BSONObj generateCredentials(const std::string& hashedPassword, int iterationCount) { - const int saltLenQWords = 2; - - // Generate salt - uint64_t userSalt[saltLenQWords]; - - unique_ptr sr(SecureRandom::create()); - - userSalt[0] = sr->nextInt64(); - userSalt[1] = sr->nextInt64(); - std::string encodedUserSalt = - base64::encode(reinterpret_cast(userSalt), sizeof(userSalt)); - - // Compute SCRAM secrets serverKey and storedKey - auto secrets = generateSecrets( - SCRAMPresecrets(hashedPassword, - std::vector(reinterpret_cast(userSalt), - reinterpret_cast(userSalt) + - saltLenQWords * sizeof(uint64_t)), - iterationCount)); - - std::string encodedStoredKey = secrets->storedKey.toString(); - std::string encodedServerKey = secrets->serverKey.toString(); - - return BSON(iterationCountFieldName << iterationCount << saltFieldName << encodedUserSalt - << storedKeyFieldName - << encodedStoredKey - << serverKeyFieldName - << encodedServerKey); -} - -std::string generateClientProof(const SCRAMSecrets& clientCredentials, - const std::string& authMessage) { - // ClientSignature := HMAC(StoredKey, AuthMessage) - SHA1Block clientSignature = - SHA1Block::computeHmac(clientCredentials->storedKey.data(), - clientCredentials->storedKey.size(), - reinterpret_cast(authMessage.c_str()), - authMessage.size()); - - clientSignature.xorInline(clientCredentials->clientKey); - return clientSignature.toString(); -} - -bool verifyServerSignature(const SCRAMSecrets& clientCredentials, - const std::string& authMessage, - const std::string& receivedServerSignature) { - // ServerSignature := HMAC(ServerKey, AuthMessage) - SHA1Block serverSignature = - SHA1Block::computeHmac(clientCredentials->serverKey.data(), - clientCredentials->serverKey.size(), - reinterpret_cast(authMessage.c_str()), - authMessage.size()); - - std::string encodedServerSignature = serverSignature.toString(); - - if (encodedServerSignature.size() != receivedServerSignature.size()) { - return false; - } - - return consttimeMemEqual( - reinterpret_cast(encodedServerSignature.c_str()), - reinterpret_cast(receivedServerSignature.c_str()), - encodedServerSignature.size()); -} - -bool verifyClientProof(StringData clientProof, StringData storedKey, StringData authMessage) { - // ClientSignature := HMAC(StoredKey, AuthMessage) - SHA1Block clientSignature = - SHA1Block::computeHmac(reinterpret_cast(storedKey.rawData()), - storedKey.size(), - reinterpret_cast(authMessage.rawData()), - authMessage.size()); - - auto clientProofSHA1Status = SHA1Block::fromBuffer( - reinterpret_cast(clientProof.rawData()), clientProof.size()); - uassertStatusOK(clientProofSHA1Status); - clientSignature.xorInline(clientProofSHA1Status.getValue()); - - // StoredKey := H(clientSignature) - SHA1Block computedStoredKey = - SHA1Block::computeHash(clientSignature.data(), clientSignature.size()); - - if (storedKey.size() != computedStoredKey.size()) { - return false; - } - - return consttimeMemEqual(reinterpret_cast(storedKey.rawData()), - computedStoredKey.data(), - computedStoredKey.size()); -} - -} // namespace scram -} // namespace mongo diff --git a/src/mongo/crypto/mechanism_scram.h b/src/mongo/crypto/mechanism_scram.h index 25afe543bbb..741566252ab 100644 --- a/src/mongo/crypto/mechanism_scram.h +++ b/src/mongo/crypto/mechanism_scram.h @@ -28,151 +28,264 @@ #pragma once +#include +#include #include +#include #include "mongo/base/secure_allocator.h" #include "mongo/base/status.h" #include "mongo/crypto/sha1_block.h" #include "mongo/db/auth/authorization_manager.h" #include "mongo/db/jsobj.h" +#include "mongo/platform/random.h" +#include "mongo/util/assert_util.h" namespace mongo { namespace scram { -const std::string serverKeyConst = "Server Key"; -const std::string clientKeyConst = "Client Key"; +constexpr auto kServerKeyConst = "Server Key"_sd; +constexpr auto kClientKeyConst = "Client Key"_sd; -const std::string iterationCountFieldName = "iterationCount"; -const std::string saltFieldName = "salt"; -const std::string storedKeyFieldName = "storedKey"; -const std::string serverKeyFieldName = "serverKey"; +constexpr auto kIterationCountFieldName = "iterationCount"_sd; +constexpr auto kSaltFieldName = "salt"_sd; +constexpr auto kStoredKeyFieldName = "storedKey"_sd; +constexpr auto kServerKeyFieldName = "serverKey"_sd; -/* - * The precursors necessary to perform the computation which produces SCRAMSecrets. +const int kIterationCountMinimum = 4096; + +/* 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 salt_, - size_t iterationCount_) - : hashedPassword(std::move(hashedPassword)), - salt(std::move(salt_)), - iterationCount(iterationCount_) { - uassert(ErrorCodes::BadValue, - "Invalid salt for SCRAM mechanism", - salt.size() >= kSaltLengthMin); - } - - std::string hashedPassword; - std::vector salt; - size_t iterationCount; +template +class Presecrets { +public: + Presecrets(std::string password, std::vector salt, size_t iterationCount) + : _password(std::move(password)), _salt(std::move(salt)), _iterationCount(iterationCount) { + uassert(17450, "invalid salt length provided", _salt.size() == saltLength()); + uassert(50662, "invalid iteration count", _iterationCount >= kIterationCountMinimum); + } + + HashBlock generateSaltedPassword() const noexcept { + // saltedPassword = Hi(hashedPassword, salt) + + // Reserve a HashBlock::kHashLength block for the initial key. + // We use saltLength() salts, and reserve the extra for a suffix mandated by RFC5802. + std::array startKey; + std::copy(_salt.cbegin(), _salt.cend(), startKey.begin()); + startKey[_salt.size() + 0] = 0; + startKey[_salt.size() + 1] = 0; + startKey[_salt.size() + 2] = 0; + startKey[_salt.size() + 3] = 1; + + // U1 = HMAC(input, salt + 0001) + auto output = + HashBlock::computeHmac(reinterpret_cast(_password.c_str()), + _password.size(), + startKey.data(), + startKey.size()); + auto intermediate = output; + + // intermediateDigest contains Ui and output contains the accumulated XOR:ed result + invariant(_iterationCount >= kIterationCountMinimum); + for (size_t i = 1; i < _iterationCount; ++i) { + intermediate = + HashBlock::computeHmac(reinterpret_cast(_password.c_str()), + _password.size(), + intermediate.data(), + intermediate.size()); + output.xorInline(intermediate); + } + + return output; + } + + static std::vector generateSecureRandomSalt() { + // Express salt length as a number of quad words, rounded up. + constexpr auto qwords = (saltLength() + sizeof(std::int64_t) - 1) / sizeof(std::int64_t); + std::array userSalt; + + std::unique_ptr sr(SecureRandom::create()); + std::generate(userSalt.begin(), userSalt.end(), [&sr] { return sr->nextInt64(); }); + return std::vector(reinterpret_cast(userSalt.data()), + reinterpret_cast(userSalt.data()) + + saltLength()); + } private: - static const size_t kSaltLengthMin = 16; + template + friend bool operator==(const Presecrets&, const Presecrets&); + + auto equalityLens() const { + return std::tie(_password, _salt, _iterationCount); + } + + static constexpr auto saltLength() { + return HashBlock::kHashLength - 4; + } + + std::string _password; + std::vector _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; +template +bool operator==(const Presecrets& lhs, const Presecrets& rhs) { + return lhs.equalityLens() == rhs.equalityLens(); +} +template +bool operator!=(const Presecrets& lhs, const Presecrets& rhs) { + return !(lhs == rhs); } -/* - * Computes the SaltedPassword from password, salt and iterationCount. - */ -SHA1Block generateSaltedPassword(const SCRAMPresecrets& presecrets); - -/* - * Stores all of the keys, generated from a password, needed for a client or server to perform a +/* Stores all of the keys, generated from a password, needed for a client or server to perform a * SCRAM handshake. * These keys are reference counted, and allocated using the SecureAllocator. * May be unpopulated. SCRAMSecrets created via the default constructor are unpopulated. * The behavior is undefined if the accessors are called when unpopulated. */ -class SCRAMSecrets { +template +class Secrets { private: - struct SCRAMSecretsHolder { - SHA1Block clientKey; - SHA1Block storedKey; - SHA1Block serverKey; + struct SecretsHolder { + HashBlock clientKey; + HashBlock storedKey; + HashBlock serverKey; }; + using SecureSecrets = SecureAllocatorAuthDomain::SecureHandle; public: - // Creates an unpopulated SCRAMSecrets object. - SCRAMSecrets() = default; + Secrets() = default; - // Creates a populated SCRAMSecrets object. First, allocates secure storage, then provides it - // to a callback, which fills the memory. - template - explicit SCRAMSecrets(T initializationFun) - : _ptr(std::make_shared>()) { - initializationFun((*this)->clientKey, (*this)->storedKey, (*this)->serverKey); + Secrets(StringData client, StringData stored, StringData server) + : _ptr(std::make_shared()) { + if (!client.empty()) { + (*_ptr)->clientKey = uassertStatusOK(HashBlock::fromBuffer( + reinterpret_cast(client.rawData()), client.size())); + } + (*_ptr)->storedKey = uassertStatusOK(HashBlock::fromBuffer( + reinterpret_cast(stored.rawData()), stored.size())); + (*_ptr)->serverKey = uassertStatusOK(HashBlock::fromBuffer( + reinterpret_cast(server.rawData()), stored.size())); } - // Returns true if the underlying shared_pointer is populated. - explicit operator bool() const { - return static_cast(_ptr); - } + Secrets(const HashBlock& saltedPassword) : _ptr(std::make_shared()) { + // ClientKey := HMAC(saltedPassword, "Client Key") + (*_ptr)->clientKey = HashBlock::computeHmac( + saltedPassword.data(), + saltedPassword.size(), + reinterpret_cast(kClientKeyConst.rawData()), + kClientKeyConst.size()); + // StoredKey := H(clientKey) + (*_ptr)->storedKey = HashBlock::computeHash(clientKey().data(), clientKey().size()); - const SecureAllocatorAuthDomain::SecureHandle& operator*() const& { - invariant(_ptr); - return *_ptr; + // ServerKey := HMAC(SaltedPassword, "Server Key") + (*_ptr)->serverKey = HashBlock::computeHmac( + saltedPassword.data(), + saltedPassword.size(), + reinterpret_cast(kServerKeyConst.rawData()), + kServerKeyConst.size()); } - void operator*() && = delete; + Secrets(const Presecrets& presecrets) + : Secrets(presecrets.generateSaltedPassword()) {} - const SecureAllocatorAuthDomain::SecureHandle& operator->() const& { - invariant(_ptr); - return *_ptr; + std::string generateClientProof(StringData authMessage) const { + // ClientProof := HMAC(StoredKey, AuthMessage) ^ ClientKey + auto proof = + HashBlock::computeHmac(storedKey().data(), + storedKey().size(), + reinterpret_cast(authMessage.rawData()), + authMessage.size()); + proof.xorInline(clientKey()); + return proof.toString(); } - void operator->() && = delete; + bool verifyClientProof(StringData authMessage, StringData proof) const { + // ClientKey := HMAC(StoredKey, AuthMessage) ^ ClientProof + auto key = + HashBlock::computeHmac(storedKey().data(), + storedKey().size(), + reinterpret_cast(authMessage.rawData()), + authMessage.size()); + key.xorInline(uassertStatusOK(HashBlock::fromBuffer( + reinterpret_cast(proof.rawData()), proof.size()))); -private: - std::shared_ptr> _ptr; -}; + // StoredKey := H(ClientKey) + auto exp = HashBlock::computeHash(key.data(), key.size()); -/* - * Computes the SCRAM secrets clientKey, storedKey, and serverKey using the salt 'salt' - * and iteration count 'iterationCount' as defined in RFC5802 (server side). - */ -SCRAMSecrets generateSecrets(const SCRAMPresecrets& presecrets); + if ((exp.size() != HashBlock::kHashLength) || + (storedKey().size() != HashBlock::kHashLength)) { + return false; + } -/* - * Computes the ClientKey and StoredKey from SaltedPassword (client side). - */ -SCRAMSecrets generateSecrets(const SHA1Block& saltedPassword); + return consttimeMemEqual(reinterpret_cast(exp.data()), + storedKey().data(), + HashBlock::kHashLength); + } + std::string generateServerSignature(StringData authMessage) const { + // ServerSignature := HMAC(ServerKey, AuthMessage) + return HashBlock::computeHmac(serverKey().data(), + serverKey().size(), + reinterpret_cast(authMessage.rawData()), + authMessage.size()) + .toString(); + } + bool verifyServerSignature(StringData authMessage, StringData sig) const { + // ServerSignature := HMAC(ServerKey, AuthMessage) + const auto exp = + HashBlock::computeHmac(serverKey().data(), + serverKey().size(), + reinterpret_cast(authMessage.rawData()), + authMessage.size()); -/* - * Generates the user salt and the SCRAM secrets storedKey and serverKey as - * defined in RFC5802 (server side). - */ -BSONObj generateCredentials(const std::string& hashedPassword, int iterationCount); + if ((sig.size() != HashBlock::kHashLength) || (exp.size() != HashBlock::kHashLength)) { + return false; + } + return consttimeMemEqual(reinterpret_cast(sig.rawData()), + reinterpret_cast(exp.data()), + HashBlock::kHashLength); + } -/* - * Computes the ClientProof from ClientKey, StoredKey, and authMessage (client side). - */ -std::string generateClientProof(const SCRAMSecrets& clientCredentials, - const std::string& authMessage); + static BSONObj generateCredentials(std::string password, int iterationCount) { + auto salt = Presecrets::generateSecureRandomSalt(); + Secrets secrets(Presecrets(password, salt, iterationCount)); + const auto encodedSalt = base64::encode(reinterpret_cast(salt.data()), salt.size()); + return BSON(kIterationCountFieldName << iterationCount << kSaltFieldName << encodedSalt + << kStoredKeyFieldName + << secrets.storedKey().toString() + << kServerKeyFieldName + << secrets.serverKey().toString()); + } -/* - * Validates that the provided password 'hashedPassword' generates the serverKey - * 'serverKey' given iteration count 'iterationCount' and salt 'salt'. - */ -bool validatePassword(const std::string& hashedPassword, - int iterationCount, - const std::string& salt, - const std::string& storedKey); + const HashBlock& clientKey() const { + auto& ret = (*_ptr)->clientKey; + uassert( + ErrorCodes::BadValue, "Invalid SCRAM client key", ret.size() == HashBlock::kHashLength); + return ret; + } + const HashBlock& storedKey() const { + auto& ret = (*_ptr)->storedKey; + uassert( + ErrorCodes::BadValue, "Invalid SCRAM stored key", ret.size() == HashBlock::kHashLength); + return ret; + } + const HashBlock& serverKey() const { + auto& ret = (*_ptr)->serverKey; + uassert( + ErrorCodes::BadValue, "Invalid SCRAM server key", ret.size() == HashBlock::kHashLength); + return ret; + } -/* - * Verifies ServerSignature (client side). - */ -bool verifyServerSignature(const SCRAMSecrets& clientCredentials, - const std::string& authMessage, - const std::string& serverSignature); + operator bool() const { + return (bool)_ptr; + } -/* - * Verifies ClientProof (server side). - */ -bool verifyClientProof(StringData clientProof, StringData storedKey, StringData authMessage); +private: + std::shared_ptr _ptr; +}; + +using SHA1Presecrets = Presecrets; +using SHA1Secrets = Secrets; } // namespace scram } // namespace mongo diff --git a/src/mongo/crypto/mechanism_scram_test.cpp b/src/mongo/crypto/mechanism_scram_test.cpp new file mode 100644 index 00000000000..5e67a1dfaa9 --- /dev/null +++ b/src/mongo/crypto/mechanism_scram_test.cpp @@ -0,0 +1,84 @@ +/** + * Copyright (C) 2018 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 . + * + * 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. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault + +#include "mongo/platform/basic.h" + +#include "mongo/crypto/mechanism_scram.h" +#include "mongo/unittest/unittest.h" +#include "mongo/util/log.h" + +namespace mongo { +namespace scram { +namespace { + +TEST(MechanismScram, BasicVectors) { + const std::vector kBadSha1Salt{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + + ASSERT_EQ(kBadSha1Salt.size(), SHA1Block::kHashLength - 4); + + SHA1Presecrets presecrets("password", kBadSha1Salt, 4096); + ASSERT_EQ(presecrets.generateSaltedPassword().toString(), "531aYHrF581Skow4E0gCWLw/Ibo="); + + SHA1Secrets secrets(presecrets); + ASSERT_EQ(secrets.clientKey().toString(), "wiHbIsPcvJo230S6Qf5xYCDrhb0="); + ASSERT_EQ(secrets.storedKey().toString(), "SjXiaB2hLRr8aMUyXMVEw7H1jSI="); + ASSERT_EQ(secrets.serverKey().toString(), "FilAoFIclBukd3xZxBvYMXTU3HM="); + + const StringData authMessage("secret"); + auto proof = secrets.generateClientProof(authMessage); + ASSERT_EQ(proof, "y+cpoAm0YlN30GuNgN4B9xghi4E="); + ASSERT_TRUE(secrets.verifyClientProof(authMessage, base64::decode(proof))); + + auto sig = secrets.generateServerSignature(authMessage); + ASSERT_EQ(sig, "kiZS90Kz4/yaYZn9JieHtcRzXR0="); + ASSERT_TRUE(secrets.verifyServerSignature(authMessage, base64::decode(sig))); +} + +TEST(MechanismScram, generateCredentials) { + const auto bson = SHA1Secrets::generateCredentials("password", 4096); + + ASSERT_EQ(bson.nFields(), 4); + + ASSERT_TRUE(bson.hasField("salt")); + ASSERT_EQ(base64::decode(bson.getStringField("salt")).size(), SHA1Block::kHashLength - 4); + + ASSERT_TRUE(bson.hasField("storedKey")); + ASSERT_EQ(base64::decode(bson.getStringField("storedKey")).size(), SHA1Block::kHashLength); + + ASSERT_TRUE(bson.hasField("serverKey")); + ASSERT_EQ(base64::decode(bson.getStringField("serverKey")).size(), SHA1Block::kHashLength); + + ASSERT_TRUE(bson.hasField("iterationCount")); + ASSERT_EQ(bson.getIntField("iterationCount"), 4096); +} + +} // namespace +} // namespace scram +} // namespace mongo diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript index 5e18c13d4b1..7c29c354026 100644 --- a/src/mongo/db/auth/SConscript +++ b/src/mongo/db/auth/SConscript @@ -74,9 +74,9 @@ env.Library( 'sasl_options', 'user_name', '$BUILD_DIR/mongo/base', + '$BUILD_DIR/mongo/base/secure_allocator', '$BUILD_DIR/mongo/bson/mutable/mutable_bson', '$BUILD_DIR/mongo/bson/util/bson_extract', - '$BUILD_DIR/mongo/crypto/scramauth', '$BUILD_DIR/mongo/db/catalog/document_validation', '$BUILD_DIR/mongo/db/common', '$BUILD_DIR/mongo/db/mongod_options', @@ -179,7 +179,6 @@ env.Library('saslauth', 'authmocks', # Wat? 'sasl_options', '$BUILD_DIR/mongo/base/secure_allocator', - '$BUILD_DIR/mongo/crypto/scramauth', '$BUILD_DIR/mongo/db/commands/test_commands_enabled', '$BUILD_DIR/mongo/util/net/network', ], diff --git a/src/mongo/db/auth/authorization_manager_test.cpp b/src/mongo/db/auth/authorization_manager_test.cpp index ddebeafefae..7beea3ede5a 100644 --- a/src/mongo/db/auth/authorization_manager_test.cpp +++ b/src/mongo/db/auth/authorization_manager_test.cpp @@ -178,7 +178,7 @@ public: externalState->setAuthorizationManager(authzManager.get()); authzManager->setAuthEnabled(true); - credentials = BSON("SCRAM-SHA-1" << scram::generateCredentials( + credentials = BSON("SCRAM-SHA-1" << scram::SHA1Secrets::generateCredentials( "password", saslGlobalParams.scramIterationCount.load())); } diff --git a/src/mongo/db/auth/authorization_session_test.cpp b/src/mongo/db/auth/authorization_session_test.cpp index 42a016a83ae..16599969c7d 100644 --- a/src/mongo/db/auth/authorization_session_test.cpp +++ b/src/mongo/db/auth/authorization_session_test.cpp @@ -112,7 +112,7 @@ public: authzSession = stdx::make_unique(std::move(localSessionState)); authzManager->setAuthEnabled(true); - credentials = BSON("SCRAM-SHA-1" << scram::generateCredentials( + credentials = BSON("SCRAM-SHA-1" << scram::SHA1Secrets::generateCredentials( "a", saslGlobalParams.scramIterationCount.load())); } }; diff --git a/src/mongo/db/auth/sasl_authentication_session_test.cpp b/src/mongo/db/auth/sasl_authentication_session_test.cpp index 355ccda35fa..dbd3e117ebe 100644 --- a/src/mongo/db/auth/sasl_authentication_session_test.cpp +++ b/src/mongo/db/auth/sasl_authentication_session_test.cpp @@ -78,7 +78,7 @@ SaslConversation::SaslConversation(std::string mech) BSONObj())); const auto authHash = (mech == "SCRAM-SHA-1") ? "frim" : createPasswordDigest("andy", "frim"); - const auto creds = BSON("SCRAM-SHA-1" << scram::generateCredentials( + const auto creds = BSON("SCRAM-SHA-1" << scram::SHA1Secrets::generateCredentials( authHash, saslGlobalParams.scramIterationCount.load())); ASSERT_OK(authManagerExternalState->insert(&opCtx, diff --git a/src/mongo/db/auth/sasl_plain_server_conversation.cpp b/src/mongo/db/auth/sasl_plain_server_conversation.cpp index 0c574a7f7fd..8acefa7b90d 100644 --- a/src/mongo/db/auth/sasl_plain_server_conversation.cpp +++ b/src/mongo/db/auth/sasl_plain_server_conversation.cpp @@ -108,14 +108,14 @@ StatusWith SaslPLAINServerConversation::step(StringData inputData, std::st // Handle schemaVersion28SCRAM (SCRAM only mode) std::string decodedSalt = base64::decode(creds.scram.salt); - scram::SCRAMSecrets secrets = scram::generateSecrets(scram::SCRAMPresecrets( + scram::SHA1Secrets secrets(scram::SHA1Presecrets( authDigest, std::vector(reinterpret_cast(decodedSalt.c_str()), reinterpret_cast(decodedSalt.c_str()) + 16), creds.scram.iterationCount)); if (creds.scram.storedKey != - base64::encode(reinterpret_cast(secrets->storedKey.data()), - secrets->storedKey.size())) { + base64::encode(reinterpret_cast(secrets.storedKey().data()), + secrets.storedKey().size())) { return StatusWith(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 47a2ac4b31d..084907333a1 100644 --- a/src/mongo/db/auth/sasl_scramsha1_server_conversation.cpp +++ b/src/mongo/db/auth/sasl_scramsha1_server_conversation.cpp @@ -277,24 +277,20 @@ StatusWith SaslSCRAMSHA1ServerConversation::_secondStep(const std::vector< // ClientKey := ClientSignature XOR ClientProof // ServerSignature := HMAC(ServerKey, AuthMessage) invariant(_creds.scram.isValid()); + scram::SHA1Secrets secrets( + "", base64::decode(_creds.scram.storedKey), base64::decode(_creds.scram.serverKey)); - if (!scram::verifyClientProof( - base64::decode(clientProof), base64::decode(_creds.scram.storedKey), _authMessage)) { + if (!secrets.verifyClientProof(_authMessage, base64::decode(clientProof))) { return StatusWith(ErrorCodes::AuthenticationFailed, mongoutils::str::stream() << "SCRAM-SHA-1 authentication failed, storedKey mismatch"); } // ServerSignature := HMAC(ServerKey, AuthMessage) - std::string decodedServerKey = base64::decode(_creds.scram.serverKey); - SHA1Block serverSignature = - SHA1Block::computeHmac(reinterpret_cast(decodedServerKey.c_str()), - decodedServerKey.size(), - reinterpret_cast(_authMessage.c_str()), - _authMessage.size()); + const auto serverSignature = secrets.generateServerSignature(_authMessage); StringBuilder sb; - sb << "v=" << serverSignature.toString(); + sb << "v=" << serverSignature; *outputData = sb.str(); return StatusWith(false); diff --git a/src/mongo/db/auth/sasl_scramsha1_test.cpp b/src/mongo/db/auth/sasl_scramsha1_test.cpp index eab9136575c..93466db1aa9 100644 --- a/src/mongo/db/auth/sasl_scramsha1_test.cpp +++ b/src/mongo/db/auth/sasl_scramsha1_test.cpp @@ -49,7 +49,7 @@ BSONObj generateSCRAMUserDocument(StringData username, StringData password) { auto database = "test"_sd; std::string digested = createPasswordDigest(username, password); - BSONObj scramCred = scram::generateCredentials(digested, scramIterationCount); + auto scramCred = scram::SHA1Secrets::generateCredentials(digested, scramIterationCount); return BSON("_id" << (str::stream() << database << "." << username).operator StringData() << AuthorizationManager::USER_NAME_FIELD_NAME << username @@ -498,7 +498,7 @@ TEST(SCRAMSHA1Cache, testGetFromEmptyCache) { std::vector salt(saltStr.begin(), saltStr.end()); HostAndPort host("localhost:27017"); - ASSERT_FALSE(cache.getCachedSecrets(host, scram::SCRAMPresecrets("aaa", salt, 10000))); + ASSERT_FALSE(cache.getCachedSecrets(host, scram::SHA1Presecrets("aaa", salt, 10000))); } @@ -510,13 +510,13 @@ TEST(SCRAMSHA1Cache, testSetAndGet) { std::vector badSalt(badSaltStr.begin(), badSaltStr.end()); HostAndPort host("localhost:27017"); - auto secret = scram::generateSecrets(scram::SCRAMPresecrets("aaa", salt, 10000)); - cache.setCachedSecrets(host, scram::SCRAMPresecrets("aaa", salt, 10000), secret); - auto cachedSecret = cache.getCachedSecrets(host, scram::SCRAMPresecrets("aaa", salt, 10000)); + auto secret = scram::SHA1Secrets(scram::SHA1Presecrets("aaa", salt, 10000)); + cache.setCachedSecrets(host, scram::SHA1Presecrets("aaa", salt, 10000), secret); + auto cachedSecret = cache.getCachedSecrets(host, scram::SHA1Presecrets("aaa", salt, 10000)); ASSERT_TRUE(cachedSecret); - ASSERT_TRUE(secret->clientKey == cachedSecret->clientKey); - ASSERT_TRUE(secret->serverKey == cachedSecret->serverKey); - ASSERT_TRUE(secret->storedKey == cachedSecret->storedKey); + ASSERT_TRUE(secret.clientKey() == cachedSecret.clientKey()); + ASSERT_TRUE(secret.serverKey() == cachedSecret.serverKey()); + ASSERT_TRUE(secret.storedKey() == cachedSecret.storedKey()); } @@ -528,14 +528,14 @@ TEST(SCRAMSHA1Cache, testSetAndGetWithDifferentParameters) { std::vector badSalt(badSaltStr.begin(), badSaltStr.end()); HostAndPort host("localhost:27017"); - auto secret = scram::generateSecrets(scram::SCRAMPresecrets("aaa", salt, 10000)); - cache.setCachedSecrets(host, scram::SCRAMPresecrets("aaa", salt, 10000), secret); + auto secret = scram::SHA1Secrets(scram::SHA1Presecrets("aaa", salt, 10000)); + cache.setCachedSecrets(host, scram::SHA1Presecrets("aaa", salt, 10000), secret); ASSERT_FALSE(cache.getCachedSecrets(HostAndPort("localhost:27018"), - scram::SCRAMPresecrets("aaa", salt, 10000))); - ASSERT_FALSE(cache.getCachedSecrets(host, scram::SCRAMPresecrets("aab", salt, 10000))); - ASSERT_FALSE(cache.getCachedSecrets(host, scram::SCRAMPresecrets("aaa", badSalt, 10000))); - ASSERT_FALSE(cache.getCachedSecrets(host, scram::SCRAMPresecrets("aaa", salt, 10001))); + scram::SHA1Presecrets("aaa", salt, 10000))); + ASSERT_FALSE(cache.getCachedSecrets(host, scram::SHA1Presecrets("aab", salt, 10000))); + ASSERT_FALSE(cache.getCachedSecrets(host, scram::SHA1Presecrets("aaa", badSalt, 10000))); + ASSERT_FALSE(cache.getCachedSecrets(host, scram::SHA1Presecrets("aaa", salt, 10001))); } @@ -545,17 +545,17 @@ TEST(SCRAMSHA1Cache, testSetAndReset) { std::vector salt(saltStr.begin(), saltStr.end()); HostAndPort host("localhost:27017"); - auto secret = scram::generateSecrets(scram::SCRAMPresecrets("aaa", salt, 10000)); - cache.setCachedSecrets(host, scram::SCRAMPresecrets("aaa", salt, 10000), secret); - auto newSecret = scram::generateSecrets(scram::SCRAMPresecrets("aab", salt, 10000)); - cache.setCachedSecrets(host, scram::SCRAMPresecrets("aab", salt, 10000), newSecret); + scram::SHA1Secrets secret(scram::SHA1Presecrets("aaa", salt, 10000)); + cache.setCachedSecrets(host, scram::SHA1Presecrets("aaa", salt, 10000), secret); + scram::SHA1Secrets newSecret(scram::SHA1Presecrets("aab", salt, 10000)); + cache.setCachedSecrets(host, scram::SHA1Presecrets("aab", salt, 10000), newSecret); - ASSERT_FALSE(cache.getCachedSecrets(host, scram::SCRAMPresecrets("aaa", salt, 10000))); - auto cachedSecret = cache.getCachedSecrets(host, scram::SCRAMPresecrets("aab", salt, 10000)); + ASSERT_FALSE(cache.getCachedSecrets(host, scram::SHA1Presecrets("aaa", salt, 10000))); + auto cachedSecret = cache.getCachedSecrets(host, scram::SHA1Presecrets("aab", salt, 10000)); ASSERT_TRUE(cachedSecret); - ASSERT_TRUE(newSecret->clientKey == cachedSecret->clientKey); - ASSERT_TRUE(newSecret->serverKey == cachedSecret->serverKey); - ASSERT_TRUE(newSecret->storedKey == cachedSecret->storedKey); + ASSERT_TRUE(newSecret.clientKey() == cachedSecret.clientKey()); + ASSERT_TRUE(newSecret.serverKey() == cachedSecret.serverKey()); + ASSERT_TRUE(newSecret.storedKey() == cachedSecret.storedKey()); } } // namespace mongo diff --git a/src/mongo/db/auth/security_key.cpp b/src/mongo/db/auth/security_key.cpp index f9d0445e65f..f146090eeca 100644 --- a/src/mongo/db/auth/security_key.cpp +++ b/src/mongo/db/auth/security_key.cpp @@ -76,12 +76,12 @@ bool setUpSecurityKey(const string& filename) { const auto password = mongo::createPasswordDigest(internalSecurity.user->getName().getUser().toString(), str); - BSONObj creds = - scram::generateCredentials(password, saslGlobalParams.scramIterationCount.load()); - credentials.scram.iterationCount = creds[scram::iterationCountFieldName].Int(); - credentials.scram.salt = creds[scram::saltFieldName].String(); - credentials.scram.storedKey = creds[scram::storedKeyFieldName].String(); - credentials.scram.serverKey = creds[scram::serverKeyFieldName].String(); + auto creds = scram::SHA1Secrets::generateCredentials( + password, saslGlobalParams.scramIterationCount.load()); + credentials.scram.iterationCount = creds[scram::kIterationCountFieldName].Int(); + credentials.scram.salt = creds[scram::kSaltFieldName].String(); + credentials.scram.storedKey = creds[scram::kStoredKeyFieldName].String(); + credentials.scram.serverKey = creds[scram::kServerKeyFieldName].String(); internalSecurity.user->setCredentials(credentials); diff --git a/src/mongo/db/auth/user_document_parser_test.cpp b/src/mongo/db/auth/user_document_parser_test.cpp index decf0155fce..ddfa9f0c052 100644 --- a/src/mongo/db/auth/user_document_parser_test.cpp +++ b/src/mongo/db/auth/user_document_parser_test.cpp @@ -63,7 +63,7 @@ public: user.reset(new User(UserName("spencer", "test"))); adminUser.reset(new User(UserName("admin", "admin"))); - credentials = BSON("SCRAM-SHA-1" << scram::generateCredentials( + credentials = BSON("SCRAM-SHA-1" << scram::SHA1Secrets::generateCredentials( "a", saslGlobalParams.scramIterationCount.load())); } }; diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp index 97255194138..f2d3bcbeed2 100644 --- a/src/mongo/db/commands/user_management_commands.cpp +++ b/src/mongo/db/commands/user_management_commands.cpp @@ -698,7 +698,7 @@ public: credentialsBuilder.append("external", true); } else { // Add SCRAM credentials. - BSONObj scramCred = scram::generateCredentials( + BSONObj scramCred = scram::SHA1Secrets::generateCredentials( args.hashedPassword, saslGlobalParams.scramIterationCount.load()); credentialsBuilder.append("SCRAM-SHA-1", scramCred); } @@ -808,7 +808,7 @@ public: BSONObjBuilder credentialsBuilder(updateSetBuilder.subobjStart("credentials")); // Add SCRAM credentials. - BSONObj scramCred = scram::generateCredentials( + BSONObj scramCred = scram::SHA1Secrets::generateCredentials( args.hashedPassword, saslGlobalParams.scramIterationCount.load()); credentialsBuilder.append("SCRAM-SHA-1", scramCred); diff --git a/src/mongo/db/logical_session_id_test.cpp b/src/mongo/db/logical_session_id_test.cpp index 0dd9d3c6c95..ecd86565f54 100644 --- a/src/mongo/db/logical_session_id_test.cpp +++ b/src/mongo/db/logical_session_id_test.cpp @@ -105,7 +105,7 @@ public: } User* addSimpleUser(UserName un) { - const auto creds = BSON("SCRAM-SHA-1" << scram::generateCredentials( + const auto creds = BSON("SCRAM-SHA-1" << scram::SHA1Secrets::generateCredentials( "a", saslGlobalParams.scramIterationCount.load())); ASSERT_OK(managerState->insertPrivilegeDocument( _opCtx.get(), @@ -120,7 +120,7 @@ public: } User* addClusterUser(UserName un) { - const auto creds = BSON("SCRAM-SHA-1" << scram::generateCredentials( + const auto creds = BSON("SCRAM-SHA-1" << scram::SHA1Secrets::generateCredentials( "a", saslGlobalParams.scramIterationCount.load())); ASSERT_OK(managerState->insertPrivilegeDocument( _opCtx.get(), -- cgit v1.2.1