// ---------------------------------------------------------------------- // File: auth.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/FileUtils.hh" #include "utils/Macros.hh" #include "utils/Random.hh" #include "auth/AuthenticationDispatcher.hh" #include "test-utils.hh" #include #include "qclient/QClient.hh" #include "qclient/ResponseBuilder.hh" using namespace qclient; using namespace quarkdb; TEST(FilePermissionChecking, BasicSanity) { ASSERT_FALSE(areFilePermissionsSecure(0700)); ASSERT_FALSE(areFilePermissionsSecure(0777)); ASSERT_FALSE(areFilePermissionsSecure(0477)); ASSERT_FALSE(areFilePermissionsSecure(0401)); ASSERT_FALSE(areFilePermissionsSecure(0455)); ASSERT_FALSE(areFilePermissionsSecure(0444)); ASSERT_FALSE(areFilePermissionsSecure(0404)); ASSERT_FALSE(areFilePermissionsSecure(0440)); ASSERT_FALSE(areFilePermissionsSecure(0500)); ASSERT_FALSE(areFilePermissionsSecure(0700)); ASSERT_TRUE(areFilePermissionsSecure(0400)); } TEST(ReadPasswordFile, BasicSanity) { ASSERT_EQ(system("mkdir -p /tmp/quarkdb-tests/auth/"), 0); ASSERT_EQ(system("rm -f /tmp/quarkdb-tests/auth/f1"), 0); std::string contents; ASSERT_FALSE(readPasswordFile("/tmp/quarkdb-tests/auth/f1", contents)); ASSERT_FALSE(readPasswordFile("/tmp/quarkdb-tests/auth/non-existing", contents)); ASSERT_FALSE(readFile("/tmp/quarkdb-tests/auth/non-existing", contents)); ASSERT_EQ(system("echo 'pickles\n\n ' > /tmp/quarkdb-tests/auth/f1"), 0); ASSERT_EQ(system("chmod 777 /tmp/quarkdb-tests/auth/f1"), 0); ASSERT_FALSE(readPasswordFile("/tmp/quarkdb-tests/auth/f1", contents)); ASSERT_EQ(system("chmod 744 /tmp/quarkdb-tests/auth/f1"), 0); ASSERT_FALSE(readPasswordFile("/tmp/quarkdb-tests/auth/f1", contents)); ASSERT_EQ(system("chmod 700 /tmp/quarkdb-tests/auth/f1"), 0); ASSERT_FALSE(readPasswordFile("/tmp/quarkdb-tests/auth/f1", contents)); ASSERT_EQ(system("chmod 500 /tmp/quarkdb-tests/auth/f1"), 0); ASSERT_FALSE(readPasswordFile("/tmp/quarkdb-tests/auth/f1", contents)); ASSERT_EQ(system("chmod 400 /tmp/quarkdb-tests/auth/f1"), 0); ASSERT_TRUE(readPasswordFile("/tmp/quarkdb-tests/auth/f1", contents)); ASSERT_EQ(contents, "pickles"); ASSERT_EQ(system("rm -f /tmp/quarkdb-tests/auth/f1"), 0); } TEST(AuthenticationDispatcher, NoPassword) { AuthenticationDispatcher dispatcher(""); std::unique_ptr unused; bool authorized = false; ASSERT_EQ(Formatter::errArgs("AUTH"), dispatcher.dispatch(make_req("AUTH"), authorized, unused)); ASSERT_TRUE(authorized); ASSERT_EQ(Formatter::err("Client sent AUTH, but no password is set").val, dispatcher.dispatch(make_req("AUTH", "test"), authorized, unused).val); ASSERT_TRUE(authorized); ASSERT_EQ(Formatter::err("no password is set"), dispatcher.dispatch(make_req("HMAC-AUTH-GENERATE-CHALLENGE", generateSecureRandomBytes(64)), authorized, unused)); ASSERT_TRUE(authorized); ASSERT_EQ(Formatter::err("no password is set"), dispatcher.dispatch(make_req("HMAC-AUTH-VALIDATE-CHALLENGE", generateSecureRandomBytes(64)), authorized, unused)); ASSERT_TRUE(authorized); } TEST(AuthenticationDispatcher, TooSmallPassword) { ASSERT_THROW(AuthenticationDispatcher("hunter2"), FatalException); } TEST(AuthenticationDispatcher, AuthBasicSanity) { AuthenticationDispatcher dispatcher("hunter2_hunter2_hunter2_hunter2_hunter2"); std::unique_ptr unused; bool authorized = false; ASSERT_EQ(Formatter::errArgs("AUTH"), dispatcher.dispatch(make_req("AUTH"), authorized, unused)); ASSERT_FALSE(authorized); ASSERT_EQ(Formatter::err("invalid password"), dispatcher.dispatch(make_req("AUTH", "hunter3"), authorized, unused )); ASSERT_FALSE(authorized); ASSERT_EQ(Formatter::ok(), dispatcher.dispatch(make_req("AUTH", "hunter2_hunter2_hunter2_hunter2_hunter2"), authorized, unused )); ASSERT_TRUE(authorized); } TEST(AuthenticationDispatcher, ChallengesBasicSanity) { std::string secretKey = "hunter2_hunter2_hunter2_hunter2_hunter2"; AuthenticationDispatcher dispatcher(secretKey); std::unique_ptr authenticator1, authenticator2; bool authorized = false; ASSERT_EQ(Formatter::errArgs("HMAC-AUTH-GENERATE-CHALLENGE"), dispatcher.dispatch(make_req("HMAC-AUTH-GENERATE-CHALLENGE"), authorized, authenticator1)); ASSERT_FALSE(authorized); ASSERT_EQ(Formatter::err("no challenge is in progress"), dispatcher.dispatch(make_req("HMAC-AUTH-VALIDATE-CHALLENGE", "asdf"), authorized, authenticator1)); ASSERT_FALSE(authorized); ASSERT_EQ(Formatter::err("exactly 64 random bytes must be provided").val, dispatcher.dispatch(make_req("HMAC-AUTH-GENERATE-CHALLENGE", "1234"), authorized, authenticator1).val); ASSERT_FALSE(authorized); RedisEncodedResponse resp = dispatcher.dispatch(make_req("HMAC-AUTH-GENERATE-CHALLENGE", generateSecureRandomBytes(64)), authorized, authenticator1); ASSERT_FALSE(authorized); // parse.. qclient::ResponseBuilder responseBuilder; responseBuilder.feed(resp.val); redisReplyPtr rr; ASSERT_EQ(responseBuilder.pull(rr), qclient::ResponseBuilder::Status::kOk); ASSERT_EQ(rr->type, REDIS_REPLY_STRING); std::string challengeString = std::string(rr->str, rr->len); resp = dispatcher.dispatch(make_req("HMAC-AUTH-VALIDATE-CHALLENGE", Authenticator::generateSignature(challengeString, secretKey)), authorized, authenticator1); ASSERT_EQ(Formatter::ok(), resp); ASSERT_TRUE(authorized); } TEST(HmacAuthHandshake, BasicSanity) { std::string pw = "hunter2_hunter2_hunter2_hunter2_hunter2"; HmacAuthHandshake handshake(pw); Authenticator authenticator(pw); std::vector cmd = handshake.provideHandshake(); ASSERT_EQ(cmd.size(), 2u); ASSERT_EQ(cmd[0], "HMAC-AUTH-GENERATE-CHALLENGE"); qclient::redisReplyPtr reply = ResponseBuilder::makeStr("some-string-to-sign"); ASSERT_EQ(qclient::Handshake::Status::INVALID, handshake.validateResponse(reply)); handshake.restart(); cmd = handshake.provideHandshake(); ASSERT_EQ(cmd.size(), 2u); ASSERT_EQ(cmd[0], "HMAC-AUTH-GENERATE-CHALLENGE"); std::string challenge = authenticator.generateChallenge(cmd[1]); reply = ResponseBuilder::makeStr(challenge); ASSERT_EQ(qclient::Handshake::Status::VALID_INCOMPLETE, handshake.validateResponse(reply)); cmd = handshake.provideHandshake(); ASSERT_EQ(cmd.size(), 2u); ASSERT_EQ(cmd[0], "HMAC-AUTH-VALIDATE-CHALLENGE"); ASSERT_EQ(authenticator.validateSignature(cmd[1]), Authenticator::ValidationStatus::kOk); reply = ResponseBuilder::makeStatus("OK"); reply->type = REDIS_REPLY_STATUS; ASSERT_EQ(qclient::Handshake::Status::VALID_COMPLETE, handshake.validateResponse(reply)); }