// ----------------------------------------------------------------------
// File: AuthenticationDispatcher.cc
// Author: Georgios Bitzes - CERN
// ----------------------------------------------------------------------
/************************************************************************
* quarkdb - a redis-like highly available key-value store *
* Copyright (C) 2016 CERN/Switzerland *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* 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 General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see .*
************************************************************************/
#include "utils/Macros.hh"
#include "auth/AuthenticationDispatcher.hh"
#include "Formatter.hh"
using namespace quarkdb;
AuthenticationDispatcher::AuthenticationDispatcher(std::string_view secr)
: secret(secr) {
if(secret.size() < 32u && !secret.empty()) {
qdb_throw("Password is too small, minimum length is 32");
}
}
RedisEncodedResponse AuthenticationDispatcher::dispatch(const RedisRequest &req, bool &authorized, std::unique_ptr &authenticator) {
authorized = secret.empty();
switch(req.getCommand()) {
case RedisCommand::AUTH: {
if(req.size() != 2u) return Formatter::errArgs(req[0]);
if(secret.empty()) return Formatter::err("Client sent AUTH, but no password is set");
qdb_warn("A client used AUTH, which is highly discouraged.");
if(secret != req[1]) {
qdb_warn("A password attempt was made with an invalid password");
return Formatter::err("invalid password");
}
authorized = true;
return Formatter::ok();
}
case RedisCommand::HMAC_AUTH_GENERATE_CHALLENGE: {
if(req.size() != 2u) return Formatter::errArgs(req[0]);
if(secret.empty()) return Formatter::err("no password is set");
if(req[1].size() != 64) return Formatter::err("exactly 64 random bytes must be provided");
authenticator.reset(new Authenticator(secret));
return Formatter::string(authenticator->generateChallenge(req[1]));
}
case RedisCommand::HMAC_AUTH_VALIDATE_CHALLENGE: {
if(req.size() != 2u) return Formatter::errArgs(req[0]);
if(secret.empty()) return Formatter::err("no password is set");
if(!authenticator) return Formatter::err("no challenge is in progress");
Authenticator::ValidationStatus validationStatus = authenticator->validateSignature(req[1]);
authenticator.reset();
if(validationStatus == Authenticator::ValidationStatus::kInvalidSignature) {
qdb_warn("An attempted hmac authentication challenge failed. Client supplied invalid signature.")
return Formatter::err("invalid signature");
}
if(validationStatus == Authenticator::ValidationStatus::kDeadlinePassed) {
return Formatter::err("deadline passed");
}
qdb_assert(validationStatus == Authenticator::ValidationStatus::kOk);
authorized = true;
return Formatter::ok();
}
default: {
qdb_throw("internal dispatching error for command " << req.toPrintableString());
}
}
}
LinkStatus AuthenticationDispatcher::dispatch(Connection *conn, RedisRequest &req) {
return conn->raw(dispatch(req, conn->authorization, conn->authenticator));
}