]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Fall back to libcrypto for authenticated encryption
authorRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 18 Dec 2023 15:12:46 +0000 (16:12 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 18 Dec 2023 15:26:03 +0000 (16:26 +0100)
We used to fall back to plain-text for console communications when
libsodium was not available, which was not great. Now that we are
also using the authenticated encryption module to secure our QUIC
tokens, let's fall back to OpenSSL's Chacha20 Poly 1305
implementation instead.
Note that, unfortunately, both implementations are not compatible
so the console communication format will be different depending on
whether libsodium is available. I believe this is still better than
plain-text :)

14 files changed:
.github/actions/spell-check/allow.txt
.not-formatted
pdns/dnsdist-console.cc
pdns/dnsdist-lua.cc
pdns/dnsdist.cc
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/dnsdist-crypto.cc [moved from pdns/sodcrypto.cc with 56% similarity]
pdns/dnsdistdist/dnsdist-crypto.hh [moved from pdns/sodcrypto.hh with 68% similarity]
pdns/dnsdistdist/docs/guides/console.rst
pdns/dnsdistdist/docs/manpages/dnsdist.1.rst
pdns/dnsdistdist/doh3.cc
pdns/dnsdistdist/doq-common.cc
pdns/dnsdistdist/doq-common.hh
pdns/dnsdistdist/doq.cc

index 6575c877b885372f8ac51abb559ef64aa2b9da37..71d60e7dd78f60219c543e366ab38a0d239e6baa 100644 (file)
@@ -3335,7 +3335,6 @@ sockname
 sockowner
 socktype
 sodbc
-sodcrypto
 sodiumsigners
 sokolov
 somedata
index 0bd94aae3a20352d6e24f7e724e52e9c6c4b4000..af886b0187dd69c835eae6f6c8640d7de8fdb78b 100644 (file)
 ./pdns/sillyrecords.cc
 ./pdns/snmp-agent.cc
 ./pdns/snmp-agent.hh
-./pdns/sodcrypto.cc
-./pdns/sodcrypto.hh
 ./pdns/sortlist.cc
 ./pdns/sortlist.hh
 ./pdns/speedtest.cc
index 6461a9298639401bfd748dfab5152857f726437d..b814a3579b4b2dfd34771180c539633716db87ab 100644 (file)
@@ -43,7 +43,7 @@
 #include "dolog.hh"
 #include "dnsdist.hh"
 #include "dnsdist-console.hh"
-#include "sodcrypto.hh"
+#include "dnsdist-crypto.hh"
 #include "threadname.hh"
 
 GlobalStateHolder<NetmaskGroup> g_consoleACL;
@@ -171,9 +171,9 @@ static bool putMsgLen32(int fd, uint32_t len)
   }
 }
 
