From 10c09477dd67543a417423254b1e7c4dd4f9d05c Mon Sep 17 00:00:00 2001 From: Sara Golemon Date: Tue, 13 Feb 2018 15:39:46 -0500 Subject: SERVER-33305 Expand scramsha1 tests to include SHA256 --- src/mongo/client/scram_client_cache.h | 2 - src/mongo/crypto/mechanism_scram.h | 3 - src/mongo/crypto/mechanism_scram_test.cpp | 78 ++- src/mongo/db/auth/SConscript | 4 +- src/mongo/db/auth/authorization_manager_test.cpp | 11 +- src/mongo/db/auth/authorization_session_test.cpp | 10 +- src/mongo/db/auth/sasl_scram_test.cpp | 649 +++++++++++++++++++++++ src/mongo/db/auth/sasl_scramsha1_test.cpp | 609 --------------------- src/mongo/db/auth/security_key.cpp | 3 +- src/mongo/db/logical_session_id_test.cpp | 8 +- 10 files changed, 729 insertions(+), 648 deletions(-) create mode 100644 src/mongo/db/auth/sasl_scram_test.cpp delete mode 100644 src/mongo/db/auth/sasl_scramsha1_test.cpp diff --git a/src/mongo/client/scram_client_cache.h b/src/mongo/client/scram_client_cache.h index 0f8d6f8a55a..6b8b7b20d7b 100644 --- a/src/mongo/client/scram_client_cache.h +++ b/src/mongo/client/scram_client_cache.h @@ -120,6 +120,4 @@ private: HostToSecretsMap _hostToSecrets; }; -using SCRAMSHA1ClientCache = SCRAMClientCache; - } // namespace mongo diff --git a/src/mongo/crypto/mechanism_scram.h b/src/mongo/crypto/mechanism_scram.h index 741566252ab..8ff6d488c06 100644 --- a/src/mongo/crypto/mechanism_scram.h +++ b/src/mongo/crypto/mechanism_scram.h @@ -284,8 +284,5 @@ 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 index 5e67a1dfaa9..88a10bc3569 100644 --- a/src/mongo/crypto/mechanism_scram_test.cpp +++ b/src/mongo/crypto/mechanism_scram_test.cpp @@ -31,6 +31,8 @@ #include "mongo/platform/basic.h" #include "mongo/crypto/mechanism_scram.h" +#include "mongo/crypto/sha1_block.h" +#include "mongo/crypto/sha256_block.h" #include "mongo/unittest/unittest.h" #include "mongo/util/log.h" @@ -38,47 +40,77 @@ 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="); +template +void testBasicVectors(StringData saltedPw, + StringData clientKey, + StringData storedKey, + StringData serverKey, + StringData proof, + StringData signature) { + // Predictable salts yield predictable secrets. + // salt = {0, 1, 2, 3, ..., n-1} + std::vector salt; + salt.resize(HashBlock::kHashLength - 4); + int i = 0; + std::generate(salt.begin(), salt.end(), [&i] { return i++; }); + + Presecrets presecrets("password", salt, 4096); + ASSERT_EQ(presecrets.generateSaltedPassword().toString(), saltedPw); + + Secrets secrets(presecrets); + ASSERT_EQ(secrets.clientKey().toString(), clientKey); + ASSERT_EQ(secrets.storedKey().toString(), storedKey); + ASSERT_EQ(secrets.serverKey().toString(), serverKey); const StringData authMessage("secret"); - auto proof = secrets.generateClientProof(authMessage); - ASSERT_EQ(proof, "y+cpoAm0YlN30GuNgN4B9xghi4E="); - ASSERT_TRUE(secrets.verifyClientProof(authMessage, base64::decode(proof))); + const auto generatedProof = secrets.generateClientProof(authMessage); + ASSERT_EQ(generatedProof, proof); + ASSERT_TRUE(secrets.verifyClientProof(authMessage, base64::decode(generatedProof))); - auto sig = secrets.generateServerSignature(authMessage); - ASSERT_EQ(sig, "kiZS90Kz4/yaYZn9JieHtcRzXR0="); - ASSERT_TRUE(secrets.verifyServerSignature(authMessage, base64::decode(sig))); + const auto generatedSig = secrets.generateServerSignature(authMessage); + ASSERT_EQ(generatedSig, signature); + ASSERT_TRUE(secrets.verifyServerSignature(authMessage, base64::decode(generatedSig))); } -TEST(MechanismScram, generateCredentials) { - const auto bson = SHA1Secrets::generateCredentials("password", 4096); +TEST(MechanismScram, BasicVectors) { + testBasicVectors("531aYHrF581Skow4E0gCWLw/Ibo=", + "wiHbIsPcvJo230S6Qf5xYCDrhb0=", + "SjXiaB2hLRr8aMUyXMVEw7H1jSI=", + "FilAoFIclBukd3xZxBvYMXTU3HM=", + "y+cpoAm0YlN30GuNgN4B9xghi4E=", + "kiZS90Kz4/yaYZn9JieHtcRzXR0="); + testBasicVectors("UA7rgIQG0u7EQJuOrJ99qaWVlcWnY0e/ijWBuyzSN0M=", + "xdYqTeBpV5U7m/j9EdpKT1Ls+5ublIEeYGND2RUB18k=", + "w4nwnR0Mck11lMY3EeF4pCcpJMgaToIguPbEk/ipNGY=", + "oKgZqeFO8FDpB14Y8QDLbiX1TurT6XZTdlexUt/Ny5g=", + "D6x37wuGhm1HegzIrJhedSb26XOdg5IRyR47oFqzKIo=", + "ybHsTJuRLmeT0/1YvQZKrlsgDE40RobAX7o8fu9sbdk="); +} + +template +void testGenerateCredentials() { + const auto bson = Secrets::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_EQ(base64::decode(bson.getStringField("salt")).size(), HashBlock::kHashLength - 4); ASSERT_TRUE(bson.hasField("storedKey")); - ASSERT_EQ(base64::decode(bson.getStringField("storedKey")).size(), SHA1Block::kHashLength); + ASSERT_EQ(base64::decode(bson.getStringField("storedKey")).size(), HashBlock::kHashLength); ASSERT_TRUE(bson.hasField("serverKey")); - ASSERT_EQ(base64::decode(bson.getStringField("serverKey")).size(), SHA1Block::kHashLength); + ASSERT_EQ(base64::decode(bson.getStringField("serverKey")).size(), HashBlock::kHashLength); ASSERT_TRUE(bson.hasField("iterationCount")); ASSERT_EQ(bson.getIntField("iterationCount"), 4096); } +TEST(MechanismScram, generateCredentials) { + testGenerateCredentials(); + testGenerateCredentials(); +} + } // namespace } // namespace scram } // namespace mongo diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript index 42873a49826..db4f9cfce13 100644 --- a/src/mongo/db/auth/SConscript +++ b/src/mongo/db/auth/SConscript @@ -295,10 +295,10 @@ env.CppUnitTest( ) env.CppUnitTest( - target='sasl_scramsha1_test', + target='sasl_scram_test', source=[ 'sasl_authentication_session_test.cpp', - 'sasl_scramsha1_test.cpp', + 'sasl_scram_test.cpp', ], LIBDEPS_PRIVATE=[ 'saslauth', diff --git a/src/mongo/db/auth/authorization_manager_test.cpp b/src/mongo/db/auth/authorization_manager_test.cpp index 603cc770bb3..a2c14d71be9 100644 --- a/src/mongo/db/auth/authorization_manager_test.cpp +++ b/src/mongo/db/auth/authorization_manager_test.cpp @@ -33,6 +33,8 @@ #include "mongo/base/status.h" #include "mongo/bson/mutable/document.h" #include "mongo/crypto/mechanism_scram.h" +#include "mongo/crypto/sha1_block.h" +#include "mongo/crypto/sha256_block.h" #include "mongo/db/auth/action_set.h" #include "mongo/db/auth/action_type.h" #include "mongo/db/auth/authorization_manager.h" @@ -173,13 +175,16 @@ public: void setUp() override { auto localExternalState = stdx::make_unique(); externalState = localExternalState.get(); - externalState->setAuthzVersion(AuthorizationManager::schemaVersion26Final); authzManager = stdx::make_unique(std::move(localExternalState)); externalState->setAuthorizationManager(authzManager.get()); authzManager->setAuthEnabled(true); - credentials = BSON("SCRAM-SHA-1" << scram::SHA1Secrets::generateCredentials( - "password", saslGlobalParams.scramSHA1IterationCount.load())); + credentials = BSON("SCRAM-SHA-1" + << scram::Secrets::generateCredentials( + "password", saslGlobalParams.scramSHA1IterationCount.load()) + << "SCRAM-SHA-256" + << scram::Secrets::generateCredentials( + "password", saslGlobalParams.scramSHA256IterationCount.load())); } std::unique_ptr authzManager; diff --git a/src/mongo/db/auth/authorization_session_test.cpp b/src/mongo/db/auth/authorization_session_test.cpp index 4bbca4e2e17..7933bb8f46b 100644 --- a/src/mongo/db/auth/authorization_session_test.cpp +++ b/src/mongo/db/auth/authorization_session_test.cpp @@ -33,6 +33,8 @@ #include "mongo/base/status.h" #include "mongo/bson/bson_depth.h" #include "mongo/crypto/mechanism_scram.h" +#include "mongo/crypto/sha1_block.h" +#include "mongo/crypto/sha256_block.h" #include "mongo/db/auth/action_type.h" #include "mongo/db/auth/authorization_manager.h" #include "mongo/db/auth/authorization_session_for_test.h" @@ -112,8 +114,12 @@ public: authzSession = stdx::make_unique(std::move(localSessionState)); authzManager->setAuthEnabled(true); - credentials = BSON("SCRAM-SHA-1" << scram::SHA1Secrets::generateCredentials( - "a", saslGlobalParams.scramSHA1IterationCount.load())); + credentials = + BSON("SCRAM-SHA-1" << scram::Secrets::generateCredentials( + "a", saslGlobalParams.scramSHA1IterationCount.load()) + << "SCRAM-SHA-256" + << scram::Secrets::generateCredentials( + "a", saslGlobalParams.scramSHA256IterationCount.load())); } }; diff --git a/src/mongo/db/auth/sasl_scram_test.cpp b/src/mongo/db/auth/sasl_scram_test.cpp new file mode 100644 index 00000000000..c75cae5f260 --- /dev/null +++ b/src/mongo/db/auth/sasl_scram_test.cpp @@ -0,0 +1,649 @@ +/* + * Copyright (C) 2016 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/client/native_sasl_client_session.h" +#include "mongo/client/scram_client_cache.h" +#include "mongo/crypto/mechanism_scram.h" +#include "mongo/crypto/sha1_block.h" +#include "mongo/crypto/sha256_block.h" +#include "mongo/db/auth/authorization_manager.h" +#include "mongo/db/auth/authz_manager_external_state_mock.h" +#include "mongo/db/auth/authz_session_external_state_mock.h" +#include "mongo/db/auth/native_sasl_authentication_session.h" +#include "mongo/db/auth/sasl_scram_server_conversation.h" +#include "mongo/db/service_context_noop.h" +#include "mongo/stdx/memory.h" +#include "mongo/unittest/unittest.h" +#include "mongo/util/base64.h" +#include "mongo/util/log.h" +#include "mongo/util/password_digest.h" + +namespace mongo { +namespace { + +BSONObj generateSCRAMUserDocument(StringData username, StringData password) { + const auto database = "test"_sd; + + const auto digested = createPasswordDigest(username, password); + const auto sha1Cred = scram::Secrets::generateCredentials(digested, 10000); + const auto sha256Cred = + scram::Secrets::generateCredentials(password.toString(), 15000); + return BSON("_id" << (str::stream() << database << "." << username).operator StringData() + << AuthorizationManager::USER_NAME_FIELD_NAME + << username + << AuthorizationManager::USER_DB_FIELD_NAME + << database + << "credentials" + << BSON("SCRAM-SHA-1" << sha1Cred << "SCRAM-SHA-256" << sha256Cred) + << "roles" + << BSONArray() + << "privileges" + << BSONArray()); +} + +std::string corruptEncodedPayload(const std::string& message, + std::string::const_iterator begin, + std::string::const_iterator end) { + std::string raw = base64::decode( + message.substr(std::distance(message.begin(), begin), std::distance(begin, end))); + if (raw[0] == std::numeric_limits::max()) { + raw[0] -= 1; + } else { + raw[0] += 1; + } + return base64::encode(raw); +} + +class SaslTestState { +public: + enum Participant { kClient, kServer }; + SaslTestState() : SaslTestState(kClient, 0) {} + SaslTestState(Participant participant, size_t stage) : participant(participant), stage(stage) {} + +private: + // Define members here, so that they can be used in declaration of lens(). In C++14, lens() + // can be declared with a return of decltype(auto), without a trailing return type, and these + // members can go at the end of the class. + Participant participant; + size_t stage; + +public: + auto lens() const -> decltype(std::tie(this->stage, this->participant)) { + return std::tie(stage, participant); + } + + friend bool operator==(const SaslTestState& lhs, const SaslTestState& rhs) { + return lhs.lens() == rhs.lens(); + } + + friend bool operator<(const SaslTestState& lhs, const SaslTestState& rhs) { + return lhs.lens() < rhs.lens(); + } + + void next() { + if (participant == kClient) { + participant = kServer; + } else { + participant = kClient; + stage++; + } + } + + std::string toString() const { + std::stringstream ss; + if (participant == kClient) { + ss << "Client"; + } else { + ss << "Server"; + } + ss << "Step" << stage; + + return ss.str(); + } +}; + +class SCRAMMutators { +public: + SCRAMMutators() {} + + void setMutator(SaslTestState state, stdx::function fun) { + mutators.insert(std::make_pair(state, fun)); + } + + void execute(SaslTestState state, std::string& str) { + auto it = mutators.find(state); + if (it != mutators.end()) { + it->second(str); + } + } + +private: + std::map> mutators; +}; + +struct SCRAMStepsResult { + SCRAMStepsResult() : outcome(SaslTestState::kClient, 1), status(Status::OK()) {} + SCRAMStepsResult(SaslTestState outcome, Status status) : outcome(outcome), status(status) {} + bool operator==(const SCRAMStepsResult& other) const { + return outcome == other.outcome && status.code() == other.status.code() && + status.reason() == other.status.reason(); + } + SaslTestState outcome; + Status status; + + friend std::ostream& operator<<(std::ostream& os, const SCRAMStepsResult& result) { + return os << "{outcome: " << result.outcome.toString() << ", status: " << result.status + << "}"; + } +}; + +SCRAMStepsResult runSteps(NativeSaslAuthenticationSession* saslServerSession, + NativeSaslClientSession* saslClientSession, + SCRAMMutators interposers = SCRAMMutators{}) { + SCRAMStepsResult result{}; + std::string clientOutput = ""; + std::string serverOutput = ""; + + for (size_t step = 1; step <= 3; step++) { + ASSERT_FALSE(saslClientSession->isDone()); + ASSERT_FALSE(saslServerSession->isDone()); + + // Client step + result.status = saslClientSession->step(serverOutput, &clientOutput); + if (result.status != Status::OK()) { + return result; + } + interposers.execute(result.outcome, clientOutput); + std::cout << result.outcome.toString() << ": " << clientOutput << std::endl; + result.outcome.next(); + + // Server step + result.status = saslServerSession->step(clientOutput, &serverOutput); + if (result.status != Status::OK()) { + return result; + } + interposers.execute(result.outcome, serverOutput); + std::cout << result.outcome.toString() << ": " << serverOutput << std::endl; + result.outcome.next(); + } + ASSERT_TRUE(saslClientSession->isDone()); + ASSERT_TRUE(saslServerSession->isDone()); + + return result; +} + +class SCRAMFixture : public mongo::unittest::Test { +protected: + const SCRAMStepsResult goalState = + SCRAMStepsResult(SaslTestState(SaslTestState::kClient, 4), Status::OK()); + + ServiceContextNoop serviceContext; + ServiceContextNoop::UniqueClient client; + ServiceContextNoop::UniqueOperationContext opCtx; + + AuthzManagerExternalStateMock* authzManagerExternalState; + std::unique_ptr authzManager; + std::unique_ptr authzSession; + + std::unique_ptr saslServerSession; + std::unique_ptr saslClientSession; + + void setUp() final { + client = serviceContext.makeClient("test"); + opCtx = serviceContext.makeOperationContext(client.get()); + + auto uniqueAuthzManagerExternalStateMock = + stdx::make_unique(); + authzManagerExternalState = uniqueAuthzManagerExternalStateMock.get(); + authzManager = + stdx::make_unique(std::move(uniqueAuthzManagerExternalStateMock)); + authzSession = stdx::make_unique( + stdx::make_unique(authzManager.get())); + + saslServerSession = stdx::make_unique(authzSession.get()); + saslServerSession->setOpCtxt(opCtx.get()); + ASSERT_OK( + saslServerSession->start("test", _mechanism, "mongodb", "MockServer.test", 1, false)); + saslClientSession = stdx::make_unique(); + saslClientSession->setParameter(NativeSaslClientSession::parameterMechanism, _mechanism); + saslClientSession->setParameter(NativeSaslClientSession::parameterServiceName, "mongodb"); + saslClientSession->setParameter(NativeSaslClientSession::parameterServiceHostname, + "MockServer.test"); + saslClientSession->setParameter(NativeSaslClientSession::parameterServiceHostAndPort, + "MockServer.test:27017"); + } + + void tearDown() final { + saslClientSession.reset(); + saslServerSession.reset(); + authzSession.reset(); + authzManager.reset(); + authzManagerExternalState = nullptr; + opCtx.reset(); + client.reset(); + } + + std::string createPasswordDigest(StringData username, StringData password) { + if (_digestPassword) { + return mongo::createPasswordDigest(username, password); + } else { + return password.toString(); + } + } + + std::string _mechanism; + bool _digestPassword; + +public: + void run() { + log() << "SCRAM-SHA-1 variant"; + _mechanism = "SCRAM-SHA-1"; + _digestPassword = true; + Test::run(); + + log() << "SCRAM-SHA-256 variant"; + _mechanism = "SCRAM-SHA-256"; + _digestPassword = false; + Test::run(); + } +}; + +TEST_F(SCRAMFixture, testServerStep1DoesNotIncludeNonceFromClientStep1) { + ASSERT_OK(authzManagerExternalState->insertPrivilegeDocument( + opCtx.get(), generateSCRAMUserDocument("sajack", "sajack"), BSONObj())); + + saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); + saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, + createPasswordDigest("sajack", "sajack")); + + ASSERT_OK(saslClientSession->initialize()); + + SCRAMMutators mutator; + mutator.setMutator(SaslTestState(SaslTestState::kServer, 1), [](std::string& serverMessage) { + std::string::iterator nonceBegin = serverMessage.begin() + serverMessage.find("r="); + std::string::iterator nonceEnd = std::find(nonceBegin, serverMessage.end(), ','); + serverMessage = serverMessage.replace(nonceBegin, nonceEnd, "r="); + + }); + ASSERT_EQ( + SCRAMStepsResult(SaslTestState(SaslTestState::kClient, 2), + Status(ErrorCodes::BadValue, "Incorrect SCRAM client|server nonce: r=")), + runSteps(saslServerSession.get(), saslClientSession.get(), mutator)); +} + +TEST_F(SCRAMFixture, testClientStep2DoesNotIncludeNonceFromServerStep1) { + ASSERT_OK(authzManagerExternalState->insertPrivilegeDocument( + opCtx.get(), generateSCRAMUserDocument("sajack", "sajack"), BSONObj())); + + saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); + saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, + createPasswordDigest("sajack", "sajack")); + + ASSERT_OK(saslClientSession->initialize()); + + SCRAMMutators mutator; + mutator.setMutator(SaslTestState(SaslTestState::kClient, 2), [](std::string& clientMessage) { + std::string::iterator nonceBegin = clientMessage.begin() + clientMessage.find("r="); + std::string::iterator nonceEnd = std::find(nonceBegin, clientMessage.end(), ','); + clientMessage = clientMessage.replace(nonceBegin, nonceEnd, "r="); + }); + ASSERT_EQ( + SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 2), + Status(ErrorCodes::BadValue, "Incorrect SCRAM client|server nonce: r=")), + runSteps(saslServerSession.get(), saslClientSession.get(), mutator)); +} + +TEST_F(SCRAMFixture, testClientStep2GivesBadProof) { + ASSERT_OK(authzManagerExternalState->insertPrivilegeDocument( + opCtx.get(), generateSCRAMUserDocument("sajack", "sajack"), BSONObj())); + + saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); + saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, + createPasswordDigest("sajack", "sajack")); + + ASSERT_OK(saslClientSession->initialize()); + + SCRAMMutators mutator; + mutator.setMutator(SaslTestState(SaslTestState::kClient, 2), [](std::string& clientMessage) { + std::string::iterator proofBegin = clientMessage.begin() + clientMessage.find("p=") + 2; + std::string::iterator proofEnd = std::find(proofBegin, clientMessage.end(), ','); + clientMessage = clientMessage.replace( + proofBegin, proofEnd, corruptEncodedPayload(clientMessage, proofBegin, proofEnd)); + + }); + + ASSERT_EQ(SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 2), + Status(ErrorCodes::AuthenticationFailed, + "SCRAM authentication failed, storedKey mismatch")), + + runSteps(saslServerSession.get(), saslClientSession.get(), mutator)); +} + +TEST_F(SCRAMFixture, testServerStep2GivesBadVerifier) { + ASSERT_OK(authzManagerExternalState->insertPrivilegeDocument( + opCtx.get(), generateSCRAMUserDocument("sajack", "sajack"), BSONObj())); + + saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); + saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, + createPasswordDigest("sajack", "sajack")); + + ASSERT_OK(saslClientSession->initialize()); + + std::string encodedVerifier; + SCRAMMutators mutator; + mutator.setMutator( + SaslTestState(SaslTestState::kServer, 2), [&encodedVerifier](std::string& serverMessage) { + std::string::iterator verifierBegin = + serverMessage.begin() + serverMessage.find("v=") + 2; + std::string::iterator verifierEnd = std::find(verifierBegin, serverMessage.end(), ','); + encodedVerifier = corruptEncodedPayload(serverMessage, verifierBegin, verifierEnd); + + serverMessage = serverMessage.replace(verifierBegin, verifierEnd, encodedVerifier); + + }); + + auto result = runSteps(saslServerSession.get(), saslClientSession.get(), mutator); + + ASSERT_EQ(SCRAMStepsResult( + SaslTestState(SaslTestState::kClient, 3), + Status(ErrorCodes::BadValue, + str::stream() << "Client failed to verify SCRAM ServerSignature, received " + << encodedVerifier)), + result); +} + + +TEST_F(SCRAMFixture, testSCRAM) { + ASSERT_OK(authzManagerExternalState->insertPrivilegeDocument( + opCtx.get(), generateSCRAMUserDocument("sajack", "sajack"), BSONObj())); + + saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); + saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, + createPasswordDigest("sajack", "sajack")); + + ASSERT_OK(saslClientSession->initialize()); + + ASSERT_EQ(goalState, runSteps(saslServerSession.get(), saslClientSession.get())); +} + +TEST_F(SCRAMFixture, testSCRAMWithChannelBindingSupportedByClient) { + ASSERT_OK(authzManagerExternalState->insertPrivilegeDocument( + opCtx.get(), generateSCRAMUserDocument("sajack", "sajack"), BSONObj())); + + saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); + saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, + createPasswordDigest("sajack", "sajack")); + + ASSERT_OK(saslClientSession->initialize()); + + SCRAMMutators mutator; + mutator.setMutator(SaslTestState(SaslTestState::kClient, 1), [](std::string& clientMessage) { + clientMessage.replace(clientMessage.begin(), clientMessage.begin() + 1, "y"); + }); + + ASSERT_EQ(goalState, runSteps(saslServerSession.get(), saslClientSession.get(), mutator)); +} + +TEST_F(SCRAMFixture, testSCRAMWithChannelBindingRequiredByClient) { + ASSERT_OK(authzManagerExternalState->insertPrivilegeDocument( + opCtx.get(), generateSCRAMUserDocument("sajack", "sajack"), BSONObj())); + + saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); + saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, + createPasswordDigest("sajack", "sajack")); + + ASSERT_OK(saslClientSession->initialize()); + + SCRAMMutators mutator; + mutator.setMutator(SaslTestState(SaslTestState::kClient, 1), [](std::string& clientMessage) { + clientMessage.replace(clientMessage.begin(), clientMessage.begin() + 1, "p=tls-unique"); + }); + + ASSERT_EQ( + SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 1), + Status(ErrorCodes::BadValue, "Server does not support channel binding")), + runSteps(saslServerSession.get(), saslClientSession.get(), mutator)); +} + +TEST_F(SCRAMFixture, testSCRAMWithInvalidChannelBinding) { + ASSERT_OK(authzManagerExternalState->insertPrivilegeDocument( + opCtx.get(), generateSCRAMUserDocument("sajack", "sajack"), BSONObj())); + + saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); + saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, + createPasswordDigest("sajack", "sajack")); + + ASSERT_OK(saslClientSession->initialize()); + + SCRAMMutators mutator; + mutator.setMutator(SaslTestState(SaslTestState::kClient, 1), [](std::string& clientMessage) { + clientMessage.replace(clientMessage.begin(), clientMessage.begin() + 1, "v=illegalGarbage"); + }); + + ASSERT_EQ(SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 1), + Status(ErrorCodes::BadValue, + "Incorrect SCRAM client message prefix: v=illegalGarbage")), + runSteps(saslServerSession.get(), saslClientSession.get(), mutator)); +} + +TEST_F(SCRAMFixture, testNULLInPassword) { + ASSERT_OK(authzManagerExternalState->insertPrivilegeDocument( + opCtx.get(), generateSCRAMUserDocument("sajack", "saj\0ack"), BSONObj())); + + saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); + saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, + createPasswordDigest("sajack", "saj\0ack")); + + ASSERT_OK(saslClientSession->initialize()); + + ASSERT_EQ(goalState, runSteps(saslServerSession.get(), saslClientSession.get())); +} + + +TEST_F(SCRAMFixture, testCommasInUsernameAndPassword) { + ASSERT_OK(authzManagerExternalState->insertPrivilegeDocument( + opCtx.get(), generateSCRAMUserDocument("s,a,jack", "s,a,jack"), BSONObj())); + + saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "s,a,jack"); + saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, + createPasswordDigest("s,a,jack", "s,a,jack")); + + ASSERT_OK(saslClientSession->initialize()); + + ASSERT_EQ(goalState, runSteps(saslServerSession.get(), saslClientSession.get())); +} + +TEST_F(SCRAMFixture, testIncorrectUser) { + saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); + saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, + createPasswordDigest("sajack", "sajack")); + + ASSERT_OK(saslClientSession->initialize()); + + ASSERT_EQ(SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 1), + Status(ErrorCodes::UserNotFound, "Could not find user sajack@test")), + runSteps(saslServerSession.get(), saslClientSession.get())); +} + +TEST_F(SCRAMFixture, testIncorrectPassword) { + ASSERT_OK(authzManagerExternalState->insertPrivilegeDocument( + opCtx.get(), generateSCRAMUserDocument("sajack", "sajack"), BSONObj())); + + saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); + saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, + createPasswordDigest("sajack", "invalidPassword")); + + ASSERT_OK(saslClientSession->initialize()); + + ASSERT_EQ(SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 2), + Status(ErrorCodes::AuthenticationFailed, + "SCRAM authentication failed, storedKey mismatch")), + runSteps(saslServerSession.get(), saslClientSession.get())); +} + +TEST_F(SCRAMFixture, testOptionalClientExtensions) { + // Verify server ignores unknown/optional extensions sent by client. + ASSERT_OK(authzManagerExternalState->insertPrivilegeDocument( + opCtx.get(), generateSCRAMUserDocument("sajack", "sajack"), BSONObj())); + + saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); + saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, + createPasswordDigest("sajack", "sajack")); + + ASSERT_OK(saslClientSession->initialize()); + + SCRAMMutators mutator; + mutator.setMutator(SaslTestState(SaslTestState::kClient, 1), [](std::string& clientMessage) { + clientMessage += ",x=unsupported-extension"; + }); + + // Optional client extension is successfully ignored, or we'd have failed in step 1. + // We still fail at step 2, because client was unaware of the injected extension. + ASSERT_EQ(SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 2), + Status(ErrorCodes::AuthenticationFailed, + "SCRAM authentication failed, storedKey mismatch")), + runSteps(saslServerSession.get(), saslClientSession.get(), mutator)); +} + +TEST_F(SCRAMFixture, testOptionalServerExtensions) { + // Verify client errors on unknown/optional extensions sent by server. + ASSERT_OK(authzManagerExternalState->insertPrivilegeDocument( + opCtx.get(), generateSCRAMUserDocument("sajack", "sajack"), BSONObj())); + + saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); + saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, + createPasswordDigest("sajack", "sajack")); + + ASSERT_OK(saslClientSession->initialize()); + + SCRAMMutators mutator; + mutator.setMutator(SaslTestState(SaslTestState::kServer, 1), [](std::string& serverMessage) { + serverMessage += ",x=unsupported-extension"; + }); + + // As with testOptionalClientExtensions, we can be confident that the optionality + // is respected because we would have failed at client step 2. + // We do still fail at server step 2 because server was unaware of injected extension. + ASSERT_EQ(SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 2), + Status(ErrorCodes::AuthenticationFailed, + "SCRAM authentication failed, storedKey mismatch")), + runSteps(saslServerSession.get(), saslClientSession.get(), mutator)); +} + +template +void testGetFromEmptyCache() { + SCRAMClientCache cache; + const auto salt = scram::Presecrets::generateSecureRandomSalt(); + HostAndPort host("localhost:27017"); + ASSERT_FALSE(cache.getCachedSecrets(host, scram::Presecrets("aaa", salt, 10000))); +} + +TEST(SCRAMCache, testGetFromEmptyCache) { + testGetFromEmptyCache(); + testGetFromEmptyCache(); +} + +template +void testSetAndGet() { + SCRAMClientCache cache; + const auto salt = scram::Presecrets::generateSecureRandomSalt(); + HostAndPort host("localhost:27017"); + + const auto presecrets = scram::Presecrets("aaa", salt, 10000); + const auto secrets = scram::Secrets(presecrets); + cache.setCachedSecrets(host, presecrets, secrets); + const auto cachedSecrets = cache.getCachedSecrets(host, presecrets); + + ASSERT_TRUE(cachedSecrets); + ASSERT_TRUE(secrets.clientKey() == cachedSecrets.clientKey()); + ASSERT_TRUE(secrets.serverKey() == cachedSecrets.serverKey()); + ASSERT_TRUE(secrets.storedKey() == cachedSecrets.storedKey()); +} + +TEST(SCRAMCache, testSetAndGet) { + testSetAndGet(); + testSetAndGet(); +} + +template +void testSetAndGetWithDifferentParameters() { + SCRAMClientCache cache; + const auto salt = scram::Presecrets::generateSecureRandomSalt(); + HostAndPort host("localhost:27017"); + + const auto presecrets = scram::Presecrets("aaa", salt, 10000); + const auto secrets = scram::Secrets(presecrets); + cache.setCachedSecrets(host, presecrets, secrets); + ASSERT_TRUE(cache.getCachedSecrets(host, presecrets)); + + // Alter each of: host, password, salt, iterationCount. + // Any one of which should fail to retreive from cache. + ASSERT_FALSE(cache.getCachedSecrets(HostAndPort("localhost:27018"), presecrets)); + ASSERT_FALSE(cache.getCachedSecrets(host, scram::Presecrets("aab", salt, 10000))); + const auto badSalt = scram::Presecrets::generateSecureRandomSalt(); + ASSERT_FALSE(cache.getCachedSecrets(host, scram::Presecrets("aaa", badSalt, 10000))); + ASSERT_FALSE(cache.getCachedSecrets(host, scram::Presecrets("aaa", salt, 10001))); +} + +TEST(SCRAMCache, testSetAndGetWithDifferentParameters) { + testSetAndGetWithDifferentParameters(); + testSetAndGetWithDifferentParameters(); +} + +template +void testSetAndReset() { + SCRAMClientCache cache; + const auto salt = scram::Presecrets::generateSecureRandomSalt(); + HostAndPort host("localhost:27017"); + + const auto presecretsA = scram::Presecrets("aaa", salt, 10000); + const auto secretsA = scram::Secrets(presecretsA); + cache.setCachedSecrets(host, presecretsA, secretsA); + const auto presecretsB = scram::Presecrets("aab", salt, 10000); + const auto secretsB = scram::Secrets(presecretsB); + cache.setCachedSecrets(host, presecretsB, secretsB); + + ASSERT_FALSE(cache.getCachedSecrets(host, presecretsA)); + const auto cachedSecret = cache.getCachedSecrets(host, presecretsB); + ASSERT_TRUE(cachedSecret); + ASSERT_TRUE(secretsB.clientKey() == cachedSecret.clientKey()); + ASSERT_TRUE(secretsB.serverKey() == cachedSecret.serverKey()); + ASSERT_TRUE(secretsB.storedKey() == cachedSecret.storedKey()); +} + +TEST(SCRAMCache, testSetAndReset) { + testSetAndReset(); + testSetAndReset(); +} + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/auth/sasl_scramsha1_test.cpp b/src/mongo/db/auth/sasl_scramsha1_test.cpp deleted file mode 100644 index d0c326ea570..00000000000 --- a/src/mongo/db/auth/sasl_scramsha1_test.cpp +++ /dev/null @@ -1,609 +0,0 @@ -/* - * Copyright (C) 2016 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. - */ - -#include "mongo/platform/basic.h" - -#include "mongo/client/native_sasl_client_session.h" -#include "mongo/client/scram_client_cache.h" -#include "mongo/crypto/mechanism_scram.h" -#include "mongo/db/auth/authorization_manager.h" -#include "mongo/db/auth/authz_manager_external_state_mock.h" -#include "mongo/db/auth/authz_session_external_state_mock.h" -#include "mongo/db/auth/native_sasl_authentication_session.h" -#include "mongo/db/auth/sasl_scram_server_conversation.h" -#include "mongo/db/service_context_noop.h" -#include "mongo/stdx/memory.h" -#include "mongo/unittest/unittest.h" -#include "mongo/util/base64.h" -#include "mongo/util/password_digest.h" - -namespace mongo { - -BSONObj generateSCRAMUserDocument(StringData username, StringData password) { - const size_t scramIterationCount = 10000; - auto database = "test"_sd; - - std::string digested = createPasswordDigest(username, password); - auto scramCred = scram::SHA1Secrets::generateCredentials(digested, scramIterationCount); - return BSON("_id" << (str::stream() << database << "." << username).operator StringData() - << AuthorizationManager::USER_NAME_FIELD_NAME - << username - << AuthorizationManager::USER_DB_FIELD_NAME - << database - << "credentials" - << BSON("SCRAM-SHA-1" << scramCred) - << "roles" - << BSONArray() - << "privileges" - << BSONArray()); -} - -std::string corruptEncodedPayload(const std::string& message, - std::string::const_iterator begin, - std::string::const_iterator end) { - std::string raw = base64::decode( - message.substr(std::distance(message.begin(), begin), std::distance(begin, end))); - if (raw[0] == std::numeric_limits::max()) { - raw[0] -= 1; - } else { - raw[0] += 1; - } - return base64::encode(raw); -} - -class SaslTestState { -public: - enum Participant { kClient, kServer }; - SaslTestState() : SaslTestState(kClient, 0) {} - SaslTestState(Participant participant, size_t stage) : participant(participant), stage(stage) {} - -private: - // Define members here, so that they can be used in declaration of lens(). In C++14, lens() - // can be declared with a return of decltype(auto), without a trailing return type, and these - // members can go at the end of the class. - Participant participant; - size_t stage; - -public: - auto lens() const -> decltype(std::tie(this->stage, this->participant)) { - return std::tie(stage, participant); - } - - friend bool operator==(const SaslTestState& lhs, const SaslTestState& rhs) { - return lhs.lens() == rhs.lens(); - } - - friend bool operator<(const SaslTestState& lhs, const SaslTestState& rhs) { - return lhs.lens() < rhs.lens(); - } - - void next() { - if (participant == kClient) { - participant = kServer; - } else { - participant = kClient; - stage++; - } - } - - std::string toString() const { - std::stringstream ss; - if (participant == kClient) { - ss << "Client"; - } else { - ss << "Server"; - } - ss << "Step" << stage; - - return ss.str(); - } -}; - -class SCRAMMutators { -public: - SCRAMMutators() {} - - void setMutator(SaslTestState state, stdx::function fun) { - mutators.insert(std::make_pair(state, fun)); - } - - void execute(SaslTestState state, std::string& str) { - auto it = mutators.find(state); - if (it != mutators.end()) { - it->second(str); - } - } - -private: - std::map> mutators; -}; - -struct SCRAMStepsResult { - SCRAMStepsResult() : outcome(SaslTestState::kClient, 1), status(Status::OK()) {} - SCRAMStepsResult(SaslTestState outcome, Status status) : outcome(outcome), status(status) {} - bool operator==(const SCRAMStepsResult& other) const { - return outcome == other.outcome && status.code() == other.status.code() && - status.reason() == other.status.reason(); - } - SaslTestState outcome; - Status status; - - friend std::ostream& operator<<(std::ostream& os, const SCRAMStepsResult& result) { - return os << "{outcome: " << result.outcome.toString() << ", status: " << result.status - << "}"; - } -}; - -SCRAMStepsResult runSteps(NativeSaslAuthenticationSession* saslServerSession, - NativeSaslClientSession* saslClientSession, - SCRAMMutators interposers = SCRAMMutators{}) { - SCRAMStepsResult result{}; - std::string clientOutput = ""; - std::string serverOutput = ""; - - for (size_t step = 1; step <= 3; step++) { - ASSERT_FALSE(saslClientSession->isDone()); - ASSERT_FALSE(saslServerSession->isDone()); - - // Client step - result.status = saslClientSession->step(serverOutput, &clientOutput); - if (result.status != Status::OK()) { - return result; - } - interposers.execute(result.outcome, clientOutput); - std::cout << result.outcome.toString() << ": " << clientOutput << std::endl; - result.outcome.next(); - - // Server step - result.status = saslServerSession->step(clientOutput, &serverOutput); - if (result.status != Status::OK()) { - return result; - } - interposers.execute(result.outcome, serverOutput); - std::cout << result.outcome.toString() << ": " << serverOutput << std::endl; - result.outcome.next(); - } - ASSERT_TRUE(saslClientSession->isDone()); - ASSERT_TRUE(saslServerSession->isDone()); - - return result; -} - -class SCRAMSHA1Fixture : public mongo::unittest::Test { -protected: - const SCRAMStepsResult goalState = - SCRAMStepsResult(SaslTestState(SaslTestState::kClient, 4), Status::OK()); - - ServiceContextNoop serviceContext; - ServiceContextNoop::UniqueClient client; - ServiceContextNoop::UniqueOperationContext opCtx; - - AuthzManagerExternalStateMock* authzManagerExternalState; - std::unique_ptr authzManager; - std::unique_ptr authzSession; - - std::unique_ptr saslServerSession; - std::unique_ptr saslClientSession; - - void setUp() { - client = serviceContext.makeClient("test"); - opCtx = serviceContext.makeOperationContext(client.get()); - - auto uniqueAuthzManagerExternalStateMock = - stdx::make_unique(); - authzManagerExternalState = uniqueAuthzManagerExternalStateMock.get(); - authzManager = - stdx::make_unique(std::move(uniqueAuthzManagerExternalStateMock)); - authzSession = stdx::make_unique( - stdx::make_unique(authzManager.get())); - - saslServerSession = stdx::make_unique(authzSession.get()); - saslServerSession->setOpCtxt(opCtx.get()); - saslServerSession->start("test", "SCRAM-SHA-1", "mongodb", "MockServer.test", 1, false) - .transitional_ignore(); - saslClientSession = stdx::make_unique(); - saslClientSession->setParameter(NativeSaslClientSession::parameterMechanism, "SCRAM-SHA-1"); - saslClientSession->setParameter(NativeSaslClientSession::parameterServiceName, "mongodb"); - saslClientSession->setParameter(NativeSaslClientSession::parameterServiceHostname, - "MockServer.test"); - saslClientSession->setParameter(NativeSaslClientSession::parameterServiceHostAndPort, - "MockServer.test:27017"); - } -}; - -TEST_F(SCRAMSHA1Fixture, testServerStep1DoesNotIncludeNonceFromClientStep1) { - authzManagerExternalState - ->insertPrivilegeDocument( - opCtx.get(), generateSCRAMUserDocument("sajack", "sajack"), BSONObj()) - .transitional_ignore(); - - saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); - saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, - createPasswordDigest("sajack", "sajack")); - - ASSERT_OK(saslClientSession->initialize()); - - SCRAMMutators mutator; - mutator.setMutator(SaslTestState(SaslTestState::kServer, 1), [](std::string& serverMessage) { - std::string::iterator nonceBegin = serverMessage.begin() + serverMessage.find("r="); - std::string::iterator nonceEnd = std::find(nonceBegin, serverMessage.end(), ','); - serverMessage = serverMessage.replace(nonceBegin, nonceEnd, "r="); - - }); - ASSERT_EQ( - SCRAMStepsResult(SaslTestState(SaslTestState::kClient, 2), - Status(ErrorCodes::BadValue, "Incorrect SCRAM client|server nonce: r=")), - runSteps(saslServerSession.get(), saslClientSession.get(), mutator)); -} - -TEST_F(SCRAMSHA1Fixture, testClientStep2DoesNotIncludeNonceFromServerStep1) { - authzManagerExternalState - ->insertPrivilegeDocument( - opCtx.get(), generateSCRAMUserDocument("sajack", "sajack"), BSONObj()) - .transitional_ignore(); - - saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); - saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, - createPasswordDigest("sajack", "sajack")); - - ASSERT_OK(saslClientSession->initialize()); - - SCRAMMutators mutator; - mutator.setMutator(SaslTestState(SaslTestState::kClient, 2), [](std::string& clientMessage) { - std::string::iterator nonceBegin = clientMessage.begin() + clientMessage.find("r="); - std::string::iterator nonceEnd = std::find(nonceBegin, clientMessage.end(), ','); - clientMessage = clientMessage.replace(nonceBegin, nonceEnd, "r="); - }); - ASSERT_EQ( - SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 2), - Status(ErrorCodes::BadValue, "Incorrect SCRAM client|server nonce: r=")), - runSteps(saslServerSession.get(), saslClientSession.get(), mutator)); -} - -TEST_F(SCRAMSHA1Fixture, testClientStep2GivesBadProof) { - authzManagerExternalState - ->insertPrivilegeDocument( - opCtx.get(), generateSCRAMUserDocument("sajack", "sajack"), BSONObj()) - .transitional_ignore(); - - saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); - saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, - createPasswordDigest("sajack", "sajack")); - - ASSERT_OK(saslClientSession->initialize()); - - SCRAMMutators mutator; - mutator.setMutator(SaslTestState(SaslTestState::kClient, 2), [](std::string& clientMessage) { - std::string::iterator proofBegin = clientMessage.begin() + clientMessage.find("p=") + 2; - std::string::iterator proofEnd = std::find(proofBegin, clientMessage.end(), ','); - clientMessage = clientMessage.replace( - proofBegin, proofEnd, corruptEncodedPayload(clientMessage, proofBegin, proofEnd)); - - }); - - ASSERT_EQ(SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 2), - Status(ErrorCodes::AuthenticationFailed, - "SCRAM authentication failed, storedKey mismatch")), - - runSteps(saslServerSession.get(), saslClientSession.get(), mutator)); -} - -TEST_F(SCRAMSHA1Fixture, testServerStep2GivesBadVerifier) { - authzManagerExternalState - ->insertPrivilegeDocument( - opCtx.get(), generateSCRAMUserDocument("sajack", "sajack"), BSONObj()) - .transitional_ignore(); - - saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); - saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, - createPasswordDigest("sajack", "sajack")); - - ASSERT_OK(saslClientSession->initialize()); - - std::string encodedVerifier; - SCRAMMutators mutator; - mutator.setMutator( - SaslTestState(SaslTestState::kServer, 2), [&encodedVerifier](std::string& serverMessage) { - std::string::iterator verifierBegin = - serverMessage.begin() + serverMessage.find("v=") + 2; - std::string::iterator verifierEnd = std::find(verifierBegin, serverMessage.end(), ','); - encodedVerifier = corruptEncodedPayload(serverMessage, verifierBegin, verifierEnd); - - serverMessage = serverMessage.replace(verifierBegin, verifierEnd, encodedVerifier); - - }); - - auto result = runSteps(saslServerSession.get(), saslClientSession.get(), mutator); - - ASSERT_EQ(SCRAMStepsResult( - SaslTestState(SaslTestState::kClient, 3), - Status(ErrorCodes::BadValue, - str::stream() << "Client failed to verify SCRAM ServerSignature, received " - << encodedVerifier)), - result); -} - - -TEST_F(SCRAMSHA1Fixture, testSCRAM) { - authzManagerExternalState - ->insertPrivilegeDocument( - opCtx.get(), generateSCRAMUserDocument("sajack", "sajack"), BSONObj()) - .transitional_ignore(); - - saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); - saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, - createPasswordDigest("sajack", "sajack")); - - ASSERT_OK(saslClientSession->initialize()); - - ASSERT_EQ(goalState, runSteps(saslServerSession.get(), saslClientSession.get())); -} - -TEST_F(SCRAMSHA1Fixture, testSCRAMWithChannelBindingSupportedByClient) { - authzManagerExternalState - ->insertPrivilegeDocument( - opCtx.get(), generateSCRAMUserDocument("sajack", "sajack"), BSONObj()) - .transitional_ignore(); - - saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); - saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, - createPasswordDigest("sajack", "sajack")); - - ASSERT_OK(saslClientSession->initialize()); - - SCRAMMutators mutator; - mutator.setMutator(SaslTestState(SaslTestState::kClient, 1), [](std::string& clientMessage) { - clientMessage.replace(clientMessage.begin(), clientMessage.begin() + 1, "y"); - }); - - ASSERT_EQ(goalState, runSteps(saslServerSession.get(), saslClientSession.get(), mutator)); -} - -TEST_F(SCRAMSHA1Fixture, testSCRAMWithChannelBindingRequiredByClient) { - authzManagerExternalState - ->insertPrivilegeDocument( - opCtx.get(), generateSCRAMUserDocument("sajack", "sajack"), BSONObj()) - .transitional_ignore(); - - saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); - saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, - createPasswordDigest("sajack", "sajack")); - - ASSERT_OK(saslClientSession->initialize()); - - SCRAMMutators mutator; - mutator.setMutator(SaslTestState(SaslTestState::kClient, 1), [](std::string& clientMessage) { - clientMessage.replace(clientMessage.begin(), clientMessage.begin() + 1, "p=tls-unique"); - }); - - ASSERT_EQ( - SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 1), - Status(ErrorCodes::BadValue, "Server does not support channel binding")), - runSteps(saslServerSession.get(), saslClientSession.get(), mutator)); -} - -TEST_F(SCRAMSHA1Fixture, testSCRAMWithInvalidChannelBinding) { - authzManagerExternalState - ->insertPrivilegeDocument( - opCtx.get(), generateSCRAMUserDocument("sajack", "sajack"), BSONObj()) - .transitional_ignore(); - - saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); - saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, - createPasswordDigest("sajack", "sajack")); - - ASSERT_OK(saslClientSession->initialize()); - - SCRAMMutators mutator; - mutator.setMutator(SaslTestState(SaslTestState::kClient, 1), [](std::string& clientMessage) { - clientMessage.replace(clientMessage.begin(), clientMessage.begin() + 1, "v=illegalGarbage"); - }); - - ASSERT_EQ(SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 1), - Status(ErrorCodes::BadValue, - "Incorrect SCRAM client message prefix: v=illegalGarbage")), - runSteps(saslServerSession.get(), saslClientSession.get(), mutator)); -} - -TEST_F(SCRAMSHA1Fixture, testNULLInPassword) { - authzManagerExternalState - ->insertPrivilegeDocument( - opCtx.get(), generateSCRAMUserDocument("sajack", "saj\0ack"), BSONObj()) - .transitional_ignore(); - - saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); - saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, - createPasswordDigest("sajack", "saj\0ack")); - - ASSERT_OK(saslClientSession->initialize()); - - ASSERT_EQ(goalState, runSteps(saslServerSession.get(), saslClientSession.get())); -} - - -TEST_F(SCRAMSHA1Fixture, testCommasInUsernameAndPassword) { - authzManagerExternalState - ->insertPrivilegeDocument( - opCtx.get(), generateSCRAMUserDocument("s,a,jack", "s,a,jack"), BSONObj()) - .transitional_ignore(); - - saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "s,a,jack"); - saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, - createPasswordDigest("s,a,jack", "s,a,jack")); - - ASSERT_OK(saslClientSession->initialize()); - - ASSERT_EQ(goalState, runSteps(saslServerSession.get(), saslClientSession.get())); -} - -TEST_F(SCRAMSHA1Fixture, testIncorrectUser) { - saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); - saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, - createPasswordDigest("sajack", "sajack")); - - ASSERT_OK(saslClientSession->initialize()); - - ASSERT_EQ(SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 1), - Status(ErrorCodes::UserNotFound, "Could not find user sajack@test")), - runSteps(saslServerSession.get(), saslClientSession.get())); -} - -TEST_F(SCRAMSHA1Fixture, testIncorrectPassword) { - authzManagerExternalState - ->insertPrivilegeDocument( - opCtx.get(), generateSCRAMUserDocument("sajack", "sajack"), BSONObj()) - .transitional_ignore(); - - saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); - saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, - createPasswordDigest("sajack", "invalidPassword")); - - ASSERT_OK(saslClientSession->initialize()); - - ASSERT_EQ(SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 2), - Status(ErrorCodes::AuthenticationFailed, - "SCRAM authentication failed, storedKey mismatch")), - runSteps(saslServerSession.get(), saslClientSession.get())); -} - -TEST_F(SCRAMSHA1Fixture, testOptionalClientExtensions) { - // Verify server ignores unknown/optional extensions sent by client. - ASSERT_OK(authzManagerExternalState->insertPrivilegeDocument( - opCtx.get(), generateSCRAMUserDocument("sajack", "sajack"), BSONObj())); - - saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); - saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, - createPasswordDigest("sajack", "sajack")); - - ASSERT_OK(saslClientSession->initialize()); - - SCRAMMutators mutator; - mutator.setMutator(SaslTestState(SaslTestState::kClient, 1), [](std::string& clientMessage) { - clientMessage += ",x=unsupported-extension"; - }); - - // Optional client extension is successfully ignored, or we'd have failed in step 1. - // We still fail at step 2, because client was unaware of the injected extension. - ASSERT_EQ(SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 2), - Status(ErrorCodes::AuthenticationFailed, - "SCRAM authentication failed, storedKey mismatch")), - runSteps(saslServerSession.get(), saslClientSession.get(), mutator)); -} - -TEST_F(SCRAMSHA1Fixture, testOptionalServerExtensions) { - // Verify client errors on unknown/optional extensions sent by server. - ASSERT_OK(authzManagerExternalState->insertPrivilegeDocument( - opCtx.get(), generateSCRAMUserDocument("sajack", "sajack"), BSONObj())); - - saslClientSession->setParameter(NativeSaslClientSession::parameterUser, "sajack"); - saslClientSession->setParameter(NativeSaslClientSession::parameterPassword, - createPasswordDigest("sajack", "sajack")); - - ASSERT_OK(saslClientSession->initialize()); - - SCRAMMutators mutator; - mutator.setMutator(SaslTestState(SaslTestState::kServer, 1), [](std::string& serverMessage) { - serverMessage += ",x=unsupported-extension"; - }); - - // As with testOptionalClientExtensions, we can be confident that the optionality - // is respected because we would have failed at client step 2. - // We do still fail at server step 2 because server was unaware of injected extension. - ASSERT_EQ(SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 2), - Status(ErrorCodes::AuthenticationFailed, - "SCRAM authentication failed, storedKey mismatch")), - runSteps(saslServerSession.get(), saslClientSession.get(), mutator)); -} - -TEST(SCRAMSHA1Cache, testGetFromEmptyCache) { - SCRAMSHA1ClientCache cache; - std::string saltStr("saltsaltsaltsalt"); - std::vector salt(saltStr.begin(), saltStr.end()); - HostAndPort host("localhost:27017"); - - ASSERT_FALSE(cache.getCachedSecrets(host, scram::SHA1Presecrets("aaa", salt, 10000))); -} - - -TEST(SCRAMSHA1Cache, testSetAndGet) { - SCRAMSHA1ClientCache cache; - std::string saltStr("saltsaltsaltsalt"); - std::string badSaltStr("s@lts@lts@lts@lt"); - std::vector salt(saltStr.begin(), saltStr.end()); - std::vector badSalt(badSaltStr.begin(), badSaltStr.end()); - HostAndPort host("localhost:27017"); - - 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()); -} - - -TEST(SCRAMSHA1Cache, testSetAndGetWithDifferentParameters) { - SCRAMSHA1ClientCache cache; - std::string saltStr("saltsaltsaltsalt"); - std::string badSaltStr("s@lts@lts@lts@lt"); - std::vector salt(saltStr.begin(), saltStr.end()); - std::vector badSalt(badSaltStr.begin(), badSaltStr.end()); - HostAndPort host("localhost:27017"); - - 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::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))); -} - - -TEST(SCRAMSHA1Cache, testSetAndReset) { - SCRAMSHA1ClientCache cache; - StringData saltStr("saltsaltsaltsalt"); - std::vector salt(saltStr.begin(), saltStr.end()); - HostAndPort host("localhost:27017"); - - 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::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()); -} - -} // namespace mongo diff --git a/src/mongo/db/auth/security_key.cpp b/src/mongo/db/auth/security_key.cpp index 04c46e3e4dd..3e14d65c00e 100644 --- a/src/mongo/db/auth/security_key.cpp +++ b/src/mongo/db/auth/security_key.cpp @@ -39,6 +39,7 @@ #include "mongo/base/status_with.h" #include "mongo/client/sasl_client_authenticate.h" #include "mongo/crypto/mechanism_scram.h" +#include "mongo/crypto/sha1_block.h" #include "mongo/db/auth/action_set.h" #include "mongo/db/auth/action_type.h" #include "mongo/db/auth/authorization_manager.h" @@ -76,7 +77,7 @@ bool setUpSecurityKey(const string& filename) { const auto password = mongo::createPasswordDigest(internalSecurity.user->getName().getUser().toString(), str); - auto creds = scram::SHA1Secrets::generateCredentials( + auto creds = scram::Secrets::generateCredentials( password, saslGlobalParams.scramSHA1IterationCount.load()); credentials.scram_sha1.iterationCount = creds[scram::kIterationCountFieldName].Int(); credentials.scram_sha1.salt = creds[scram::kSaltFieldName].String(); diff --git a/src/mongo/db/logical_session_id_test.cpp b/src/mongo/db/logical_session_id_test.cpp index c2e0bf17105..f80d4eca0f4 100644 --- a/src/mongo/db/logical_session_id_test.cpp +++ b/src/mongo/db/logical_session_id_test.cpp @@ -33,6 +33,8 @@ #include "mongo/db/logical_session_id.h" #include "mongo/crypto/mechanism_scram.h" +#include "mongo/crypto/sha1_block.h" +#include "mongo/crypto/sha256_block.h" #include "mongo/db/auth/action_set.h" #include "mongo/db/auth/action_type.h" #include "mongo/db/auth/authorization_manager.h" @@ -105,7 +107,7 @@ public: } User* addSimpleUser(UserName un) { - const auto creds = BSON("SCRAM-SHA-1" << scram::SHA1Secrets::generateCredentials( + const auto creds = BSON("SCRAM-SHA-1" << scram::Secrets::generateCredentials( "a", saslGlobalParams.scramSHA1IterationCount.load())); ASSERT_OK(managerState->insertPrivilegeDocument( _opCtx.get(), @@ -120,8 +122,8 @@ public: } User* addClusterUser(UserName un) { - const auto creds = BSON("SCRAM-SHA-1" << scram::SHA1Secrets::generateCredentials( - "a", saslGlobalParams.scramSHA1IterationCount.load())); + const auto creds = BSON("SCRAM-SHA-256" << scram::Secrets::generateCredentials( + "a", saslGlobalParams.scramSHA256IterationCount.load())); ASSERT_OK(managerState->insertPrivilegeDocument( _opCtx.get(), BSON("user" << un.getUser() << "db" << un.getDB() << "credentials" << creds << "roles" -- cgit v1.2.1