base32.cc \
base64.hh \
dns.cc \
+ dnscrypt.cc dnscrypt.hh \
dnsparser.hh dnsparser.cc \
ednssubnet.cc ednssubnet.hh \
dnsdist.cc \
dnsdist-carbon.cc \
dnsdist-console.cc \
+ dnsdist-dnscrypt.cc \
dnsdist-ecs.cc dnsdist-ecs.hh \
dnsdist-lua.cc \
dnsdist-lua2.cc \
Metronome](https://github.com/ahupowerdns/metronome) comes with attractive
graphs for dnsdist by default.
+DNSCrypt
+--------
+`dnsdist`, when compiled with --enable-dnscrypt, can be used as a DNSCrypt server,
+uncurving queries before forwarding them to downstream servers and curving responses back.
+To make `dnsdist` listen to incoming DNSCrypt queries on 127.0.0.1 port 443,
+with a provider name of "2.providername", using a resolver certificate and associated key
+stored respectively in the `resolver.cert` and `resolver.key` files, the `addDnsCryptBind()`
+directive can be used:
+
+```
+addDNSCryptBind("127.0.0.1:8443", "2.providername", "/path/to/resolver.cert", "/path/to/resolver.key")
+```
+
+To generate the provider and resolver certificates and keys, you can simply do:
+
+```
+> generateDNSCryptProviderKeys("/path/to/providerPublic.key", "/path/to/providerPrivate.key")
+> generateDNSCryptCertificate("/path/to/providerPrivate.key", "/path/to/resolver.cert", "/path/to/resolver.key", serial, validFrom, validUntil)
+```
+
+Ideally, the certificates and keys should be generated on an offline dedicated hardware and not on the resolver.
+The resolver key should be regularly rotated and should never touch persistent storage, being stored in a tmpfs
+with no swap configured.
+
+You can display the currently configured DNSCrypt binds with:
+```
+> showDNSCryptBinds()
+# Address Provider Name Serial Validity P. Serial P. Validity
+0 127.0.0.1:8443 2.name 14 2016-04-10 08:14:15 0 -
+```
+
All functions and types
-----------------------
Within `dnsdist` several core object types exist:
--- /dev/null
+
+#include "config.h"
+#ifdef HAVE_DNSCRYPT
+#include <fstream>
+#include "dolog.hh"
+#include "dnscrypt.hh"
+#include "dnswriter.hh"
+
+DnsCryptPrivateKey::DnsCryptPrivateKey()
+{
+ sodium_memzero(key, sizeof(key));
+ sodium_mlock(key, sizeof(key));
+}
+
+void DnsCryptPrivateKey::loadFromFile(const std::string& keyFile)
+{
+ ifstream file(keyFile);
+ sodium_memzero(key, sizeof(key));
+ file.read((char*) key, sizeof(key));
+
+ if (file.fail()) {
+ sodium_memzero(key, sizeof(key));
+ file.close();
+ throw std::runtime_error("Invalid DNSCrypt key file " + keyFile);
+ }
+
+ file.close();
+}
+
+void DnsCryptPrivateKey::saveToFile(const std::string& keyFile) const
+{
+ ofstream file(keyFile);
+ file.write((char*) key, sizeof(key));
+ file.close();
+}
+
+DnsCryptPrivateKey::~DnsCryptPrivateKey()
+{
+ sodium_memzero(key, sizeof(key));
+ sodium_munlock(key, sizeof(key));
+}
+
+void DnsCryptContext::generateProviderKeys(unsigned char publicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE], unsigned char privateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE])
+{
+ int res = crypto_sign_ed25519_keypair(publicKey, privateKey);
+
+ if (res != 0) {
+ throw std::runtime_error("Error generating DNSCrypt provider keys");
+ }
+}
+
+std::string DnsCryptContext::getProviderFingerprint(unsigned char publicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE])
+{
+ boost::format fmt("%02X%02X");
+ ostringstream ret;
+
+ for (size_t idx = 0; idx < DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE; idx += 2)
+ {
+ ret << (fmt % static_cast<int>(publicKey[idx]) % static_cast<int>(publicKey[idx+1]));
+ if (idx < (DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE - 2)) {
+ ret << ":";
+ }
+ }
+
+ return ret.str();
+}
+
+void DnsCryptContext::generateCertificate(uint32_t serial, time_t begin, time_t end, const unsigned char providerPrivateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE], DnsCryptPrivateKey& privateKey, DnsCryptCert& cert)
+{
+ unsigned char magic[DNSCRYPT_CERT_MAGIC_SIZE] = DNSCRYPT_CERT_MAGIC_VALUE;
+ unsigned char esVersion[] = DNSCRYPT_CERT_ES_VERSION_VALUE;
+ unsigned char protocolMinorVersion[] = DNSCRYPT_CERT_PROTOCOL_MINOR_VERSION_VALUE;
+ unsigned char pubK[DNSCRYPT_PUBLIC_KEY_SIZE];
+ generateResolverKeyPair(privateKey, pubK);
+
+ memcpy(cert.magic, magic, sizeof(magic));
+ memcpy(cert.esVersion, esVersion, sizeof(esVersion));
+ memcpy(cert.protocolMinorVersion, protocolMinorVersion, sizeof(protocolMinorVersion));
+ memcpy(cert.signedData.resolverPK, pubK, sizeof(cert.signedData.resolverPK));
+ memcpy(cert.signedData.clientMagic, pubK, sizeof(cert.signedData.clientMagic));
+ cert.signedData.serial = serial;
+ cert.signedData.tsStart = htonl((uint32_t) begin);
+ cert.signedData.tsEnd = htonl((uint32_t) end);
+
+ unsigned long long signatureSize = 0;
+
+ int res = crypto_sign_ed25519(cert.signature,
+ &signatureSize,
+ (unsigned char*) &cert.signedData,
+ sizeof(cert.signedData),
+ providerPrivateKey);
+
+ if (res == 0) {
+ assert(signatureSize == sizeof(DnsCryptCertSignedData) + DNSCRYPT_SIGNATURE_SIZE);
+ }
+ else {
+ throw std::runtime_error("Error generating DNSCrypt certificate");
+ }
+}
+
+void DnsCryptContext::loadCertFromFile(const std::string&filename, DnsCryptCert& dest)
+{
+ ifstream file(filename);
+ file.read((char *) &dest, sizeof(dest));
+
+ if (file.fail())
+ throw std::runtime_error("Invalid dnscrypt certificate file " + filename);
+
+ file.close();
+}
+
+void DnsCryptContext::saveCertFromFile(const DnsCryptCert& cert, const std::string&filename)
+{
+ ofstream file(filename);
+ file.write((char *) &cert, sizeof(cert));
+ file.close();
+}
+
+void DnsCryptContext::generateResolverKeyPair(DnsCryptPrivateKey& privK, unsigned char pubK[DNSCRYPT_PUBLIC_KEY_SIZE])
+{
+ int res = crypto_box_keypair(pubK, privK.key);
+
+ if (res != 0) {
+ throw std::runtime_error("Error generating DNSCrypt resolver keys");
+ }
+}
+
+void DnsCryptContext::computePublicKeyFromPrivate(const DnsCryptPrivateKey& privK, unsigned char* pubK)
+{
+ int res = crypto_scalarmult_base(pubK,
+ privK.key);
+
+ if (res != 0) {
+ throw std::runtime_error("Error computing dnscrypt public key from the private one");
+ }
+}
+
+std::string DnsCryptContext::certificateDateToStr(uint32_t date)
+{
+ char buf[20];
+ time_t tdate = (time_t) ntohl(date);
+ struct tm date_tm;
+
+ localtime_r(&tdate, &date_tm);
+ strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &date_tm);
+
+ return string(buf);
+}
+
+void DnsCryptContext::setNewCertificate(const DnsCryptCert& newCert, const DnsCryptPrivateKey& newKey)
+{
+ // XXX TODO: this could use a lock
+ oldPrivateKey = privateKey;
+ oldCert = cert;
+ hasOldCert = true;
+ privateKey = newKey;
+ cert = newCert;
+}
+
+void DnsCryptContext::loadNewCertificate(const std::string& certFile, const std::string& keyFile)
+{
+ DnsCryptCert newCert;
+ DnsCryptPrivateKey newPrivateKey;
+
+ loadCertFromFile(certFile, newCert);
+ newPrivateKey.loadFromFile(keyFile);
+ setNewCertificate(newCert, newPrivateKey);
+}
+
+void DnsCryptContext::parsePlaintextQuery(const char * packet, uint16_t packetSize, std::shared_ptr<DnsCryptQuery> query) const
+{
+ if (packetSize < sizeof(dnsheader)) {
+ return;
+ }
+
+ struct dnsheader * dh = (struct dnsheader *) packet;
+ if (dh->qr || ntohs(dh->qdcount) != 1 || dh->ancount != 0 || dh->nscount != 0 || dh->opcode != Opcode::Query)
+ return;
+
+ unsigned int consumed;
+ uint16_t qtype, qclass;
+ DNSName qname(packet, packetSize, sizeof(dnsheader), false, &qtype, &qclass, &consumed);
+
+ if (qtype != QType::TXT || qclass != QClass::IN)
+ return;
+
+ if (qname != DNSName(providerName))
+ return;
+
+ query->qname = qname;
+ query->id = dh->id;
+ query->valid = true;
+}
+
+void DnsCryptContext::getCertificateResponse(const std::shared_ptr<DnsCryptQuery> query, vector<uint8_t>& response) const
+{
+ DNSPacketWriter pw(response, query->qname, QType::TXT, QClass::IN, Opcode::Query);
+ struct dnsheader * dh = pw.getHeader();
+ dh->id = query->id;
+ dh->qr = true;
+ dh->rcode = RCode::NoError;
+ pw.startRecord(query->qname, QType::TXT, (DNSCRYPT_CERTIFICATE_RESPONSE_TTL), QClass::IN, DNSResourceRecord::ANSWER, true);
+ std::string scert;
+ uint8_t certSize = sizeof(cert);
+ scert.assign((const char*) &certSize, sizeof(certSize));
+ scert.append((const char*) &cert, certSize);
+
+ pw.xfrBlob(scert);
+ pw.commit();
+}
+
+bool DnsCryptContext::magicMatchesPublicKey(std::shared_ptr<DnsCryptQuery> query) const
+{
+ const unsigned char* magic = query->header.clientMagic;
+
+ if (memcmp(magic, cert.signedData.clientMagic, DNSCRYPT_CLIENT_MAGIC_SIZE) == 0) {
+ return true;
+ }
+
+ if (hasOldCert == true &&
+ memcmp(magic, oldCert.signedData.clientMagic, DNSCRYPT_CLIENT_MAGIC_SIZE) == 0) {
+ query->useOldCert = true;
+ return true;
+ }
+
+ return false;
+}
+
+void DnsCryptContext::isQueryEncrypted(const char * packet, uint16_t packetSize, std::shared_ptr<DnsCryptQuery> query, bool tcp) const
+{
+ query->encrypted = false;
+
+ if (packetSize < sizeof(DnsCryptQueryHeader)) {
+ return;
+ }
+
+ if (!tcp && packetSize < DnsCryptQuery::minUDPLength) {
+ return;
+ }
+
+ struct DnsCryptQueryHeader* header = (struct DnsCryptQueryHeader*) packet;
+
+ query->header = *(header);
+
+ if (!magicMatchesPublicKey(query)) {
+ return;
+ }
+
+ query->encrypted = true;
+}
+
+void DnsCryptContext::getDecryptedQuery(std::shared_ptr<DnsCryptQuery> query, bool tcp, char* packet, uint16_t packetSize, uint16_t* decryptedQueryLen) const
+{
+ assert(decryptedQueryLen != NULL);
+ assert(query->encrypted);
+ assert(query->valid == false);
+
+#ifdef DNSCRYPT_STRICT_PADDING_LENGTH
+ if (tcp && ((packetSize - sizeof(DnsCryptQueryHeader)) % DNSCRYPT_PADDED_BLOCK_SIZE) != 0) {
+ vinfolog("Dropping encrypted query with invalid size of %d (should be a multiple of %d)", (packetSize - sizeof(DnsCryptQueryHeader)), DNSCRYPT_PADDED_BLOCK_SIZE);
+ return;
+ }
+#endif
+
+ unsigned char nonce[DNSCRYPT_NONCE_SIZE];
+ static_assert(sizeof(nonce) == (2* sizeof(query->header.clientNonce)), "Nonce should be larger than clientNonce (half)");
+ static_assert(sizeof(query->header.clientPK) == DNSCRYPT_PUBLIC_KEY_SIZE, "Client Publick key size is not right");
+ static_assert(sizeof(privateKey.key) == DNSCRYPT_PRIVATE_KEY_SIZE, "Private key size is not right");
+
+ memcpy(nonce, &query->header.clientNonce, sizeof(query->header.clientNonce));
+ memset(nonce + sizeof(query->header.clientNonce), 0, sizeof(nonce) - sizeof(query->header.clientNonce));
+
+ /* we could compute and store the intermediary shared key, in order to not having to compute it a second
+ time for the response:
+ - crypto_box_beforenm() into an unsigned char[crypto_box_BEFORENMBYTES]
+ - crypto_box_open_easy_afternm()
+ - crypto_box_easy_afternm()
+ */
+ int res = crypto_box_open_easy((unsigned char*) packet,
+ (unsigned char*) packet + sizeof(DnsCryptQueryHeader),
+ packetSize - sizeof(DnsCryptQueryHeader),
+ nonce,
+ query->header.clientPK,
+ query->useOldCert ? oldPrivateKey.key : privateKey.key);
+
+ if (res != 0) {
+ vinfolog("Dropping encrypted query we can't decrypt");
+ return;
+ }
+
+ *decryptedQueryLen = packetSize - sizeof(DnsCryptQueryHeader) - DNSCRYPT_MAC_SIZE;
+ uint16_t pos = *decryptedQueryLen;
+ assert(pos < packetSize);
+ query->paddedLen = *decryptedQueryLen;
+
+ while(pos > 0 && packet[pos - 1] == 0) pos--;
+
+ if (pos == 0 || ((uint8_t) packet[pos - 1]) != 0x80) {
+ vinfolog("Dropping encrypted query with invalid padding value");
+ return;
+ }
+
+ pos--;
+
+ size_t paddingLen = *decryptedQueryLen - pos;
+ *decryptedQueryLen = pos;
+
+ if (tcp && paddingLen > DNSCRYPT_MAX_TCP_PADDING_SIZE) {
+ vinfolog("Dropping encrypted query with too long padding size");
+ return;
+ }
+
+ query->len = pos;
+
+ query->valid = true;
+}
+
+void DnsCryptContext::parsePacket(char* packet, uint16_t packetSize, std::shared_ptr<DnsCryptQuery> query, bool tcp, uint16_t* decryptedQueryLen) const
+{
+ assert(packet != NULL);
+ assert(decryptedQueryLen != NULL);
+
+ query->valid = false;
+
+ /* might be a plaintext certificate request or an authenticated request */
+ isQueryEncrypted(packet, packetSize, query, tcp);
+
+ if (query->encrypted) {
+ getDecryptedQuery(query, tcp, packet, packetSize, decryptedQueryLen);
+ }
+ else {
+ parsePlaintextQuery(packet, packetSize, query);
+ }
+}
+
+void DnsCryptContext::fillServerNonce(unsigned char* nonce) const
+{
+ uint32_t* dest = (uint32_t*) nonce;
+ static const size_t nonceSize = DNSCRYPT_NONCE_SIZE / 2;
+
+ for (size_t pos = 0; pos < (nonceSize / sizeof(*dest)); pos++)
+ {
+ const uint32_t value = randombytes_random();
+ memcpy(dest + pos, &value, sizeof(value));
+ }
+}
+
+/*
+ "The length of <resolver-response-pad> must be between 0 and 256 bytes,
+ and must be constant for a given (<resolver-sk>, <client-nonce>) tuple."
+*/
+uint16_t DnsCryptContext::computePaddingSize(uint16_t unpaddedLen, size_t maxLen, const unsigned char* clientNonce) const
+{
+ size_t paddedLen = 0;
+ uint16_t result = 0;
+ uint32_t rnd = 0;
+ assert(clientNonce != NULL);
+ unsigned char nonce[DNSCRYPT_NONCE_SIZE];
+ memcpy(nonce, clientNonce, (DNSCRYPT_NONCE_SIZE / 2));
+ memcpy(&(nonce[DNSCRYPT_NONCE_SIZE / 2]), clientNonce, (DNSCRYPT_NONCE_SIZE / 2));
+ crypto_stream((unsigned char*) &rnd, sizeof(rnd), nonce, privateKey.key);
+
+ paddedLen = unpaddedLen + rnd % (maxLen - unpaddedLen + 1);
+ paddedLen += DNSCRYPT_PADDED_BLOCK_SIZE - (paddedLen % DNSCRYPT_PADDED_BLOCK_SIZE);
+
+ if (paddedLen > maxLen)
+ paddedLen = maxLen;
+
+ result = paddedLen - unpaddedLen;
+
+ return result;
+}
+
+int DnsCryptContext::encryptResponse(char* response, uint16_t responseLen, uint16_t responseSize, const std::shared_ptr<DnsCryptQuery> query, bool tcp, uint16_t* encryptedResponseLen) const
+{
+ struct DnsCryptResponseHeader header;
+ assert(response != NULL);
+ assert(responseLen > 0);
+ assert(responseSize >= responseLen);
+ assert(encryptedResponseLen != NULL);
+ assert(query->encrypted == true);
+
+ if (!tcp && query->paddedLen < responseLen) {
+ struct dnsheader* dh = (struct dnsheader*) response;
+ responseLen = query->paddedLen;
+ dh->tc = 1;
+ }
+
+ size_t requiredSize = sizeof(header) + DNSCRYPT_MAC_SIZE + responseLen;
+ size_t maxSize = (responseSize > (requiredSize + DNSCRYPT_MAX_RESPONSE_PADDING_SIZE)) ? (requiredSize + DNSCRYPT_MAX_RESPONSE_PADDING_SIZE) : responseSize;
+ uint16_t paddingSize = computePaddingSize(requiredSize, maxSize, query->header.clientNonce);
+ requiredSize += paddingSize;
+
+ if (requiredSize > responseSize)
+ return ENOBUFS;
+
+ memcpy(&header.nonce, &query->header.clientNonce, sizeof query->header.clientNonce);
+ fillServerNonce(&(header.nonce[sizeof(query->header.clientNonce)]));
+
+ /* moving the existing response after the header + MAC */
+ memmove(response + sizeof(header) + DNSCRYPT_MAC_SIZE, response, responseLen);
+
+ uint16_t pos = 0;
+ /* copying header */
+ memcpy(response + pos, &header, sizeof(header));
+ pos += sizeof(header);
+ /* setting MAC bytes to 0 */
+ memset(response + pos, 0, DNSCRYPT_MAC_SIZE);
+ pos += DNSCRYPT_MAC_SIZE;
+ uint16_t toEncryptPos = pos;
+ /* skipping response */
+ pos += responseLen;
+ /* padding */
+ response[pos] = (uint8_t) 0x80;
+ pos++;
+ memset(response + pos, 0, paddingSize - 1);
+ pos += (paddingSize - 1);
+ /* encrypting */
+ int res = crypto_box_easy((unsigned char*) (response + sizeof(header)),
+ (unsigned char*) (response + toEncryptPos),
+ responseLen + paddingSize,
+ header.nonce,
+ query->header.clientPK,
+ query->useOldCert ? oldPrivateKey.key : privateKey.key);
+
+ if (res == 0) {
+ assert(pos == requiredSize);
+ *encryptedResponseLen = requiredSize;
+ }
+
+ return res;
+}
+
+int DnsCryptContext::encryptQuery(char* query, uint16_t queryLen, uint16_t querySize, const unsigned char clientPublicKey[DNSCRYPT_PUBLIC_KEY_SIZE], const DnsCryptPrivateKey& clientPrivateKey, const unsigned char clientNonce[DNSCRYPT_NONCE_SIZE / 2], bool tcp, uint16_t* encryptedResponseLen) const
+{
+ assert(query != NULL);
+ assert(queryLen > 0);
+ assert(querySize >= queryLen);
+ assert(encryptedResponseLen != NULL);
+ unsigned char nonce[DNSCRYPT_NONCE_SIZE];
+ size_t requiredSize = sizeof(DnsCryptQueryHeader) + DNSCRYPT_MAC_SIZE + queryLen;
+ /* this is not optimal, we should compute a random padding size, multiple of DNSCRYPT_PADDED_BLOCK_SIZE,
+ DNSCRYPT_PADDED_BLOCK_SIZE <= padding size <= 4096? */
+ uint16_t paddingSize = DNSCRYPT_PADDED_BLOCK_SIZE - (queryLen % DNSCRYPT_PADDED_BLOCK_SIZE);
+ requiredSize += paddingSize;
+
+ if (!tcp && requiredSize < DnsCryptQuery::minUDPLength) {
+ paddingSize += (DnsCryptQuery::minUDPLength - requiredSize);
+ requiredSize = DnsCryptQuery::minUDPLength;
+ }
+
+ if (requiredSize > querySize)
+ return ENOBUFS;
+
+ /* moving the existing query after the header + MAC */
+ memmove(query + sizeof(DnsCryptQueryHeader) + DNSCRYPT_MAC_SIZE, query, queryLen);
+
+ size_t pos = 0;
+ /* client magic */
+ memcpy(query + pos, cert.signedData.clientMagic, sizeof(cert.signedData.clientMagic));
+ pos += sizeof(cert.signedData.clientMagic);
+
+ /* client PK */
+ memcpy(query + pos, clientPublicKey, DNSCRYPT_PUBLIC_KEY_SIZE);
+ pos += DNSCRYPT_PUBLIC_KEY_SIZE;
+
+ /* client nonce */
+ memcpy(query + pos, clientNonce, DNSCRYPT_NONCE_SIZE / 2);
+ pos += DNSCRYPT_NONCE_SIZE / 2;
+ size_t encryptedPos = pos;
+
+ /* clear the MAC bytes */
+ memset(query + pos, 0, DNSCRYPT_MAC_SIZE);
+ pos += DNSCRYPT_MAC_SIZE;
+
+ /* skipping data */
+ pos += queryLen;
+
+ /* padding */
+ query[pos] = (uint8_t) 0x80;
+ pos++;
+ memset(query + pos, 0, paddingSize - 1);
+ pos += paddingSize - 1;
+
+ memcpy(nonce, clientNonce, DNSCRYPT_NONCE_SIZE / 2);
+ memset(nonce + (DNSCRYPT_NONCE_SIZE / 2), 0, DNSCRYPT_NONCE_SIZE / 2);
+
+ int res = crypto_box_easy((unsigned char*) query + encryptedPos,
+ (unsigned char*) query + encryptedPos + DNSCRYPT_MAC_SIZE,
+ queryLen + paddingSize,
+ nonce,
+ cert.signedData.resolverPK,
+ clientPrivateKey.key);
+
+ if (res == 0) {
+ assert(pos == requiredSize);
+ *encryptedResponseLen = requiredSize;
+ }
+
+ return res;
+}
+
+#endif
--- /dev/null
+#pragma once
+#include "config.h"
+
+#ifdef HAVE_DNSCRYPT
+
+#include <memory>
+#include <string>
+#include <vector>
+#include <sodium.h>
+
+#include "dnsname.hh"
+
+#define DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE (crypto_sign_ed25519_PUBLICKEYBYTES)
+#define DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE (crypto_sign_ed25519_SECRETKEYBYTES)
+#define DNSCRYPT_PUBLIC_KEY_SIZE (crypto_box_curve25519xsalsa20poly1305_PUBLICKEYBYTES)
+#define DNSCRYPT_PRIVATE_KEY_SIZE (crypto_box_curve25519xsalsa20poly1305_SECRETKEYBYTES)
+#define DNSCRYPT_NONCE_SIZE (crypto_box_curve25519xsalsa20poly1305_NONCEBYTES)
+#define DNSCRYPT_BEFORENM_SIZE (crypto_box_curve25519xsalsa20poly1305_BEFORENMBYTES)
+#define DNSCRYPT_SIGNATURE_SIZE (crypto_sign_ed25519_BYTES)
+#define DNSCRYPT_MAC_SIZE (crypto_box_curve25519xsalsa20poly1305_MACBYTES)
+#define DNSCRYPT_CERT_MAGIC_SIZE (4)
+#define DNSCRYPT_CERT_MAGIC_VALUE { 0x44, 0x4e, 0x53, 0x43 }
+#define DNSCRYPT_CERT_ES_VERSION_VALUE { 0x00, 0x01 }
+#define DNSCRYPT_CERT_PROTOCOL_MINOR_VERSION_VALUE { 0x00, 0x00 }
+#define DNSCRYPT_CLIENT_MAGIC_SIZE (8)
+#define DNSCRYPT_RESOLVER_MAGIC { 0x72, 0x36, 0x66, 0x6e, 0x76, 0x57, 0x6a, 0x38 }
+#define DNSCRYPT_RESOLVER_MAGIC_SIZE (8)
+#define DNSCRYPT_PADDED_BLOCK_SIZE (64)
+#define DNSCRYPT_MAX_TCP_PADDING_SIZE (256)
+#define DNSCRYPT_MAX_RESPONSE_PADDING_SIZE (256)
+#define DNSCRYPT_MAX_RESPONSE_PADDING_AND_MAC_SIZE (DNSCRYPT_MAX_RESPONSE_PADDING_SIZE + DNSCRYPT_MAC_SIZE)
+
+/* "The client must check for new certificates every hour", so let's use one hour TTL */
+#define DNSCRYPT_CERTIFICATE_RESPONSE_TTL (3600)
+
+static_assert(DNSCRYPT_CLIENT_MAGIC_SIZE <= DNSCRYPT_PUBLIC_KEY_SIZE, "Dnscrypt Client Nonce size should be smaller or equal to public key size.");
+
+class DnsCryptContext;
+
+struct DnsCryptCertSignedData
+{
+ unsigned char resolverPK[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE];
+ unsigned char clientMagic[DNSCRYPT_CLIENT_MAGIC_SIZE];
+ uint32_t serial;
+ uint32_t tsStart;
+ uint32_t tsEnd;
+};
+
+struct DnsCryptCert
+{
+ unsigned char magic[4];
+ unsigned char esVersion[2];
+ unsigned char protocolMinorVersion[2];
+ unsigned char signature[DNSCRYPT_SIGNATURE_SIZE];
+ struct DnsCryptCertSignedData signedData;
+};
+
+static_assert((sizeof(DnsCryptCertSignedData) + DNSCRYPT_SIGNATURE_SIZE) == 116, "Dnscrypt cert signed data size + signature size should be 116!");
+static_assert(sizeof(DnsCryptCert) == 124, "Dnscrypt cert size should be 124!");
+
+struct DnsCryptQueryHeader
+{
+ unsigned char clientMagic[DNSCRYPT_CLIENT_MAGIC_SIZE];
+ unsigned char clientPK[DNSCRYPT_PUBLIC_KEY_SIZE];
+ unsigned char clientNonce[DNSCRYPT_NONCE_SIZE / 2];
+};
+
+static_assert(sizeof(DnsCryptQueryHeader) == 52, "Dnscrypt query header size should be 52!");
+
+class DnsCryptQuery
+{
+public:
+ static const size_t minUDPLength = 256;
+
+ DnsCryptQueryHeader header;
+ DNSName qname;
+ DnsCryptContext* ctx;
+ uint16_t id{0};
+ uint16_t len{0};
+ uint16_t paddedLen;
+ bool useOldCert{false};
+ bool encrypted{false};
+ bool valid{false};
+};
+
+struct DnsCryptResponseHeader
+{
+ const unsigned char resolverMagic[DNSCRYPT_RESOLVER_MAGIC_SIZE] = DNSCRYPT_RESOLVER_MAGIC;
+ unsigned char nonce[DNSCRYPT_NONCE_SIZE];
+};
+
+class DnsCryptPrivateKey
+{
+public:
+ DnsCryptPrivateKey();
+ ~DnsCryptPrivateKey();
+ void loadFromFile(const std::string& keyFile);
+ void saveToFile(const std::string& keyFile) const;
+
+ unsigned char key[DNSCRYPT_PRIVATE_KEY_SIZE];
+};
+
+class DnsCryptContext
+{
+public:
+ static void generateProviderKeys(unsigned char publicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE], unsigned char privateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE]);
+ static std::string getProviderFingerprint(unsigned char publicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE]);
+ static void generateCertificate(uint32_t serial, time_t begin, time_t end, const unsigned char providerPrivateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE], DnsCryptPrivateKey& privateKey, DnsCryptCert& cert);
+ static void saveCertFromFile(const DnsCryptCert& cert, const std::string&filename);
+ static std::string certificateDateToStr(uint32_t date);
+ static void generateResolverKeyPair(DnsCryptPrivateKey& privK, unsigned char pubK[DNSCRYPT_PUBLIC_KEY_SIZE]);
+
+ DnsCryptContext(const std::string& pName, const std::string& certFile, const std::string& keyFile): providerName(pName)
+ {
+ loadCertFromFile(certFile, cert);
+ privateKey.loadFromFile(keyFile);
+ computePublicKeyFromPrivate(privateKey, publicKey);
+ }
+
+ DnsCryptContext(const std::string& pName, const DnsCryptCert& certificate, const DnsCryptPrivateKey& pKey): providerName(pName), cert(certificate), privateKey(pKey)
+ {
+ computePublicKeyFromPrivate(privateKey, publicKey);
+ }
+
+ void parsePacket(char* packet, uint16_t packetSize, std::shared_ptr<DnsCryptQuery> query, bool tcp, uint16_t* decryptedQueryLen) const;
+ int encryptResponse(char* response, uint16_t responseLen, uint16_t responseSize, const std::shared_ptr<DnsCryptQuery> query, bool tcp, uint16_t* encryptedResponseLen) const;
+ void getCertificateResponse(const std::shared_ptr<DnsCryptQuery> query, std::vector<uint8_t>& response) const;
+ void loadNewCertificate(const std::string& certFile, const std::string& keyFile);
+ void setNewCertificate(const DnsCryptCert& newCert, const DnsCryptPrivateKey& newKey);
+ const DnsCryptCert& getCurrentCertificate() const { return cert; };
+ const DnsCryptCert& getOldCertificate() const { return oldCert; };
+ bool hadOldCertificate() const { return hasOldCert; };
+ const std::string& getProviderName() const { return providerName; }
+ int encryptQuery(char* query, uint16_t queryLen, uint16_t querySize, const unsigned char clientPublicKey[DNSCRYPT_PUBLIC_KEY_SIZE], const DnsCryptPrivateKey& clientPrivateKey, const unsigned char clientNonce[DNSCRYPT_NONCE_SIZE / 2], bool tcp, uint16_t* encryptedResponseLen) const;
+
+
+private:
+ static void computePublicKeyFromPrivate(const DnsCryptPrivateKey& privK, unsigned char pubK[DNSCRYPT_PUBLIC_KEY_SIZE]);
+ static void loadCertFromFile(const std::string&filename, DnsCryptCert& dest);
+
+ void parsePlaintextQuery(const char * packet, uint16_t packetSize, std::shared_ptr<DnsCryptQuery> query) const;
+ bool magicMatchesPublicKey(std::shared_ptr<DnsCryptQuery> query) const;
+ void isQueryEncrypted(const char * packet, uint16_t packetSize, std::shared_ptr<DnsCryptQuery> query, bool tcp) const;
+ void getDecryptedQuery(std::shared_ptr<DnsCryptQuery> query, bool tcp, char* packet, uint16_t packetSize, uint16_t* decryptedQueryLen) const;
+ void fillServerNonce(unsigned char* dest) const;
+ uint16_t computePaddingSize(uint16_t unpaddedLen, size_t maxLen, const unsigned char* clientNonce) const;
+
+ std::string providerName;
+ DnsCryptCert cert;
+ DnsCryptCert oldCert;
+ DnsCryptPrivateKey privateKey;
+ unsigned char publicKey[DNSCRYPT_PUBLIC_KEY_SIZE];
+ DnsCryptPrivateKey oldPrivateKey;
+ unsigned char oldPublicKey[DNSCRYPT_PUBLIC_KEY_SIZE];
+ bool hasOldCert{false};
+};
+
+#endif
--- /dev/null
+
+#include "dolog.hh"
+#include "dnsdist.hh"
+#include "dnscrypt.hh"
+
+#ifdef HAVE_DNSCRYPT
+int handleDnsCryptQuery(DnsCryptContext* ctx, char* packet, uint16_t len, std::shared_ptr<DnsCryptQuery>& query, uint16_t* decryptedQueryLen, bool tcp, std::vector<uint8_t>& response)
+{
+ query->ctx = ctx;
+
+ ctx->parsePacket(packet, len, query, tcp, decryptedQueryLen);
+
+ if (query->valid == false) {
+ vinfolog("Dropping DnsCrypt invalid query");
+ return false;
+ }
+
+ if (query->encrypted == false) {
+ ctx->getCertificateResponse(query, response);
+
+ return false;
+ }
+
+ if(*decryptedQueryLen < (int)sizeof(struct dnsheader)) {
+ g_stats.nonCompliantQueries++;
+ return false;
+ }
+
+ return true;
+}
+#endif
g_outputBuffer+=p.second;
}
});
+
+ g_lua.writeFunction("addDNSCryptBind", [](const std::string& addr, const std::string& providerName, const std::string& certFile, const std::string keyFile) {
+ if (g_configurationDone) {
+ g_outputBuffer="addDNSCryptBind cannot be used at runtime!\n";
+ return;
+ }
+#ifdef HAVE_DNSCRYPT
+ try {
+ DnsCryptContext ctx(providerName, certFile, keyFile);
+ g_dnsCryptLocals.push_back({ComboAddress(addr, 443), ctx});
+ }
+ catch(std::exception& e) {
+ errlog(e.what());
+ g_outputBuffer="Error: "+string(e.what())+"\n";
+ }
+#else
+ g_outputBuffer="Error: DNSCrypt support is not enabled.\n";
+#endif
+ });
+
+ g_lua.writeFunction("showDNSCryptBinds", []() {
+ setLuaNoSideEffect();
+#ifdef HAVE_DNSCRYPT
+ ostringstream ret;
+ boost::format fmt("%1$-3d %2% %|25t|%3$-20.20s %|26t|%4$-8d %|35t|%5$-21.21s %|56t|%6$-9d %|66t|%7$-21.21s" );
+ ret << (fmt % "#" % "Address" % "Provider Name" % "Serial" % "Validity" % "P. Serial" % "P. Validity") << endl;
+ size_t idx = 0;
+
+ for (const auto& local : g_dnsCryptLocals) {
+ const DnsCryptContext& ctx = local.second;
+ bool const hasOldCert = ctx.hadOldCertificate();
+ const DnsCryptCert& cert = ctx.getCurrentCertificate();
+ const DnsCryptCert& oldCert = ctx.getOldCertificate();
+
+ ret<< (fmt % idx % local.first.toStringWithPort() % ctx.getProviderName() % cert.signedData.serial % DnsCryptContext::certificateDateToStr(cert.signedData.tsEnd) % (hasOldCert ? oldCert.signedData.serial : 0) % (hasOldCert ? DnsCryptContext::certificateDateToStr(oldCert.signedData.tsEnd) : "-")) << endl;
+ idx++;
+ }
+
+ g_outputBuffer=ret.str();
+#else
+ g_outputBuffer="Error: DNSCrypt support is not enabled.\n";
+#endif
+ });
+
+ g_lua.writeFunction("generateDNSCryptProviderKeys", [](const std::string& publicKeyFile, const std::string privateKeyFile) {
+ setLuaNoSideEffect();
+#ifdef HAVE_DNSCRYPT
+ unsigned char publicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE];
+ unsigned char privateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE];
+ sodium_mlock(privateKey, sizeof(privateKey));
+
+ try {
+ DnsCryptContext::generateProviderKeys(publicKey, privateKey);
+
+ ofstream pubKStream(publicKeyFile);
+ pubKStream.write((char*) publicKey, sizeof(publicKey));
+ pubKStream.close();
+
+ ofstream privKStream(privateKeyFile);
+ privKStream.write((char*) privateKey, sizeof(privateKey));
+ privKStream.close();
+
+ g_outputBuffer="Provider fingerprint is: " + DnsCryptContext::getProviderFingerprint(publicKey) + "\n";
+ }
+ catch(std::exception& e) {
+ errlog(e.what());
+ g_outputBuffer="Error: "+string(e.what())+"\n";
+ }
+
+ sodium_memzero(privateKey, sizeof(privateKey));
+ sodium_munlock(privateKey, sizeof(privateKey));
+#else
+ g_outputBuffer="Error: DNSCrypt support is not enabled.\n";
+#endif
+ });
+
+ g_lua.writeFunction("generateDNSCryptCertificate", [](const std::string& providerPrivateKeyFile, const std::string& certificateFile, const std::string privateKeyFile, uint32_t serial, time_t begin, time_t end) {
+ setLuaNoSideEffect();
+#ifdef HAVE_DNSCRYPT
+ unsigned char privateKey[DNSCRYPT_PRIVATE_KEY_SIZE];
+ unsigned char providerPrivateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE];
+ sodium_mlock(providerPrivateKey, sizeof(providerPrivateKey));
+ sodium_mlock(privateKey, sizeof(privateKey));
+ sodium_memzero(providerPrivateKey, sizeof(providerPrivateKey));
+ sodium_memzero(privateKey, sizeof(privateKey));
+
+ try {
+ DnsCryptPrivateKey privateKey;
+ DnsCryptCert cert;
+ ifstream providerKStream(providerPrivateKeyFile);
+ providerKStream.read((char*) providerPrivateKey, sizeof(providerPrivateKey));
+ if (providerKStream.fail()) {
+ providerKStream.close();
+ throw std::runtime_error("Invalid DNSCrypt provider key file " + providerPrivateKeyFile);
+ }
+
+ DnsCryptContext::generateCertificate(serial, begin, end, providerPrivateKey, privateKey, cert);
+
+ privateKey.saveToFile(privateKeyFile);
+ DnsCryptContext::saveCertFromFile(cert, certificateFile);
+ }
+ catch(std::exception& e) {
+ errlog(e.what());
+ g_outputBuffer="Error: "+string(e.what())+"\n";
+ }
+
+ sodium_memzero(providerPrivateKey, sizeof(providerPrivateKey));
+ sodium_memzero(privateKey, sizeof(privateKey));
+ sodium_munlock(providerPrivateKey, sizeof(providerPrivateKey));
+ sodium_munlock(privateKey, sizeof(privateKey));
+#else
+ g_outputBuffer="Error: DNSCrypt support is not enabled.\n";
+#endif
+ });
+
}
}
char queryBuffer[qlen];
- const char * query = queryBuffer;
+ const char* query = queryBuffer;
uint16_t queryLen = qlen;
+ size_t querySize = qlen;
readn2WithTimeout(ci.fd, queryBuffer, queryLen, g_tcpRecvTimeout);
+#ifdef HAVE_DNSCRYPT
+ std::shared_ptr<DnsCryptQuery> dnsCryptQuery = 0;
+
+ if (ci.cs->dnscryptCtx) {
+ dnsCryptQuery = std::make_shared<DnsCryptQuery>();
+ uint16_t decryptedQueryLen = 0;
+ vector<uint8_t> response;
+ bool decrypted = handleDnsCryptQuery(ci.cs->dnscryptCtx, queryBuffer, queryLen, dnsCryptQuery, &decryptedQueryLen, true, response);
+
+ if (!decrypted) {
+ if (response.size() > 0) {
+ if (putNonBlockingMsgLen(ci.fd, response.size(), g_tcpSendTimeout))
+ writen2WithTimeout(ci.fd, (const char *) response.data(), response.size(), g_tcpSendTimeout);
+ }
+ break;
+ }
+ queryLen = decryptedQueryLen;
+ }
+#endif
+
uint16_t qtype;
unsigned int consumed = 0;
DNSName qname(query, queryLen, sizeof(dnsheader), false, &qtype, 0, &consumed);
{
WriteLock wl(&g_rings.queryLock);
- g_rings.queryRing.push_back({now,ci.remote,qname,(uint16_t)queryLen,qtype,*dh});
+ g_rings.queryRing.push_back({now,ci.remote,qname,queryLen,qtype,*dh});
}
g_stats.queries++;
if (ds->useECS) {
uint16_t newLen = queryLen;
- handleEDNSClientSubnet(queryBuffer, queryLen, consumed, &newLen, largerQuery, &ednsAdded, ci.remote);
+ handleEDNSClientSubnet(queryBuffer, querySize, consumed, &newLen, largerQuery, &ednsAdded, ci.remote);
if (largerQuery.empty() == false) {
query = largerQuery.c_str();
queryLen = largerQuery.size();
+ querySize = largerQuery.size();
} else {
queryLen = newLen;
}
goto retry;
}
- char answerbuffer[rlen];
+ uint16_t responseSize = rlen;
+#ifdef HAVE_DNSCRYPT
+ if (ci.cs->dnscryptCtx && (UINT16_MAX - DNSCRYPT_MAX_RESPONSE_PADDING_AND_MAC_SIZE) > rlen) {
+ responseSize += DNSCRYPT_MAX_RESPONSE_PADDING_AND_MAC_SIZE;
+ }
+#endif
+ char answerbuffer[responseSize];
readn2WithTimeout(dsock, answerbuffer, rlen, ds->tcpRecvTimeout);
struct dnsheader* responseHeaders = (struct dnsheader*)answerbuffer;
uint16_t * responseFlags = getFlagsFromDNSHeader(responseHeaders);
origFlags &= ~restoreFlagsMask;
/* set the saved flags as they were */
*responseFlags |= origFlags;
- char * response = answerbuffer;
- size_t responseLen = rlen;
+ char* response = answerbuffer;
+ uint16_t responseLen = rlen;
if (ednsAdded) {
const char * optStart = NULL;
else {
/* Removing an intermediary RR could lead to compression error */
if (rewriteResponseWithoutEDNS(response, responseLen, rewrittenResponse) == 0) {
- response = reinterpret_cast<char*>(rewrittenResponse.data());
+#ifdef HAVE_DNSCRYPT
+ if (ci.cs->dnscryptCtx && rewrittenResponse.capacity() < responseSize && ci.cs->dnscryptCtx) {
+ /* we preserve room for dnscrypt */
+ rewrittenResponse.reserve(responseSize);
+ }
+#endif
+ responseSize = responseLen;
responseLen = rewrittenResponse.size();
+ response = reinterpret_cast<char*>(rewrittenResponse.data());
}
else {
warnlog("Error rewriting content");
if(g_fixupCase) {
string realname = qname.toDNSString();
- memcpy(response+12, realname.c_str(), realname.length());
+ memcpy(response + sizeof(dnsheader), realname.c_str(), realname.length());
}
+#ifdef HAVE_DNSCRYPT
+ if (ci.cs->dnscryptCtx) {
+ uint16_t encryptedResponseLen = 0;
+ int res = ci.cs->dnscryptCtx->encryptResponse(response, responseLen, responseSize, dnsCryptQuery, true, &encryptedResponseLen);
+
+ if (res == 0) {
+ responseLen = encryptedResponseLen;
+ } else {
+ /* dropping response */
+ vinfolog("Error encrypting the response, dropping.");
+ break;
+ }
+ }
+#endif
+
if (putNonBlockingMsgLen(ci.fd, responseLen, ds->tcpSendTimeout))
writen2WithTimeout(ci.fd, response, responseLen, ds->tcpSendTimeout);
GlobalStateHolder<NetmaskGroup> g_ACL;
string g_outputBuffer;
vector<std::pair<ComboAddress, bool>> g_locals;
+#ifdef HAVE_DNSCRYPT
+std::vector<std::pair<ComboAddress,DnsCryptContext>> g_dnsCryptLocals;
+#endif
vector<ClientState *> g_frontends;
/* UDP: the grand design. Per socket we listen on for incoming queries there is one thread.
// listens on a dedicated socket, lobs answers from downstream servers to original requestors
void* responderThread(std::shared_ptr<DownstreamState> state)
{
+#ifdef HAVE_DNSCRYPT
+ char packet[4096 + DNSCRYPT_MAX_RESPONSE_PADDING_AND_MAC_SIZE];
+#else
char packet[4096];
+#endif
const uint16_t rdMask = 1 << FLAGS_RD_OFFSET;
const uint16_t cdMask = 1 << FLAGS_CD_OFFSET;
const uint16_t restoreFlagsMask = UINT16_MAX & ~(rdMask | cdMask);
int len;
for(;;) {
len = recv(state->fd, packet, sizeof(packet), 0);
- const char * response = packet;
+ char * response = packet;
size_t responseLen = len;
+#ifdef HAVE_DNSCRYPT
+ uint16_t responseSize = sizeof(packet);
+#endif
if(len < (signed)sizeof(dnsheader))
continue;
size_t optLen = 0;
bool last = false;
- int res = locateEDNSOptRR(packet, len, &optStart, &optLen, &last);
+ int res = locateEDNSOptRR(response, responseLen, &optStart, &optLen, &last);
if (res == 0) {
if (last) {
}
else {
/* Removing an intermediary RR could lead to compression error */
- if (rewriteResponseWithoutEDNS(packet, len, rewrittenResponse) == 0) {
- response = reinterpret_cast<char*>(rewrittenResponse.data());
+ if (rewriteResponseWithoutEDNS(response, responseLen, rewrittenResponse) == 0) {
responseLen = rewrittenResponse.size();
+#ifdef HAVE_DNSCRYPT
+ if (ids->dnsCryptQuery && (UINT16_MAX - DNSCRYPT_MAX_RESPONSE_PADDING_AND_MAC_SIZE) > responseLen) {
+ rewrittenResponse.reserve(responseLen + DNSCRYPT_MAX_RESPONSE_PADDING_AND_MAC_SIZE);
+ }
+ responseSize = rewrittenResponse.capacity();
+#endif
+ response = reinterpret_cast<char*>(rewrittenResponse.data());
}
else {
warnlog("Error rewriting content");
g_stats.responses++;
+#ifdef HAVE_DNSCRYPT
+ uint16_t encryptedResponseLen = 0;
+ if(ids->dnsCryptQuery) {
+ int res = ids->dnsCryptQuery->ctx->encryptResponse(response, responseLen, responseSize, ids->dnsCryptQuery, false, &encryptedResponseLen);
+
+ if (res == 0) {
+ responseLen = encryptedResponseLen;
+ } else {
+ /* dropping response */
+ vinfolog("Error encrypting the response, dropping.");
+ continue;
+ }
+ }
+#endif
+
if(ids->delayMsec && g_delay) {
DelayedPacket dp{origFD, string(response,responseLen), ids->origRemote, ids->origDest};
g_delay->submit(dp, ids->delayMsec);
else
sendfromto(origFD, response, responseLen, 0, ids->origDest, ids->origRemote);
}
+
double udiff = ids->sentTime.udiff();
vinfolog("Got answer from %s, relayed to %s, took %f usec", state->remote.toStringWithPort(), ids->origRemote.toStringWithPort(), udiff);
doAvg(g_stats.latencyAvg10000, udiff, 10000);
doAvg(g_stats.latencyAvg1000000, udiff, 1000000);
- if (ids->origFD == origFD)
+ if (ids->origFD == origFD) {
+#ifdef HAVE_DNSCRYPT
+ ids->dnsCryptQuery = 0;
+#endif
ids->origFD = -1;
+ }
rewrittenResponse.clear();
}
ComboAddress remote;
remote.sin4.sin_family = cs->local.sin4.sin_family;
char packet[1500];
- struct dnsheader* dh = (struct dnsheader*) packet;
string largerQuery;
uint16_t qtype;
typedef std::function<bool(ComboAddress, DNSName, uint16_t, dnsheader*)> blockfilter_t;
blockfilter_t blockFilter = 0;
-
-
{
std::lock_guard<std::mutex> lock(g_luamutex);
auto candidate = g_lua.readVariable<boost::optional<blockfilter_t> >("blockFilter");
struct iovec iov;
/* used by HarvestDestinationAddress */
char cbuf[256];
-
remote.sin6.sin6_family=cs->local.sin6.sin6_family;
fillMSGHdr(&msgh, &iov, cbuf, sizeof(cbuf), packet, sizeof(packet), &remote);
for(;;) {
try {
+#ifdef HAVE_DNSCRYPT
+ std::shared_ptr<DnsCryptQuery> dnsCryptQuery = 0;
+#endif
+ char* query = packet;
+ size_t querySize = sizeof(packet);
ssize_t ret = recvmsg(cs->udpFD, &msgh, 0);
cs->queries++;
g_stats.nonCompliantQueries++;
continue;
}
- uint16_t len=ret;
+
if (msgh.msg_flags & MSG_TRUNC) {
/* message was too large for our buffer */
vinfolog("Dropping message too large for our buffer");
g_stats.aclDrops++;
continue;
}
-
+
+ uint16_t len = ret;
+
+#ifdef HAVE_DNSCRYPT
+ if (cs->dnscryptCtx) {
+ vector<uint8_t> response;
+ uint16_t decryptedQueryLen = 0;
+ dnsCryptQuery = std::make_shared<DnsCryptQuery>();
+
+ bool decrypted = handleDnsCryptQuery(cs->dnscryptCtx, query, len, dnsCryptQuery, &decryptedQueryLen, false, response);
+
+ if (!decrypted) {
+ if (response.size() > 0) {
+ ComboAddress dest;
+ if(HarvestDestinationAddress(&msgh, &dest))
+ sendfromto(cs->udpFD, (const char *) response.data(), response.size(), 0, dest, remote);
+ else
+ sendto(cs->udpFD, response.data(), response.size(), 0, (struct sockaddr*)&remote, remote.getSocklen());
+ }
+ continue;
+ }
+ len = decryptedQueryLen;
+ }
+#endif
+
+ struct dnsheader* dh = (struct dnsheader*) query;
+
if(dh->qr) { // don't respond to responses
g_stats.nonCompliantQueries++;
continue;
if (dh->rd) {
g_stats.rdQueries++;
}
-
+
const uint16_t * flags = getFlagsFromDNSHeader(dh);
const uint16_t origFlags = *flags;
unsigned int consumed = 0;
- DNSName qname(packet, len, sizeof(dnsheader), false, &qtype, NULL, &consumed);
+ DNSName qname(query, len, sizeof(dnsheader), false, &qtype, NULL, &consumed);
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
{
WriteLock wl(&g_rings.queryLock);
- g_rings.queryRing.push_back({now,remote,qname,len,qtype, *dh});
+ g_rings.queryRing.push_back({now,remote,qname,len,qtype,*dh});
}
-
+
if(auto got=localDynBlock->lookup(remote)) {
if(now < got->second.until) {
vinfolog("Query from %s dropped because of dynamic block", remote.toStringWithPort());
case DNSAction::Action::Pool:
pool=ruleresult;
break;
-
case DNSAction::Action::Spoof:
;
case DNSAction::Action::HeaderModify:
dh->qr=true;
break;
-
case DNSAction::Action::Delay:
delayMsec = atoi(ruleresult.c_str()); // sorry
break;
}
if(dh->qr) { // something turned it into a response
- g_stats.selfAnswered++;
- ComboAddress dest;
- if(HarvestDestinationAddress(&msgh, &dest))
- sendfromto(cs->udpFD, packet, len, 0, dest, remote);
- else
- sendto(cs->udpFD, packet, len, 0, (struct sockaddr*)&remote, remote.getSocklen());
+ char* response = query;
+ uint16_t responseLen = len;
+#ifdef HAVE_DNSCRYPT
+ uint16_t responseSize = querySize;
+#endif
+ g_stats.selfAnswered++;
- continue;
+#ifdef HAVE_DNSCRYPT
+ uint16_t encryptedResponseLen = 0;
+
+ if(dnsCryptQuery) {
+ int res = cs->dnscryptCtx->encryptResponse(response, responseLen, responseSize, dnsCryptQuery, false, &encryptedResponseLen);
+
+ if (res == 0) {
+ responseLen = encryptedResponseLen;
+ } else {
+ /* dropping response */
+ continue;
+ }
+ }
+#endif
+ ComboAddress dest;
+ if(HarvestDestinationAddress(&msgh, &dest))
+ sendfromto(cs->udpFD, response, responseLen, 0, dest, remote);
+ else
+ sendto(cs->udpFD, response, responseLen, 0, (struct sockaddr*)&remote, remote.getSocklen());
+
+ continue;
}
DownstreamState* ss = 0;
if(!ss) {
g_stats.noPolicy++;
- continue;
+ continue;
}
-
+
ss->queries++;
-
+
unsigned int idOffset = (ss->idOffset++) % ss->idStates.size();
IDState* ids = &ss->idStates[idOffset];
ids->age = 0;
ids->delayMsec = delayMsec;
ids->origFlags = origFlags;
ids->ednsAdded = false;
+#ifdef HAVE_DNSCRYPT
+ ids->dnsCryptQuery = dnsCryptQuery;
+#endif
HarvestDestinationAddress(&msgh, &ids->origDest);
dh->id = idOffset;
if (ss->useECS) {
- handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, largerQuery, &(ids->ednsAdded), remote);
+ handleEDNSClientSubnet(query, querySize, consumed, &len, largerQuery, &(ids->ednsAdded), remote);
}
-
+
if (largerQuery.empty()) {
- ret = send(ss->fd, packet, len, 0);
+ ret = send(ss->fd, query, len, 0);
}
else {
ret = send(ss->fd, largerQuery.c_str(), largerQuery.size(), 0);
largerQuery.clear();
}
-
+
if(ret < 0) {
ss->sendErrors++;
g_stats.downstreamSendErrors++;
int one=1;
setsockopt(cs->udpFD, IPPROTO_IP, GEN_IP_PKTINFO, &one, sizeof(one)); // linux supports this, so why not - might fail on other systems
#ifdef IPV6_RECVPKTINFO
- setsockopt(cs->udpFD, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one));
+ setsockopt(cs->udpFD, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one));
#endif
}
- SBind(cs->udpFD, cs->local);
+ SBind(cs->udpFD, cs->local);
toLaunch.push_back(cs);
g_frontends.push_back(cs);
}
g_frontends.push_back(cs);
}
+#ifdef HAVE_DNSCRYPT
+ for(auto& dcLocal : g_dnsCryptLocals) {
+ ClientState* cs = new ClientState;
+ cs->local = dcLocal.first;
+ cs->dnscryptCtx = &dcLocal.second;
+ cs->udpFD = SSocket(cs->local.sin4.sin_family, SOCK_DGRAM, 0);
+ if(cs->local.sin4.sin_family == AF_INET6) {
+ SSetsockopt(cs->udpFD, IPPROTO_IPV6, IPV6_V6ONLY, 1);
+ }
+ bindAny(cs->local.sin4.sin_family, cs->udpFD);
+ if(IsAnyAddress(cs->local)) {
+ int one=1;
+ setsockopt(cs->udpFD, IPPROTO_IP, GEN_IP_PKTINFO, &one, sizeof(one)); // linux supports this, so why not - might fail on other systems
+#ifdef IPV6_RECVPKTINFO
+ setsockopt(cs->udpFD, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one));
+#endif
+ }
+ SBind(cs->udpFD, cs->local);
+ toLaunch.push_back(cs);
+ g_frontends.push_back(cs);
+
+ cs = new ClientState;
+ cs->local = dcLocal.first;
+ cs->dnscryptCtx = &dcLocal.second;
+ cs->tcpFD = SSocket(cs->local.sin4.sin_family, SOCK_STREAM, 0);
+ SSetsockopt(cs->tcpFD, SOL_SOCKET, SO_REUSEADDR, 1);
+#ifdef TCP_DEFER_ACCEPT
+ SSetsockopt(cs->tcpFD, SOL_TCP,TCP_DEFER_ACCEPT, 1);
+#endif
+ if(cs->local.sin4.sin_family == AF_INET6) {
+ SSetsockopt(cs->tcpFD, IPPROTO_IPV6, IPV6_V6ONLY, 1);
+ }
+ bindAny(cs->local.sin4.sin_family, cs->tcpFD);
+ SBind(cs->tcpFD, cs->local);
+ SListen(cs->tcpFD, 64);
+ warnlog("Listening on %s", cs->local.toStringWithPort());
+ toLaunch.push_back(cs);
+ g_frontends.push_back(cs);
+ }
+#endif
+
uid_t newgid=0;
gid_t newuid=0;
#pragma once
+#include "config.h"
#include "ext/luawrapper/include/LuaContext.hpp"
#include <time.h>
#include "misc.hh"
#include <mutex>
#include <thread>
#include "sholder.hh"
+#include "dnscrypt.hh"
void* carbonDumpThread();
uint64_t uptimeOfProcess(const std::string& str);
mutable unsigned int d_blocked{0};
};
-
struct IDState
{
IDState() : origFD(-1), delayMsec(0) { origDest.sin4.sin_family = 0;}
ComboAddress origDest; // 28
StopWatch sentTime; // 16
DNSName qname; // 80
+#ifdef HAVE_DNSCRYPT
+ std::shared_ptr<DnsCryptQuery> dnsCryptQuery{0};
+#endif
std::atomic<uint16_t> age; // 4
uint16_t qtype; // 2
uint16_t origID; // 2
struct ClientState
{
ComboAddress local;
+#ifdef HAVE_DNSCRYPT
+ DnsCryptContext* dnscryptCtx{0};
+#endif
std::atomic<uint64_t> queries{0};
int udpFD{-1};
int tcpFD{-1};
void setLuaSideEffect(); // set to report a side effect, cancelling all _no_ side effect calls
bool getLuaNoSideEffect(); // set if there were only explicit declarations of _no_ side effect
void resetLuaSideEffect(); // reset to indeterminate state
+
+#ifdef HAVE_DNSCRYPT
+extern std::vector<std::pair<ComboAddress,DnsCryptContext>> g_dnsCryptLocals;
+
+int handleDnsCryptQuery(DnsCryptContext* ctx, char* packet, uint16_t len, std::shared_ptr<DnsCryptQuery>& query, uint16_t* decryptedQueryLen, bool tcp, std::vector<uint8_t>& reponse);
+#endif
dnsdist_SOURCES = \
base64.hh \
dns.cc dns.hh \
+ dnscrypt.cc dnscrypt.hh \
dnsdist.cc dnsdist.hh \
dnsdist-carbon.cc \
dnsdist-console.cc \
+ dnsdist-dnscrypt.cc \
dnsdist-ecs.cc dnsdist-ecs.hh \
dnsdist-lua.cc \
dnsdist-lua2.cc \
testrunner_SOURCES = \
- base64.hh dns.hh \
+ base64.hh \
+ dns.hh \
test-base64_cc.cc \
- test-dnsdist_cc.cc dnsdist.hh \
+ test-dnsdist_cc.cc \
+ test-dnscrypt_cc.cc \
+ dnsdist.hh \
dnsdist-ecs.cc dnsdist-ecs.hh \
+ dnscrypt.cc dnscrypt.hh \
dnslabeltext.cc \
dnsname.cc dnsname.hh \
dnsparser.hh dnsparser.cc \
testrunner_LDADD = \
$(BOOST_UNIT_TEST_FRAMEWORK_LIBS) \
+ $(LIBSODIUM_LIBS) \
$(RT_LIBS)
BOOST_REQUIRE([1.35])
BOOST_FOREACH
PDNS_ENABLE_UNIT_TESTS
+DNSDIST_ENABLE_DNSCRYPT
+
AC_SUBST([YAHTTP_CFLAGS], ['-I$(top_srcdir)/ext/yahttp'])
AC_SUBST([YAHTTP_LIBS], ['-L$(top_builddir)/ext/yahttp/yahttp -lyahttp'])
--- /dev/null
+../dnscrypt.cc
\ No newline at end of file
--- /dev/null
+../dnscrypt.hh
\ No newline at end of file
--- /dev/null
+../dnsdist-dnscrypt.cc
\ No newline at end of file
--- /dev/null
+../dnsdist-dnscrypt.hh
\ No newline at end of file
--- /dev/null
+AC_DEFUN([DNSDIST_ENABLE_DNSCRYPT], [
+ AC_MSG_CHECKING([whether to enable DNSCrypt support])
+ AC_ARG_ENABLE([dnscrypt],
+ AS_HELP_STRING([--enable-dnscrypt], [enable DNSCrypt support (require libsodium) @<:@default=no@:>@]),
+ [enable_dnscrypt=$enableval],
+ [enable_dnscrypt=no]
+ )
+ AC_MSG_RESULT([$enable_dnscrypt])
+ AM_CONDITIONAL([DNSCRYPT], [test "x$enable_dnscrypt" != "xno"])
+
+ AM_COND_IF([DNSCRYPT], [
+ AM_COND_IF([LIBSODIUM], [
+ AC_DEFINE([HAVE_DNSCRYPT], [1], [Define to 1 if you enable dnscrypt support])
+ ],[
+ AC_MSG_ERROR([dnscrypt support requested but libsodium is not available])
+ ])
+ ])
+])
--- /dev/null
+../test-dnscrypt_cc.cc
\ No newline at end of file
--- /dev/null
+
+/*
+ PowerDNS Versatile Database Driven Nameserver
+ Copyright (C) 2013 - 2015 PowerDNS.COM BV
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2
+ as published by the Free Software Foundation
+
+ Additionally, the license of this program contains a special
+ exception which allows to distribute the program in binary form when
+ it is linked against OpenSSL.
+
+ 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+
+#include <boost/test/unit_test.hpp>
+
+#include "dnscrypt.hh"
+#include "dnsname.hh"
+#include "dnsparser.hh"
+#include "dnswriter.hh"
+#include <unistd.h>
+
+bool g_verbose{true};
+bool g_console{true};
+
+BOOST_AUTO_TEST_SUITE(dnscrypt_cc)
+
+#ifdef HAVE_DNSCRYPT
+
+// plaintext query for cert
+BOOST_AUTO_TEST_CASE(DNSCryptPlaintextQuery) {
+ DnsCryptPrivateKey resolverPrivateKey;
+ DnsCryptCert resolverCert;
+ unsigned char providerPublicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE];
+ unsigned char providerPrivateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE];
+ time_t now = time(NULL);
+ DnsCryptContext::generateProviderKeys(providerPublicKey, providerPrivateKey);
+ DnsCryptContext::generateCertificate(1, now, now + (24 * 60 * 3600), providerPrivateKey, resolverPrivateKey, resolverCert);
+ DnsCryptContext ctx("2.name", resolverCert, resolverPrivateKey);
+
+ DNSName name("2.name.");
+ vector<uint8_t> plainQuery;
+ DNSPacketWriter pw(plainQuery, name, QType::TXT, QClass::IN, 0);
+ pw.getHeader()->rd = 0;
+ uint16_t len = plainQuery.size();
+
+ std::shared_ptr<DnsCryptQuery> query = std::make_shared<DnsCryptQuery>();
+ uint16_t decryptedLen = 0;
+
+ ctx.parsePacket((char*) plainQuery.data(), len, query, false, &decryptedLen);
+
+ BOOST_CHECK_EQUAL(query->valid, true);
+ BOOST_CHECK_EQUAL(query->encrypted, false);
+
+ std::vector<uint8_t> response;
+
+ ctx.getCertificateResponse(query, response);
+
+ MOADNSParser mdp((char*) response.data(), response.size());
+
+ BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1);
+ BOOST_CHECK_EQUAL(mdp.d_header.ancount, 1);
+ BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0);
+ BOOST_CHECK_EQUAL(mdp.d_header.arcount, 0);
+
+ BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "2.name.");
+ BOOST_CHECK_EQUAL(mdp.d_qclass, QClass::IN);
+ BOOST_CHECK_EQUAL(mdp.d_qtype, QType::TXT);
+}
+
+// invalid plaintext query (A)
+BOOST_AUTO_TEST_CASE(DNSCryptPlaintextQueryInvalidA) {
+ DnsCryptPrivateKey resolverPrivateKey;
+ DnsCryptCert resolverCert;
+ unsigned char providerPublicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE];
+ unsigned char providerPrivateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE];
+ time_t now = time(NULL);
+ DnsCryptContext::generateProviderKeys(providerPublicKey, providerPrivateKey);
+ DnsCryptContext::generateCertificate(1, now, now + (24 * 60 * 3600), providerPrivateKey, resolverPrivateKey, resolverCert);
+ DnsCryptContext ctx("2.name", resolverCert, resolverPrivateKey);
+
+ DNSName name("2.name.");
+
+ vector<uint8_t> plainQuery;
+ DNSPacketWriter pw(plainQuery, name, QType::A, QClass::IN, 0);
+ pw.getHeader()->rd = 0;
+ uint16_t len = plainQuery.size();
+
+ std::shared_ptr<DnsCryptQuery> query = std::make_shared<DnsCryptQuery>();
+ uint16_t decryptedLen = 0;
+
+ ctx.parsePacket((char*) plainQuery.data(), len, query, false, &decryptedLen);
+
+ BOOST_CHECK_EQUAL(query->valid, false);
+}
+
+// invalid plaintext query (wrong provider name)
+BOOST_AUTO_TEST_CASE(DNSCryptPlaintextQueryInvalidProviderName) {
+ DnsCryptPrivateKey resolverPrivateKey;
+ DnsCryptCert resolverCert;
+ unsigned char providerPublicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE];
+ unsigned char providerPrivateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE];
+ time_t now = time(NULL);
+ DnsCryptContext::generateProviderKeys(providerPublicKey, providerPrivateKey);
+ DnsCryptContext::generateCertificate(1, now, now + (24 * 60 * 3600), providerPrivateKey, resolverPrivateKey, resolverCert);
+ DnsCryptContext ctx("2.name", resolverCert, resolverPrivateKey);
+
+ DNSName name("2.WRONG.name.");
+
+ vector<uint8_t> plainQuery;
+ DNSPacketWriter pw(plainQuery, name, QType::TXT, QClass::IN, 0);
+ pw.getHeader()->rd = 0;
+ uint16_t len = plainQuery.size();
+
+ std::shared_ptr<DnsCryptQuery> query = std::make_shared<DnsCryptQuery>();
+ uint16_t decryptedLen = 0;
+
+ ctx.parsePacket((char*) plainQuery.data(), len, query, false, &decryptedLen);
+
+ BOOST_CHECK_EQUAL(query->valid, false);
+}
+
+// valid encrypted query
+BOOST_AUTO_TEST_CASE(DNSCryptEncryptedQueryValid) {
+ DnsCryptPrivateKey resolverPrivateKey;
+ DnsCryptCert resolverCert;
+ unsigned char providerPublicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE];
+ unsigned char providerPrivateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE];
+ time_t now = time(NULL);
+ DnsCryptContext::generateProviderKeys(providerPublicKey, providerPrivateKey);
+ DnsCryptContext::generateCertificate(1, now, now + (24 * 60 * 3600), providerPrivateKey, resolverPrivateKey, resolverCert);
+ DnsCryptContext ctx("2.name", resolverCert, resolverPrivateKey);
+
+ DnsCryptPrivateKey clientPrivateKey;
+ unsigned char clientPublicKey[DNSCRYPT_PUBLIC_KEY_SIZE];
+
+ DnsCryptContext::generateResolverKeyPair(clientPrivateKey, clientPublicKey);
+
+ unsigned char clientNonce[DNSCRYPT_NONCE_SIZE / 2] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x09, 0x0A, 0x0B };
+
+ DNSName name("www.powerdns.com.");
+ vector<uint8_t> plainQuery;
+ DNSPacketWriter pw(plainQuery, name, QType::AAAA, QClass::IN, 0);
+ pw.getHeader()->rd = 1;
+ size_t requiredSize = plainQuery.size() + sizeof(DnsCryptQueryHeader) + DNSCRYPT_MAC_SIZE;
+ if (requiredSize < DnsCryptQuery::minUDPLength) {
+ requiredSize = DnsCryptQuery::minUDPLength;
+ }
+
+ plainQuery.reserve(requiredSize);
+ uint16_t len = plainQuery.size();
+ uint16_t encryptedResponseLen = 0;
+
+ int res = ctx.encryptQuery((char*) plainQuery.data(), len, plainQuery.capacity(), clientPublicKey, clientPrivateKey, clientNonce, false, &encryptedResponseLen);
+
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK(encryptedResponseLen > len);
+
+ std::shared_ptr<DnsCryptQuery> query = std::make_shared<DnsCryptQuery>();
+ uint16_t decryptedLen = 0;
+
+ ctx.parsePacket((char*) plainQuery.data(), encryptedResponseLen, query, false, &decryptedLen);
+
+ BOOST_CHECK_EQUAL(query->valid, true);
+ BOOST_CHECK_EQUAL(query->encrypted, true);
+
+ MOADNSParser mdp((char*) plainQuery.data(), decryptedLen);
+
+ BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1);
+ BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0);
+ BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0);
+ BOOST_CHECK_EQUAL(mdp.d_header.arcount, 0);
+
+ BOOST_CHECK_EQUAL(mdp.d_qname, name);
+ BOOST_CHECK_EQUAL(mdp.d_qclass, QClass::IN);
+ BOOST_CHECK_EQUAL(mdp.d_qtype, QType::AAAA);
+}
+
+// valid encrypted query with not enough room
+BOOST_AUTO_TEST_CASE(DNSCryptEncryptedQueryValidButShort) {
+ DnsCryptPrivateKey resolverPrivateKey;
+ DnsCryptCert resolverCert;
+ unsigned char providerPublicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE];
+ unsigned char providerPrivateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE];
+ time_t now = time(NULL);
+ DnsCryptContext::generateProviderKeys(providerPublicKey, providerPrivateKey);
+ DnsCryptContext::generateCertificate(1, now, now + (24 * 60 * 3600), providerPrivateKey, resolverPrivateKey, resolverCert);
+ DnsCryptContext ctx("2.name", resolverCert, resolverPrivateKey);
+
+ DnsCryptPrivateKey clientPrivateKey;
+ unsigned char clientPublicKey[DNSCRYPT_PUBLIC_KEY_SIZE];
+
+ DnsCryptContext::generateResolverKeyPair(clientPrivateKey, clientPublicKey);
+
+ unsigned char clientNonce[DNSCRYPT_NONCE_SIZE / 2] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x09, 0x0A, 0x0B };
+
+ DNSName name("www.powerdns.com.");
+ vector<uint8_t> plainQuery;
+ DNSPacketWriter pw(plainQuery, name, QType::AAAA, QClass::IN, 0);
+ pw.getHeader()->rd = 1;
+
+ uint16_t len = plainQuery.size();
+ uint16_t encryptedResponseLen = 0;
+
+ int res = ctx.encryptQuery((char*) plainQuery.data(), len, plainQuery.capacity(), clientPublicKey, clientPrivateKey, clientNonce, false, &encryptedResponseLen);
+
+ BOOST_CHECK_EQUAL(res, ENOBUFS);
+}
+
+// valid encrypted query with old key
+BOOST_AUTO_TEST_CASE(DNSCryptEncryptedQueryValidWithOldKey) {
+ DnsCryptPrivateKey resolverPrivateKey;
+ DnsCryptCert resolverCert;
+ unsigned char providerPublicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE];
+ unsigned char providerPrivateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE];
+ time_t now = time(NULL);
+ DnsCryptContext::generateProviderKeys(providerPublicKey, providerPrivateKey);
+ DnsCryptContext::generateCertificate(1, now, now + (24 * 60 * 3600), providerPrivateKey, resolverPrivateKey, resolverCert);
+ DnsCryptContext ctx("2.name", resolverCert, resolverPrivateKey);
+
+ DnsCryptPrivateKey clientPrivateKey;
+ unsigned char clientPublicKey[DNSCRYPT_PUBLIC_KEY_SIZE];
+
+ DnsCryptContext::generateResolverKeyPair(clientPrivateKey, clientPublicKey);
+
+ unsigned char clientNonce[DNSCRYPT_NONCE_SIZE / 2] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x09, 0x0A, 0x0B };
+
+ DNSName name("www.powerdns.com.");
+ vector<uint8_t> plainQuery;
+ DNSPacketWriter pw(plainQuery, name, QType::AAAA, QClass::IN, 0);
+ pw.getHeader()->rd = 1;
+
+ size_t requiredSize = plainQuery.size() + sizeof(DnsCryptQueryHeader) + DNSCRYPT_MAC_SIZE;
+ if (requiredSize < DnsCryptQuery::minUDPLength) {
+ requiredSize = DnsCryptQuery::minUDPLength;
+ }
+
+ plainQuery.reserve(requiredSize);
+
+ uint16_t len = plainQuery.size();
+ uint16_t encryptedResponseLen = 0;
+
+ int res = ctx.encryptQuery((char*) plainQuery.data(), len, plainQuery.capacity(), clientPublicKey, clientPrivateKey, clientNonce, false, &encryptedResponseLen);
+
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK(encryptedResponseLen > len);
+
+ DnsCryptContext::generateCertificate(1, now, now + (24 * 60 * 3600), providerPrivateKey, resolverPrivateKey, resolverCert);
+ ctx.setNewCertificate(resolverCert, resolverPrivateKey);
+
+ std::shared_ptr<DnsCryptQuery> query = std::make_shared<DnsCryptQuery>();
+ uint16_t decryptedLen = 0;
+
+ ctx.parsePacket((char*) plainQuery.data(), encryptedResponseLen, query, false, &decryptedLen);
+
+ BOOST_CHECK_EQUAL(query->valid, true);
+ BOOST_CHECK_EQUAL(query->encrypted, true);
+
+ MOADNSParser mdp((char*) plainQuery.data(), decryptedLen);
+
+ BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1);
+ BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0);
+ BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0);
+ BOOST_CHECK_EQUAL(mdp.d_header.arcount, 0);
+
+ BOOST_CHECK_EQUAL(mdp.d_qname, name);
+ BOOST_CHECK_EQUAL(mdp.d_qclass, QClass::IN);
+ BOOST_CHECK_EQUAL(mdp.d_qtype, QType::AAAA);
+}
+
+// valid encrypted query with wrong key
+BOOST_AUTO_TEST_CASE(DNSCryptEncryptedQueryInvalidWithWrongKey) {
+ DnsCryptPrivateKey resolverPrivateKey;
+ DnsCryptCert resolverCert;
+ unsigned char providerPublicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE];
+ unsigned char providerPrivateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE];
+ time_t now = time(NULL);
+ DnsCryptContext::generateProviderKeys(providerPublicKey, providerPrivateKey);
+ DnsCryptContext::generateCertificate(1, now, now + (24 * 60 * 3600), providerPrivateKey, resolverPrivateKey, resolverCert);
+ DnsCryptContext ctx("2.name", resolverCert, resolverPrivateKey);
+
+ DnsCryptPrivateKey clientPrivateKey;
+ unsigned char clientPublicKey[DNSCRYPT_PUBLIC_KEY_SIZE];
+
+ DnsCryptContext::generateResolverKeyPair(clientPrivateKey, clientPublicKey);
+
+ unsigned char clientNonce[DNSCRYPT_NONCE_SIZE / 2] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x09, 0x0A, 0x0B };
+
+ DNSName name("www.powerdns.com.");
+ vector<uint8_t> plainQuery;
+ DNSPacketWriter pw(plainQuery, name, QType::AAAA, QClass::IN, 0);
+ pw.getHeader()->rd = 1;
+
+ size_t requiredSize = plainQuery.size() + sizeof(DnsCryptQueryHeader) + DNSCRYPT_MAC_SIZE;
+ if (requiredSize < DnsCryptQuery::minUDPLength) {
+ requiredSize = DnsCryptQuery::minUDPLength;
+ }
+
+ plainQuery.reserve(requiredSize);
+
+ uint16_t len = plainQuery.size();
+ uint16_t encryptedResponseLen = 0;
+
+ int res = ctx.encryptQuery((char*) plainQuery.data(), len, plainQuery.capacity(), clientPublicKey, clientPrivateKey, clientNonce, false, &encryptedResponseLen);
+
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK(encryptedResponseLen > len);
+
+ DnsCryptContext::generateCertificate(1, now, now + (24 * 60 * 3600), providerPrivateKey, resolverPrivateKey, resolverCert);
+ ctx.setNewCertificate(resolverCert, resolverPrivateKey);
+
+ DnsCryptContext::generateCertificate(1, now, now + (24 * 60 * 3600), providerPrivateKey, resolverPrivateKey, resolverCert);
+ ctx.setNewCertificate(resolverCert, resolverPrivateKey);
+
+ /* we have changed the key two times, we don't have the one used to encrypt this query */
+
+ std::shared_ptr<DnsCryptQuery> query = std::make_shared<DnsCryptQuery>();
+ uint16_t decryptedLen = 0;
+
+ ctx.parsePacket((char*) plainQuery.data(), encryptedResponseLen, query, false, &decryptedLen);
+
+ BOOST_CHECK_EQUAL(query->valid, false);
+}
+
+#endif
+
+BOOST_AUTO_TEST_SUITE_END();