//------------------------------------------------------------------------------ // File: Handshake.cc // Author: Georgios Bitzes - CERN //------------------------------------------------------------------------------ /************************************************************************ * qclient - A simple redis C++ client with support for redirects * * 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 #include #include #include "qclient/Handshake.hh" #include "qclient/utils/Macros.hh" using namespace qclient; //------------------------------------------------------------------------------ // AuthHandshake: Constructor //------------------------------------------------------------------------------ AuthHandshake::AuthHandshake(const std::string &pw) : password(pw) { } //------------------------------------------------------------------------------ // AuthHandshake: Destructor //------------------------------------------------------------------------------ AuthHandshake::~AuthHandshake() {} //------------------------------------------------------------------------------ // AuthHandshake: ProvideHandshake //------------------------------------------------------------------------------ std::vector AuthHandshake::provideHandshake() { return { "AUTH", password }; } //------------------------------------------------------------------------------ // AuthHandshake: Response validation //------------------------------------------------------------------------------ Handshake::Status AuthHandshake::validateResponse(const redisReplyPtr &reply) { if(!reply) return Status::INVALID; if(reply->type != REDIS_REPLY_STATUS) return Status::INVALID; std::string response(reply->str, reply->len); if(response != "OK") return Status::INVALID; return Status::VALID_COMPLETE; } //------------------------------------------------------------------------------ // AuthHandshake: Restart //------------------------------------------------------------------------------ void AuthHandshake::restart() {} //------------------------------------------------------------------------------ // Create a new handshake object of this type //------------------------------------------------------------------------------ std::unique_ptr AuthHandshake::clone() const { return std::unique_ptr(new AuthHandshake(password)); } //------------------------------------------------------------------------------ // HmacAuthHandshake: Constructor //------------------------------------------------------------------------------ HmacAuthHandshake::HmacAuthHandshake(const std::string &pw) : password(pw) {} //------------------------------------------------------------------------------ // HmacAuthHandshake: Destructor //------------------------------------------------------------------------------ HmacAuthHandshake::~HmacAuthHandshake() {} //------------------------------------------------------------------------------ // HmacAuthHandshake: Generate cryptographically secure random bytes //------------------------------------------------------------------------------ std::string HmacAuthHandshake::generateSecureRandomBytes(size_t nbytes) { char buffer[nbytes + 1]; // We might want to keep a pool of open "/dev/urandom" on standby, to avoid // opening and closing /dev/urandom too often, but meh, this'll do for now. FILE *in = fopen("/dev/urandom", "rb"); if(!in) { std::cerr << "unable to open /dev/urandom" << std::endl; std::terminate(); } size_t bytes_read = fread(buffer, 1, nbytes, in); if(bytes_read != nbytes) { std::cerr << "qclient: assertion violation, bytes_read != nbytes. " << std::endl; std::terminate(); } qclient_assert(bytes_read == nbytes); qclient_assert(fclose(in) == 0); return std::string(buffer, nbytes); } //------------------------------------------------------------------------------ // HmacAuthHandshake: Generate signature //------------------------------------------------------------------------------ std::string HmacAuthHandshake::generateSignature() { std::string ret; ret.resize(SHA256_DIGEST_LENGTH); unsigned int bufferLen = SHA256_DIGEST_LENGTH; HMAC(EVP_sha256(), (const unsigned char*) password.c_str(), password.size(), (const unsigned char*) stringToSign.c_str(), stringToSign.size(), (unsigned char*) ret.data(), &bufferLen); return ret; } //------------------------------------------------------------------------------ // HmacAuthHandshake: Provide handshake //------------------------------------------------------------------------------ std::vector HmacAuthHandshake::provideHandshake() { if(initiated == false) { initiated = true; randomBytes = generateSecureRandomBytes(64); return { "HMAC-AUTH-GENERATE-CHALLENGE", randomBytes }; } return { "HMAC-AUTH-VALIDATE-CHALLENGE", generateSignature() }; } //------------------------------------------------------------------------------ // HmacAuthHandshake: Validate response //------------------------------------------------------------------------------ Handshake::Status HmacAuthHandshake::validateResponse(const redisReplyPtr &reply) { if(!reply) return Status::INVALID; if(reply->type == REDIS_REPLY_ERROR) { std::cerr << "qclient: HmacAuthHandshake failed with error " << std::string(reply->str, reply->len) << std::endl; return Status::INVALID; } if(!receivedChallenge) { if(reply->type != REDIS_REPLY_STRING) { std::cerr << "qclient: Received invalid response type in HmacAuthHandshake" << std::endl; return Status::INVALID; } stringToSign = std::string(reply->str, reply->len); receivedChallenge = true; if(!startswith(stringToSign, randomBytes)) { std::cerr << "qclient: HmacAuthHandshake: My random bytes were not used by the server for the construction of string-to-sign" << std::endl; return Status::INVALID; } return Status::VALID_INCOMPLETE; } if(reply->type != REDIS_REPLY_STATUS) { std::cerr << "qclient: Received invalid response type in HmacAuthHandshake" << std::endl; return Status::INVALID; } if(std::string(reply->str, reply->len) != "OK") { std::cerr << "qclient: HmacAuthHandshake received invalid response - " << std::string(reply->str, reply->len) << std::endl; return Status::INVALID; } return Status::VALID_COMPLETE; } //------------------------------------------------------------------------------ // HmacAuthHandshake: Restart //------------------------------------------------------------------------------ void HmacAuthHandshake::restart() { initiated = false; receivedChallenge = false; randomBytes.clear(); stringToSign.clear(); } //------------------------------------------------------------------------------ // Create a new handshake object of this type //------------------------------------------------------------------------------ std::unique_ptr HmacAuthHandshake::clone() const { return std::unique_ptr(new HmacAuthHandshake(password)); } //------------------------------------------------------------------------------ // HandshakeChainer: Constructor //------------------------------------------------------------------------------ HandshakeChainer::HandshakeChainer(std::unique_ptr h1, std::unique_ptr h2) : first(std::move(h1)), second(std::move(h2)) {} //------------------------------------------------------------------------------ // HandshakeChainer: Destructor //------------------------------------------------------------------------------ HandshakeChainer::~HandshakeChainer() {} //------------------------------------------------------------------------------ // HandshakeChainer: Provide handshake //------------------------------------------------------------------------------ std::vector HandshakeChainer::provideHandshake() { if(!firstDone) { return first->provideHandshake(); } return second->provideHandshake(); } //------------------------------------------------------------------------------ // HandshakeChainer: Validate response //------------------------------------------------------------------------------ Handshake::Status HandshakeChainer::validateResponse(const redisReplyPtr &reply) { if(!firstDone) { Status st = first->validateResponse(reply); if(st == Status::VALID_COMPLETE) { firstDone = true; return Status::VALID_INCOMPLETE; } return st; } return second->validateResponse(reply); } //------------------------------------------------------------------------------ // HandshakeChainer: Restart //------------------------------------------------------------------------------ void HandshakeChainer::restart() { firstDone = false; first->restart(); second->restart(); } //------------------------------------------------------------------------------ // Create a new handshake object of this type //------------------------------------------------------------------------------ std::unique_ptr HandshakeChainer::clone() const { return std::unique_ptr(new HandshakeChainer(first->clone(), second->clone())); } //------------------------------------------------------------------------------ // PingHandshake: Constructor //------------------------------------------------------------------------------ PingHandshake::PingHandshake(const std::string &text) : pingToSend(text) { if(pingToSend.empty()) { pingToSend = "qclient-connection-initialization"; } } //------------------------------------------------------------------------------ // PingHandshake: Destructor //------------------------------------------------------------------------------ PingHandshake::~PingHandshake() {} //------------------------------------------------------------------------------ // PingHandshake: Provide handshake //------------------------------------------------------------------------------ std::vector PingHandshake::provideHandshake() { return { "PING", pingToSend }; } //------------------------------------------------------------------------------ // PingHandshake: Validate response //------------------------------------------------------------------------------ Handshake::Status PingHandshake::validateResponse(const redisReplyPtr &reply) { if(!reply) return Status::INVALID; if(reply->type != REDIS_REPLY_STRING) return Status::INVALID; std::string response(reply->str, reply->len); if(response != pingToSend) return Status::INVALID; return Status::VALID_COMPLETE; } //------------------------------------------------------------------------------ // PingHandshake: Restart //------------------------------------------------------------------------------ void PingHandshake::restart() { } //------------------------------------------------------------------------------ // Create a new handshake object of this type //------------------------------------------------------------------------------ std::unique_ptr PingHandshake::clone() const { return std::unique_ptr(new PingHandshake(pingToSend)); } //------------------------------------------------------------------------------ // Activate push types handshake: Constructor //------------------------------------------------------------------------------ ActivatePushTypesHandshake::ActivatePushTypesHandshake() {} //------------------------------------------------------------------------------ // Activate push types handshake: Destructor //------------------------------------------------------------------------------ ActivatePushTypesHandshake::~ActivatePushTypesHandshake() {} //------------------------------------------------------------------------------ // Activate push types handshake: Provide handshake //------------------------------------------------------------------------------ std::vector ActivatePushTypesHandshake::provideHandshake() { return { "ACTIVATE-PUSH-TYPES" }; } //------------------------------------------------------------------------------ // Activate push types handshake: Validate response, expect OK //------------------------------------------------------------------------------ Handshake::Status ActivatePushTypesHandshake::validateResponse(const redisReplyPtr &reply) { if(reply->type != REDIS_REPLY_STATUS) { std::cerr << "qclient: Received invalid response type in ActivatePushTypesHandshake" << std::endl; return Status::INVALID; } if(std::string(reply->str, reply->len) != "OK") { std::cerr << "qclient: ActivatePushTypesHandshake received invalid response - " << std::string(reply->str, reply->len) << std::endl; return Status::INVALID; } return Status::VALID_COMPLETE; } //------------------------------------------------------------------------------ // Activate push types handshake: Restart //------------------------------------------------------------------------------ void ActivatePushTypesHandshake::restart() {} //------------------------------------------------------------------------------ // Activate push types handshake: Clone //------------------------------------------------------------------------------ std::unique_ptr ActivatePushTypesHandshake::clone() const { return std::unique_ptr(new ActivatePushTypesHandshake()); } //------------------------------------------------------------------------------ // Set client name handshake: Constructor //------------------------------------------------------------------------------ SetClientNameHandshake::SetClientNameHandshake(const std::string &name, bool ignorefail) : clientName(name), ignoreFailures(ignorefail) {} //------------------------------------------------------------------------------ // Set client name handshake: Destructor //------------------------------------------------------------------------------ SetClientNameHandshake::~SetClientNameHandshake() {} //------------------------------------------------------------------------------ // Set client name handshake: Provide handshake //------------------------------------------------------------------------------ std::vector SetClientNameHandshake::provideHandshake() { return {"CLIENT", "SETNAME", clientName }; } //------------------------------------------------------------------------------ // Set client name handshake: Validate response //------------------------------------------------------------------------------ Handshake::Status SetClientNameHandshake::validateResponse(const redisReplyPtr &reply) { if(ignoreFailures) { return Status::VALID_COMPLETE; } if(!reply || reply->type != REDIS_REPLY_STATUS || std::string(reply->str, reply->len) != "OK") { return Status::INVALID; } return Status::VALID_COMPLETE; } void SetClientNameHandshake::restart() {} std::unique_ptr SetClientNameHandshake::clone() const { return std::unique_ptr(new SetClientNameHandshake(clientName, ignoreFailures)); }