]> granicus.if.org Git - pdns/commitdiff
Add DNSCrypt support for dnsdist
authorRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 21 Dec 2015 08:35:21 +0000 (09:35 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 22 Dec 2015 09:39:10 +0000 (10:39 +0100)
The support is disabled by default and can be enabled
with --enable-dnscrypt.
Creating certificates and keys is supported, as well
as basic unit tests.

19 files changed:
pdns/Makefile.am
pdns/README-dnsdist.md
pdns/dnscrypt.cc [new file with mode: 0644]
pdns/dnscrypt.hh [new file with mode: 0644]
pdns/dnsdist-dnscrypt.cc [new file with mode: 0644]
pdns/dnsdist-dnscrypt.hh [new file with mode: 0644]
pdns/dnsdist-lua2.cc
pdns/dnsdist-tcp.cc
pdns/dnsdist.cc
pdns/dnsdist.hh
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/configure.ac
pdns/dnsdistdist/dnscrypt.cc [new symlink]
pdns/dnsdistdist/dnscrypt.hh [new symlink]
pdns/dnsdistdist/dnsdist-dnscrypt.cc [new symlink]
pdns/dnsdistdist/dnsdist-dnscrypt.hh [new symlink]
pdns/dnsdistdist/m4/dnsdist_enable_dnscrypt.m4 [new file with mode: 0644]
pdns/dnsdistdist/test-dnscrypt_cc.cc [new symlink]
pdns/test-dnscrypt_cc.cc [new file with mode: 0644]

index 81c80156bc0a7554ddb0ac4a50d2b2a3bd0af17a..ac4525f5a30e311f583465b63654e92f915245a3 100644 (file)
@@ -600,11 +600,13 @@ dnsdist_SOURCES = \
        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 \
index 18fd4eb0211e9e97a0428eeb9b44d8ea2810ac49..4bed3836c03babe8f6a48fb921576a2990ca3381 100644 (file)
@@ -561,6 +561,37 @@ latest version of [PowerDNS
 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:
diff --git a/pdns/dnscrypt.cc b/pdns/dnscrypt.cc
new file mode 100644 (file)
index 0000000..02492be
--- /dev/null
@@ -0,0 +1,503 @@
+
+#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
diff --git a/pdns/dnscrypt.hh b/pdns/dnscrypt.hh
new file mode 100644 (file)
index 0000000..2d9215d
--- /dev/null
@@ -0,0 +1,158 @@
+#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
diff --git a/pdns/dnsdist-dnscrypt.cc b/pdns/dnsdist-dnscrypt.cc
new file mode 100644 (file)
index 0000000..9b9c991
--- /dev/null
@@ -0,0 +1,31 @@
+
+#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
diff --git a/pdns/dnsdist-dnscrypt.hh b/pdns/dnsdist-dnscrypt.hh
new file mode 100644 (file)
index 0000000..e69de29
index 91d600f1c83bd48b1d1165e658d3a83223a4a8df..396a11d259887a62e2526c1f112aba4fc4f8338e 100644 (file)
@@ -307,4 +307,119 @@ void moreLua()
         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
+    });
+
 }
index a92820c963fa93b6c0770f09abd2c0c12e629812..51ecb73b27f9febe6d22e64101c75925150c2fd1 100644 (file)
@@ -159,9 +159,30 @@ void* tcpClientThread(int pipefd)
         }
 
         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);
@@ -174,7 +195,7 @@ void* tcpClientThread(int pipefd)
 
        {
          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++;
@@ -263,10 +284,11 @@ void* tcpClientThread(int pipefd)
 
         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;
           }
@@ -325,7 +347,13 @@ void* tcpClientThread(int pipefd)
           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);
@@ -335,8 +363,8 @@ void* tcpClientThread(int pipefd)
         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;
@@ -356,8 +384,15 @@ void* tcpClientThread(int pipefd)
             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");
@@ -368,9 +403,24 @@ void* tcpClientThread(int pipefd)
 
        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);
 
index 813e2a7902eaa231704439b38c11889bfca8269d..2b9d17d2fa399265d26ad97a20290df879a8b0a8 100644 (file)
@@ -65,6 +65,9 @@ bool g_console;
 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.
@@ -145,7 +148,11 @@ DelayPipe<DelayedPacket> * g_delay = 0;
 // 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);
@@ -155,8 +162,11 @@ void* responderThread(std::shared_ptr<DownstreamState> state)
   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;
@@ -196,7 +206,7 @@ void* responderThread(std::shared_ptr<DownstreamState> state)
       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) {
@@ -208,9 +218,15 @@ void* responderThread(std::shared_ptr<DownstreamState> state)
         }
         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");
@@ -221,6 +237,21 @@ void* responderThread(std::shared_ptr<DownstreamState> state)
 
     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);
@@ -231,6 +262,7 @@ void* responderThread(std::shared_ptr<DownstreamState> state)
       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);
 
@@ -260,8 +292,12 @@ void* responderThread(std::shared_ptr<DownstreamState> state)
     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();
   }
@@ -451,14 +487,11 @@ try
   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");
