summaryrefslogtreecommitdiffstats
path: root/dnsdist-crypto.cc
diff options
context:
space:
mode:
Diffstat (limited to 'dnsdist-crypto.cc')
-rw-r--r--dnsdist-crypto.cc573
1 files changed, 573 insertions, 0 deletions
diff --git a/dnsdist-crypto.cc b/dnsdist-crypto.cc
new file mode 100644
index 0000000..df4beeb
--- /dev/null
+++ b/dnsdist-crypto.cc
@@ -0,0 +1,573 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include <iostream>
+#include <arpa/inet.h>
+
+#include "dnsdist-crypto.hh"
+
+#include "namespaces.hh"
+#include "noinitvector.hh"
+#include "misc.hh"
+#include "base64.hh"
+
+namespace dnsdist::crypto::authenticated
+{
+#ifdef HAVE_LIBSODIUM
+string newKey(bool base64Encoded)
+{
+ std::string key;
+ key.resize(crypto_secretbox_KEYBYTES);
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ randombytes_buf(reinterpret_cast<unsigned char*>(key.data()), key.size());
+
+ if (!base64Encoded) {
+ return key;
+ }
+ return "\"" + Base64Encode(key) + "\"";
+}
+
+bool isValidKey(const std::string& key)
+{
+ return key.size() == crypto_secretbox_KEYBYTES;
+}
+
+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(crypto_secretbox_KEYBYTES) + " expected), use setKey() to set a valid key");
+ }
+
+ std::string ciphertext;
+ ciphertext.resize(msg.length() + crypto_secretbox_MACBYTES);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ crypto_secretbox_easy(reinterpret_cast<unsigned char*>(ciphertext.data()),
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ reinterpret_cast<const unsigned char*>(msg.data()),
+ msg.length(),
+ nonce.value.data(),
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ reinterpret_cast<const unsigned char*>(key.data()));
+
+ if (incrementNonce) {
+ nonce.increment();
+ }
+
+ return ciphertext;
+}
+
+std::string decryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce)
+{
+ std::string decrypted;
+
+ if (msg.length() < crypto_secretbox_MACBYTES) {
+ 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");
+ }
+
+ decrypted.resize(msg.length() - crypto_secretbox_MACBYTES);
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ if (crypto_secretbox_open_easy(reinterpret_cast<unsigned char*>(decrypted.data()),
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ reinterpret_cast<const unsigned char*>(msg.data()),
+ msg.length(),
+ nonce.value.data(),
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ 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");
+ }
+
+ if (incrementNonce) {
+ nonce.increment();
+ }
+
+ return decrypted;
+}
+
+void Nonce::init()
+{
+ randombytes_buf(value.data(), value.size());
+}
+
+#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 Nonce::increment()
+{
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ auto* ptr = reinterpret_cast<uint32_t*>(value.data());
+ uint32_t count = htonl(*ptr);
+ *ptr = ntohl(++count);
+}
+
+#else
+void Nonce::init()
+{
+}
+
+void Nonce::merge(const Nonce& lower, const Nonce& higher)
+{
+}
+
+void Nonce::increment()
+{
+}
+
+std::string encryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce)
+{
+ return std::string(msg);
+}
+std::string decryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce)
+{
+ return std::string(msg);
+}
+
+string newKey(bool base64Encoded)
+{
+ return "\"plaintext\"";
+}
+
+bool isValidKey(const std::string& key)
+{
+ return true;
+}
+
+#endif
+}
+
+#include <cinttypes>
+
+namespace anonpdns
+{
+static char B64Decode1(char cInChar)
+{
+ // The incoming character will be A-Z, a-z, 0-9, +, /, or =.
+ // The idea is to quickly determine which grouping the
+ // letter belongs to and return the associated value
+ // without having to search the global encoding string
+ // (the value we're looking for would be the resulting
+ // index into that string).
+ //
+ // To do that, we'll play some tricks...
+ unsigned char iIndex = '\0';
+ switch (cInChar) {
+ case '+':
+ iIndex = 62;
+ break;
+
+ case '/':
+ iIndex = 63;
+ break;
+
+ case '=':
+ iIndex = 0;
+ break;
+
+ default:
+ // Must be 'A'-'Z', 'a'-'z', '0'-'9', or an error...
+ //
+ // Numerically, small letters are "greater" in value than
+ // capital letters and numerals (ASCII value), and capital
+ // letters are "greater" than numerals (again, ASCII value),
+ // so we check for numerals first, then capital letters,
+ // and finally small letters.
+ iIndex = '9' - cInChar;
+ if (iIndex > 0x3F) {
+ // Not from '0' to '9'...
+ iIndex = 'Z' - cInChar;
+ if (iIndex > 0x3F) {
+ // Not from 'A' to 'Z'...
+ iIndex = 'z' - cInChar;
+ if (iIndex > 0x3F) {
+ // Invalid character...cannot
+ // decode!
+ iIndex = 0x80; // set the high bit
+ } // if
+ else {
+ // From 'a' to 'z'
+ iIndex = (('z' - iIndex) - 'a') + 26;
+ } // else
+ } // if
+ else {
+ // From 'A' to 'Z'
+ iIndex = ('Z' - iIndex) - 'A';
+ } // else
+ } // if
+ else {
+ // Adjust the index...
+ iIndex = (('9' - iIndex) - '0') + 52;
+ } // else
+ break;
+
+ } // switch
+
+ return static_cast<char>(iIndex);
+}
+
+static inline char B64Encode1(unsigned char input)
+{
+ if (input < 26) {
+ return static_cast<char>('A' + input);
+ }
+ if (input < 52) {
+ return static_cast<char>('a' + (input - 26));
+ }
+ if (input < 62) {
+ return static_cast<char>('0' + (input - 52));
+ }
+ if (input == 62) {
+ return '+';
+ }
+ return '/';
+};
+
+}
+using namespace anonpdns;
+
+template <typename Container>
+int B64Decode(const std::string& strInput, Container& strOutput)
+{
+ // Set up a decoding buffer
+ long cBuf = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ char* pBuf = reinterpret_cast<char*>(&cBuf);
+
+ // Decoding management...
+ int iBitGroup = 0;
+ int iInNum = 0;
+
+ // While there are characters to process...
+ //
+ // We'll decode characters in blocks of 4, as
+ // there are 4 groups of 6 bits in 3 bytes. The
+ // incoming Base64 character is first decoded, and
+ // then it is inserted into the decode buffer
+ // (with any relevant shifting, as required).
+ // Later, after all 3 bytes have been reconstituted,
+ // we assign them to the output string, ultimately
+ // to be returned as the original message.
+ int iInSize = static_cast<int>(strInput.size());
+ unsigned char cChar = '\0';
+ uint8_t pad = 0;
+ while (iInNum < iInSize) {
+ // Fill the decode buffer with 4 groups of 6 bits
+ cBuf = 0; // clear
+ pad = 0;
+ for (iBitGroup = 0; iBitGroup < 4; ++iBitGroup) {
+ if (iInNum < iInSize) {
+ // Decode a character
+ if (strInput.at(iInNum) == '=') {
+ pad++;
+ }
+ while (isspace(strInput.at(iInNum))) {
+ iInNum++;
+ }
+ cChar = B64Decode1(strInput.at(iInNum++));
+
+ } // if
+ else {
+ // Decode a padded zero
+ cChar = '\0';
+ } // else
+
+ // Check for valid decode
+ if (cChar > 0x7F) {
+ return -1;
+ }
+
+ // Adjust the bits
+ switch (iBitGroup) {
+ case 0:
+ // The first group is copied into
+ // the least significant 6 bits of
+ // the decode buffer...these 6 bits
+ // will eventually shift over to be
+ // the most significant bits of the
+ // third byte.
+ cBuf = cBuf | cChar;
+ break;
+
+ default:
+ // For groupings 1-3, simply shift
+ // the bits in the decode buffer over
+ // by 6 and insert the 6 from the
+ // current decode character.
+ cBuf = (cBuf << 6) | cChar;
+ break;
+
+ } // switch
+ } // for
+
+ // Interpret the resulting 3 bytes...note there
+ // may have been padding, so those padded bytes
+ // are actually ignored.
+#if BYTE_ORDER == BIG_ENDIAN
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ strOutput.push_back(pBuf[sizeof(long) - 3]);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ strOutput.push_back(pBuf[sizeof(long) - 2]);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ strOutput.push_back(pBuf[sizeof(long) - 1]);
+#else
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ strOutput.push_back(pBuf[2]);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ strOutput.push_back(pBuf[1]);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ strOutput.push_back(pBuf[0]);
+#endif
+ } // while
+ if (pad) {
+ strOutput.resize(strOutput.size() - pad);
+ }
+
+ return 1;
+}
+
+template int B64Decode<std::vector<uint8_t>>(const std::string& strInput, std::vector<uint8_t>& strOutput);
+template int B64Decode<PacketBuffer>(const std::string& strInput, PacketBuffer& strOutput);
+template int B64Decode<std::string>(const std::string& strInput, std::string& strOutput);
+
+/*
+www.kbcafe.com
+Copyright 2001-2002 Randy Charles Morin
+The Encode static method takes an array of 8-bit values and returns a base-64 stream.
+*/
+
+std::string Base64Encode(const std::string& src)
+{
+ std::string retval;
+ if (src.empty()) {
+ return retval;
+ }
+ for (unsigned int i = 0; i < src.size(); i += 3) {
+ unsigned char by1 = 0;
+ unsigned char by2 = 0;
+ unsigned char by3 = 0;
+ by1 = src[i];
+ if (i + 1 < src.size()) {
+ by2 = src[i + 1];
+ };
+ if (i + 2 < src.size()) {
+ by3 = src[i + 2];
+ }
+ unsigned char by4 = 0;
+ unsigned char by5 = 0;
+ unsigned char by6 = 0;
+ unsigned char by7 = 0;
+ by4 = by1 >> 2;
+ by5 = ((by1 & 0x3) << 4) | (by2 >> 4);
+ by6 = ((by2 & 0xf) << 2) | (by3 >> 6);
+ by7 = by3 & 0x3f;
+ retval += B64Encode1(by4);
+ retval += B64Encode1(by5);
+ if (i + 1 < src.size()) {
+ retval += B64Encode1(by6);
+ }
+ else {
+ retval += "=";
+ };
+ if (i + 2 < src.size()) {
+ retval += B64Encode1(by7);
+ }
+ else {
+ retval += "=";
+ };
+ /* if ((i % (76 / 4 * 3)) == 0)
+ {
+ retval += "\r\n";
+ }*/
+ };
+ return retval;
+};