mirror of https://github.com/oxen-io/lokinet
Merge pull request #1830 from majestrate/static-auth-codes-2021-12-26
static endpoint auth codes
This commit is contained in:
commit
64d6ba8a53
|
@ -244,6 +244,14 @@ endif()
|
|||
|
||||
target_link_libraries(liblokinet PUBLIC cxxopts lokinet-platform lokinet-util lokinet-cryptography sqlite_orm ngtcp2_static)
|
||||
target_link_libraries(liblokinet PRIVATE libunbound)
|
||||
pkg_check_modules(CRYPT libcrypt IMPORTED_TARGET)
|
||||
if(CRYPT_FOUND AND NOT CMAKE_CROSSCOMPILING)
|
||||
add_definitions(-DHAVE_CRYPT)
|
||||
add_library(libcrypt INTERFACE)
|
||||
target_link_libraries(libcrypt INTERFACE PkgConfig::CRYPT)
|
||||
target_link_libraries(liblokinet PRIVATE libcrypt)
|
||||
message(STATUS "using libcrypt ${CRYPT_VERSION}")
|
||||
endif()
|
||||
|
||||
|
||||
if(BUILD_LIBLOKINET)
|
||||
|
|
|
@ -317,7 +317,7 @@ namespace llarp
|
|||
ClientOnly,
|
||||
Comment{
|
||||
"Set the endpoint authentication mechanism.",
|
||||
"none/whitelist/lmq",
|
||||
"none/whitelist/lmq/file",
|
||||
},
|
||||
[this](std::string arg) {
|
||||
if (arg.empty())
|
||||
|
@ -366,6 +366,42 @@ namespace llarp
|
|||
m_AuthWhitelist.emplace(std::move(addr));
|
||||
});
|
||||
|
||||
conf.defineOption<fs::path>(
|
||||
"network",
|
||||
"auth-file",
|
||||
ClientOnly,
|
||||
MultiValue,
|
||||
Comment{
|
||||
"Read auth tokens from file to accept endpoint auth",
|
||||
"Can be provided multiple times",
|
||||
},
|
||||
[this](fs::path arg) {
|
||||
if (not fs::exists(arg))
|
||||
throw std::invalid_argument{
|
||||
stringify("cannot load auth file ", arg, " as it does not seem to exist")};
|
||||
m_AuthFiles.emplace(std::move(arg));
|
||||
});
|
||||
conf.defineOption<std::string>(
|
||||
"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<std::string>(
|
||||
"network",
|
||||
"auth-static",
|
||||
ClientOnly,
|
||||
MultiValue,
|
||||
Comment{
|
||||
"Manually add a static auth code to accept for endpoint auth",
|
||||
"Can be provided multiple times",
|
||||
},
|
||||
[this](std::string arg) { m_AuthStaticTokens.emplace(std::move(arg)); });
|
||||
|
||||
conf.defineOption<bool>(
|
||||
"network",
|
||||
"reachable",
|
||||
|
|
|
@ -115,9 +115,12 @@ namespace llarp
|
|||
std::unordered_map<huint128_t, service::Address> m_mapAddrs;
|
||||
|
||||
service::AuthType m_AuthType = service::AuthType::eAuthTypeNone;
|
||||
service::AuthFileType m_AuthFileType = service::AuthFileType::eAuthFileHashes;
|
||||
std::optional<std::string> m_AuthUrl;
|
||||
std::optional<std::string> m_AuthMethod;
|
||||
std::unordered_set<service::Address> m_AuthWhitelist;
|
||||
std::unordered_set<std::string> m_AuthStaticTokens;
|
||||
std::set<fs::path> m_AuthFiles;
|
||||
|
||||
std::vector<llarp::dns::SRVData> m_SRVRecords;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -13,6 +13,11 @@
|
|||
#include <llarp/util/str.hpp>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#ifdef HAVE_CRYPT
|
||||
#include <crypt.h>
|
||||
#endif
|
||||
|
||||
#include <llarp/util/str.hpp>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
|
@ -463,6 +468,25 @@ namespace llarp
|
|||
auto d = keypair.data();
|
||||
crypto_kem_keypair(d + PQ_SECRETKEYSIZE, d);
|
||||
}
|
||||
|
||||
bool
|
||||
CryptoLibSodium::check_passwd_hash(std::string pwhash, std::string challenge)
|
||||
{
|
||||
(void)pwhash;
|
||||
(void)challenge;
|
||||
bool ret = false;
|
||||
#ifdef HAVE_CRYPT
|
||||
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));
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
} // namespace sodium
|
||||
|
||||
const byte_t*
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -174,7 +174,11 @@ namespace llarp
|
|||
LogInfo(Name(), " setting to be not reachable by default");
|
||||
}
|
||||
|
||||
if (conf.m_AuthType != service::AuthType::eAuthTypeNone)
|
||||
if (conf.m_AuthType == service::AuthType::eAuthTypeFile)
|
||||
{
|
||||
m_AuthPolicy = service::MakeFileAuthPolicy(m_router, conf.m_AuthFiles, conf.m_AuthFileType);
|
||||
}
|
||||
else if (conf.m_AuthType != service::AuthType::eAuthTypeNone)
|
||||
{
|
||||
std::string url, method;
|
||||
if (conf.m_AuthUrl.has_value() and conf.m_AuthMethod.has_value())
|
||||
|
@ -183,7 +187,12 @@ namespace llarp
|
|||
method = *conf.m_AuthMethod;
|
||||
}
|
||||
auto auth = std::make_shared<rpc::EndpointAuthRPC>(
|
||||
url, method, conf.m_AuthWhitelist, Router()->lmq(), shared_from_this());
|
||||
url,
|
||||
method,
|
||||
conf.m_AuthWhitelist,
|
||||
conf.m_AuthStaticTokens,
|
||||
Router()->lmq(),
|
||||
shared_from_this());
|
||||
auth->Start();
|
||||
m_AuthPolicy = std::move(auth);
|
||||
}
|
||||
|
|
|
@ -6,14 +6,16 @@ namespace llarp::rpc
|
|||
EndpointAuthRPC::EndpointAuthRPC(
|
||||
std::string url,
|
||||
std::string method,
|
||||
Whitelist_t whitelist,
|
||||
Whitelist_t whitelist_addrs,
|
||||
std::unordered_set<std::string> whitelist_tokens,
|
||||
LMQ_ptr lmq,
|
||||
Endpoint_ptr endpoint)
|
||||
: m_AuthURL(std::move(url))
|
||||
, m_AuthMethod(std::move(method))
|
||||
, m_AuthWhitelist(std::move(whitelist))
|
||||
, m_LMQ(std::move(lmq))
|
||||
, m_Endpoint(std::move(endpoint))
|
||||
: m_AuthURL{std::move(url)}
|
||||
, m_AuthMethod{std::move(method)}
|
||||
, m_AuthWhitelist{std::move(whitelist_addrs)}
|
||||
, m_AuthStaticTokens{std::move(whitelist_tokens)}
|
||||
, m_LMQ{std::move(lmq)}
|
||||
, m_Endpoint{std::move(endpoint)}
|
||||
{}
|
||||
|
||||
void
|
||||
|
@ -57,13 +59,6 @@ namespace llarp::rpc
|
|||
reply(service::AuthResult{service::AuthResultCode::eAuthAccepted, "explicitly whitelisted"});
|
||||
return;
|
||||
}
|
||||
if (not m_Conn.has_value())
|
||||
{
|
||||
// we don't have a connection to the backend so it's failed
|
||||
reply(service::AuthResult{
|
||||
service::AuthResultCode::eAuthFailed, "remote has no connection to auth backend"});
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg->proto != llarp::service::ProtocolType::Auth)
|
||||
{
|
||||
|
@ -72,9 +67,32 @@ namespace llarp::rpc
|
|||
return;
|
||||
}
|
||||
|
||||
std::string payload{(char*)msg->payload.data(), msg->payload.size()};
|
||||
|
||||
if (m_AuthStaticTokens.count(payload))
|
||||
{
|
||||
reply(service::AuthResult{service::AuthResultCode::eAuthAccepted, "explicitly whitelisted"});
|
||||
return;
|
||||
}
|
||||
|
||||
if (not m_Conn.has_value())
|
||||
{
|
||||
if (m_AuthStaticTokens.empty())
|
||||
{
|
||||
// we don't have a connection to the backend so it's failed
|
||||
reply(service::AuthResult{
|
||||
service::AuthResultCode::eAuthFailed, "remote has no connection to auth backend"});
|
||||
}
|
||||
else
|
||||
{
|
||||
// static auth mode
|
||||
reply(service::AuthResult{service::AuthResultCode::eAuthRejected, "access not permitted"});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const auto authinfo = msg->EncodeAuthInfo();
|
||||
std::string_view metainfo{authinfo.data(), authinfo.size()};
|
||||
std::string_view payload{(char*)msg->payload.data(), msg->payload.size()};
|
||||
// call method with 2 parameters: metainfo and userdata
|
||||
m_LMQ->request(
|
||||
*m_Conn,
|
||||
|
|
|
@ -20,7 +20,8 @@ namespace llarp::rpc
|
|||
explicit EndpointAuthRPC(
|
||||
std::string url,
|
||||
std::string method,
|
||||
Whitelist_t whitelist,
|
||||
Whitelist_t addr_whitelist,
|
||||
std::unordered_set<std::string> token_whitelist,
|
||||
LMQ_ptr lmq,
|
||||
Endpoint_ptr endpoint);
|
||||
virtual ~EndpointAuthRPC() = default;
|
||||
|
@ -40,6 +41,7 @@ namespace llarp::rpc
|
|||
const std::string m_AuthURL;
|
||||
const std::string m_AuthMethod;
|
||||
const Whitelist_t m_AuthWhitelist;
|
||||
const std::unordered_set<std::string> m_AuthStaticTokens;
|
||||
LMQ_ptr m_LMQ;
|
||||
Endpoint_ptr m_Endpoint;
|
||||
std::optional<oxenmq::ConnectionID> m_Conn;
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
#include "auth.hpp"
|
||||
#include <unordered_map>
|
||||
|
||||
#include <llarp/router/abstractrouter.hpp>
|
||||
#include "protocol.hpp"
|
||||
#include <llarp/util/str.hpp>
|
||||
#include <llarp/util/fs.hpp>
|
||||
|
||||
namespace llarp::service
|
||||
{
|
||||
/// maybe get auth result from string
|
||||
|
@ -22,6 +27,7 @@ namespace llarp::service
|
|||
ParseAuthType(std::string data)
|
||||
{
|
||||
std::unordered_map<std::string, AuthType> values = {
|
||||
{"file", AuthType::eAuthTypeFile},
|
||||
{"lmq", AuthType::eAuthTypeLMQ},
|
||||
{"whitelist", AuthType::eAuthTypeWhitelist},
|
||||
{"none", AuthType::eAuthTypeNone}};
|
||||
|
@ -31,6 +37,25 @@ namespace llarp::service
|
|||
return itr->second;
|
||||
}
|
||||
|
||||
AuthFileType
|
||||
ParseAuthFileType(std::string data)
|
||||
{
|
||||
std::unordered_map<std::string, AuthFileType> 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);
|
||||
#ifndef HAVE_CRYPT
|
||||
if (itr->second == AuthFileType::eAuthFileHashes)
|
||||
throw std::invalid_argument("unsupported auth file type: " + data);
|
||||
#endif
|
||||
return itr->second;
|
||||
}
|
||||
|
||||
/// turn an auth result code into an int
|
||||
uint64_t
|
||||
AuthResultCodeAsInt(AuthResultCode code)
|
||||
|
@ -58,4 +83,106 @@ namespace llarp::service
|
|||
}
|
||||
}
|
||||
|
||||
class FileAuthPolicy : public IAuthPolicy, public std::enable_shared_from_this<FileAuthPolicy>
|
||||
{
|
||||
const std::set<fs::path> m_Files;
|
||||
const AuthFileType m_Type;
|
||||
AbstractRouter* const m_Router;
|
||||
mutable util::Mutex m_Access;
|
||||
std::unordered_set<ConvoTag> m_Pending;
|
||||
/// returns an auth result for a auth info challange, opens every file until it finds a token
|
||||
/// matching it
|
||||
/// this is expected to be done in the IO thread
|
||||
AuthResult
|
||||
CheckFiles(const AuthInfo& info) const
|
||||
{
|
||||
for (const auto& f : m_Files)
|
||||
{
|
||||
fs::ifstream i{f};
|
||||
std::string line{};
|
||||
while (std::getline(i, line))
|
||||
{
|
||||
// split off comments
|
||||
const auto parts = split_any(line, "#;", true);
|
||||
if (auto part = parts[0]; not parts.empty() and not parts[0].empty())
|
||||
{
|
||||
// split off whitespaces and check password
|
||||
if (CheckPasswd(std::string{TrimWhitespace(part)}, info.token))
|
||||
return AuthResult{AuthResultCode::eAuthAccepted, "accepted by whitelist"};
|
||||
}
|
||||
}
|
||||
}
|
||||
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<fs::path> files, AuthFileType filetype)
|
||||
: m_Files{std::move(files)}, m_Type{filetype}, m_Router{r}
|
||||
{}
|
||||
|
||||
void
|
||||
AuthenticateAsync(
|
||||
std::shared_ptr<ProtocolMessage> msg, std::function<void(AuthResult)> hook) override
|
||||
{
|
||||
auto reply = m_Router->loop()->make_caller(
|
||||
[tag = msg->tag, hook, self = shared_from_this()](AuthResult result) {
|
||||
{
|
||||
util::Lock _lock{self->m_Access};
|
||||
self->m_Pending.erase(tag);
|
||||
}
|
||||
hook(result);
|
||||
});
|
||||
{
|
||||
util::Lock _lock{m_Access};
|
||||
m_Pending.emplace(msg->tag);
|
||||
}
|
||||
if (msg->proto == ProtocolType::Auth)
|
||||
{
|
||||
m_Router->QueueDiskIO(
|
||||
[self = shared_from_this(),
|
||||
auth = AuthInfo{std::string{
|
||||
reinterpret_cast<const char*>(msg->payload.data()), msg->payload.size()}},
|
||||
reply]() {
|
||||
try
|
||||
{
|
||||
reply(self->CheckFiles(auth));
|
||||
}
|
||||
catch (std::exception& ex)
|
||||
{
|
||||
reply(AuthResult{AuthResultCode::eAuthFailed, ex.what()});
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
reply(AuthResult{AuthResultCode::eAuthRejected, "protocol error"});
|
||||
}
|
||||
bool
|
||||
AsyncAuthPending(ConvoTag tag) const override
|
||||
{
|
||||
util::Lock _lock{m_Access};
|
||||
return m_Pending.count(tag);
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<IAuthPolicy>
|
||||
MakeFileAuthPolicy(AbstractRouter* r, std::set<fs::path> files, AuthFileType filetype)
|
||||
{
|
||||
return std::make_shared<FileAuthPolicy>(r, std::move(files), filetype);
|
||||
}
|
||||
|
||||
} // namespace llarp::service
|
||||
|
|
|
@ -44,7 +44,7 @@ namespace llarp::service
|
|||
|
||||
struct IAuthPolicy
|
||||
{
|
||||
~IAuthPolicy() = default;
|
||||
virtual ~IAuthPolicy() = default;
|
||||
|
||||
/// asynchronously determine if we accept new convotag from remote service, call hook with
|
||||
/// result later
|
||||
|
@ -71,7 +71,16 @@ namespace llarp::service
|
|||
/// manual whitelist
|
||||
eAuthTypeWhitelist,
|
||||
/// LMQ server
|
||||
eAuthTypeLMQ
|
||||
eAuthTypeLMQ,
|
||||
/// static file
|
||||
eAuthTypeFile,
|
||||
};
|
||||
|
||||
/// how to interpret an file for auth
|
||||
enum class AuthFileType
|
||||
{
|
||||
eAuthFilePlain,
|
||||
eAuthFileHashes,
|
||||
};
|
||||
|
||||
/// get an auth type from a string
|
||||
|
@ -79,4 +88,13 @@ namespace llarp::service
|
|||
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<IAuthPolicy>
|
||||
MakeFileAuthPolicy(AbstractRouter*, std::set<fs::path> files, AuthFileType fileType);
|
||||
|
||||
} // namespace llarp::service
|
||||
|
|
|
@ -47,3 +47,48 @@ TEST_CASE("PQ crypto")
|
|||
REQUIRE(c->pqe_decrypt(block, otherShared, pq_keypair_to_secret(keys)));
|
||||
REQUIRE(otherShared == shared);
|
||||
}
|
||||
|
||||
#ifdef HAVE_CRYPT
|
||||
|
||||
TEST_CASE("passwd hash valid")
|
||||
{
|
||||
llarp::sodium::CryptoLibSodium crypto;
|
||||
|
||||
// poggers password hashes
|
||||
std::set<std::string> 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<std::string> 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"));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue