/** * Copyright (C) 2018-present MongoDB, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the Server Side Public License, version 1, * as published by MongoDB, Inc. * * 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 * Server Side Public License for more details. * * You should have received a copy of the Server Side Public License * along with this program. If not, see * . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the Server Side Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #include "mongo/platform/basic.h" #include "mongo/client/sasl_scram_client_conversation.h" #include #include "mongo/base/parse_number.h" #include "mongo/client/scram_client_cache.h" #include "mongo/platform/random.h" #include "mongo/util/base64.h" #include "mongo/util/password_digest.h" #include "mongo/util/str.h" #include "mongo/util/text.h" namespace mongo { using std::string; using std::unique_ptr; StatusWith SaslSCRAMClientConversation::step(StringData inputData, std::string* outputData) { _step++; switch (_step) { case 1: return _firstStep(outputData); case 2: return _secondStep(inputData, outputData); case 3: return _thirdStep(inputData, outputData); default: return StatusWith(ErrorCodes::AuthenticationFailed, str::stream() << "Invalid SCRAM authentication step: " << _step); } } /* * RFC 5802 specifies that in SCRAM user names characters ',' and '=' are encoded as * =2C and =3D respectively. */ static void encodeSCRAMUsername(std::string& user) { boost::replace_all(user, "=", "=3D"); boost::replace_all(user, ",", "=2C"); } /* * Generate client-first-message of the form: * n,a=authzid,n=encoded-username,r=client-nonce */ StatusWith SaslSCRAMClientConversation::_firstStep(std::string* outputData) { if (_saslClientSession->getParameter(SaslClientSession::parameterPassword).empty()) { return Status(ErrorCodes::BadValue, "Empty client password provided"); } // Create text-based nonce as base64 encoding of a binary blob of length multiple of 3 static constexpr size_t nonceLenQWords = 3; uint64_t binaryNonce[nonceLenQWords]; SecureRandom().fill(binaryNonce, sizeof(binaryNonce)); std::string user = _saslClientSession->getParameter(SaslClientSession::parameterUser).toString(); encodeSCRAMUsername(user); _clientNonce = base64::encode(StringData(reinterpret_cast(binaryNonce), sizeof(binaryNonce))); // Append client-first-message-bare to authMessage _authMessage = "n=" + user + ",r=" + _clientNonce; StringBuilder sb; sb << "n,," << _authMessage; *outputData = sb.str(); return false; } /** * Parse server-first-message on the form: * [reserved-mext ',']r=client-nonce|server-nonce,s=user-salt,i=iteration-count[,extensions] * * Generate client-final-message of the form: * c=channel-binding(base64),r=client-nonce|server-nonce,p=ClientProof * **/ StatusWith SaslSCRAMClientConversation::_secondStep(StringData inputData, std::string* outputData) { if (inputData.startsWith("m=")) { return Status(ErrorCodes::BadValue, "SCRAM required extensions not supported"); } const auto input = StringSplitter::split(inputData.toString(), ","); if (input.size() < 3) { return Status(ErrorCodes::BadValue, str::stream() << "Incorrect number of arguments for first SCRAM server message, got " << input.size() << " expected at least 3"); } if (!str::startsWith(input[0], "r=") || input[0].size() < 3) { return Status(ErrorCodes::BadValue, str::stream() << "Incorrect SCRAM client|server nonce: " << input[0]); } const auto nonce = input[0].substr(2); if (!str::startsWith(nonce, _clientNonce)) { return Status(ErrorCodes::BadValue, str::stream() << "Server SCRAM nonce does not match client nonce: " << nonce); } if (!str::startsWith(input[1], "s=") || input[1].size() < 6) { return Status(ErrorCodes::BadValue, str::stream() << "Incorrect SCRAM salt: " << input[1]); } const auto salt64 = input[1].substr(2); if (!str::startsWith(input[2], "i=") || input[2].size() < 3) { return Status(ErrorCodes::BadValue, str::stream() << "Incorrect SCRAM iteration count: " << input[2]); } size_t iterationCount; Status status = NumberParser().base(10)(input[2].substr(2), &iterationCount); if (!status.isOK()) { return Status(ErrorCodes::BadValue, str::stream() << "Failed to parse SCRAM iteration count: " << input[2]); } // Append server-first-message and client-final-message-without-proof. _authMessage += "," + inputData.toString() + ",c=biws,r=" + nonce; std::string decodedSalt, clientProof; try { decodedSalt = base64::decode(salt64); clientProof = generateClientProof( std::vector(decodedSalt.begin(), decodedSalt.end()), iterationCount); } catch (const DBException& ex) { return ex.toStatus(); } StringBuilder sb; sb << "c=biws,r=" << nonce << ",p=" << clientProof; *outputData = sb.str(); return false; } /** * Verify server-final-message on the form: * v=ServerSignature * * or failed authentication server-final-message on the form: * e=message **/ StatusWith SaslSCRAMClientConversation::_thirdStep(StringData inputData, std::string* outputData) { const auto input = StringSplitter::split(inputData.toString(), ","); if (input.empty()) { return Status( ErrorCodes::BadValue, "Incorrect number of arguments for final SCRAM server message, got 0 expected 1"); } if (input[0].size() < 3) { return Status(ErrorCodes::BadValue, str::stream() << "Incorrect SCRAM server message length: " << input[0]); } if (str::startsWith(input[0], "e=")) { return Status(ErrorCodes::AuthenticationFailed, str::stream() << "SCRAM authentication failure: " << input[0].substr(2)); } if (!str::startsWith(input[0], "v=")) { return Status(ErrorCodes::BadValue, str::stream() << "Incorrect SCRAM ServerSignature: " << input[0]); } if (!verifyServerSignature(base64::decode(input[0].substr(2)))) { *outputData = "e=Invalid server signature"; return Status(ErrorCodes::BadValue, str::stream() << "Client failed to verify SCRAM ServerSignature, received " << input[0].substr(2)); } *outputData = ""; return true; } } // namespace mongo