diff options
Diffstat (limited to 'src/mongo/db')
-rw-r--r-- | src/mongo/db/auth/sasl_plain_server_conversation.cpp | 61 | ||||
-rw-r--r-- | src/mongo/db/auth/sasl_scram_server_conversation.cpp | 28 | ||||
-rw-r--r-- | src/mongo/db/auth/sasl_scram_server_conversation.h | 41 | ||||
-rw-r--r-- | src/mongo/db/auth/security_key.cpp | 8 | ||||
-rw-r--r-- | src/mongo/db/auth/user.cpp | 20 | ||||
-rw-r--r-- | src/mongo/db/auth/user.h | 32 | ||||
-rw-r--r-- | src/mongo/db/auth/user_document_parser.cpp | 93 | ||||
-rw-r--r-- | src/mongo/db/auth/user_document_parser_test.cpp | 42 |
8 files changed, 242 insertions, 83 deletions
diff --git a/src/mongo/db/auth/sasl_plain_server_conversation.cpp b/src/mongo/db/auth/sasl_plain_server_conversation.cpp index 8acefa7b90d..a21686f4af0 100644 --- a/src/mongo/db/auth/sasl_plain_server_conversation.cpp +++ b/src/mongo/db/auth/sasl_plain_server_conversation.cpp @@ -35,6 +35,31 @@ #include "mongo/util/text.h" namespace mongo { +namespace { +template <typename HashBlock> +StatusWith<bool> trySCRAM(const User::CredentialData& credentials, StringData pwd) { + const auto scram = credentials.scram<HashBlock>(); + if (!scram.isValid()) { + // No stored credentials available. + return false; + } + + const auto decodedSalt = base64::decode(scram.salt); + scram::Secrets<HashBlock> secrets(scram::Presecrets<HashBlock>( + pwd.toString(), + std::vector<std::uint8_t>(reinterpret_cast<const std::uint8_t*>(decodedSalt.c_str()), + reinterpret_cast<const std::uint8_t*>(decodedSalt.c_str()) + + decodedSalt.size()), + scram.iterationCount)); + if (scram.storedKey != base64::encode(reinterpret_cast<const char*>(secrets.storedKey().data()), + secrets.storedKey().size())) { + return Status(ErrorCodes::AuthenticationFailed, + str::stream() << "Incorrect user name or password"); + } + + return true; +} +} // namespace SaslPLAINServerConversation::SaslPLAINServerConversation(SaslAuthenticationSession* saslAuthSession) : SaslServerConversation(saslAuthSession) {} @@ -98,31 +123,31 @@ StatusWith<bool> SaslPLAINServerConversation::step(StringData inputData, std::st &userObj); if (!status.isOK()) { - return StatusWith<bool>(status); + return status; } - const User::CredentialData creds = userObj->getCredentials(); + const auto creds = userObj->getCredentials(); _saslAuthSession->getAuthorizationSession()->getAuthorizationManager().releaseUser(userObj); - std::string authDigest = createPasswordDigest(_user, pwd->c_str()); - - // Handle schemaVersion28SCRAM (SCRAM only mode) - std::string decodedSalt = base64::decode(creds.scram.salt); - scram::SHA1Secrets secrets(scram::SHA1Presecrets( - authDigest, - std::vector<std::uint8_t>(reinterpret_cast<const std::uint8_t*>(decodedSalt.c_str()), - reinterpret_cast<const std::uint8_t*>(decodedSalt.c_str()) + 16), - creds.scram.iterationCount)); - if (creds.scram.storedKey != - base64::encode(reinterpret_cast<const char*>(secrets.storedKey().data()), - secrets.storedKey().size())) { - return StatusWith<bool>(ErrorCodes::AuthenticationFailed, - mongoutils::str::stream() << "Incorrect user name or password"); + *outputData = ""; + const auto sha256Status = trySCRAM<SHA256Block>(creds, pwd->c_str()); + if (!sha256Status.isOK()) { + return sha256Status; + } + if (sha256Status.getValue()) { + return true; } - *outputData = ""; + const auto authDigest = createPasswordDigest(_user, pwd->c_str()); + const auto sha1Status = trySCRAM<SHA1Block>(creds, authDigest); + if (!sha1Status.isOK()) { + return sha1Status; + } + if (sha1Status.getValue()) { + return true; + } - return StatusWith<bool>(true); + return Status(ErrorCodes::AuthenticationFailed, str::stream() << "No credentials available."); } } // namespace mongo diff --git a/src/mongo/db/auth/sasl_scram_server_conversation.cpp b/src/mongo/db/auth/sasl_scram_server_conversation.cpp index f332d7e200d..2ac64aedb34 100644 --- a/src/mongo/db/auth/sasl_scram_server_conversation.cpp +++ b/src/mongo/db/auth/sasl_scram_server_conversation.cpp @@ -173,18 +173,18 @@ StatusWith<bool> SaslSCRAMServerConversation::_firstStep(std::vector<string>& in _saslAuthSession->getAuthorizationSession()->getAuthorizationManager().releaseUser(userObj); - // Check for authentication attempts of the __system user on - // systems started without a keyfile. - if (userName == internalSecurity.user->getName() && _creds.scram.salt.empty()) { - return StatusWith<bool>(ErrorCodes::AuthenticationFailed, - "It is not possible to authenticate as the __system user " - "on servers started without a --keyFile parameter"); - } - - if (!_creds.scram.isValid()) { - return Status(ErrorCodes::AuthenticationFailed, - "Unable to perform SCRAM authentication for a user with missing " - "or invalid SCRAM credentials"); + if (!initAndValidateCredentials()) { + // Check for authentication attempts of the __system user on + // systems started without a keyfile. + if (userName == internalSecurity.user->getName()) { + return Status(ErrorCodes::AuthenticationFailed, + "It is not possible to authenticate as the __system user " + "on servers started without a --keyFile parameter"); + } else { + return Status(ErrorCodes::AuthenticationFailed, + "Unable to perform SCRAM authentication for a user with missing " + "or invalid SCRAM credentials"); + } } // Generate server-first-message @@ -201,7 +201,7 @@ StatusWith<bool> SaslSCRAMServerConversation::_firstStep(std::vector<string>& in _nonce = clientNonce + base64::encode(reinterpret_cast<char*>(binaryNonce), sizeof(binaryNonce)); StringBuilder sb; - sb << "r=" << _nonce << ",s=" << _creds.scram.salt << ",i=" << _creds.scram.iterationCount; + sb << "r=" << _nonce << ",s=" << getSalt() << ",i=" << getIterationCount(); *outputData = sb.str(); // add server-first-message to authMessage @@ -269,7 +269,7 @@ StatusWith<bool> SaslSCRAMServerConversation::_secondStep(const std::vector<stri // ClientSignature := HMAC(StoredKey, AuthMessage) // ClientKey := ClientSignature XOR ClientProof // ServerSignature := HMAC(ServerKey, AuthMessage) - invariant(_creds.scram.isValid()); + invariant(initAndValidateCredentials()); if (!verifyClientProof(base64::decode(clientProof))) { return StatusWith<bool>(ErrorCodes::AuthenticationFailed, diff --git a/src/mongo/db/auth/sasl_scram_server_conversation.h b/src/mongo/db/auth/sasl_scram_server_conversation.h index d131433f494..4c08140ef41 100644 --- a/src/mongo/db/auth/sasl_scram_server_conversation.h +++ b/src/mongo/db/auth/sasl_scram_server_conversation.h @@ -59,9 +59,24 @@ public: StatusWith<bool> step(StringData inputData, std::string* outputData) override; /** + * Initialize details, called after _creds has been loaded. + */ + virtual bool initAndValidateCredentials() = 0; + + /** + * Provide the predetermined salt to the client. + */ + virtual std::string getSalt() const = 0; + + /** + * Provide the predetermined iteration count to the client. + */ + virtual size_t getIterationCount() const = 0; + + /** * Verify proof submitted by authenticating client. */ - virtual bool verifyClientProof(StringData) = 0; + virtual bool verifyClientProof(StringData) const = 0; /** * Generate a signature to prove ourselves. @@ -95,9 +110,27 @@ public: : SaslSCRAMServerConversation(session) {} ~SaslSCRAMServerConversationImpl() override = default; - bool verifyClientProof(StringData clientProof) final { - _credentials = scram::Secrets<HashBlock>( - "", base64::decode(_creds.scram.storedKey), base64::decode(_creds.scram.serverKey)); + bool initAndValidateCredentials() final { + const auto& scram = _creds.scram<HashBlock>(); + if (!scram.isValid()) { + return false; + } + if (!_credentials) { + _credentials = scram::Secrets<HashBlock>( + "", base64::decode(scram.storedKey), base64::decode(scram.serverKey)); + } + return true; + } + + std::string getSalt() const final { + return _creds.scram<HashBlock>().salt; + } + + size_t getIterationCount() const final { + return _creds.scram<HashBlock>().iterationCount; + } + + bool verifyClientProof(StringData clientProof) const final { return _credentials.verifyClientProof(_authMessage, clientProof); } diff --git a/src/mongo/db/auth/security_key.cpp b/src/mongo/db/auth/security_key.cpp index 8ee1a40df68..04c46e3e4dd 100644 --- a/src/mongo/db/auth/security_key.cpp +++ b/src/mongo/db/auth/security_key.cpp @@ -78,10 +78,10 @@ bool setUpSecurityKey(const string& filename) { auto creds = scram::SHA1Secrets::generateCredentials( password, saslGlobalParams.scramSHA1IterationCount.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(); + credentials.scram_sha1.iterationCount = creds[scram::kIterationCountFieldName].Int(); + credentials.scram_sha1.salt = creds[scram::kSaltFieldName].String(); + credentials.scram_sha1.storedKey = creds[scram::kStoredKeyFieldName].String(); + credentials.scram_sha1.serverKey = creds[scram::kServerKeyFieldName].String(); internalSecurity.user->setCredentials(credentials); diff --git a/src/mongo/db/auth/user.cpp b/src/mongo/db/auth/user.cpp index 698e78297fd..a14a033a536 100644 --- a/src/mongo/db/auth/user.cpp +++ b/src/mongo/db/auth/user.cpp @@ -30,6 +30,8 @@ #include <vector> +#include "mongo/crypto/sha1_block.h" +#include "mongo/crypto/sha256_block.h" #include "mongo/db/auth/authorization_manager.h" #include "mongo/db/auth/privilege.h" #include "mongo/db/auth/resource_pattern.h" @@ -57,6 +59,24 @@ User::~User() { dassert(_refCount == 0); } +template <> +User::SCRAMCredentials<SHA1Block>& User::CredentialData::scram<SHA1Block>() { + return scram_sha1; +} +template <> +const User::SCRAMCredentials<SHA1Block>& User::CredentialData::scram<SHA1Block>() const { + return scram_sha1; +} + +template <> +User::SCRAMCredentials<SHA256Block>& User::CredentialData::scram<SHA256Block>() { + return scram_sha256; +} +template <> +const User::SCRAMCredentials<SHA256Block>& User::CredentialData::scram<SHA256Block>() const { + return scram_sha256; +} + const UserName& User::getName() const { return _name; } diff --git a/src/mongo/db/auth/user.h b/src/mongo/db/auth/user.h index 8d180e9b8b9..742fd96d17e 100644 --- a/src/mongo/db/auth/user.h +++ b/src/mongo/db/auth/user.h @@ -31,6 +31,8 @@ #include <vector> #include "mongo/base/disallow_copying.h" +#include "mongo/crypto/sha1_block.h" +#include "mongo/crypto/sha256_block.h" #include "mongo/db/auth/privilege.h" #include "mongo/db/auth/resource_pattern.h" #include "mongo/db/auth/restriction_set.h" @@ -60,6 +62,7 @@ class User { MONGO_DISALLOW_COPYING(User); public: + template <typename HashBlock> struct SCRAMCredentials { SCRAMCredentials() : iterationCount(0), salt(""), serverKey(""), storedKey("") {} @@ -69,21 +72,30 @@ public: std::string storedKey; bool isValid() const { - // 160bit -> 20octets -> * 4/3 -> 26.667 -> padded to 28 - const size_t kEncodedSHA1Length = 28; - // 128bit -> 16octets -> * 4/3 -> 21.333 -> padded to 24 - const size_t kEncodedSaltLength = 24; - - return (salt.size() == kEncodedSaltLength) && base64::validate(salt) && - (serverKey.size() == kEncodedSHA1Length) && base64::validate(serverKey) && - (storedKey.size() == kEncodedSHA1Length) && base64::validate(storedKey); + constexpr auto kEncodedHashLength = base64::encodedLength(HashBlock::kHashLength); + constexpr auto kEncodedSaltLength = base64::encodedLength(HashBlock::kHashLength - 4); + + return (iterationCount > 0) && (salt.size() == kEncodedSaltLength) && + base64::validate(salt) && (serverKey.size() == kEncodedHashLength) && + base64::validate(serverKey) && (storedKey.size() == kEncodedHashLength) && + base64::validate(storedKey); } }; struct CredentialData { - CredentialData() : scram(), isExternal(false) {} + CredentialData() : scram_sha1(), scram_sha256(), isExternal(false) {} - SCRAMCredentials scram; + SCRAMCredentials<SHA1Block> scram_sha1; + SCRAMCredentials<SHA256Block> scram_sha256; bool isExternal; + + // Select the template determined version of SCRAMCredentials. + // For example: creds.scram<SHA1Block>().isValid() + // is equivalent to creds.scram_sha1.isValid() + template <typename HashBlock> + SCRAMCredentials<HashBlock>& scram(); + + template <typename HashBlock> + const SCRAMCredentials<HashBlock>& scram() const; }; typedef unordered_map<ResourcePattern, Privilege> ResourcePrivilegeMap; diff --git a/src/mongo/db/auth/user_document_parser.cpp b/src/mongo/db/auth/user_document_parser.cpp index ee2ff308dbd..55bfb6460bd 100644 --- a/src/mongo/db/auth/user_document_parser.cpp +++ b/src/mongo/db/auth/user_document_parser.cpp @@ -57,7 +57,8 @@ const std::string READONLY_FIELD_NAME = "readOnly"; const std::string CREDENTIALS_FIELD_NAME = "credentials"; const std::string ROLE_NAME_FIELD_NAME = "role"; const std::string ROLE_DB_FIELD_NAME = "db"; -const std::string SCRAM_CREDENTIAL_FIELD_NAME = "SCRAM-SHA-1"; +const std::string SCRAMSHA1_CREDENTIAL_FIELD_NAME = "SCRAM-SHA-1"; +const std::string SCRAMSHA256_CREDENTIAL_FIELD_NAME = "SCRAM-SHA-256"; const std::string MONGODB_EXTERNAL_CREDENTIAL_FIELD_NAME = "external"; constexpr StringData AUTHENTICATION_RESTRICTIONS_FIELD_NAME = "authenticationRestrictions"_sd; constexpr StringData INHERITED_AUTHENTICATION_RESTRICTIONS_FIELD_NAME = @@ -71,6 +72,39 @@ inline Status _badValue(const std::string& reason) { return Status(ErrorCodes::BadValue, reason); } +template <typename Credentials> +bool parseSCRAMCredentials(const BSONElement& credentialsElement, + Credentials& scram, + const std::string& fieldName) { + const auto scramElement = credentialsElement[fieldName]; + if (scramElement.eoo()) { + return false; + } + + // We are asserting rather then returning errors since these + // fields should have been prepopulated by the calling code. + scram.iterationCount = scramElement["iterationCount"].numberInt(); + uassert(17501, + str::stream() << "Invalid or missing " << fieldName << " iteration count", + scram.iterationCount > 0); + + scram.salt = scramElement["salt"].str(); + uassert(17502, str::stream() << "Missing " << fieldName << " salt", !scram.salt.empty()); + + scram.serverKey = scramElement["serverKey"].str(); + uassert( + 17503, str::stream() << "Missing " << fieldName << " serverKey", !scram.serverKey.empty()); + + scram.storedKey = scramElement["storedKey"].str(); + uassert( + 17504, str::stream() << "Missing " << fieldName << " storedKey", !scram.storedKey.empty()); + + uassert(50684, + str::stream() << "credential document " << fieldName << " failed validation", + scram.isValid()); + return true; +} + } // namespace Status _checkV2RolesArray(const BSONElement& rolesElement) { @@ -134,13 +168,30 @@ Status V2UserDocumentParser::checkValidUserDocument(const BSONObj& doc) const { "'credentials' field set to {external: true}"); } } else { - BSONElement scramElement = credentialsObj[SCRAM_CREDENTIAL_FIELD_NAME]; + const auto validateScram = [&credentialsObj](const auto& fieldName) { + auto scramElement = credentialsObj[fieldName]; - if (!scramElement.eoo()) { + if (scramElement.eoo()) { + return Status(ErrorCodes::NoSuchKey, + str::stream() << fieldName << " does not exist"); + } if (scramElement.type() != Object) { - return _badValue("SCRAM credential must be an object, if present"); + return _badValue(str::stream() << fieldName + << " credential must be an object, if present"); } - } else { + return Status::OK(); + }; + + const auto sha1status = validateScram(SCRAMSHA1_CREDENTIAL_FIELD_NAME); + if (!sha1status.isOK() && (sha1status.code() != ErrorCodes::NoSuchKey)) { + return sha1status; + } + const auto sha256status = validateScram(SCRAMSHA256_CREDENTIAL_FIELD_NAME); + if (!sha256status.isOK() && (sha256status.code() != ErrorCodes::NoSuchKey)) { + return sha256status; + } + + if (!sha1status.isOK() && !sha256status.isOK()) { return _badValue( "User document must provide credentials for all " "non-external users"); @@ -191,29 +242,15 @@ Status V2UserDocumentParser::initializeUserCredentialsFromUserDocument( "credentials to {external:true}"); } } else { - BSONElement scramElement = credentialsElement.Obj()[SCRAM_CREDENTIAL_FIELD_NAME]; - - if (scramElement.eoo()) { - return Status(ErrorCodes::UnsupportedFormat, - "User documents must provide credentials for SCRAM-SHA-1"); - } - - if (!scramElement.eoo()) { - // We are asserting rather then returning errors since these - // fields should have been prepopulated by the calling code. - credentials.scram.iterationCount = scramElement.Obj()["iterationCount"].numberInt(); - uassert(17501, - "Invalid or missing SCRAM iteration count", - credentials.scram.iterationCount > 0); - - credentials.scram.salt = scramElement.Obj()["salt"].str(); - uassert(17502, "Missing SCRAM salt", !credentials.scram.salt.empty()); - - credentials.scram.serverKey = scramElement["serverKey"].str(); - uassert(17503, "Missing SCRAM serverKey", !credentials.scram.serverKey.empty()); - - credentials.scram.storedKey = scramElement["storedKey"].str(); - uassert(17504, "Missing SCRAM storedKey", !credentials.scram.storedKey.empty()); + const bool haveSha1 = parseSCRAMCredentials( + credentialsElement, credentials.scram_sha1, SCRAMSHA1_CREDENTIAL_FIELD_NAME); + const bool haveSha256 = parseSCRAMCredentials( + credentialsElement, credentials.scram_sha256, SCRAMSHA256_CREDENTIAL_FIELD_NAME); + + if (!haveSha1 && !haveSha256) { + return Status( + ErrorCodes::UnsupportedFormat, + "User documents must provide credentials for SCRAM-SHA-1 and/or SCRAM-SHA-256"); } credentials.isExternal = false; diff --git a/src/mongo/db/auth/user_document_parser_test.cpp b/src/mongo/db/auth/user_document_parser_test.cpp index b6e15b4674a..94f9f9d70b0 100644 --- a/src/mongo/db/auth/user_document_parser_test.cpp +++ b/src/mongo/db/auth/user_document_parser_test.cpp @@ -58,13 +58,17 @@ public: unique_ptr<User> adminUser; V2UserDocumentParser v2parser; BSONObj credentials; + BSONObj sha1_creds, sha256_creds; void setUp() { user.reset(new User(UserName("spencer", "test"))); adminUser.reset(new User(UserName("admin", "admin"))); - credentials = BSON("SCRAM-SHA-1" << scram::SHA1Secrets::generateCredentials( - "a", saslGlobalParams.scramSHA1IterationCount.load())); + sha1_creds = scram::Secrets<SHA1Block>::generateCredentials( + "a", saslGlobalParams.scramSHA1IterationCount.load()); + sha256_creds = scram::Secrets<SHA256Block>::generateCredentials( + "a", saslGlobalParams.scramSHA256IterationCount.load()); + credentials = BSON("SCRAM-SHA-1" << sha1_creds << "SCRAM-SHA-256" << sha256_creds); } }; @@ -285,7 +289,33 @@ TEST_F(V2UserDocumentParsing, V2CredentialExtraction) { << BSON("foo" << "bar")))); - // Make sure extracting valid credentials works + // May specify only SCRAM-SHA-1 credentials + ASSERT_OK(v2parser.initializeUserCredentialsFromUserDocument(user.get(), + BSON("user" + << "spencer" + << "db" + << "test" + << "credentials" + << BSON("SCRAM-SHA-1" + << sha1_creds)))); + ASSERT(user->getCredentials().scram_sha1.isValid()); + ASSERT(!user->getCredentials().scram_sha256.isValid()); + ASSERT(!user->getCredentials().isExternal); + + // May specify only SCRAM-SHA-256 credentials + ASSERT_OK(v2parser.initializeUserCredentialsFromUserDocument(user.get(), + BSON("user" + << "spencer" + << "db" + << "test" + << "credentials" + << BSON("SCRAM-SHA-256" + << sha256_creds)))); + ASSERT(!user->getCredentials().scram_sha1.isValid()); + ASSERT(user->getCredentials().scram_sha256.isValid()); + ASSERT(!user->getCredentials().isExternal); + + // Make sure extracting valid combined credentials works ASSERT_OK(v2parser.initializeUserCredentialsFromUserDocument(user.get(), BSON("user" << "spencer" @@ -293,7 +323,8 @@ TEST_F(V2UserDocumentParsing, V2CredentialExtraction) { << "test" << "credentials" << credentials))); - ASSERT(user->getCredentials().scram.isValid()); + ASSERT(user->getCredentials().scram_sha1.isValid()); + ASSERT(user->getCredentials().scram_sha256.isValid()); ASSERT(!user->getCredentials().isExternal); // Credentials are {external:true if users's db is $external @@ -305,7 +336,8 @@ TEST_F(V2UserDocumentParsing, V2CredentialExtraction) { << "$external" << "credentials" << BSON("external" << true)))); - ASSERT(!user->getCredentials().scram.isValid()); + ASSERT(!user->getCredentials().scram_sha1.isValid()); + ASSERT(!user->getCredentials().scram_sha256.isValid()); ASSERT(user->getCredentials().isExternal); } |