Implement publickey whitelist (using libsodium)

Renamed "mutex" to "conMapMutex" since it is mainly used to lock access
to the connection map.

Removed UDPC_client_initiate_connection_pk() as publickey whitelisting
replaces its functionality.
This commit is contained in:
Stephen Seo 2020-01-02 20:54:32 +09:00
parent 26e8b95d94
commit 42fde9a2d0
4 changed files with 171 additions and 201 deletions

View file

@ -82,6 +82,16 @@ struct IPV6_Hasher {
std::size_t operator()(const UDPC_IPV6_ADDR_TYPE& addr) const;
};
struct PKContainer {
PKContainer();
PKContainer(unsigned char *pk);
unsigned char pk[crypto_sign_PUBLICKEYBYTES];
std::size_t operator()(const PKContainer& container) const;
bool operator==(const PKContainer& other) const;
};
struct ConnectionData {
ConnectionData(bool isUsingLibsodium);
ConnectionData(
@ -112,7 +122,6 @@ struct ConnectionData {
* 4 - is id set
* 5 - error initializing keys for public key encryption
* 6 - using libsodium for header verification
* 7 - peer_pk pre-set
*/
std::bitset<8> flags;
uint32_t id;
@ -225,6 +234,7 @@ public:
// id to ipv6 address and port (as UDPC_ConnectionId)
std::unordered_map<uint32_t, UDPC_ConnectionId> idMap;
std::unordered_set<UDPC_ConnectionId, ConnectionIdHasher> deletionMap;
std::unordered_set<PKContainer, PKContainer> peerPKWhitelist;
TSLQueue<UDPC_PacketInfo> receivedPkts;
TSLQueue<UDPC_PacketInfo> cSendPkts;
// handled internally
@ -236,7 +246,8 @@ public:
std::thread thread;
std::atomic_bool threadRunning;
std::mutex mutex;
std::mutex conMapMutex;
std::mutex peerPKWhitelistMutex;
std::chrono::milliseconds threadedSleepTime;
unsigned char sk[crypto_sign_SECRETKEYBYTES];

View file

@ -65,6 +65,24 @@ std::size_t UDPC::IPV6_Hasher::operator()(const UDPC_IPV6_ADDR_TYPE& addr) const
return std::hash<std::string>()(std::string((const char*)UDPC_IPV6_ADDR_SUB(addr), 16));
}
UDPC::PKContainer::PKContainer() {
std::memset(pk, 0, crypto_sign_PUBLICKEYBYTES);
}
UDPC::PKContainer::PKContainer(unsigned char *pk) {
std::memcpy(this->pk, pk, crypto_sign_PUBLICKEYBYTES);
}
std::size_t UDPC::PKContainer::operator()(const PKContainer& container) const {
return std::hash<std::string>()(std::string(
(const char*)container.pk,
crypto_sign_PUBLICKEYBYTES));
}
bool UDPC::PKContainer::operator==(const PKContainer& other) const {
return std::memcmp(pk, other.pk, crypto_sign_PUBLICKEYBYTES) == 0;
}
bool operator ==(const UDPC_ConnectionId& a, const UDPC_ConnectionId& b) {
return a.addr == b.addr && a.scope_id == b.scope_id && a.port == b.port;
}
@ -208,7 +226,7 @@ atostrBufIndex(0),
receivedPkts(),
cSendPkts(),
rng_engine(),
mutex()
conMapMutex()
{
for(unsigned int i = 0; i < UDPC_ATOSTR_SIZE; ++i) {
atostrBuf[i] = 0;
@ -317,103 +335,6 @@ void UDPC::Context::update_impl() {
std::memcpy(newCon.verifyMessage.get(), &timeInt, 8);
}
if(conMap.find(optE->conId) == conMap.end()) {
conMap.insert(std::make_pair(
optE->conId,
std::move(newCon)));
auto addrConIter = addrConMap.find(optE->conId.addr);
if(addrConIter == addrConMap.end()) {
auto insertResult = addrConMap.insert(std::make_pair(
optE->conId.addr,
std::unordered_set<UDPC_ConnectionId, UDPC::ConnectionIdHasher>{}));
assert(insertResult.second &&
"new connection insert into addrConMap must not fail");
addrConIter = insertResult.first;
}
addrConIter->second.insert(optE->conId);
UDPC_CHECK_LOG(this,
UDPC_LoggingType::UDPC_INFO,
"Client initiating connection to ",
UDPC_atostr((UDPC_HContext)this, optE->conId.addr),
" port ",
optE->conId.port,
" ...");
} else {
UDPC_CHECK_LOG(this,
UDPC_LoggingType::UDPC_WARNING,
"Client initiate connection, already connected to peer ",
UDPC_atostr((UDPC_HContext)this, optE->conId.addr),
" port ",
optE->conId.port);
}
}
break;
case UDPC_ET_REQUEST_CONNECT_PK:
{
assert(flags.test(2) &&
"libsodium should be explictly enabled");
unsigned char *sk = nullptr;
unsigned char *pk = nullptr;
if(keysSet.load()) {
sk = this->sk;
pk = this->pk;
}
UDPC::ConnectionData newCon(
false,
this,
optE->conId.addr,
optE->conId.scope_id,
optE->conId.port,
#ifdef UDPC_LIBSODIUM_ENABLED
true,
sk, pk);
#else
false,
sk, pk);
assert(!"compiled without libsodium support");
delete[] optE->v.pk;
break;
#endif
if(newCon.flags.test(5)) {
delete[] optE->v.pk;
UDPC_CHECK_LOG(this,
UDPC_LoggingType::UDPC_ERROR,
"Failed to init ConnectionData instance (libsodium "
"init fail) while client establishing connection with ",
UDPC_atostr((UDPC_HContext)this, optE->conId.addr),
" port ",
optE->conId.port);
continue;
}
newCon.sent = std::chrono::steady_clock::now() - UDPC::INIT_PKT_INTERVAL_DT;
if(flags.test(2) && newCon.flags.test(6)) {
// set up verification string to send to server
std::time_t time = std::time(nullptr);
if(time <= 0) {
UDPC_CHECK_LOG(this, UDPC_LoggingType::UDPC_ERROR,
"Failed to get current epoch time");
continue;
}
uint64_t timeInt = time;
#ifndef NDEBUG
UDPC_CHECK_LOG(this, UDPC_LoggingType::UDPC_DEBUG,
"Client set up verification epoch time \"",
timeInt, "\"");
#endif
UDPC::be64((char*)&timeInt);
newCon.verifyMessage = std::unique_ptr<char[]>(new char[8]);
std::memcpy(newCon.verifyMessage.get(), &timeInt, 8);
// set peer public key
std::memcpy(
newCon.peer_pk,
optE->v.pk,
crypto_sign_PUBLICKEYBYTES);
newCon.flags.set(7);
}
delete[] optE->v.pk;
if(conMap.find(optE->conId) == conMap.end()) {
conMap.insert(std::make_pair(
optE->conId,
@ -1303,6 +1224,15 @@ void UDPC::Context::update_impl() {
newConnection.peer_pk,
recvBuf + UDPC_MIN_HEADER_SIZE + 4,
crypto_sign_PUBLICKEYBYTES);
{
std::lock_guard<std::mutex> lock(peerPKWhitelistMutex);
if(!peerPKWhitelist.empty() && peerPKWhitelist.find(UDPC::PKContainer(newConnection.peer_pk)) == peerPKWhitelist.end()) {
UDPC_CHECK_LOG(this, UDPC_LoggingType::UDPC_WARNING,
"peer_pk is not in whitelist, not establishing "
"connection with client");
return;
}
}
newConnection.verifyMessage = std::unique_ptr<char[]>(new char[crypto_sign_BYTES]);
std::time_t currentTime = std::time(nullptr);
uint64_t receivedTime;
@ -1403,19 +1333,17 @@ void UDPC::Context::update_impl() {
if(pktType == 2 && flags.test(2) && iter->second.flags.test(6)) {
#ifdef UDPC_LIBSODIUM_ENABLED
if(iter->second.flags.test(7)) {
if(std::memcmp(iter->second.peer_pk,
recvBuf + UDPC_MIN_HEADER_SIZE + 4,
crypto_sign_PUBLICKEYBYTES) != 0) {
std::memcpy(iter->second.peer_pk,
recvBuf + UDPC_MIN_HEADER_SIZE + 4,
crypto_sign_PUBLICKEYBYTES);
{
std::lock_guard<std::mutex> lock(peerPKWhitelistMutex);
if(!peerPKWhitelist.empty() && peerPKWhitelist.find(UDPC::PKContainer(iter->second.peer_pk)) == peerPKWhitelist.end()) {
UDPC_CHECK_LOG(this, UDPC_LoggingType::UDPC_WARNING,
"peer_pk did not match pre-set peer_pk, not "
"establishing connection");
"peer_pk is not in whitelist, not establishing "
"connection with server");
return;
}
} else {
std::memcpy(iter->second.peer_pk,
recvBuf + UDPC_MIN_HEADER_SIZE + 4,
crypto_sign_PUBLICKEYBYTES);
}
if(crypto_sign_verify_detached(
(unsigned char*)(recvBuf + UDPC_MIN_HEADER_SIZE + 4 + crypto_sign_PUBLICKEYBYTES),
@ -1849,7 +1777,7 @@ void UDPC::threadedUpdate(Context *ctx) {
while(ctx->threadRunning.load()) {
now = std::chrono::steady_clock::now();
{
std::lock_guard<std::mutex> lock(ctx->mutex);
std::lock_guard<std::mutex> lock(ctx->conMapMutex);
ctx->update_impl();
}
nextNow = std::chrono::steady_clock::now();
@ -2122,12 +2050,6 @@ void UDPC_destroy(UDPC_HContext ctx) {
#if UDPC_PLATFORM == UDPC_PLATFORM_WINDOWS
WSACleanup();
#endif
while(!UDPC_ctx->internalEvents.empty()) {
auto optE = UDPC_ctx->internalEvents.top_and_pop();
if(optE && optE->type == UDPC_ET_REQUEST_CONNECT_PK) {
delete[] optE->v.pk;
}
}
UDPC_ctx->_contextIdentifier = 0;
delete UDPC_ctx;
}
@ -2140,7 +2062,7 @@ void UDPC_update(UDPC_HContext ctx) {
return;
}
std::lock_guard<std::mutex> lock(c->mutex);
std::lock_guard<std::mutex> lock(c->conMapMutex);
c->update_impl();
}
@ -2163,34 +2085,6 @@ void UDPC_client_initiate_connection(
c->internalEvents.push(UDPC_Event{UDPC_ET_REQUEST_CONNECT, connectionId, enableLibSodium});
}
void UDPC_client_initiate_connection_pk(
UDPC_HContext ctx,
UDPC_ConnectionId connectionId,
unsigned char *serverPK) {
UDPC::Context *c = UDPC::verifyContext(ctx);
if(!c || !c->flags.test(1)) {
return;
}
#ifndef UDPC_LIBSODIUM_ENABLED
UDPC_CHECK_LOG(c, UDPC_LoggingType::UDPC_ERROR,
"Cannot initiate connection with public key, UDPC was compiled "
"without libsodium");
return;
#else
else if(!c->flags.test(2)) {
UDPC_CHECK_LOG(c, UDPC_LoggingType::UDPC_ERROR,
"Cannot initiate connection with public key, libsodium is not "
"enabled");
return;
}
#endif
UDPC_Event event{UDPC_ET_REQUEST_CONNECT_PK, connectionId, 0};
event.v.pk = new unsigned char[crypto_sign_PUBLICKEYBYTES];
std::memcpy(event.v.pk, serverPK, crypto_sign_PUBLICKEYBYTES);
c->internalEvents.push(event);
}
void UDPC_queue_send(UDPC_HContext ctx, UDPC_ConnectionId destinationId,
int isChecked, void *data, uint32_t size) {
if(size == 0 || !data) {
@ -2229,7 +2123,7 @@ unsigned long UDPC_get_queued_size(UDPC_HContext ctx, UDPC_ConnectionId id, int
return 0;
}
std::lock_guard<std::mutex> lock(c->mutex);
std::lock_guard<std::mutex> lock(c->conMapMutex);
auto iter = c->conMap.find(id);
if(iter != c->conMap.end()) {
if(exists) {
@ -2272,7 +2166,7 @@ int UDPC_has_connection(UDPC_HContext ctx, UDPC_ConnectionId connectionId) {
return 0;
}
std::lock_guard<std::mutex> lock(c->mutex);
std::lock_guard<std::mutex> lock(c->conMapMutex);
return c->conMap.find(connectionId) == c->conMap.end() ? 0 : 1;
}
@ -2283,7 +2177,7 @@ UDPC_ConnectionId* UDPC_get_list_connected(UDPC_HContext ctx, unsigned int *size
return nullptr;
}
std::lock_guard<std::mutex> lock(c->mutex);
std::lock_guard<std::mutex> lock(c->conMapMutex);
if(c->conMap.empty()) {
if(size) {
@ -2396,7 +2290,7 @@ int UDPC_set_libsodium_keys(UDPC_HContext ctx, unsigned char *sk, unsigned char
return 0;
}
std::lock_guard<std::mutex> lock(c->mutex);
std::lock_guard<std::mutex> lock(c->conMapMutex);
std::memcpy(c->sk, sk, crypto_sign_SECRETKEYBYTES);
std::memcpy(c->pk, pk, crypto_sign_PUBLICKEYBYTES);
c->keysSet.store(true);
@ -2417,13 +2311,64 @@ int UDPC_unset_libsodium_keys(UDPC_HContext ctx) {
return 0;
}
std::lock_guard<std::mutex> lock(c->mutex);
std::lock_guard<std::mutex> lock(c->conMapMutex);
c->keysSet.store(false);
std::memset(c->pk, 0, crypto_sign_PUBLICKEYBYTES);
std::memset(c->sk, 0, crypto_sign_SECRETKEYBYTES);
return 1;
}
int UDPC_add_whitelist_pk(UDPC_HContext ctx, unsigned char *pk) {
UDPC::Context *c = UDPC::verifyContext(ctx);
if(!c || !c->flags.test(2)) {
return 0;
}
std::lock_guard<std::mutex> lock(c->peerPKWhitelistMutex);
auto result = c->peerPKWhitelist.insert(UDPC::PKContainer(pk));
if(result.second) {
return c->peerPKWhitelist.size();
}
return 0;
}
int UDPC_has_whitelist_pk(UDPC_HContext ctx, unsigned char *pk) {
UDPC::Context *c = UDPC::verifyContext(ctx);
if(!c || !c->flags.test(2)) {
return 0;
}
std::lock_guard<std::mutex> lock(c->peerPKWhitelistMutex);
if(c->peerPKWhitelist.find(UDPC::PKContainer(pk)) != c->peerPKWhitelist.end()) {
return 1;
}
return 0;
}
int UDPC_remove_whitelist_pk(UDPC_HContext ctx, unsigned char *pk) {
UDPC::Context *c = UDPC::verifyContext(ctx);
if(!c || !c->flags.test(2)) {
return 0;
}
std::lock_guard<std::mutex> lock(c->peerPKWhitelistMutex);
if(c->peerPKWhitelist.erase(UDPC::PKContainer(pk)) != 0) {
return 1;
}
return 0;
}
int UDPC_clear_whitelist(UDPC_HContext ctx) {
UDPC::Context *c = UDPC::verifyContext(ctx);
if(!c || !c->flags.test(2)) {
return 0;
}
std::lock_guard<std::mutex> lock(c->peerPKWhitelistMutex);
c->peerPKWhitelist.clear();
return 1;
}
int UDPC_get_auth_policy(UDPC_HContext ctx) {
UDPC::Context *c = UDPC::verifyContext(ctx);
if(!c) {

View file

@ -196,8 +196,7 @@ typedef enum {
UDPC_ET_CONNECTED,
UDPC_ET_DISCONNECTED,
UDPC_ET_GOOD_MODE,
UDPC_ET_BAD_MODE,
UDPC_ET_REQUEST_CONNECT_PK
UDPC_ET_BAD_MODE
} UDPC_EventType;
/*!
@ -216,7 +215,6 @@ typedef struct {
union Value {
int dropAllWithAddr;
int enableLibSodium;
unsigned char *pk;
} v;
} UDPC_Event;
@ -403,25 +401,6 @@ void UDPC_client_initiate_connection(
UDPC_ConnectionId connectionId,
int enableLibSodium);
/*!
* \brief Initiate a connection to a server peer with an expected public key
*
* Note that this function does nothing on a server context.
*
* \param ctx The context to initiate a connection from
* \param connectionId The server peer to initiate a connection to
* \param serverPK A pointer to the public key that the server is expected to
* use (if the server does not use this public key, then the connection will
* fail; it must point to a buffer of size \p crypto_sign_PUBLICKEYBYTES)
*
* This function assumes that support for libsodium was enabled when UDPC was
* compiled. If it has not, then this function will fail.
*/
void UDPC_client_initiate_connection_pk(
UDPC_HContext ctx,
UDPC_ConnectionId connectionId,
unsigned char *serverPK);
/*!
* \brief Queues a packet to be sent to the specified peer
*
@ -510,6 +489,11 @@ int UDPC_set_libsodium_key_easy(UDPC_HContext ctx, unsigned char *sk);
int UDPC_unset_libsodium_keys(UDPC_HContext ctx);
int UDPC_add_whitelist_pk(UDPC_HContext ctx, unsigned char *pk);
int UDPC_has_whitelist_pk(UDPC_HContext ctx, unsigned char *pk);
int UDPC_remove_whitelist_pk(UDPC_HContext ctx, unsigned char *pk);
int UDPC_clear_whitelist(UDPC_HContext ctx);
int UDPC_get_auth_policy(UDPC_HContext ctx);
int UDPC_set_auth_policy(UDPC_HContext ctx, int value);

View file

@ -12,6 +12,7 @@
#define QUEUED_MAX_SIZE 32
#define SEND_IDS_SIZE 64
#define WHITELIST_FILES_SIZE 64
void usage() {
puts("[-c | -s] - client or server (default server)");
@ -24,7 +25,7 @@ void usage() {
puts("-l (silent|error|warning|info|verbose|debug) - log level, default debug");
puts("-e - enable receiving events");
puts("-ls - enable libsodium");
puts("-ck <pubkey_file> - connect to server expecting this public key");
puts("-ck <pubkey_file> - add pubkey to whitelist");
puts("-sk <pubkey> <seckey> - start with pub/sec key pair");
puts("-p <\"fallback\" or \"strict\"> - set auth policy");
}
@ -57,6 +58,9 @@ int main(int argc, char **argv) {
const char *seckey_file = NULL;
unsigned char pubkey[crypto_sign_PUBLICKEYBYTES];
unsigned char seckey[crypto_sign_SECRETKEYBYTES];
const char *whitelist_pk_files[WHITELIST_FILES_SIZE];
unsigned int whitelist_pk_files_index = 0;
unsigned char whitelist_pks[WHITELIST_FILES_SIZE][crypto_sign_PUBLICKEYBYTES];
int authPolicy = UDPC_AUTH_POLICY_FALLBACK;
while(argc > 0) {
@ -111,7 +115,11 @@ int main(int argc, char **argv) {
puts("Enabled libsodium");
} else if(strcmp(argv[0], "-ck") == 0 && argc > 1) {
--argc; ++argv;
pubkey_file = argv[0];
if(whitelist_pk_files_index >= WHITELIST_FILES_SIZE) {
puts("ERROR: limit reached for whitelisted pks");
return 1;
}
whitelist_pk_files[whitelist_pk_files_index++] = argv[0];
} else if(strcmp(argv[0], "-sk") == 0 && argc > 2) {
--argc; ++argv;
pubkey_file = argv[0];
@ -141,38 +149,53 @@ int main(int argc, char **argv) {
if(isLibSodiumEnabled == 0) {
puts("Disabled libsodium");
} else {
if(pubkey_file) {
FILE *pubkey_f = fopen(pubkey_file, "r");
if(pubkey_file && seckey_file) {
FILE *pubkey_f = fopen(pubkey_file, "rb");
if(!pubkey_f) {
printf("ERROR: Failed to open pubkey_file \"%s\"\n", pubkey_file);
return 1;
}
size_t count = fread(pubkey, 1, crypto_sign_PUBLICKEYBYTES, pubkey_f);
if(count != crypto_sign_PUBLICKEYBYTES) {
size_t count = fread(pubkey, crypto_sign_PUBLICKEYBYTES, 1, pubkey_f);
if(count != 1) {
fclose(pubkey_f);
printf("ERROR: Failed to read pubkey_file \"%s\"\n", pubkey_file);
return 1;
}
fclose(pubkey_f);
if(seckey_file) {
FILE *seckey_f = fopen(seckey_file, "r");
if(!seckey_f) {
printf("ERROR: Failed to open seckey_file \"%s\"\n", seckey_file);
return 1;
}
count = fread(seckey, 1, crypto_sign_SECRETKEYBYTES, seckey_f);
if(count != crypto_sign_SECRETKEYBYTES) {
fclose(seckey_f);
printf("ERROR: Failed to read seckey_file \"%s\"\n", seckey_file);
return 1;
}
fclose(seckey_f);
FILE *seckey_f = fopen(seckey_file, "rb");
if(!seckey_f) {
printf("ERROR: Failed to open seckey_file \"%s\"\n", seckey_file);
return 1;
}
} else if(seckey_file) {
printf("ERROR: Invalid state (seckey_file defined but not pubkey_file)\n");
count = fread(seckey, crypto_sign_SECRETKEYBYTES, 1, seckey_f);
if(count != 1) {
fclose(seckey_f);
printf("ERROR: Failed to read seckey_file \"%s\"\n", seckey_file);
return 1;
}
fclose(seckey_f);
} else if(pubkey_file || seckey_file) {
printf("ERROR: Invalid state (pubkey_file and seckey_file not "
"defined)\n");
return 1;
}
for(unsigned int i = 0; i < whitelist_pk_files_index; ++i) {
FILE *pkf = fopen(whitelist_pk_files[i], "rb");
if(!pkf) {
printf("ERROR: Failed to open whitelist pubkey file \"%s\"\n",
whitelist_pk_files[i]);
return 1;
}
size_t count = fread(whitelist_pks[i], crypto_sign_PUBLICKEYBYTES, 1, pkf);
if(count != 1) {
fclose(pkf);
printf("ERROR: Failed to read whitelist pubkey file \"%s\"\n",
whitelist_pk_files[i]);
return 1;
}
fclose(pkf);
}
}
if(!listenAddr) {
@ -207,9 +230,9 @@ int main(int argc, char **argv) {
UDPC_set_logging_type(context, logLevel);
UDPC_set_receiving_events(context, isReceivingEvents);
if(!isClient && pubkey_file && seckey_file) {
if(pubkey_file && seckey_file) {
UDPC_set_libsodium_keys(context, seckey, pubkey);
puts("Set pubkey/seckey for server");
puts("Set pubkey/seckey");
}
UDPC_set_auth_policy(context, authPolicy);
@ -220,6 +243,16 @@ int main(int argc, char **argv) {
puts("Auth policy set to \"strict\"");
}
if(isLibSodiumEnabled && whitelist_pk_files_index > 0) {
puts("Enabling pubkey whitelist...");
for(unsigned int i = 0; i < whitelist_pk_files_index; ++i) {
if(UDPC_add_whitelist_pk(context, whitelist_pks[i]) != i + 1) {
puts("Failed to add pubkey to whitelist");
return 1;
}
}
}
UDPC_enable_threaded_update(context);
unsigned int tick = 0;
@ -235,11 +268,8 @@ int main(int argc, char **argv) {
while(1) {
sleep_seconds(1);
if(isClient && UDPC_has_connection(context, connectionId) == 0) {
if(isLibSodiumEnabled && pubkey_file) {
UDPC_client_initiate_connection_pk(context, connectionId, pubkey);
} else {
UDPC_client_initiate_connection(context, connectionId, isLibSodiumEnabled);
}
UDPC_client_initiate_connection(context, connectionId, isLibSodiumEnabled);
}
if(!noPayload) {
list = UDPC_get_list_connected(context, &temp);