]> git.seodisparate.com - UDPConnection/commitdiff
Implement publickey whitelist (using libsodium)
authorStephen Seo <seo.disparate@gmail.com>
Thu, 2 Jan 2020 11:54:32 +0000 (20:54 +0900)
committerStephen Seo <seo.disparate@gmail.com>
Thu, 2 Jan 2020 11:54:32 +0000 (20:54 +0900)
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.

src/UDPC_Defines.hpp
src/UDPConnection.cpp
src/UDPConnection.h
src/test/UDPC_NetworkTest.c

index 392bb4416b639fea3769146dbca15f3258956054..aa4135687ce39a06a1a1f97882fed441f2a00707 100644 (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];
index 0f87d00345ec7583a4169f365be58cead042e117..bf861a56014e6fdcadf2c03bfa513ee3d8b3276d 100644 (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) {
index fdda87af64143a6856e9f540f0daadc5aa95b0dd..290365dde80cf810d7f06b91708040239d62bd85 100644 (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);
 
index a982482d8efecb15124d96324285607d2e10340c..0a64070c6b2929334a9728f308e33b27b26fef12 100644 (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;
-                }
+            FILE *seckey_f = fopen(seckey_file, "rb");
+            if(!seckey_f) {
+                printf("ERROR: Failed to open seckey_file \"%s\"\n", seckey_file);
+                return 1;
+            }
+            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;
             }
-        } else if(seckey_file) {
-            printf("ERROR: Invalid state (seckey_file defined but not pubkey_file)\n");
+            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);