diff options
author | Andreas <agralius@gmail.com> | 2014-08-13 12:10:38 -0400 |
---|---|---|
committer | Andreas <agralius@gmail.com> | 2014-08-28 13:20:25 -0400 |
commit | e6a8e256ca73e7596d9f8b7b3a3b00d8c08f6554 (patch) | |
tree | cc5ca0887015b6c8d09bdc932756faaac8df3c24 /src/mongo/db/auth | |
parent | ca6a26760c2150736562f49201fcb7b4de5e53f1 (diff) | |
download | mongo-e6a8e256ca73e7596d9f8b7b3a3b00d8c08f6554.tar.gz |
SERVER-7596 Native SCRAM-SHA-1 server side support
Diffstat (limited to 'src/mongo/db/auth')
-rw-r--r-- | src/mongo/db/auth/SConscript | 14 | ||||
-rw-r--r-- | src/mongo/db/auth/mechanism_scram.cpp | 70 | ||||
-rw-r--r-- | src/mongo/db/auth/native_sasl_authentication_session.cpp | 160 | ||||
-rw-r--r-- | src/mongo/db/auth/native_sasl_authentication_session.h | 70 | ||||
-rw-r--r-- | src/mongo/db/auth/sasl_authentication_session.cpp | 96 | ||||
-rw-r--r-- | src/mongo/db/auth/sasl_authentication_session.h | 167 | ||||
-rw-r--r-- | src/mongo/db/auth/sasl_commands.cpp | 362 | ||||
-rw-r--r-- | src/mongo/db/auth/sasl_conversation.cpp | 43 | ||||
-rw-r--r-- | src/mongo/db/auth/sasl_conversation.h | 89 | ||||
-rw-r--r-- | src/mongo/db/auth/sasl_options.cpp | 165 | ||||
-rw-r--r-- | src/mongo/db/auth/sasl_options.h | 61 | ||||
-rw-r--r-- | src/mongo/db/auth/sasl_plain_server_conversation.cpp | 86 | ||||
-rw-r--r-- | src/mongo/db/auth/sasl_plain_server_conversation.h | 57 | ||||
-rw-r--r-- | src/mongo/db/auth/sasl_scramsha1_server_conversation.cpp | 304 | ||||
-rw-r--r-- | src/mongo/db/auth/sasl_scramsha1_server_conversation.h | 82 |
15 files changed, 1789 insertions, 37 deletions
diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript index 62664ad8ced..fd83b9d4adb 100644 --- a/src/mongo/db/auth/SConscript +++ b/src/mongo/db/auth/SConscript @@ -40,9 +40,19 @@ env.Library('authcore', ['action_set.cpp', env.Library('authservercommon', ['authorization_manager_global.cpp', 'authz_session_external_state_server_common.cpp', - 'mechanism_scram.cpp', + 'sasl_commands.cpp', 'security_key.cpp'], - LIBDEPS=['authcore']) + LIBDEPS=['authcore', 'authmocks', 'saslauth']) + +env.Library('saslauth', + ['mechanism_scram.cpp', + 'native_sasl_authentication_session.cpp', + 'sasl_authentication_session.cpp', + 'sasl_conversation.cpp', + 'sasl_options.cpp', + 'sasl_plain_server_conversation.cpp', + 'sasl_scramsha1_server_conversation.cpp'], + LIBDEPS=['authcore']) env.Library('authmongod', ['authz_manager_external_state_d.cpp', diff --git a/src/mongo/db/auth/mechanism_scram.cpp b/src/mongo/db/auth/mechanism_scram.cpp index d8fb309d78c..728dd1b1899 100644 --- a/src/mongo/db/auth/mechanism_scram.cpp +++ b/src/mongo/db/auth/mechanism_scram.cpp @@ -69,24 +69,24 @@ const int scramHashSize = 20; // U1 = HMAC(input, salt || 1) fassert(17494, HMAC(EVP_sha1(), - input, - inputLen, - startKey, - saltLen + 4, - output, - &hashLen)); + input, + inputLen, + startKey, + saltLen + 4, + output, + &hashLen)); memcpy(tmpRes, output, scramHashSize); // tmpRes contain Uj and result contains the accumulated XOR:ed result for (size_t i = 2; i <= iterationCount; i++) { fassert(17495, HMAC(EVP_sha1(), - input, - inputLen, - tmpRes, - scramHashSize, - tmpRes, - &hashLen)); + input, + inputLen, + tmpRes, + scramHashSize, + tmpRes, + &hashLen)); for (int k = 0; k < scramHashSize; k++) { output[k] ^= tmpRes[k]; @@ -97,11 +97,11 @@ const int scramHashSize = 20; /* Compute the SCRAM secrets storedKey and serverKey * as defined in RFC5802 */ static void computeSCRAMProperties(const std::string& password, - const unsigned char salt[], - size_t saltLen, - size_t iterationCount, - unsigned char storedKey[scramHashSize], - unsigned char serverKey[scramHashSize]) { + const unsigned char salt[], + size_t saltLen, + size_t iterationCount, + unsigned char storedKey[scramHashSize], + unsigned char serverKey[scramHashSize]) { unsigned char saltedPassword[scramHashSize]; unsigned char clientKey[scramHashSize]; @@ -118,12 +118,12 @@ const int scramHashSize = 20; // clientKey = HMAC(saltedPassword, "Client Key") const std::string clientKeyConst = "Client Key"; fassert(17498, HMAC(EVP_sha1(), - saltedPassword, - scramHashSize, - reinterpret_cast<const unsigned char*>(clientKeyConst.data()), - clientKeyConst.size(), - clientKey, - &hashLen)); + saltedPassword, + scramHashSize, + reinterpret_cast<const unsigned char*>(clientKeyConst.data()), + clientKeyConst.size(), + clientKey, + &hashLen)); // storedKey = H(clientKey) fassert(17499, SHA1(clientKey, scramHashSize, storedKey)); @@ -131,12 +131,12 @@ const int scramHashSize = 20; // serverKey = HMAC(saltedPassword, "Server Key") const std::string serverKeyConst = "Server Key"; fassert(17500, HMAC(EVP_sha1(), - saltedPassword, - scramHashSize, - reinterpret_cast<const unsigned char*>(serverKeyConst.data()), - serverKeyConst.size(), - serverKey, - &hashLen)); + saltedPassword, + scramHashSize, + reinterpret_cast<const unsigned char*>(serverKeyConst.data()), + serverKeyConst.size(), + serverKey, + &hashLen)); } #endif //MONGO_SSL @@ -158,7 +158,7 @@ const int scramHashSize = 20; userSalt[0] = sr->nextInt64(); userSalt[1] = sr->nextInt64(); std::string encodedUserSalt = - base64::encode(reinterpret_cast<char*>(&userSalt[0]), sizeof(userSalt)); + base64::encode(reinterpret_cast<char*>(userSalt), sizeof(userSalt)); // Compute SCRAM secrets serverKey and storedKey unsigned char storedKey[scramHashSize]; @@ -172,14 +172,14 @@ const int scramHashSize = 20; serverKey); std::string encodedStoredKey = - base64::encode(reinterpret_cast<char*>(&storedKey[0]), scramHashSize); + base64::encode(reinterpret_cast<char*>(storedKey), scramHashSize); std::string encodedServerKey = - base64::encode(reinterpret_cast<char*>(&serverKey[0]), scramHashSize); + base64::encode(reinterpret_cast<char*>(serverKey), scramHashSize); return BSON("iterationCount" << iterationCount << - "salt" << encodedUserSalt << - "storedKey" << encodedStoredKey << - "serverKey" << encodedServerKey); + "salt" << encodedUserSalt << + "storedKey" << encodedStoredKey << + "serverKey" << encodedServerKey); #endif } } // namespace mongo diff --git a/src/mongo/db/auth/native_sasl_authentication_session.cpp b/src/mongo/db/auth/native_sasl_authentication_session.cpp new file mode 100644 index 00000000000..4c4189d17a6 --- /dev/null +++ b/src/mongo/db/auth/native_sasl_authentication_session.cpp @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2014 MongoDB Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kAccessControl + +#include "mongo/db/auth/native_sasl_authentication_session.h" + +#include <boost/range/size.hpp> + +#include "mongo/base/init.h" +#include "mongo/base/status.h" +#include "mongo/base/string_data.h" +#include "mongo/bson/util/bson_extract.h" +#include "mongo/client/sasl_client_authenticate.h" +#include "mongo/db/commands.h" +#include "mongo/db/auth/authorization_manager.h" +#include "mongo/db/auth/authorization_manager_global.h" +#include "mongo/db/auth/authorization_session.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/sasl_options.h" +#include "mongo/db/auth/sasl_plain_server_conversation.h" +#include "mongo/db/auth/sasl_scramsha1_server_conversation.h" +#include "mongo/db/operation_context_noop.h" +#include "mongo/util/assert_util.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { +namespace { + SaslAuthenticationSession* createNativeSaslAuthenticationSession( + AuthorizationSession* authzSession) { + return new NativeSaslAuthenticationSession(authzSession); + } + + MONGO_INITIALIZER(NativeSaslServerCore)(InitializerContext* context) { + if (saslGlobalParams.hostName.empty()) + saslGlobalParams.hostName = getHostNameCached(); + if (saslGlobalParams.serviceName.empty()) + saslGlobalParams.serviceName = "mongodb"; + + SaslAuthenticationSession::create = createNativeSaslAuthenticationSession; + return Status::OK(); + } + + // PostSaslCommands is reversely dependent on CyrusSaslCommands having been run + MONGO_INITIALIZER_WITH_PREREQUISITES(PostSaslCommands, + ("NativeSaslServerCore")) + (InitializerContext*) { + + AuthorizationManager authzManager(new AuthzManagerExternalStateMock()); + AuthorizationSession authzSession(new AuthzSessionExternalStateMock(&authzManager)); + + for (size_t i = 0; i < saslGlobalParams.authenticationMechanisms.size(); ++i) { + const std::string& mechanism = saslGlobalParams.authenticationMechanisms[i]; + if (mechanism == "MONGODB-CR" || mechanism == "MONGODB-X509") { + // Not a SASL mechanism; no need to smoke test built-in mechanisms. + continue; + } + scoped_ptr<SaslAuthenticationSession> + session(SaslAuthenticationSession::create(&authzSession)); + Status status = session->start("test", + mechanism, + saslGlobalParams.serviceName, + saslGlobalParams.hostName, + 1, + true); + if (!status.isOK()) + return status; + } + + return Status::OK(); + } +} //namespace + + NativeSaslAuthenticationSession::NativeSaslAuthenticationSession( + AuthorizationSession* authzSession) : + SaslAuthenticationSession(authzSession), + _mechanism("") { + } + + NativeSaslAuthenticationSession::~NativeSaslAuthenticationSession() {} + + Status NativeSaslAuthenticationSession::start(const StringData& authenticationDatabase, + const StringData& mechanism, + const StringData& serviceName, + const StringData& serviceHostname, + int64_t conversationId, + bool autoAuthorize) { + fassert(18626, conversationId > 0); + + if (_conversationId != 0) { + return Status(ErrorCodes::AlreadyInitialized, + "Cannot call start() twice on same NativeSaslAuthenticationSession."); + } + + _authenticationDatabase = authenticationDatabase.toString(); + _mechanism = mechanism.toString(); + _serviceName = serviceName.toString(); + _serviceHostname = serviceHostname.toString(); + _conversationId = conversationId; + _autoAuthorize = autoAuthorize; + + if (mechanism == "PLAIN") { + _saslConversation.reset(new SaslPLAINServerConversation(this)); + } + else if (mechanism == "SCRAM-SHA-1") { + _saslConversation.reset(new SaslSCRAMSHA1ServerConversation(this)); + } + else { + return Status(ErrorCodes::BadValue, + mongoutils::str::stream() << "SASL mechanism " << mechanism << + "is not supported"); + } + + return Status::OK(); + } + + Status NativeSaslAuthenticationSession::step(const StringData& inputData, + std::string* outputData) { + StatusWith<bool> status = _saslConversation->step(inputData, outputData); + if (status.isOK()) { + _done = status.getValue(); + } + return status.getStatus(); + } + + std::string NativeSaslAuthenticationSession::getPrincipalId() const { + return _saslConversation->getPrincipalId(); + } + + const char* NativeSaslAuthenticationSession::getMechanism() const { + return _mechanism.c_str(); + } + +} // namespace mongo diff --git a/src/mongo/db/auth/native_sasl_authentication_session.h b/src/mongo/db/auth/native_sasl_authentication_session.h new file mode 100644 index 00000000000..104e84445f2 --- /dev/null +++ b/src/mongo/db/auth/native_sasl_authentication_session.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2014 10gen, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include <string> + +#include "mongo/base/disallow_copying.h" +#include "mongo/base/status.h" +#include "mongo/base/string_data.h" +#include "mongo/db/auth/authentication_session.h" +#include "mongo/platform/cstdint.h" +#include "mongo/db/auth/sasl_authentication_session.h" +#include "mongo/db/auth/sasl_conversation.h" + +namespace mongo { + + /** + * Authentication session data for the server side of SASL authentication. + */ + class NativeSaslAuthenticationSession : public SaslAuthenticationSession { + MONGO_DISALLOW_COPYING(NativeSaslAuthenticationSession); + public: + + explicit NativeSaslAuthenticationSession(AuthorizationSession* authSession); + virtual ~NativeSaslAuthenticationSession(); + + virtual Status start(const StringData& authenticationDatabase, + const StringData& mechanism, + const StringData& serviceName, + const StringData& serviceHostname, + int64_t conversationId, + bool autoAuthorize); + + virtual Status step(const StringData& inputData, std::string* outputData); + + virtual std::string getPrincipalId() const; + + virtual const char* getMechanism() const; + + private: + std::string _mechanism; + boost::scoped_ptr<SaslConversation> _saslConversation; + }; +} // namespace mongo diff --git a/src/mongo/db/auth/sasl_authentication_session.cpp b/src/mongo/db/auth/sasl_authentication_session.cpp new file mode 100644 index 00000000000..57d842999ec --- /dev/null +++ b/src/mongo/db/auth/sasl_authentication_session.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2012 10gen, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kAccessControl + +#include "mongo/db/auth/sasl_authentication_session.h" + +#include <boost/range/size.hpp> + +#include "mongo/base/init.h" +#include "mongo/base/string_data.h" +#include "mongo/bson/util/bson_extract.h" +#include "mongo/client/sasl_client_authenticate.h" +#include "mongo/db/commands.h" +#include "mongo/db/auth/authorization_manager.h" +#include "mongo/db/auth/authorization_manager_global.h" +#include "mongo/db/auth/authorization_session.h" +#include "mongo/db/auth/authz_manager_external_state_mock.h" +#include "mongo/db/auth/authz_session_external_state_mock.h" +#include "mongo/db/operation_context_noop.h" +#include "mongo/util/assert_util.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { + SaslAuthenticationSession::SaslSessionFactoryFn SaslAuthenticationSession::create = NULL; + + // Mechanism name constants. + const char SaslAuthenticationSession::mechanismCRAMMD5[] = "CRAM-MD5"; + const char SaslAuthenticationSession::mechanismDIGESTMD5[] = "DIGEST-MD5"; + const char SaslAuthenticationSession::mechanismSCRAMSHA1[] = "SCRAM-SHA-1"; + const char SaslAuthenticationSession::mechanismGSSAPI[] = "GSSAPI"; + const char SaslAuthenticationSession::mechanismPLAIN[] = "PLAIN"; + + /** + * Standard method in mongodb for determining if "authenticatedUser" may act as "requestedUser." + * + * The standard rule in MongoDB is simple. The authenticated user name must be the same as the + * requested user name. + */ + bool isAuthorizedCommon(SaslAuthenticationSession* session, + const StringData& requestedUser, + const StringData& authenticatedUser) { + + return requestedUser == authenticatedUser; + } + + SaslAuthenticationSession::SaslAuthenticationSession(AuthorizationSession* authzSession) : + AuthenticationSession(AuthenticationSession::SESSION_TYPE_SASL), + _authzSession(authzSession), + _saslStep(0), + _conversationId(0), + _autoAuthorize(false), + _done(false) { + } + + SaslAuthenticationSession::~SaslAuthenticationSession() {}; + + StringData SaslAuthenticationSession::getAuthenticationDatabase() const { + if (Command::testCommandsEnabled && + _authenticationDatabase == "admin" && + getPrincipalId() == internalSecurity.user->getName().getUser()) { + // Allows authenticating as the internal user against the admin database. This is to + // support the auth passthrough test framework on mongos (since you can't use the local + // database on a mongos, so you can't auth as the internal user without this). + return internalSecurity.user->getName().getDB(); + } else { + return _authenticationDatabase; + } + } + +} // namespace mongo diff --git a/src/mongo/db/auth/sasl_authentication_session.h b/src/mongo/db/auth/sasl_authentication_session.h new file mode 100644 index 00000000000..e4b8ac42655 --- /dev/null +++ b/src/mongo/db/auth/sasl_authentication_session.h @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2012 10gen, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include <string> +#include <vector> + +#include "mongo/base/disallow_copying.h" +#include "mongo/base/status.h" +#include "mongo/base/string_data.h" +#include "mongo/db/auth/authentication_session.h" +#include "mongo/db/auth/authorization_session.h" +#include "mongo/platform/cstdint.h" +#include "mongo/stdx/functional.h" + +namespace mongo { + + class AuthorizationSession; + class OperationContext; + + /** + * Authentication session data for the server side of SASL authentication. + */ + class SaslAuthenticationSession : public AuthenticationSession { + MONGO_DISALLOW_COPYING(SaslAuthenticationSession); + public: + typedef stdx::function<SaslAuthenticationSession* (AuthorizationSession*)> + SaslSessionFactoryFn; + static SaslSessionFactoryFn create; + + // Mechanism name constants. + static const char mechanismCRAMMD5[]; + static const char mechanismDIGESTMD5[]; + static const char mechanismSCRAMSHA1[]; + static const char mechanismGSSAPI[]; + static const char mechanismPLAIN[]; + + explicit SaslAuthenticationSession(AuthorizationSession* authSession); + virtual ~SaslAuthenticationSession(); + + /** + * Start the server side of a SASL authentication. + * + * "authenticationDatabase" is the database against which the user is authenticating. + * "mechanism" is the SASL mechanism to use. + * "serviceName" is the SASL service name to use. + * "serviceHostname" is the FQDN of this server. + * "conversationId" is the conversation identifier to use for this session. + * + * If "autoAuthorize" is set to true, the server will automatically acquire all privileges + * for a successfully authenticated user. If it is false, the client will need to + * explicilty acquire privileges on resources it wishes to access. + * + * Must be called only once on an instance. + */ + virtual Status start(const StringData& authenticationDatabase, + const StringData& mechanism, + const StringData& serviceName, + const StringData& serviceHostname, + int64_t conversationId, + bool autoAuthorize) = 0; + + /** + * Perform one step of the server side of the authentication session, + * consuming "inputData" and producing "*outputData". + * + * A return of Status::OK() indiciates succesful progress towards authentication. + * Any other return code indicates that authentication has failed. + * + * Must not be called before start(). + */ + virtual Status step(const StringData& inputData, std::string* outputData) = 0; + + /** + * Returns the the operation context associated with the currently executing command.
+ * Authentication commands must set this on their associated + * SaslAuthenticationSession. + */ + OperationContext* getOpCtxt() const { return _txn; } + void setOpCtxt(OperationContext* txn) { _txn = txn; } + + /** + * Gets the name of the database against which this authentication conversation is running. + * + * Not meaningful before a successful call to start(). + */ + StringData getAuthenticationDatabase() const; + + /** + * Get the conversation id for this authentication session. + * + * Must not be called before start(). + */ + int64_t getConversationId() const { return _conversationId; } + + /** + * If the last call to step() returned Status::OK(), this method returns true if the + * authentication conversation has completed, from the server's perspective. If it returns + * false, the server expects more input from the client. If the last call to step() did not + * return Status::OK(), returns true. + * + * Behavior is undefined if step() has not been called. + */ + bool isDone() const { return _done; } + + /** + * Gets the string identifier of the principal being authenticated. + * + * Returns the empty string if the session does not yet know the identity being + * authenticated. + */ + virtual std::string getPrincipalId() const = 0; + + /** + * Gets the name of the SASL mechanism in use. + * + * Returns "" if start() has not been called or if start() did not return Status::OK(). + */ + virtual const char* getMechanism() const = 0; + + /** + * Returns true if automatic privilege acquisition should be used for this principal, after + * authentication. Not meaningful before a successful call to start(). + */ + bool shouldAutoAuthorize() const { return _autoAuthorize; } + + AuthorizationSession* getAuthorizationSession() { return _authzSession; } + + protected: + OperationContext* _txn; + AuthorizationSession* _authzSession; + std::string _authenticationDatabase; + std::string _serviceName; + std::string _serviceHostname; + int _saslStep; + int64_t _conversationId; + bool _autoAuthorize; + bool _done; + }; + +} // namespace mongo diff --git a/src/mongo/db/auth/sasl_commands.cpp b/src/mongo/db/auth/sasl_commands.cpp new file mode 100644 index 00000000000..441744bfda5 --- /dev/null +++ b/src/mongo/db/auth/sasl_commands.cpp @@ -0,0 +1,362 @@ +/* + * Copyright (C) 2012 10gen, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kAccessControl + +#include "mongo/platform/basic.h" + +#include <boost/scoped_ptr.hpp> + +#include "mongo/base/init.h" +#include "mongo/base/status.h" +#include "mongo/base/string_data.h" +#include "mongo/bson/util/bson_extract.h" +#include "mongo/client/sasl_client_authenticate.h" +#include "mongo/db/audit.h" +#include "mongo/db/auth/authorization_session.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/mongo_authentication_session.h" +#include "mongo/db/auth/sasl_authentication_session.h" +#include "mongo/db/auth/sasl_options.h" +#include "mongo/db/client_basic.h" +#include "mongo/db/commands.h" +#include "mongo/db/commands/authentication_commands.h" +#include "mongo/util/base64.h" +#include "mongo/util/log.h" +#include "mongo/util/mongoutils/str.h" +#include "mongo/util/sequence_util.h" +#include "mongo/util/stringutils.h" + +namespace mongo { +namespace { + + const bool autoAuthorizeDefault = true; + + class CmdSaslStart : public Command { + public: + CmdSaslStart(); + virtual ~CmdSaslStart(); + + virtual void addRequiredPrivileges( + const std::string&, const BSONObj&, std::vector<Privilege>*) {} + + virtual bool run(OperationContext* txn, + const std::string& db, + BSONObj& cmdObj, + int options, + std::string& ignored, + BSONObjBuilder& result, + bool fromRepl); + + virtual void help(stringstream& help) const; + virtual bool isWriteCommandForConfigServer() const { return false; } + virtual bool slaveOk() const { return true; } + virtual bool requiresAuth() { return false; } + + }; + + class CmdSaslContinue : public Command { + public: + CmdSaslContinue(); + virtual ~CmdSaslContinue(); + + virtual void addRequiredPrivileges( + const std::string&, const BSONObj&, std::vector<Privilege>*) {} + + virtual bool run(OperationContext* txn, + const std::string& db, + BSONObj& cmdObj, + int options, + std::string& ignored, + BSONObjBuilder& result, + bool fromRepl); + + virtual void help(stringstream& help) const; + virtual bool isWriteCommandForConfigServer() const { return false; } + virtual bool slaveOk() const { return true; } + virtual bool requiresAuth() { return false; } + }; + + CmdSaslStart cmdSaslStart; + CmdSaslContinue cmdSaslContinue; + Status buildResponse(const SaslAuthenticationSession* session, + const std::string& responsePayload, + BSONType responsePayloadType, + BSONObjBuilder* result) { + result->appendIntOrLL(saslCommandConversationIdFieldName, session->getConversationId()); + result->appendBool(saslCommandDoneFieldName, session->isDone()); + + if (responsePayload.size() > size_t(std::numeric_limits<int>::max())) { + return Status(ErrorCodes::InvalidLength, "Response payload too long"); + } + if (responsePayloadType == BinData) { + result->appendBinData(saslCommandPayloadFieldName, + int(responsePayload.size()), + BinDataGeneral, + responsePayload.data()); + } + else if (responsePayloadType == String) { + result->append(saslCommandPayloadFieldName, base64::encode(responsePayload)); + } + else { + fassertFailed(4003); + } + + return Status::OK(); + } + + Status extractConversationId(const BSONObj& cmdObj, int64_t* conversationId) { + BSONElement element; + Status status = bsonExtractField(cmdObj, saslCommandConversationIdFieldName, &element); + if (!status.isOK()) + return status; + + if (!element.isNumber()) { + return Status(ErrorCodes::TypeMismatch, + str::stream() << "Wrong type for field; expected number for " << element); + } + *conversationId = element.numberLong(); + return Status::OK(); + } + + Status extractMechanism(const BSONObj& cmdObj, std::string* mechanism) { + return bsonExtractStringField(cmdObj, saslCommandMechanismFieldName, mechanism); + } + + void addStatus(const Status& status, BSONObjBuilder* builder) { + builder->append("ok", status.isOK() ? 1.0: 0.0); + if (!status.isOK()) + builder->append(saslCommandCodeFieldName, status.code()); + if (!status.reason().empty()) + builder->append(saslCommandErrmsgFieldName, status.reason()); + } + + Status doSaslStep(SaslAuthenticationSession* session, + const BSONObj& cmdObj, + BSONObjBuilder* result) { + + std::string payload; + BSONType type = EOO; + Status status = saslExtractPayload(cmdObj, &payload, &type); + if (!status.isOK()) + return status; + + std::string responsePayload; + // Passing in a payload and extracting a responsePayload + status = session->step(payload, &responsePayload); + + if (!status.isOK()) { + log() << session->getMechanism() << " authentication failed for " << + session->getPrincipalId() << " on " << + session->getAuthenticationDatabase() << " ; " << status.toString() << std::endl; + // All the client needs to know is that authentication has failed. + return Status(ErrorCodes::AuthenticationFailed, "Authentication failed."); + } + + status = buildResponse(session, responsePayload, type, result); + if (!status.isOK()) + return status; + + if (session->isDone()) { + UserName userName(session->getPrincipalId(), session->getAuthenticationDatabase()); + status = session->getAuthorizationSession()->addAndAuthorizeUser( + session->getOpCtxt(), userName); + if (!status.isOK()) { + return status; + } + + log() << "Successfully authenticated as principal " << + session->getPrincipalId() << " on " << session->getAuthenticationDatabase() << + std::endl; + } + return Status::OK(); + } + + Status doSaslStart(SaslAuthenticationSession* session, + const std::string& db, + const BSONObj& cmdObj, + BSONObjBuilder* result) { + + bool autoAuthorize = false; + Status status = bsonExtractBooleanFieldWithDefault(cmdObj, + saslCommandAutoAuthorizeFieldName, + autoAuthorizeDefault, + &autoAuthorize); + if (!status.isOK()) + return status; + + std::string mechanism; + status = extractMechanism(cmdObj, &mechanism); + if (!status.isOK()) + return status; + + + if (!sequenceContains(saslGlobalParams.authenticationMechanisms, mechanism)) { + result->append(saslCommandMechanismListFieldName, + saslGlobalParams.authenticationMechanisms); + return Status(ErrorCodes::BadValue, + mongoutils::str::stream() << "Unsupported mechanism " << mechanism); + } + + status = session->start(db, + mechanism, + saslGlobalParams.serviceName, + saslGlobalParams.hostName, + 1, + autoAuthorize); + if (!status.isOK()) + return status; + + return doSaslStep(session, cmdObj, result); + } + + Status doSaslContinue(SaslAuthenticationSession* session, + const BSONObj& cmdObj, + BSONObjBuilder* result) { + + int64_t conversationId = 0; + Status status = extractConversationId(cmdObj, &conversationId); + if (!status.isOK()) + return status; + if (conversationId != session->getConversationId()) + return Status(ErrorCodes::ProtocolError, "sasl: Mismatched conversation id"); + + return doSaslStep(session, cmdObj, result); + } + + CmdSaslStart::CmdSaslStart() : Command(saslStartCommandName) {} + CmdSaslStart::~CmdSaslStart() {} + + void CmdSaslStart::help(std::stringstream& os) const { + os << "First step in a SASL authentication conversation."; + } + + bool CmdSaslStart::run(OperationContext* txn, + const std::string& db, + BSONObj& cmdObj, + int options, + std::string& ignored, + BSONObjBuilder& result, + bool fromRepl) { + + ClientBasic* client = ClientBasic::getCurrent(); + client->resetAuthenticationSession(NULL); + + SaslAuthenticationSession* session = + SaslAuthenticationSession::create(client->getAuthorizationSession()); + + boost::scoped_ptr<AuthenticationSession> sessionGuard(session); + + session->setOpCtxt(txn); + + Status status = doSaslStart(session, db, cmdObj, &result); + addStatus(status, &result); + + if (session->isDone()) { + audit::logAuthentication( + client, + session->getMechanism(), + UserName(session->getPrincipalId(), db), + status.code()); + } + else { + client->swapAuthenticationSession(sessionGuard); + } + return status.isOK(); + } + + CmdSaslContinue::CmdSaslContinue() : Command(saslContinueCommandName) {} + CmdSaslContinue::~CmdSaslContinue() {} + + void CmdSaslContinue::help(std::stringstream& os) const { + os << "Subsequent steps in a SASL authentication conversation."; + } + + bool CmdSaslContinue::run(OperationContext* txn, + const std::string& db, + BSONObj& cmdObj, + int options, + std::string& ignored, + BSONObjBuilder& result, + bool fromRepl) { + + ClientBasic* client = ClientBasic::getCurrent(); + boost::scoped_ptr<AuthenticationSession> sessionGuard(NULL); + client->swapAuthenticationSession(sessionGuard); + + if (!sessionGuard || sessionGuard->getType() != AuthenticationSession::SESSION_TYPE_SASL) { + addStatus(Status(ErrorCodes::ProtocolError, "No SASL session state found"), &result); + return false; + } + + SaslAuthenticationSession* session = + static_cast<SaslAuthenticationSession*>(sessionGuard.get()); + + if (session->getAuthenticationDatabase() != db) { + addStatus(Status(ErrorCodes::ProtocolError, + "Attempt to switch database target during SASL authentication."), + &result); + return false; + } + + session->setOpCtxt(txn); + + Status status = doSaslContinue(session, cmdObj, &result); + addStatus(status, &result); + + if (session->isDone()) { + audit::logAuthentication( + client, + session->getMechanism(), + UserName(session->getPrincipalId(), db), + status.code()); + } + else { + client->swapAuthenticationSession(sessionGuard); + } + + return status.isOK(); + } + + // The CyrusSaslCommands Enterprise initializer is dependent on PreSaslCommands + MONGO_INITIALIZER_WITH_PREREQUISITES(PreSaslCommands, + ("NativeSaslServerCore")) + (InitializerContext*) { + + if (!sequenceContains(saslGlobalParams.authenticationMechanisms, "MONGODB-CR")) + CmdAuthenticate::disableAuthMechanism("MONGODB-CR"); + + if (!sequenceContains(saslGlobalParams.authenticationMechanisms, "MONGODB-X509")) + CmdAuthenticate::disableAuthMechanism("MONGODB-X509"); + + return Status::OK(); + } + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/auth/sasl_conversation.cpp b/src/mongo/db/auth/sasl_conversation.cpp new file mode 100644 index 00000000000..2c3af408b12 --- /dev/null +++ b/src/mongo/db/auth/sasl_conversation.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2014 MongoDB Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/db/auth/sasl_conversation.h" + +#include <string> + +#include "mongo/util/log.h" + +namespace mongo { + + SaslConversation::~SaslConversation() {}; + + std::string SaslConversation::getPrincipalId() { + return _user; + } + +} // namespace mongo diff --git a/src/mongo/db/auth/sasl_conversation.h b/src/mongo/db/auth/sasl_conversation.h new file mode 100644 index 00000000000..e3e2ab43399 --- /dev/null +++ b/src/mongo/db/auth/sasl_conversation.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2014 MongoDB Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include <string> +#include <vector> + +#include "mongo/base/disallow_copying.h" +#include "mongo/base/status.h" +#include "mongo/base/string_data.h" +#include "mongo/db/auth/sasl_authentication_session.h" +#include "mongo/db/auth/user.h" + +namespace mongo { + + class SaslAuthenticationSession; + template <typename T> class StatusWith; + + /** + * Abstract class for implementing the client or server-side + * of a SASL mechanism conversation. + */ + class SaslConversation { + MONGO_DISALLOW_COPYING(SaslConversation); + public: + /** + * Implements the server side of a SASL authentication mechanism. + * + * "saslAuthSession" is the corresponding SASLAuthenticationSession. + * "saslAuthSession" must stay in scope until the SaslConversation's + * destructor completes. + * + **/ + explicit SaslConversation(SaslAuthenticationSession* saslAuthSession) : + _saslAuthSession(saslAuthSession), + _user("") {} + + virtual ~SaslConversation(); + + /** + * Performs one step of the server side of the authentication session, + * consuming "inputData" and producing "*outputData". + * + * A return of Status::OK() indicates successful progress towards authentication. + * A return of !Status::OK() indicates failed authentication + * + * A return of true means that the authentication process has finished. + * A return of false means that the authentication process has more steps. + * + */ + virtual StatusWith<bool> step(const StringData& inputData, std::string* outputData) = 0; + + /** + * Gets the SASL principal id (user name) for the conversation + **/ + std::string getPrincipalId(); + + protected: + SaslAuthenticationSession* _saslAuthSession; + std::string _user; + }; + +} // namespace mongo diff --git a/src/mongo/db/auth/sasl_options.cpp b/src/mongo/db/auth/sasl_options.cpp new file mode 100644 index 00000000000..40fc536656c --- /dev/null +++ b/src/mongo/db/auth/sasl_options.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2013 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 <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kAccessControl + +#include "mongo/db/auth/sasl_options.h" + +#include "mongo/base/status.h" +#include "mongo/db/server_parameters.h" +#include "mongo/util/log.h" +#include "mongo/util/options_parser/startup_option_init.h" +#include "mongo/util/options_parser/startup_options.h" + +namespace mongo { + + SASLGlobalParams saslGlobalParams; + + // Authentication mechanisms supported by default + SASLGlobalParams::SASLGlobalParams() { + authenticationMechanisms.push_back("MONGODB-CR"); + authenticationMechanisms.push_back("MONGODB-X509"); + authenticationMechanisms.push_back("SCRAM-SHA-1"); + } + + Status addSASLOptions(moe::OptionSection* options) { + + moe::OptionSection saslOptions("SASL Options"); + + saslOptions.addOptionChaining("security.authenticationMechanisms", "", + moe::StringVector, "List of supported authentication mechanisms. " + "Default is MONGODB-CR, SCRAM-SHA-1 and MONGODB-X509.") + .setSources(moe::SourceYAMLConfig); + + saslOptions.addOptionChaining("security.sasl.hostName", "", moe::String, + "Fully qualified server domain name") + .setSources(moe::SourceYAMLConfig); + + saslOptions.addOptionChaining("security.sasl.serviceName", "", moe::String, + "Registered name of the service using SASL") + .setSources(moe::SourceYAMLConfig); + + saslOptions.addOptionChaining("security.sasl.saslauthdSocketPath", "", moe::String, + "Path to Unix domain socket file for saslauthd") + .setSources(moe::SourceYAMLConfig); + + Status ret = options->addSection(saslOptions); + if (!ret.isOK()) { + log() << "Failed to add sasl option section: " << ret.toString(); + return ret; + } + + return Status::OK(); + } + + Status storeSASLOptions(const moe::Environment& params) { + + bool haveAuthenticationMechanisms = false; + bool haveHostName = false; + bool haveServiceName = false; + bool haveAuthdPath = false; + + // Check our setParameter options first so that these values can be properly overridden via + // the command line even though the options have different names. + if (params.count("setParameter")) { + std::map<std::string, std::string> parameters = + params["setParameter"].as<std::map<std::string, std::string> >(); + for (std::map<std::string, std::string>::iterator parametersIt = parameters.begin(); + parametersIt != parameters.end(); parametersIt++) { + if (parametersIt->first == "authenticationMechanisms") { + haveAuthenticationMechanisms = true; + } + else if (parametersIt->first == "saslHostName") { + haveHostName = true; + } + else if (parametersIt->first == "saslServiceName") { + haveServiceName = true; + } + else if (parametersIt->first == "saslauthdPath") { + haveAuthdPath = true; + } + } + } + + if (params.count("security.authenticationMechanisms") && + !haveAuthenticationMechanisms) { + saslGlobalParams.authenticationMechanisms = + params["security.authenticationMechanisms"].as<std::vector<std::string> >(); + } + if (params.count("security.sasl.hostName") && !haveHostName) { + saslGlobalParams.hostName = + params["security.sasl.hostName"].as<std::string>(); + } + if (params.count("security.sasl.serviceName") && !haveServiceName) { + saslGlobalParams.serviceName = + params["security.sasl.serviceName"].as<std::string>(); + } + if (params.count("security.sasl.saslauthdSocketPath") && !haveAuthdPath) { + saslGlobalParams.authdPath = + params["security.sasl.saslauthdSocketPath"].as<std::string>(); + } + + return Status::OK(); + } + + MONGO_MODULE_STARTUP_OPTIONS_REGISTER(SASLOptions)(InitializerContext* context) { + return addSASLOptions(&moe::startupOptions); + } + + MONGO_STARTUP_OPTIONS_STORE(SASLOptions)(InitializerContext* context) { + return storeSASLOptions(moe::startupOptionsParsed); + } + + // SASL Startup Parameters, making them settable via setParameter on the command line or in the + // legacy INI config file. None of these parameters are modifiable at runtime. + ExportedServerParameter<std::vector<std::string> > SASLAuthenticationMechanismsSetting( + ServerParameterSet::getGlobal(), + "authenticationMechanisms", + &saslGlobalParams.authenticationMechanisms, + true, // Change at startup + false); // Change at runtime + + ExportedServerParameter<std::string> SASLHostNameSetting(ServerParameterSet::getGlobal(), + "saslHostName", + &saslGlobalParams.hostName, + true, // Change at startup + false); // Change at runtime + + ExportedServerParameter<std::string> SASLServiceNameSetting(ServerParameterSet::getGlobal(), + "saslServiceName", + &saslGlobalParams.serviceName, + true, // Change at startup + false); // Change at runtime + + ExportedServerParameter<std::string> SASLAuthdPathSetting(ServerParameterSet::getGlobal(), + "saslauthdPath", + &saslGlobalParams.authdPath, + true, // Change at startup + false); // Change at runtime + +} // namespace mongo diff --git a/src/mongo/db/auth/sasl_options.h b/src/mongo/db/auth/sasl_options.h new file mode 100644 index 00000000000..77ca66ad1d0 --- /dev/null +++ b/src/mongo/db/auth/sasl_options.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2013 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 <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include <string> +#include <vector> + +#include "mongo/base/status.h" + +namespace mongo { + +namespace optionenvironment { + class OptionSection; + class Environment; +} // namespace optionenvironment + + namespace moe = optionenvironment; + + struct SASLGlobalParams { + + std::vector<std::string> authenticationMechanisms; + std::string hostName; + std::string serviceName; + std::string authdPath; + + SASLGlobalParams(); + }; + + extern SASLGlobalParams saslGlobalParams; + + Status addSASLOptions(moe::OptionSection* options); + + Status storeSASLOptions(const moe::Environment& params); + +} // namespace mongo diff --git a/src/mongo/db/auth/sasl_plain_server_conversation.cpp b/src/mongo/db/auth/sasl_plain_server_conversation.cpp new file mode 100644 index 00000000000..6bf06682851 --- /dev/null +++ b/src/mongo/db/auth/sasl_plain_server_conversation.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2014 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/db/auth/sasl_plain_server_conversation.h" + +#include "mongo/db/auth/sasl_authentication_session.h" +#include "mongo/util/password_digest.h" +#include "mongo/util/text.h" + +namespace mongo { + + SaslPLAINServerConversation::SaslPLAINServerConversation( + SaslAuthenticationSession* saslAuthSession) : + SaslConversation(saslAuthSession) { + } + + SaslPLAINServerConversation::~SaslPLAINServerConversation() {}; + + StatusWith<bool> SaslPLAINServerConversation::step(const StringData& inputData, + std::string* outputData) { + // Expecting user input on the form: user\0user\0pwd + std::string input = inputData.toString(); + std::string pwd = ""; + + try { + _user = input.substr(0, inputData.find('\0')); + pwd = input.substr(inputData.find('\0', _user.size()+1)+1); + } + catch (std::out_of_range& exception) { + return StatusWith<bool>(ErrorCodes::AuthenticationFailed, + mongoutils::str::stream() << "Incorrectly formatted PLAIN client message"); + } + + User* userObj; + // The authentication database is also the source database for the user. + Status status = _saslAuthSession->getAuthorizationSession()->getAuthorizationManager(). + acquireUser(_saslAuthSession->getOpCtxt(), + UserName(_user, _saslAuthSession->getAuthenticationDatabase()), + &userObj); + + if (!status.isOK()) { + return StatusWith<bool>(status); + } + + const User::CredentialData creds = userObj->getCredentials(); + _saslAuthSession->getAuthorizationSession()->getAuthorizationManager(). + releaseUser(userObj); + + std::string authDigest = createPasswordDigest(_user, pwd); + + if (authDigest != creds.password) { + return StatusWith<bool>(ErrorCodes::AuthenticationFailed, + mongoutils::str::stream() << "Incorrect user name or password"); + } + + *outputData = ""; + + return StatusWith<bool>(true); + } + +} // namespace mongo diff --git a/src/mongo/db/auth/sasl_plain_server_conversation.h b/src/mongo/db/auth/sasl_plain_server_conversation.h new file mode 100644 index 00000000000..855522f9566 --- /dev/null +++ b/src/mongo/db/auth/sasl_plain_server_conversation.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2014 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include <string> +#include <vector> + +#include "mongo/base/disallow_copying.h" +#include "mongo/base/status.h" +#include "mongo/base/string_data.h" +#include "mongo/db/auth/sasl_conversation.h" + +namespace mongo { + /** + * Server side authentication session for SASL PLAIN. + */ + class SaslPLAINServerConversation : public SaslConversation { + MONGO_DISALLOW_COPYING(SaslPLAINServerConversation); + public: + /** + * Implements the server side of a SASL PLAIN mechanism session. + * + **/ + explicit SaslPLAINServerConversation(SaslAuthenticationSession* saslAuthSession); + + virtual ~SaslPLAINServerConversation(); + + virtual StatusWith<bool> step(const StringData& inputData, std::string* outputData); + }; + +} // namespace mongo diff --git a/src/mongo/db/auth/sasl_scramsha1_server_conversation.cpp b/src/mongo/db/auth/sasl_scramsha1_server_conversation.cpp new file mode 100644 index 00000000000..3bdebaf9677 --- /dev/null +++ b/src/mongo/db/auth/sasl_scramsha1_server_conversation.cpp @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2014 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/auth/sasl_scramsha1_server_conversation.h" + +#include <boost/algorithm/string/join.hpp> +#include <boost/algorithm/string/replace.hpp> +#ifdef MONGO_SSL +#include <openssl/sha.h> +#include <openssl/evp.h> +#include <openssl/hmac.h> +#endif + +#include "mongo/platform/random.h" +#include "mongo/util/base64.h" +#include "mongo/util/log.h" +#include "mongo/util/mongoutils/str.h" +#include "mongo/util/password_digest.h" +#include "mongo/util/text.h" + +namespace mongo { + SaslSCRAMSHA1ServerConversation::SaslSCRAMSHA1ServerConversation( + SaslAuthenticationSession* saslAuthSession) : + SaslConversation(saslAuthSession), + _step(0), + _authMessage(""), + _nonce("") { + } + + StatusWith<bool> SaslSCRAMSHA1ServerConversation::step(const StringData& inputData, + std::string* outputData) { + + std::vector<std::string> input = StringSplitter::split(inputData.toString(), ","); + _step++; + + if (_step > 3 || _step <= 0) { + return StatusWith<bool>(ErrorCodes::AuthenticationFailed, + mongoutils::str::stream() << "Invalid SCRAM-SHA-1 authentication step: " << _step); + } + if (_step == 1) { + return _firstStep(input, outputData); + } + if (_step == 2) { + return _secondStep(input, outputData); + } + + *outputData = ""; + + return StatusWith<bool>(true); + } + + /* + * RFC 5802 specifies that in SCRAM user names characters ',' and '=' are encoded as + * =2C and =3D respectively. + */ + static void decodeSCRAMUsername(std::string& user) { + boost::replace_all(user, "=2C", ","); + boost::replace_all(user, "=3D", "="); + } + + /* + * Parse client-first-message of the form: + * n,a=authzid,n=encoded-username,r=client-nonce + * + * Generate server-first-message on the form: + * r=client-nonce|server-nonce,s=user-salt,i=iteration-count + * + * NOTE: we are ignoring the authorization ID part of the message + */ + StatusWith<bool> SaslSCRAMSHA1ServerConversation::_firstStep(std::vector<string>& input, + std::string* outputData) { +#ifndef MONGO_SSL + return StatusWith<bool>(ErrorCodes::InternalError, + "The server is not compiled with SSL support"); +#else + std::string authzId = ""; + + if (input.size() == 4) { + /* The second entry a=authzid is optional. If provided it will be + * validated against the encoded username. + * + * The two allowed input forms are: + * n,,n=encoded-username,r=client-nonce + * n,a=authzid,n=encoded-username,r=client-nonce + */ + if (!str::startsWith(input[1], "a=") || input[1].size() < 3) { + return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << + "Incorrect SCRAM-SHA-1 authzid: " << input[1]); + } + authzId = input[1].substr(2); + input.erase(input.begin() + 1); + } + + if (input.size() != 3) { + return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << + "Incorrect number of arguments for first SCRAM-SHA-1 client message, got " << + input.size() << " expected 4"); + } + else if (input[0] != "n") { + return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << + "Incorrect SCRAM-SHA-1 client message prefix: " << input[0]); + } + else if (!str::startsWith(input[1], "n=") || input[1].size() < 3) { + return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << + "Incorrect SCRAM-SHA-1 user name: " << input[1]); + } + else if(!str::startsWith(input[2], "r=") || input[2].size() < 6) { + return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << + "Incorrect SCRAM-SHA-1 client nonce: " << input[2]); + } + + // add client-first-message-bare to _authMessage + _authMessage += input[1] + "," + input[2] + ","; + + _user = input[1].substr(2); + if (!authzId.empty() && _user != authzId) { + return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << + "SCRAM-SHA-1 user name " << _user << " does not match authzid " << authzId); + } + + decodeSCRAMUsername(_user); + std::string clientNonce = input[2].substr(2); + + // The authentication database is also the source database for the user. + User* userObj; + Status status = _saslAuthSession->getAuthorizationSession()->getAuthorizationManager(). + acquireUser(_saslAuthSession->getOpCtxt(), + UserName(_user, _saslAuthSession->getAuthenticationDatabase()), + &userObj); + + if (!status.isOK()) { + return StatusWith<bool>(status); + } + + _creds = userObj->getCredentials(); + _saslAuthSession->getAuthorizationSession()->getAuthorizationManager(). + releaseUser(userObj); + + // Generate server-first-message + // Create text-based nonce as base64 encoding of a binary blob of length multiple of 3 + const int nonceLenQWords = 3; + uint64_t binaryNonce[nonceLenQWords]; + + scoped_ptr<SecureRandom> sr(SecureRandom::create()); + + binaryNonce[0] = sr->nextInt64(); + binaryNonce[1] = sr->nextInt64(); + binaryNonce[2] = sr->nextInt64(); + + _nonce = clientNonce + + base64::encode(reinterpret_cast<char*>(binaryNonce), sizeof(binaryNonce)); + StringBuilder sb; + sb << "r=" << _nonce << + ",s=" << _creds.scram.salt << + ",i=" << _creds.scram.iterationCount; + *outputData = sb.str(); + + // add server-first-message to authMessage + _authMessage += *outputData + ","; + + return StatusWith<bool>(false); +#endif // MONGO_SSL + } + + /** + * Parse client-final-message of the form: + * c=channel-binding(base64),r=client-nonce|server-nonce,p=ClientProof + * + * Generate successful authentication server-final-message on the form: + * v=ServerSignature + * + * or failed authentication server-final-message on the form: + * e=message + * + * NOTE: we are ignoring the channel binding part of the message + **/ + StatusWith<bool> SaslSCRAMSHA1ServerConversation::_secondStep(const std::vector<string>& input, + std::string* outputData) { +#ifndef MONGO_SSL + return StatusWith<bool>(ErrorCodes::InternalError, + "The server is not compiled with SSL support"); +#else + if (input.size() != 3) { + return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << + "Incorrect number of arguments for second SCRAM-SHA-1 client message, got " << + input.size() << " expected 3"); + } + else if (!str::startsWith(input[0], "c=") || input[0].size() < 3) { + return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << + "Incorrect SCRAM-SHA-1 channel binding: " << input[0]); + } + else if (!str::startsWith(input[1], "r=") || input[1].size() < 6) { + return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << + "Incorrect SCRAM-SHA-1 client|server nonce: " << input[1]); + } + else if(!str::startsWith(input[2], "p=") || input[2].size() < 3) { + return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << + "Incorrect SCRAM-SHA-1 ClientProof: " << input[2]); + } + + // add client-final-message-without-proof to authMessage + _authMessage += input[0] + "," + input[1]; + + // Concatenated nonce sent by client should equal the one in server-first-message + std::string nonce = input[1].substr(2); + if (nonce != _nonce) { + return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << + "Unmatched SCRAM-SHA-1 nonce received from client in second step, expected " << + _nonce << " but received " << nonce); + } + + std::string clientProof = input[2].substr(2); + + // Do server side computations, compare storedKeys and generate client-final-message + // AuthMessage := client-first-message-bare + "," + + // server-first-message + "," + + // client-final-message-without-proof + // ClientSignature := HMAC(StoredKey, AuthMessage) + // ClientKey := ClientSignature XOR ClientProof + // ServerSignature := HMAC(ServerKey, AuthMessage) + + const unsigned int scramHashSize = 20; + unsigned int hashLen = 0; + unsigned char clientSignature[scramHashSize]; + + std::string decodedStoredKey = base64::decode(_creds.scram.storedKey); + // ClientSignature := HMAC(StoredKey, AuthMessage) + fassert(18657, HMAC(EVP_sha1(), + reinterpret_cast<const unsigned char*>(decodedStoredKey.c_str()), + scramHashSize, + reinterpret_cast<const unsigned char*>(_authMessage.c_str()), + _authMessage.size(), + clientSignature, + &hashLen)); + + fassert(18658, hashLen == scramHashSize); + + const unsigned char *decodedClientProof = + reinterpret_cast<const unsigned char*>(base64::decode(clientProof).c_str()); + + // ClientKey := ClientSignature XOR ClientProof + unsigned char clientKey[scramHashSize]; + for(size_t i=0; i<scramHashSize; i++) { + clientKey[i] = clientSignature[i]^decodedClientProof[i]; + } + + // StoredKey := H(ClientKey) + unsigned char computedStoredKey[scramHashSize]; + fassert(18659, SHA1(clientKey, scramHashSize, computedStoredKey)); + + if (memcmp(decodedStoredKey.c_str(), computedStoredKey, scramHashSize) != 0) { + return StatusWith<bool>(ErrorCodes::AuthenticationFailed, + mongoutils::str::stream() << + "SCRAM-SHA-1 auhentication failed, storedKey mismatch"); + } + + // ServerSignature := HMAC(ServerKey, AuthMessage) + unsigned char serverSignature[scramHashSize]; + std::string decodedServerKey = base64::decode(_creds.scram.serverKey); + fassert(18660, HMAC(EVP_sha1(), + reinterpret_cast<const unsigned char*>(decodedServerKey.c_str()), + scramHashSize, + reinterpret_cast<const unsigned char*>(_authMessage.c_str()), + _authMessage.size(), + serverSignature, + &hashLen)); + + fassert(18661, hashLen == scramHashSize); + + StringBuilder sb; + sb << "v=" << base64::encode(reinterpret_cast<char*>(serverSignature), scramHashSize); + *outputData = sb.str(); + + return StatusWith<bool>(false); +#endif // MONGO_SSL + } +} // namespace mongo diff --git a/src/mongo/db/auth/sasl_scramsha1_server_conversation.h b/src/mongo/db/auth/sasl_scramsha1_server_conversation.h new file mode 100644 index 00000000000..aa3d3f844ae --- /dev/null +++ b/src/mongo/db/auth/sasl_scramsha1_server_conversation.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2014 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include <boost/scoped_ptr.hpp> +#include <string> +#include <vector> + +#include "mongo/base/disallow_copying.h" +#include "mongo/base/status.h" +#include "mongo/base/string_data.h" +#include "mongo/db/auth/sasl_conversation.h" + +namespace mongo { + /** + * Server side authentication session for SASL PLAIN. + */ + class SaslSCRAMSHA1ServerConversation : public SaslConversation { + MONGO_DISALLOW_COPYING(SaslSCRAMSHA1ServerConversation); + public: + /** + * Implements the server side of a SASL PLAIN mechanism session. + **/ + explicit SaslSCRAMSHA1ServerConversation(SaslAuthenticationSession* saslAuthSession); + + virtual ~SaslSCRAMSHA1ServerConversation() {}; + + /** + * Take one step in a SCRAM-SHA-1 conversation. + * + * @return !Status::OK() if auth failed. The boolean part indicates if the + * authentication conversation is finished or not. + * + **/ + virtual StatusWith<bool> step(const StringData& inputData, std::string* outputData); + + private: + /** + * Parse client-first-message and generate server-first-message + **/ + StatusWith<bool> _firstStep(std::vector<std::string>& input, std::string* outputData); + + /** + * Parse client-final-message and generate server-final-message + **/ + StatusWith<bool> _secondStep(const std::vector<string>& input, std::string* outputData); + + int _step; + std::string _authMessage; + User::CredentialData _creds; + + // client and server nonce concatenated + std::string _nonce; + }; + +} // namespace mongo |