@@ -474,12 +507,16 @@ try
   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++;
@@ -489,7 +526,7 @@ try
        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");
@@ -502,7 +539,33 @@ try
        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;
@@ -511,18 +574,18 @@ try
       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());
@@ -567,13 +630,11 @@ try
       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;
@@ -583,14 +644,34 @@ try
       }
 
       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;
@@ -603,11 +684,11 @@ try
 
       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;
@@ -629,22 +710,25 @@ try
       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++;
@@ -1024,11 +1108,11 @@ try
       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);
   }
@@ -1060,6 +1144,47 @@ try
     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;
 
index 724dbc13989e828a55b1ee9fe56ef55a7f49b4c2..619dd8106d60d0ac79be7c95ed46be55c6a10538 100644 (file)
@@ -1,4 +1,5 @@
 #pragma once
+#include "config.h"
 #include "ext/luawrapper/include/LuaContext.hpp"
 #include <time.h>
 #include "misc.hh"
@@ -10,6 +11,7 @@
 #include <mutex>
 #include <thread>
 #include "sholder.hh"
+#include "dnscrypt.hh"
 void* carbonDumpThread();
 uint64_t uptimeOfProcess(const std::string& str);
 
@@ -173,7 +175,6 @@ private:
   mutable unsigned int d_blocked{0};
 };
 
-
 struct IDState
 {
   IDState() : origFD(-1), delayMsec(0) { origDest.sin4.sin_family = 0;}
@@ -193,6 +194,9 @@ struct IDState
   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
@@ -241,6 +245,9 @@ extern Rings g_rings;
 struct ClientState
 {
   ComboAddress local;
+#ifdef HAVE_DNSCRYPT
+  DnsCryptContext* dnscryptCtx{0};
+#endif
   std::atomic<uint64_t> queries{0};
   int udpFD{-1};
   int tcpFD{-1};
@@ -441,3 +448,9 @@ void setLuaNoSideEffect(); // if nothing has been declared, set that there are n
 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
index fba3eb59a11dafb70e95f784213db8e3634b703b..2e451670d24c442a026fe52659fc1f866dbda69e 100644 (file)
@@ -32,9 +32,11 @@ 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 \
@@ -83,10 +85,14 @@ dnsdist_LDADD = \
 
 
 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 \
@@ -111,5 +117,6 @@ testrunner_LDFLAGS = \
 
 testrunner_LDADD = \
        $(BOOST_UNIT_TEST_FRAMEWORK_LIBS) \
+       $(LIBSODIUM_LIBS) \
        $(RT_LIBS)
 
index a14f308f75eedd54d008f24d8db23df7a059ad30..4b9b52ef4326d831f36f1bac85c3c22d7b750787 100644 (file)
@@ -12,6 +12,8 @@ PDNS_CHECK_CLOCK_GETTIME
 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'])
 
diff --git a/pdns/dnsdistdist/dnscrypt.cc b/pdns/dnsdistdist/dnscrypt.cc
new file mode 120000 (symlink)
index 0000000..e89a6c2
--- /dev/null
@@ -0,0 +1 @@
+../dnscrypt.cc
\ No newline at end of file
diff --git a/pdns/dnsdistdist/dnscrypt.hh b/pdns/dnsdistdist/dnscrypt.hh
new file mode 120000 (symlink)
index 0000000..0a1e757
--- /dev/null
@@ -0,0 +1 @@
+../dnscrypt.hh
\ No newline at end of file
diff --git a/pdns/dnsdistdist/dnsdist-dnscrypt.cc b/pdns/dnsdistdist/dnsdist-dnscrypt.cc
new file mode 120000 (symlink)
index 0000000..9f90566
--- /dev/null
@@ -0,0 +1 @@
+../dnsdist-dnscrypt.cc
\ No newline at end of file
diff --git a/pdns/dnsdistdist/dnsdist-dnscrypt.hh b/pdns/dnsdistdist/dnsdist-dnscrypt.hh
new file mode 120000 (symlink)
index 0000000..b205548
--- /dev/null
@@ -0,0 +1 @@
+../dnsdist-dnscrypt.hh
\ No newline at end of file
diff --git a/pdns/dnsdistdist/m4/dnsdist_enable_dnscrypt.m4 b/pdns/dnsdistdist/m4/dnsdist_enable_dnscrypt.m4
new file mode 100644 (file)
index 0000000..80a22a8
--- /dev/null
@@ -0,0 +1,18 @@
+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])
+    ])
+  ])
+])
diff --git a/pdns/dnsdistdist/test-dnscrypt_cc.cc b/pdns/dnsdistdist/test-dnscrypt_cc.cc
new file mode 120000 (symlink)
index 0000000..8f01a82
--- /dev/null
@@ -0,0 +1 @@
+../test-dnscrypt_cc.cc
\ No newline at end of file
diff --git a/pdns/test-dnscrypt_cc.cc b/pdns/test-dnscrypt_cc.cc
new file mode 100644 (file)
index 0000000..2b2c125
--- /dev/null
@@ -0,0 +1,339 @@
+
+/*
+    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();