From 5050cd029902e03d34ffe3d77413fe200417c88a Mon Sep 17 00:00:00 2001 From: Jeff Date: Fri, 1 Apr 2022 12:52:25 -0400 Subject: [PATCH] add hashed password capability to endpoint auth by file --- llarp/CMakeLists.txt | 2 +- llarp/config/config.cpp | 9 +++++++ llarp/config/config.hpp | 1 + llarp/crypto/crypto.hpp | 4 +++ llarp/crypto/crypto_libsodium.cpp | 18 +++++++++++++ llarp/crypto/crypto_libsodium.hpp | 3 +++ llarp/handlers/tun.cpp | 2 +- llarp/service/auth.cpp | 43 ++++++++++++++++++++++++++----- llarp/service/auth.hpp | 16 ++++++++++-- test/crypto/test_llarp_crypto.cpp | 41 +++++++++++++++++++++++++++++ 10 files changed, 129 insertions(+), 10 deletions(-) diff --git a/llarp/CMakeLists.txt b/llarp/CMakeLists.txt index d328bc489..7fc9aecb4 100644 --- a/llarp/CMakeLists.txt +++ b/llarp/CMakeLists.txt @@ -243,7 +243,7 @@ if(WITH_HIVE) endif() target_link_libraries(liblokinet PUBLIC cxxopts lokinet-platform lokinet-util lokinet-cryptography sqlite_orm ngtcp2_static) -target_link_libraries(liblokinet PRIVATE libunbound) +target_link_libraries(liblokinet PRIVATE libunbound crypt) if(BUILD_LIBLOKINET) diff --git a/llarp/config/config.cpp b/llarp/config/config.cpp index 1e640373a..5e6a53115 100644 --- a/llarp/config/config.cpp +++ b/llarp/config/config.cpp @@ -381,6 +381,15 @@ namespace llarp stringify("cannot load auth file ", arg, " as it does not seem to exist")}; m_AuthFiles.emplace(std::move(arg)); }); + conf.defineOption( + "network", + "auth-file-type", + ClientOnly, + Comment{ + "How to interpret the contents of an auth file.", + "Possible values: hashes, plaintext", + }, + [this](std::string arg) { m_AuthFileType = service::ParseAuthFileType(std::move(arg)); }); conf.defineOption( "network", diff --git a/llarp/config/config.hpp b/llarp/config/config.hpp index 6f6d82ef2..09eb1febb 100644 --- a/llarp/config/config.hpp +++ b/llarp/config/config.hpp @@ -115,6 +115,7 @@ namespace llarp std::unordered_map m_mapAddrs; service::AuthType m_AuthType = service::AuthType::eAuthTypeNone; + service::AuthFileType m_AuthFileType = service::AuthFileType::eAuthFileHashes; std::optional m_AuthUrl; std::optional m_AuthMethod; std::unordered_set m_AuthWhitelist; diff --git a/llarp/crypto/crypto.hpp b/llarp/crypto/crypto.hpp index 56cc6c60f..1af34e6b4 100644 --- a/llarp/crypto/crypto.hpp +++ b/llarp/crypto/crypto.hpp @@ -100,6 +100,10 @@ namespace llarp virtual bool check_identity_privkey(const SecretKey&) = 0; + + /// check if a password hash string matches the challenge + virtual bool + check_passwd_hash(std::string pwhash, std::string challenge) = 0; }; inline Crypto::~Crypto() = default; diff --git a/llarp/crypto/crypto_libsodium.cpp b/llarp/crypto/crypto_libsodium.cpp index 8a14f66af..d901b976e 100644 --- a/llarp/crypto/crypto_libsodium.cpp +++ b/llarp/crypto/crypto_libsodium.cpp @@ -13,6 +13,9 @@ #include #include #include +#include + +#include extern "C" { @@ -463,6 +466,21 @@ namespace llarp auto d = keypair.data(); crypto_kem_keypair(d + PQ_SECRETKEYSIZE, d); } + + bool + CryptoLibSodium::check_passwd_hash(std::string pwhash, std::string challenge) + { + bool ret = false; + auto pos = pwhash.find_last_of('$'); + auto settings = pwhash.substr(0, pos); + crypt_data data{}; + if (char* ptr = crypt_r(challenge.c_str(), settings.c_str(), &data)) + { + ret = ptr == pwhash; + } + sodium_memzero(&data, sizeof(data)); + return ret; + } } // namespace sodium const byte_t* diff --git a/llarp/crypto/crypto_libsodium.hpp b/llarp/crypto/crypto_libsodium.hpp index f085aee1e..6248e9484 100644 --- a/llarp/crypto/crypto_libsodium.hpp +++ b/llarp/crypto/crypto_libsodium.hpp @@ -104,6 +104,9 @@ namespace llarp bool check_identity_privkey(const SecretKey&) override; + + bool + check_passwd_hash(std::string pwhash, std::string challenge) override; }; } // namespace sodium diff --git a/llarp/handlers/tun.cpp b/llarp/handlers/tun.cpp index 6f7f27103..d4d76c647 100644 --- a/llarp/handlers/tun.cpp +++ b/llarp/handlers/tun.cpp @@ -176,7 +176,7 @@ namespace llarp if (conf.m_AuthType == service::AuthType::eAuthTypeFile) { - m_AuthPolicy = service::MakeFileAuthPolicy(m_router, conf.m_AuthFiles); + m_AuthPolicy = service::MakeFileAuthPolicy(m_router, conf.m_AuthFiles, conf.m_AuthFileType); } else if (conf.m_AuthType != service::AuthType::eAuthTypeNone) { diff --git a/llarp/service/auth.cpp b/llarp/service/auth.cpp index a5e4518a0..c765ac7d2 100644 --- a/llarp/service/auth.cpp +++ b/llarp/service/auth.cpp @@ -37,6 +37,21 @@ namespace llarp::service return itr->second; } + AuthFileType + ParseAuthFileType(std::string data) + { + std::unordered_map values = { + {"plain", AuthFileType::eAuthFilePlain}, + {"plaintext", AuthFileType::eAuthFilePlain}, + {"hashed", AuthFileType::eAuthFileHashes}, + {"hashes", AuthFileType::eAuthFileHashes}, + {"hash", AuthFileType::eAuthFileHashes}}; + const auto itr = values.find(data); + if (itr == values.end()) + throw std::invalid_argument("no such auth file type: " + data); + return itr->second; + } + /// turn an auth result code into an int uint64_t AuthResultCodeAsInt(AuthResultCode code) @@ -67,6 +82,7 @@ namespace llarp::service class FileAuthPolicy : public IAuthPolicy, public std::enable_shared_from_this { const std::set m_Files; + const AuthFileType m_Type; AbstractRouter* const m_Router; mutable util::Mutex m_Access; std::unordered_set m_Pending; @@ -86,8 +102,8 @@ namespace llarp::service const auto parts = split_any(line, "#;", true); if (auto part = parts[0]; not parts.empty() and not parts[0].empty()) { - // split off whitespaces - if (TrimWhitespace(part) == info.token) + // split off whitespaces and check password + if (CheckPasswd(std::string{TrimWhitespace(part)}, info.token)) return AuthResult{AuthResultCode::eAuthAccepted, "accepted by whitelist"}; } } @@ -95,9 +111,24 @@ namespace llarp::service return AuthResult{AuthResultCode::eAuthRejected, "rejected by whitelist"}; } + bool + CheckPasswd(std::string hash, std::string challenge) const + { + switch (m_Type) + { + case AuthFileType::eAuthFilePlain: + return hash == challenge; + case AuthFileType::eAuthFileHashes: + return CryptoManager::instance()->check_passwd_hash( + std::move(hash), std::move(challenge)); + default: + return false; + } + } + public: - FileAuthPolicy(AbstractRouter* r, std::set files) - : m_Files{std::move(files)}, m_Router{r} + FileAuthPolicy(AbstractRouter* r, std::set files, AuthFileType filetype) + : m_Files{std::move(files)}, m_Type{filetype}, m_Router{r} {} void @@ -145,9 +176,9 @@ namespace llarp::service }; std::shared_ptr - MakeFileAuthPolicy(AbstractRouter* r, std::set files) + MakeFileAuthPolicy(AbstractRouter* r, std::set files, AuthFileType filetype) { - return std::make_shared(r, std::move(files)); + return std::make_shared(r, std::move(files), filetype); } } // namespace llarp::service diff --git a/llarp/service/auth.hpp b/llarp/service/auth.hpp index acfaadebf..b87f9eab7 100644 --- a/llarp/service/auth.hpp +++ b/llarp/service/auth.hpp @@ -72,17 +72,29 @@ namespace llarp::service eAuthTypeWhitelist, /// LMQ server eAuthTypeLMQ, - /// plain file + /// static file eAuthTypeFile, }; + /// how to interpret an file for auth + enum class AuthFileType + { + eAuthFilePlain, + eAuthFileHashes, + }; + /// get an auth type from a string /// throws std::invalid_argument if arg is invalid AuthType ParseAuthType(std::string arg); + /// get an auth file type from a string + /// throws std::invalid_argument if arg is invalid + AuthFileType + ParseAuthFileType(std::string arg); + /// make an IAuthPolicy that reads out of a static file std::shared_ptr - MakeFileAuthPolicy(AbstractRouter*, std::set files); + MakeFileAuthPolicy(AbstractRouter*, std::set files, AuthFileType fileType); } // namespace llarp::service diff --git a/test/crypto/test_llarp_crypto.cpp b/test/crypto/test_llarp_crypto.cpp index 34747e53d..cef34b931 100644 --- a/test/crypto/test_llarp_crypto.cpp +++ b/test/crypto/test_llarp_crypto.cpp @@ -47,3 +47,44 @@ TEST_CASE("PQ crypto") REQUIRE(c->pqe_decrypt(block, otherShared, pq_keypair_to_secret(keys))); REQUIRE(otherShared == shared); } + +TEST_CASE("passwd hash valid") +{ + llarp::sodium::CryptoLibSodium crypto; + + // poggers password hashes + std::set valid_hashes; + // UNIX DES + valid_hashes.emplace("CVu85Ms694POo"); + // sha256 salted + valid_hashes.emplace( + "$5$cIghotiBGjfPC7Fu$" + "TXXxPhpUcEiF9tMnjhEVJFi9AlNDSkNRQFTrXPQTKS9"); + // sha512 salted + valid_hashes.emplace( + "$6$qB77ms3wCIo.xVKP$Hl0RLuDgWNmIW4s." + "5KUbFmnauoTfrWSPJzDCD8ZTSSfwRbMgqgG6F9y3K.YEYVij8g/" + "Js0DRT2RhgXoX0sHGb."); + + for (const auto& hash : valid_hashes) + { + // make sure it is poggers ... + REQUIRE(crypto.check_passwd_hash(hash, "poggers")); + // ... and not inscrutible + REQUIRE(not crypto.check_passwd_hash(hash, "inscrutible")); + } +} + +TEST_CASE("passwd hash malformed") +{ + llarp::sodium::CryptoLibSodium crypto; + + std::set invalid_hashes = { + "stevejobs", + "$JKEDbzgzym1N6", // crypt() for "stevejobs" with a $ at the begining + "$0$zero$AAAAAAAAAAA", + "$$$AAAAAAAAAAAA", + "$LIGMA$BALLS$LMAOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO."}; + for (const auto& hash : invalid_hashes) + REQUIRE(not crypto.check_passwd_hash(hash, "stevejobs")); +}