/* * 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 SCRAM credentials on the fly for mixed MONGODB-CR/SCRAM mode. if (_creds.scram.salt.empty() && !_creds.password.empty()) { BSONObj scramCreds = scram::generateCredentials(_creds.password); _creds.scram.iterationCount = scramCreds["iterationCount"].Int(); _creds.scram.salt = scramCreds["salt"].String(); _creds.scram.storedKey = scramCreds["storedKey"].String(); _creds.scram.serverKey = scramCreds["serverKey"].String(); } // 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 }
StatusWith<std::tuple<bool, std::string>> SaslSCRAMServerMechanism<Policy>::_firstStep( OperationContext* opCtx, StringData inputData) { const auto badCount = [](int got) { return Status(ErrorCodes::BadValue, str::stream() << "Incorrect number of arguments for first SCRAM client message, got " << got << " expected at least 3"); }; /** * gs2-cbind-flag := ("p=" cb-name) / 'y' / 'n' * gs2-header := gs2-cbind-flag ',' [ authzid ] ',' * reserved-mext := "m=" 1*(value-char) * client-first-message-bare := [reserved-mext ','] username ',' nonce [',' extensions] * client-first-message := gs2-header client-first-message-bare */ const auto gs2_cbind_comma = inputData.find(','); if (gs2_cbind_comma == std::string::npos) { return badCount(1); } const auto gs2_cbind_flag = inputData.substr(0, gs2_cbind_comma); if (gs2_cbind_flag.startsWith("p=")) { return Status(ErrorCodes::BadValue, "Server does not support channel binding"); } if ((gs2_cbind_flag != "y") && (gs2_cbind_flag != "n")) { return Status(ErrorCodes::BadValue, str::stream() << "Incorrect SCRAM client message prefix: " << gs2_cbind_flag); } const auto gs2_header_comma = inputData.find(',', gs2_cbind_comma + 1); if (gs2_header_comma == std::string::npos) { return badCount(2); } auto authzId = inputData.substr(gs2_cbind_comma + 1, gs2_header_comma - (gs2_cbind_comma + 1)); if (authzId.size()) { if (authzId.startsWith("a=")) { authzId = authzId.substr(2); } else { return Status(ErrorCodes::BadValue, str::stream() << "Incorrect SCRAM authzid: " << authzId); } } const auto client_first_message_bare = inputData.substr(gs2_header_comma + 1); if (client_first_message_bare.startsWith("m=")) { return Status(ErrorCodes::BadValue, "SCRAM mandatory extensions are not supported"); } /* StringSplitter::split() will ignore consecutive delimiters. * e.g. "foo,,bar" => {"foo","bar"} * This makes our implementation of SCRAM *slightly* more generous * in what it will accept than the standard calls for. * * This does not impact _authMessage, as it's composed from the raw * string input, rather than the output of the split operation. */ const auto input = StringSplitter::split(client_first_message_bare.toString(), ","); if (input.size() < 2) { // gs2-header is not included in this count, so add it back in. return badCount(input.size() + 2); } if (!str::startsWith(input[0], "n=") || input[0].size() < 3) { return Status(ErrorCodes::BadValue, str::stream() << "Invalid SCRAM user name: " << input[0]); } ServerMechanismBase::_principalName = input[0].substr(2); decodeSCRAMUsername(ServerMechanismBase::_principalName); if (!authzId.empty() && ServerMechanismBase::_principalName != authzId) { return Status(ErrorCodes::BadValue, str::stream() << "SCRAM user name " << ServerMechanismBase::_principalName << " does not match authzid " << authzId); } if (!str::startsWith(input[1], "r=") || input[1].size() < 6) { return Status(ErrorCodes::BadValue, str::stream() << "Invalid SCRAM client nonce: " << input[1]); } const auto clientNonce = input[1].substr(2); // SERVER-16534, SCRAM-SHA-1 must be enabled for authenticating the internal user, so that // cluster members may communicate with each other. Hence ignore disabled auth mechanism // for the internal user. UserName user(ServerMechanismBase::ServerMechanismBase::_principalName, ServerMechanismBase::getAuthenticationDatabase()); if (!sequenceContains(saslGlobalParams.authenticationMechanisms, "SCRAM-SHA-1") && user != internalSecurity.user->getName()) { return Status(ErrorCodes::BadValue, "SCRAM-SHA-1 authentication is disabled"); } // The authentication database is also the source database for the user. User* userObj; auto authManager = AuthorizationManager::get(opCtx->getServiceContext()); Status status = authManager->acquireUser(opCtx, user, &userObj); if (!status.isOK()) { return status; } User::CredentialData credentials = userObj->getCredentials(); UserName userName = userObj->getName(); authManager->releaseUser(userObj); _scramCredentials = credentials.scram<HashBlock>(); if (!_scramCredentials.isValid()) { // 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"); } } _secrets = scram::Secrets<HashBlock>("", base64::decode(_scramCredentials.storedKey), base64::decode(_scramCredentials.serverKey)); // 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]; std::unique_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=" << _scramCredentials.salt << ",i=" << _scramCredentials.iterationCount; std::string outputData = sb.str(); // add client-first-message-bare and server-first-message to _authMessage _authMessage = client_first_message_bare.toString() + "," + outputData; return std::make_tuple(false, std::move(outputData)); }