-static ConsoleCommandResult sendMessageToServer(int fd, const std::string& line, SodiumNonce& readingNonce, SodiumNonce& writingNonce, const bool outputEmptyLine)
+static ConsoleCommandResult sendMessageToServer(int fd, const std::string& line, dnsdist::crypto::authenticated::Nonce& readingNonce, dnsdist::crypto::authenticated::Nonce& writingNonce, const bool outputEmptyLine)
 {
-  string msg = sodEncryptSym(line, g_consoleKey, writingNonce);
+  string msg = dnsdist::crypto::authenticated::encryptSym(line, g_consoleKey, writingNonce);
   const auto msgLen = msg.length();
   if (msgLen > std::numeric_limits<uint32_t>::max()) {
     cerr << "Encrypted message is too long to be sent to the server, "<< std::to_string(msgLen) << " > " << std::numeric_limits<uint32_t>::max() << endl;
@@ -208,7 +208,7 @@ static ConsoleCommandResult sendMessageToServer(int fd, const std::string& line,
   msg.clear();
   msg.resize(len);
   readn2(fd, msg.data(), len);
-  msg = sodDecryptSym(msg, g_consoleKey, readingNonce);
+  msg = dnsdist::crypto::authenticated::decryptSym(msg, g_consoleKey, readingNonce);
   cout << msg;
   cout.flush();
 
@@ -217,7 +217,7 @@ static ConsoleCommandResult sendMessageToServer(int fd, const std::string& line,
 
 void doClient(ComboAddress server, const std::string& command)
 {
-  if (!sodIsValidKey(g_consoleKey)) {
+  if (!dnsdist::crypto::authenticated::isValidKey(g_consoleKey)) {
     cerr << "The currently configured console key is not valid, please configure a valid key using the setKey() directive" << endl;
     return;
   }
@@ -233,7 +233,7 @@ void doClient(ComboAddress server, const std::string& command)
   }
   SConnect(fd.getHandle(), server);
   setTCPNoDelay(fd.getHandle());
-  SodiumNonce theirs, ours, readingNonce, writingNonce;
+  dnsdist::crypto::authenticated::Nonce theirs, ours, readingNonce, writingNonce;
   ours.init();
 
   writen2(fd.getHandle(), ours.value.data(), ours.value.size());
@@ -875,7 +875,7 @@ static void controlClientThread(ConsoleConnection&& conn)
 
     setTCPNoDelay(conn.getFD());
 
-    SodiumNonce theirs, ours, readingNonce, writingNonce;
+    dnsdist::crypto::authenticated::Nonce theirs, ours, readingNonce, writingNonce;
     ours.init();
     readn2(conn.getFD(), theirs.value.data(), theirs.value.size());
     writen2(conn.getFD(), ours.value.data(), ours.value.size());
@@ -899,7 +899,7 @@ static void controlClientThread(ConsoleConnection&& conn)
       line.resize(len);
       readn2(conn.getFD(), line.data(), len);
 
-      line = sodDecryptSym(line, g_consoleKey, readingNonce);
+      line = dnsdist::crypto::authenticated::decryptSym(line, g_consoleKey, readingNonce);
 
       string response;
       try {
@@ -990,7 +990,7 @@ static void controlClientThread(ConsoleConnection&& conn)
       catch (const LuaContext::SyntaxErrorException& e) {
         response = "Error: " + string(e.what()) + ": ";
       }
-      response = sodEncryptSym(response, g_consoleKey, writingNonce);
+      response = dnsdist::crypto::authenticated::encryptSym(response, g_consoleKey, writingNonce);
       putMsgLen32(conn.getFD(), response.length());
       writen2(conn.getFD(), response.c_str(), response.length());
     }
@@ -1017,7 +1017,7 @@ void controlThread(int fd, ComboAddress local)
     while ((sock = SAccept(acceptFD.getHandle(), client)) >= 0) {
 
       FDWrapper socket(sock);
-      if (!sodIsValidKey(g_consoleKey)) {
+      if (!dnsdist::crypto::authenticated::isValidKey(g_consoleKey)) {
         vinfolog("Control connection from %s dropped because we don't have a valid key configured, please configure one using setKey()", client.toStringWithPort());
         continue;
       }
index 9f3dc08bdd65d486971d0e79bc2f5fd22175edfc..aabff4045428eb3a095490a63247587d51e6ac50 100644 (file)
@@ -39,6 +39,7 @@
 #include "dnsdist-carbon.hh"
 #include "dnsdist-concurrent-connections.hh"
 #include "dnsdist-console.hh"
+#include "dnsdist-crypto.hh"
 #include "dnsdist-dynblocks.hh"
 #include "dnsdist-discovery.hh"
 #include "dnsdist-ecs.hh"
@@ -61,7 +62,6 @@
 #include "doh.hh"
 #include "doq-common.hh"
 #include "dolog.hh"
-#include "sodcrypto.hh"
 #include "threadname.hh"
 
 #ifdef HAVE_LIBSSL
@@ -1106,7 +1106,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     }
 
     g_consoleEnabled = true;
-#ifdef HAVE_LIBSODIUM
+#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO)
     if (g_configurationDone && g_consoleKey.empty()) {
       warnlog("Warning, the console has been enabled via 'controlSocket()' but no key has been set with 'setKey()' so all connections will fail until a key has been set");
     }
@@ -1136,8 +1136,8 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
 
   luaCtx.writeFunction("addConsoleACL", [](const std::string& netmask) {
     setLuaSideEffect();
-#ifndef HAVE_LIBSODIUM
-    warnlog("Allowing remote access to the console while libsodium support has not been enabled is not secure, and will result in cleartext communications");
+#if !defined(HAVE_LIBSODIUM) && !defined(HAVE_LIBCRYPTO)
+    warnlog("Allowing remote access to the console while neither libsodium not libcrypto support has been enabled is not secure, and will result in cleartext communications");
 #endif
 
     g_consoleACL.modify([netmask](NetmaskGroup& nmg) { nmg.addMask(netmask); });
@@ -1146,8 +1146,8 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
   luaCtx.writeFunction("setConsoleACL", [](LuaTypeOrArrayOf<std::string> inp) {
     setLuaSideEffect();
 
-#ifndef HAVE_LIBSODIUM
-    warnlog("Allowing remote access to the console while libsodium support has not been enabled is not secure, and will result in cleartext communications");
+#if !defined(HAVE_LIBSODIUM) && !defined(HAVE_LIBCRYPTO)
+    warnlog("Allowing remote access to the console while neither libsodium nor libcrypto support has not been enabled is not secure, and will result in cleartext communications");
 #endif
 
     NetmaskGroup nmg;
@@ -1164,8 +1164,8 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
   luaCtx.writeFunction("showConsoleACL", []() {
     setLuaNoSideEffect();
 
-#ifndef HAVE_LIBSODIUM
-    warnlog("Allowing remote access to the console while libsodium support has not been enabled is not secure, and will result in cleartext communications");
+#if !defined(HAVE_LIBSODIUM) && !defined(HAVE_LIBCRYPTO)
+    warnlog("Allowing remote access to the console while neither libsodium nor libcrypto support has not been enabled is not secure, and will result in cleartext communications");
 #endif
 
     auto aclEntries = g_consoleACL.getLocal()->toStringVector();
@@ -1215,15 +1215,15 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
 
   luaCtx.writeFunction("makeKey", []() {
     setLuaNoSideEffect();
-    g_outputBuffer = "setKey(" + newKey() + ")\n";
+    g_outputBuffer = "setKey(" + dnsdist::crypto::authenticated::newKey() + ")\n";
   });
 
   luaCtx.writeFunction("setKey", [](const std::string& key) {
     if (!g_configurationDone && !g_consoleKey.empty()) { // this makes sure the commandline -k key prevails over dnsdist.conf
       return; // but later setKeys() trump the -k value again
     }
-#ifndef HAVE_LIBSODIUM
-    warnlog("Calling setKey() while libsodium support has not been enabled is not secure, and will result in cleartext communications");
+#if !defined(HAVE_LIBSODIUM) && !defined(HAVE_LIBCRYPTO)
+    warnlog("Calling setKey() while neither libsodium nor libcrypto support has been enabled is not secure, and will result in cleartext communications");
 #endif
 
     setLuaSideEffect();
@@ -1242,7 +1242,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
 
   luaCtx.writeFunction("testCrypto", [](boost::optional<string> optTestMsg) {
     setLuaNoSideEffect();
-#ifdef HAVE_LIBSODIUM
+#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO)
     try {
       string testmsg;
 
@@ -1253,17 +1253,17 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
         testmsg = "testStringForCryptoTests";
       }
 
-      SodiumNonce sn, sn2;
+      dnsdist::crypto::authenticated::Nonce sn, sn2;
       sn.init();
       sn2 = sn;
-      string encrypted = sodEncryptSym(testmsg, g_consoleKey, sn);
-      string decrypted = sodDecryptSym(encrypted, g_consoleKey, sn2);
+      string encrypted = dnsdist::crypto::authenticated::encryptSym(testmsg, g_consoleKey, sn);
+      string decrypted = dnsdist::crypto::authenticated::decryptSym(encrypted, g_consoleKey, sn2);
 
       sn.increment();
       sn2.increment();
 
-      encrypted = sodEncryptSym(testmsg, g_consoleKey, sn);
-      decrypted = sodDecryptSym(encrypted, g_consoleKey, sn2);
+      encrypted = dnsdist::crypto::authenticated::encryptSym(testmsg, g_consoleKey, sn);
+      decrypted = dnsdist::crypto::authenticated::decryptSym(encrypted, g_consoleKey, sn2);
 
       if (testmsg == decrypted)
         g_outputBuffer = "Everything is ok!\n";
index 3df62e18e2d6e784fd55f9d3a914fa44d48cde63..74ee1a199a67c43a4ccee1108f3abcbb8896fe12 100644 (file)
@@ -52,6 +52,7 @@
 #include "dnsdist-cache.hh"
 #include "dnsdist-carbon.hh"
 #include "dnsdist-console.hh"
+#include "dnsdist-crypto.hh"
 #include "dnsdist-discovery.hh"
 #include "dnsdist-dnsparser.hh"
 #include "dnsdist-dynblocks.hh"
@@ -80,7 +81,6 @@
 #include "gettime.hh"
 #include "lock.hh"
 #include "misc.hh"
-#include "sodcrypto.hh"
 #include "sstuff.hh"
 #include "threadname.hh"
 
@@ -2499,7 +2499,7 @@ static void usage()
   cout<<"-c,--client           Operate as a client, connect to dnsdist. This reads\n";
   cout<<"                      controlSocket from your configuration file, but also\n";
   cout<<"                      accepts an IP:PORT argument\n";
-#ifdef HAVE_LIBSODIUM
+#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO)
   cout<<"-k,--setkey KEY       Use KEY for encrypted communication to dnsdist. This\n";
   cout<<"                      is similar to setting setKey in the configuration file.\n";
   cout<<"                      NOTE: this will leak this key in your shell's history\n";
@@ -2722,14 +2722,14 @@ static void parseParameters(int argc, char** argv, ComboAddress& clientAddress)
       g_ACL.modify([optstring](NetmaskGroup& nmg) { nmg.addMask(optstring); });
       break;
     case 'k':
-#ifdef HAVE_LIBSODIUM
+#if defined HAVE_LIBSODIUM || defined(HAVE_LIBCRYPTO)
       if (B64Decode(string(optarg), g_consoleKey) < 0) {
         cerr<<"Unable to decode key '"<<optarg<<"'."<<endl;
         // NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
         exit(EXIT_FAILURE);
       }
 #else
-      cerr<<"dnsdist has been built without libsodium, -k/--setkey is unsupported."<<endl;
+      cerr<<"dnsdist has been built without libsodium or libcrypto, -k/--setkey is unsupported."<<endl;
       // NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
       exit(EXIT_FAILURE);
 #endif
@@ -3081,7 +3081,7 @@ int main(int argc, char** argv)
       infolog("Console ACL allowing connections from: %s", acls.c_str());
     }
 
-#ifdef HAVE_LIBSODIUM
+#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO)
     if (g_consoleEnabled && g_consoleKey.empty()) {
       warnlog("Warning, the console has been enabled via 'controlSocket()' but no key has been set with 'setKey()' so all connections will fail until a key has been set");
     }
index bc597a6611f19c8c019e3695982aa599c22993f9..9ed171375a9ee1eb0d3d16910afba6815bdc6e62 100644 (file)
@@ -151,6 +151,7 @@ dnsdist_SOURCES = \
        dnsdist-carbon.cc dnsdist-carbon.hh \
        dnsdist-concurrent-connections.hh \
        dnsdist-console.cc dnsdist-console.hh \
+       dnsdist-crypto.cc dnsdist-crypto.hh \
        dnsdist-discovery.cc dnsdist-discovery.hh \
        dnsdist-dnscrypt.cc \
        dnsdist-dnsparser.cc dnsdist-dnsparser.hh \
@@ -245,7 +246,6 @@ dnsdist_SOURCES = \
        remote_logger.cc remote_logger.hh \
        sholder.hh \
        snmp-agent.cc snmp-agent.hh \
-       sodcrypto.cc sodcrypto.hh \
        sstuff.hh \
        stat_t.hh \
        statnode.cc statnode.hh \
@@ -271,6 +271,7 @@ testrunner_SOURCES = \
        dnsdist-backoff.hh \
        dnsdist-cache.cc dnsdist-cache.hh \
        dnsdist-concurrent-connections.hh \
+       dnsdist-crypto.cc dnsdist-crypto.hh \
        dnsdist-dnsparser.cc dnsdist-dnsparser.hh \
        dnsdist-doh-common.cc dnsdist-doh-common.hh \
        dnsdist-downstream-connection.hh \
@@ -324,7 +325,6 @@ testrunner_SOURCES = \
        proxy-protocol.cc proxy-protocol.hh \
        qtype.cc qtype.hh \
        sholder.hh \
-       sodcrypto.cc \
        sstuff.hh \
        stat_t.hh \
        statnode.cc statnode.hh \
similarity index 56%
rename from pdns/sodcrypto.cc
rename to pdns/dnsdistdist/dnsdist-crypto.cc
index d6e9e7e906e41eaa6a0255d525d7d7db24fce20b..1c2463331bf074093db67871c7fc70dce9923e3c 100644 (file)
 #include <iostream>
 #include <arpa/inet.h>
 
+#include "dnsdist-crypto.hh"
+
 #include "namespaces.hh"
 #include "noinitvector.hh"
 #include "misc.hh"
 #include "base64.hh"
-#include "sodcrypto.hh"
 
+namespace dnsdist::crypto::authenticated
+{
 #ifdef HAVE_LIBSODIUM
-
 string newKey(bool base64Encoded)
 {
   std::string key;
@@ -44,14 +46,14 @@ string newKey(bool base64Encoded)
   return "\"" + Base64Encode(key) + "\"";
 }
 
-bool sodIsValidKey(const std::string& key)
+bool isValidKey(const std::string& key)
 {
   return key.size() == crypto_secretbox_KEYBYTES;
 }
 
-std::string sodEncryptSym(const std::string_view& msg, const std::string& key, SodiumNonce& nonce, bool incrementNonce)
+std::string encryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce)
 {
-  if (!sodIsValidKey(key)) {
+  if (!isValidKey(key)) {
     throw std::runtime_error("Invalid encryption key of size " + std::to_string(key.size()) + " (" + std::to_string(crypto_secretbox_KEYBYTES) + " expected), use setKey() to set a valid key");
   }
 
@@ -73,7 +75,7 @@ std::string sodEncryptSym(const std::string_view& msg, const std::string& key, S
   return ciphertext;
 }
 
-std::string sodDecryptSym(const std::string_view& msg, const std::string& key, SodiumNonce& nonce, bool incrementNonce)
+std::string decryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce)
 {
   std::string decrypted;
 
@@ -81,7 +83,7 @@ std::string sodDecryptSym(const std::string_view& msg, const std::string& key, S
     throw std::runtime_error("Could not decrypt message of size " + std::to_string(msg.length()));
   }
 
-  if (!sodIsValidKey(key)) {
+  if (!isValidKey(key)) {
     throw std::runtime_error("Invalid decryption key of size " + std::to_string(key.size()) + ", use setKey() to set a valid key");
   }
 
@@ -94,7 +96,8 @@ std::string sodDecryptSym(const std::string_view& msg, const std::string& key, S
                                  msg.length(),
                                  nonce.value.data(),
                                  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
-                                 reinterpret_cast<const unsigned char*>(key.data())) != 0) {
+                                 reinterpret_cast<const unsigned char*>(key.data()))
+      != 0) {
     throw std::runtime_error("Could not decrypt message, please check that the key configured with setKey() is correct");
   }
 
@@ -105,19 +108,182 @@ std::string sodDecryptSym(const std::string_view& msg, const std::string& key, S
   return decrypted;
 }
 
-void SodiumNonce::init()
+void Nonce::init()
 {
   randombytes_buf(value.data(), value.size());
 }
 
-void SodiumNonce::merge(const SodiumNonce& lower, const SodiumNonce& higher)
+#elif defined(HAVE_LIBCRYPTO)
+#include <openssl/evp.h>
+#include <openssl/rand.h>
+
+static constexpr size_t s_CHACHA20_POLY1305_KEY_SIZE = 32U;
+static constexpr size_t s_POLY1305_BLOCK_SIZE = 16U;
+
+string newKey(bool base64Encoded)
+{
+  std::string key;
+  key.resize(s_CHACHA20_POLY1305_KEY_SIZE);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  if (RAND_priv_bytes(reinterpret_cast<unsigned char*>(key.data()), key.size()) != 1) {
+    throw std::runtime_error("Could not initialize random number generator for cryptographic functions");
+  }
+  if (!base64Encoded) {
+    return key;
+  }
+  return "\"" + Base64Encode(key) + "\"";
+}
+
+bool isValidKey(const std::string& key)
+{
+  return key.size() == s_CHACHA20_POLY1305_KEY_SIZE;
+}
+
+std::string encryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce)
+{
+  if (!isValidKey(key)) {
+    throw std::runtime_error("Invalid encryption key of size " + std::to_string(key.size()) + " (" + std::to_string(s_CHACHA20_POLY1305_KEY_SIZE) + " expected), use setKey() to set a valid key");
+  }
+
+  // Each thread gets its own cipher context
+  static thread_local auto ctx = std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)>(nullptr, EVP_CIPHER_CTX_free);
+
+  if (!ctx) {
+    ctx = std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)>(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
+    if (!ctx) {
+      throw std::runtime_error("encryptSym: EVP_CIPHER_CTX_new() could not initialize cipher context");
+    }
+
+    if (EVP_EncryptInit_ex(ctx.get(), EVP_chacha20_poly1305(), nullptr, nullptr, nullptr) != 1) {
+      throw std::runtime_error("encryptSym: EVP_EncryptInit_ex() could not initialize encryption operation");
+    }
+  }
+
+  std::string ciphertext;
+  /* plus one so we can access the last byte in EncryptFinal which does nothing for this algo */
+  ciphertext.resize(s_POLY1305_BLOCK_SIZE + msg.length() + 1);
+  int outLength{0};
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  if (EVP_EncryptInit_ex(ctx.get(), nullptr, nullptr, reinterpret_cast<const unsigned char*>(key.c_str()), nonce.value.data()) != 1) {
+    throw std::runtime_error("encryptSym: EVP_EncryptInit_ex() could not initialize encryption key and IV");
+  }
+
+  if (!msg.empty()) {
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    if (EVP_EncryptUpdate(ctx.get(),
+                          reinterpret_cast<unsigned char*>(&ciphertext.at(s_POLY1305_BLOCK_SIZE)), &outLength,
+                          reinterpret_cast<const unsigned char*>(msg.data()), msg.length())
+        != 1) {
+      throw std::runtime_error("encryptSym: EVP_EncryptUpdate() could not encrypt message");
+    }
+  }
+
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  if (EVP_EncryptFinal_ex(ctx.get(), reinterpret_cast<unsigned char*>(&ciphertext.at(s_POLY1305_BLOCK_SIZE + outLength)), &outLength) != 1) {
+    throw std::runtime_error("encryptSym: EVP_EncryptFinal_ex() could finalize message encryption");
+    ;
+  }
+
+  /* Get the tag */
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_GET_TAG, s_POLY1305_BLOCK_SIZE, ciphertext.data()) != 1) {
+    throw std::runtime_error("encryptSym: EVP_CIPHER_CTX_ctrl() could not get tag");
+  }
+
+  if (incrementNonce) {
+    nonce.increment();
+  }
+
+  ciphertext.resize(ciphertext.size() - 1);
+  return ciphertext;
+}
+
+std::string decryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce)
+{
+  if (msg.length() < s_POLY1305_BLOCK_SIZE) {
+    throw std::runtime_error("Could not decrypt message of size " + std::to_string(msg.length()));
+  }
+
+  if (!isValidKey(key)) {
+    throw std::runtime_error("Invalid decryption key of size " + std::to_string(key.size()) + ", use setKey() to set a valid key");
+  }
+
+  if (msg.length() == s_POLY1305_BLOCK_SIZE) {
+    if (incrementNonce) {
+      nonce.increment();
+    }
+    return std::string();
+  }
+
+  // Each thread gets its own cipher context
+  static thread_local auto ctx = std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)>(nullptr, EVP_CIPHER_CTX_free);
+  if (!ctx) {
+    ctx = std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)>(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
+    if (!ctx) {
+      throw std::runtime_error("decryptSym: EVP_CIPHER_CTX_new() could not initialize cipher context");
+    }
+
+    if (EVP_DecryptInit_ex(ctx.get(), EVP_chacha20_poly1305(), nullptr, nullptr, nullptr) != 1) {
+      throw std::runtime_error("decryptSym: EVP_DecryptInit_ex() could not initialize decryption operation");
+    }
+  }
+
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  if (EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, reinterpret_cast<const unsigned char*>(key.c_str()), nonce.value.data()) != 1) {
+    throw std::runtime_error("decryptSym: EVP_DecryptInit_ex() could not initialize decryption key and IV");
+  }
+
+  const auto tag = msg.substr(0, s_POLY1305_BLOCK_SIZE);
+  std::string decrypted;
+  /* plus one so we can access the last byte in DecryptFinal, which does nothing */
+  decrypted.resize(msg.length() - s_POLY1305_BLOCK_SIZE + 1);
+  int outLength{0};
+  if (msg.size() > s_POLY1305_BLOCK_SIZE) {
+    if (!EVP_DecryptUpdate(ctx.get(),
+                           // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+                           reinterpret_cast<unsigned char*>(decrypted.data()), &outLength,
+                           // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+                           reinterpret_cast<const unsigned char*>(&msg.at(s_POLY1305_BLOCK_SIZE)), msg.size() - s_POLY1305_BLOCK_SIZE)) {
+      throw std::runtime_error("Could not decrypt message (update failed), please check that the key configured with setKey() is correct");
+    }
+  }
+
+  /* Set expected tag value. Works in OpenSSL 1.0.1d and later */
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast): sorry, OpenSSL's API is terrible
+  if (!EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_SET_TAG, s_POLY1305_BLOCK_SIZE, const_cast<char*>(tag.data()))) {
+    throw std::runtime_error("Could not decrypt message (invalid tag), please check that the key configured with setKey() is correct");
+  }
+
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  if (!EVP_DecryptFinal_ex(ctx.get(), reinterpret_cast<unsigned char*>(&decrypted.at(outLength)), &outLength)) {
+    throw std::runtime_error("Could not decrypt message (final failed), please check that the key configured with setKey() is correct");
+  }
+
+  if (incrementNonce) {
+    nonce.increment();
+  }
+
+  decrypted.resize(decrypted.size() - 1);
+  return decrypted;
+}
+
+void Nonce::init()
+{
+  if (RAND_priv_bytes(value.data(), value.size()) != 1) {
+    throw std::runtime_error("Could not initialize random number generator for cryptographic functions");
+  }
+}
+#endif
+
+#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO)
+void Nonce::merge(const Nonce& lower, const Nonce& higher)
 {
   constexpr size_t halfSize = std::tuple_size<decltype(value)>{} / 2;
   memcpy(value.data(), lower.value.data(), halfSize);
   memcpy(value.data() + halfSize, higher.value.data() + halfSize, halfSize);
 }
 
-void SodiumNonce::increment()
+void Nonce::increment()
 {
   // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
   auto* ptr = reinterpret_cast<uint32_t*>(value.data());
@@ -126,23 +292,23 @@ void SodiumNonce::increment()
 }
 
 #else
-void SodiumNonce::init()
+void Nonce::init()
 {
 }
 
-void SodiumNonce::merge(const SodiumNonce& lower, const SodiumNonce& higher)
+void Nonce::merge(const Nonce& lower, const Nonce& higher)
 {
 }
 
-void SodiumNonce::increment()
+void Nonce::increment()
 {
 }
 
-std::string sodEncryptSym(const std::string_view& msg, const std::string& key, SodiumNonce& nonce, bool incrementNonce)
+std::string encryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce)
 {
   return std::string(msg);
 }
-std::string sodDecryptSym(const std::string_view& msg, const std::string& key, SodiumNonce& nonce, bool incrementNonce)
+std::string decryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce)
 {
   return std::string(msg);
 }
@@ -152,12 +318,13 @@ string newKey(bool base64Encoded)
   return "\"plaintext\"";
 }
 
-bool sodIsValidKey(const std::string& key)
+bool isValidKey(const std::string& key)
 {
   return true;
 }
 
 #endif
+}
 
 #include <cinttypes>
 
similarity index 68%
rename from pdns/sodcrypto.hh
rename to pdns/dnsdistdist/dnsdist-crypto.hh
index 4c22fc81d20a98b438598707f608899d000d3216..74e1c04696cb09ab087ca7854a83dfdda5c6de75 100644 (file)
 #include <sodium.h>
 #endif
 
-struct SodiumNonce
+namespace dnsdist::crypto::authenticated
+{
+struct Nonce
 {
-  SodiumNonce() = default;
-  SodiumNonce(const SodiumNonce&) = default;
-  SodiumNonce(SodiumNonce&&) = default;
-  SodiumNonce& operator=(const SodiumNonce&) = default;
-  SodiumNonce& operator=(SodiumNonce&&) = default;
-  ~SodiumNonce() = default;
+  Nonce() = default;
+  Nonce(const Nonce&) = default;
+  Nonce(Nonce&&) = default;
+  Nonce& operator=(const Nonce&) = default;
+  Nonce& operator=(Nonce&&) = default;
+  ~Nonce() = default;
 
   void init();
-  void merge(const SodiumNonce& lower, const SodiumNonce& higher);
+  void merge(const Nonce& lower, const Nonce& higher);
   void increment();
 
-#if !defined(HAVE_LIBSODIUM)
-  std::array<unsigned char, 1> value{};
-#else
+#if defined(HAVE_LIBSODIUM)
   std::array<unsigned char, crypto_secretbox_NONCEBYTES> value{};
+#elif defined(HAVE_LIBCRYPTO)
+  // IV is 96 bits
+  std::array<unsigned char, 12> value{};
+#else
+  std::array<unsigned char, 1> value{};
 #endif
 };
 
-std::string sodEncryptSym(const std::string_view& msg, const std::string& key, SodiumNonce& nonce, bool incrementNonce = true);
-std::string sodDecryptSym(const std::string_view& msg, const std::string& key, SodiumNonce& nonce, bool incrementNonce = true);
+std::string encryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce = true);
+std::string decryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce = true);
 std::string newKey(bool base64Encoded = true);
-bool sodIsValidKey(const std::string& key);
+bool isValidKey(const std::string& key);
 
-namespace dnsdist::crypto::authenticated
-{
 constexpr size_t getEncryptedSize(size_t plainTextSize)
 {
 #if defined(HAVE_LIBSODIUM)
   return plainTextSize + crypto_secretbox_MACBYTES;
+#elif defined(HAVE_LIBCRYPTO)
+  return plainTextSize + 16;
 #else
   return plainTextSize;
 #endif
index 465c7ce445a19952a3d9492885e422f390122926..35e65be9583acd5b1b4e34302956f1d2da62228d 100644 (file)
@@ -11,9 +11,9 @@ The console can be enabled with :func:`controlSocket`:
 
   controlSocket('192.0.2.53:5199')
 
-Enabling the console without encryption enabled is not recommended. Note that encryption requires building dnsdist with libsodium support enabled.
+Enabling the console without encryption enabled is not recommended. Note that encryption requires building dnsdist with either libsodium or libcrypto support enabled.
 
-Once you have a libsodium-enabled dnsdist, the first step to enable encryption is to generate a key with :func:`makeKey`::
+Once you have a console-enabled dnsdist, the first step to enable encryption is to generate a key with :func:`makeKey`::
 
   $ ./dnsdist -l 127.0.0.1:5300
   [..]
index 8a0eddc6d9f01461158c53ab4785ec6daca1fd90..5c7cc002b7c18c70949899add47a6a7071581fe7 100644 (file)
@@ -58,7 +58,7 @@ Options
                                        that is used on the server (set with **setKey()**). Note that this
                                        will leak the key into your shell's history and into the systems
                                        running process list. Only available when dnsdist is compiled with
-                                       libsodium support.
+                                       libsodium or libcrypto support.
 -e, --execute <command>                Connect to dnsdist and execute *command*.
 -h, --help                             Display a helpful message and exit.
 -l, --local <address>                  Bind to *address*, Supply as many addresses (using multiple
index e3c14ccee8cb74b34fa675d8e655736b245ffab7..c9287778d25844f0d9ed8bed986891753a82cdb4 100644 (file)
@@ -28,7 +28,6 @@
 #include "dolog.hh"
 #include "iputils.hh"
 #include "misc.hh"
-#include "sodcrypto.hh"
 #include "sstuff.hh"
 #include "threadname.hh"
 #include "base64.hh"
index 4b0b2868f935c3387e2039a6149ef5b5b866913d..5358c1c56050b8daac4ff542e26dbf255d27c35c 100644 (file)
 namespace dnsdist::doq
 {
 
-static const std::string s_quicRetryTokenKey = newKey(false);
+static const std::string s_quicRetryTokenKey = dnsdist::crypto::authenticated::newKey(false);
 
 PacketBuffer mintToken(const PacketBuffer& dcid, const ComboAddress& peer)
 {
   try {
-    SodiumNonce nonce;
+    dnsdist::crypto::authenticated::Nonce nonce;
     nonce.init();
 
     const auto addrBytes = peer.toByteString();
@@ -54,7 +54,7 @@ PacketBuffer mintToken(const PacketBuffer& dcid, const ComboAddress& peer)
     plainTextToken.insert(plainTextToken.end(), addrBytes.begin(), addrBytes.end());
     plainTextToken.insert(plainTextToken.end(), dcid.begin(), dcid.end());
     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
-    const auto encryptedToken = sodEncryptSym(std::string_view(reinterpret_cast<const char*>(plainTextToken.data()), plainTextToken.size()), s_quicRetryTokenKey, nonce, false);
+    const auto encryptedToken = dnsdist::crypto::authenticated::encryptSym(std::string_view(reinterpret_cast<const char*>(plainTextToken.data()), plainTextToken.size()), s_quicRetryTokenKey, nonce, false);
     // a bit sad, let's see if we can do better later
     auto encryptedTokenPacket = PacketBuffer(encryptedToken.begin(), encryptedToken.end());
     encryptedTokenPacket.insert(encryptedTokenPacket.begin(), nonce.value.begin(), nonce.value.end());
@@ -88,7 +88,7 @@ std::optional<PacketBuffer> getCID()
 std::optional<PacketBuffer> validateToken(const PacketBuffer& token, const ComboAddress& peer)
 {
   try {
-    SodiumNonce nonce;
+    dnsdist::crypto::authenticated::Nonce nonce;
     auto addrBytes = peer.toByteString();
     const uint64_t now = time(nullptr);
     const auto minimumSize = nonce.value.size() + sizeof(now) + addrBytes.size();
@@ -100,7 +100,7 @@ std::optional<PacketBuffer> validateToken(const PacketBuffer& token, const Combo
 
     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
     auto cipher = std::string_view(reinterpret_cast<const char*>(&token.at(nonce.value.size())), token.size() - nonce.value.size());
-    auto plainText = sodDecryptSym(cipher, s_quicRetryTokenKey, nonce, false);
+    auto plainText = dnsdist::crypto::authenticated::decryptSym(cipher, s_quicRetryTokenKey, nonce, false);
 
     if (plainText.size() <= sizeof(now) + addrBytes.size()) {
       return std::nullopt;
index 7af19ecb79540d4864832dcd5bdf8af5902fe917..0b66a6943eebe64d76f6daf4f01c9cb01b96b29a 100644 (file)
@@ -32,9 +32,9 @@
 
 #include "dolog.hh"
 #include "noinitvector.hh"
-#include "sodcrypto.hh"
 #include "sstuff.hh"
 #include "libssl.hh"
+#include "dnsdist-crypto.hh"
 
 namespace dnsdist::doq
 {
@@ -71,7 +71,7 @@ enum class DOQ_Error_Codes : uint64_t
   DOQ_UNSPECIFIED_ERROR = 5
 };
 
-static constexpr size_t MAX_TOKEN_LEN = dnsdist::crypto::authenticated::getEncryptedSize(std::tuple_size<decltype(SodiumNonce::value)>{} /* nonce */ + sizeof(uint64_t) /* TTD */ + 16 /* IPv6 */ + QUICHE_MAX_CONN_ID_LEN);
+static constexpr size_t MAX_TOKEN_LEN = dnsdist::crypto::authenticated::getEncryptedSize(std::tuple_size<decltype(dnsdist::crypto::authenticated::Nonce::value)>{} /* nonce */ + sizeof(uint64_t) /* TTD */ + 16 /* IPv6 */ + QUICHE_MAX_CONN_ID_LEN);
 static constexpr size_t MAX_DATAGRAM_SIZE = 1200;
 static constexpr size_t LOCAL_CONN_ID_LEN = 16;
 static constexpr std::array<uint8_t, 4> DOQ_ALPN{'\x03', 'd', 'o', 'q'};
index 9b626aaf8822ec164a23884b53deeb5c27ee5f60..faa7b8cf2a481b1beb015312d6fc2852b815268f 100644 (file)
@@ -28,7 +28,6 @@
 #include "dolog.hh"
 #include "iputils.hh"
 #include "misc.hh"
-#include "sodcrypto.hh"
 #include "sstuff.hh"
 #include "threadname.hh"