From: Remi Gacogne Date: Mon, 21 Dec 2015 08:35:21 +0000 (+0100) Subject: Add DNSCrypt support for dnsdist X-Git-Tag: dnsdist-1.0.0-alpha1~35^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=refs%2Fpull%2F3055%2Fhead;p=thirdparty%2Fpdns.git Add DNSCrypt support for dnsdist 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. --- diff --git a/pdns/Makefile.am b/pdns/Makefile.am index 81c80156bc..ac4525f5a3 100644 --- a/pdns/Makefile.am +++ b/pdns/Makefile.am @@ -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 \ diff --git a/pdns/README-dnsdist.md b/pdns/README-dnsdist.md index 18fd4eb021..4bed3836c0 100644 --- a/pdns/README-dnsdist.md +++ b/pdns/README-dnsdist.md @@ -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 index 0000000000..02492bedd8 --- /dev/null +++ b/pdns/dnscrypt.cc @@ -0,0 +1,503 @@ + +#include "config.h" +#ifdef HAVE_DNSCRYPT +#include +#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(publicKey[idx]) % static_cast(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 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 query, vector& 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 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 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 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 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 must be between 0 and 256 bytes, + and must be constant for a given (, ) 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 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 index 0000000000..2d9215df2f --- /dev/null +++ b/pdns/dnscrypt.hh @@ -0,0 +1,158 @@ +#pragma once +#include "config.h" + +#ifdef HAVE_DNSCRYPT + +#include +#include +#include +#include + +#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 query, bool tcp, uint16_t* decryptedQueryLen) const; + int encryptResponse(char* response, uint16_t responseLen, uint16_t responseSize, const std::shared_ptr query, bool tcp, uint16_t* encryptedResponseLen) const; + void getCertificateResponse(const std::shared_ptr query, std::vector& 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 query) const; + bool magicMatchesPublicKey(std::shared_ptr query) const; + void isQueryEncrypted(const char * packet, uint16_t packetSize, std::shared_ptr query, bool tcp) const; + void getDecryptedQuery(std::shared_ptr 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 index 0000000000..9b9c991a9b --- /dev/null +++ b/pdns/dnsdist-dnscrypt.cc @@ -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& query, uint16_t* decryptedQueryLen, bool tcp, std::vector& 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 index 0000000000..e69de29bb2 diff --git a/pdns/dnsdist-lua2.cc b/pdns/dnsdist-lua2.cc index 91d600f1c8..396a11d259 100644 --- a/pdns/dnsdist-lua2.cc +++ b/pdns/dnsdist-lua2.cc @@ -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 + }); + } diff --git a/pdns/dnsdist-tcp.cc b/pdns/dnsdist-tcp.cc index a92820c963..51ecb73b27 100644 --- a/pdns/dnsdist-tcp.cc +++ b/pdns/dnsdist-tcp.cc @@ -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 = 0; + + if (ci.cs->dnscryptCtx) { + dnsCryptQuery = std::make_shared(); + uint16_t decryptedQueryLen = 0; + vector 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(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(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); diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc index 813e2a7902..2b9d17d2fa 100644 --- a/pdns/dnsdist.cc +++ b/pdns/dnsdist.cc @@ -65,6 +65,9 @@ bool g_console; GlobalStateHolder g_ACL; string g_outputBuffer; vector> g_locals; +#ifdef HAVE_DNSCRYPT +std::vector> g_dnsCryptLocals; +#endif vector g_frontends; /* UDP: the grand design. Per socket we listen on for incoming queries there is one thread. @@ -145,7 +148,11 @@ DelayPipe * g_delay = 0; // listens on a dedicated socket, lobs answers from downstream servers to original requestors void* responderThread(std::shared_ptr 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 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 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 state) } else { /* Removing an intermediary RR could lead to compression error */ - if (rewriteResponseWithoutEDNS(packet, len, rewrittenResponse) == 0) { - response = reinterpret_cast(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(rewrittenResponse.data()); } else { warnlog("Error rewriting content"); @@ -221,6 +237,21 @@ void* responderThread(std::shared_ptr 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 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 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 blockfilter_t; blockfilter_t blockFilter = 0; - - { std::lock_guard lock(g_luamutex); auto candidate = g_lua.readVariable >("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 = 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 response; + uint16_t decryptedQueryLen = 0; + dnsCryptQuery = std::make_shared(); + + 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; diff --git a/pdns/dnsdist.hh b/pdns/dnsdist.hh index 724dbc1398..619dd8106d 100644 --- a/pdns/dnsdist.hh +++ b/pdns/dnsdist.hh @@ -1,4 +1,5 @@ #pragma once +#include "config.h" #include "ext/luawrapper/include/LuaContext.hpp" #include #include "misc.hh" @@ -10,6 +11,7 @@ #include #include #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{0}; +#endif std::atomic 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 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> g_dnsCryptLocals; + +int handleDnsCryptQuery(DnsCryptContext* ctx, char* packet, uint16_t len, std::shared_ptr& query, uint16_t* decryptedQueryLen, bool tcp, std::vector& reponse); +#endif diff --git a/pdns/dnsdistdist/Makefile.am b/pdns/dnsdistdist/Makefile.am index fba3eb59a1..2e451670d2 100644 --- a/pdns/dnsdistdist/Makefile.am +++ b/pdns/dnsdistdist/Makefile.am @@ -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) diff --git a/pdns/dnsdistdist/configure.ac b/pdns/dnsdistdist/configure.ac index a14f308f75..4b9b52ef43 100644 --- a/pdns/dnsdistdist/configure.ac +++ b/pdns/dnsdistdist/configure.ac @@ -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 index 0000000000..e89a6c2742 --- /dev/null +++ b/pdns/dnsdistdist/dnscrypt.cc @@ -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 index 0000000000..0a1e757e16 --- /dev/null +++ b/pdns/dnsdistdist/dnscrypt.hh @@ -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 index 0000000000..9f90566bff --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-dnscrypt.cc @@ -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 index 0000000000..b20554880c --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-dnscrypt.hh @@ -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 index 0000000000..80a22a81b4 --- /dev/null +++ b/pdns/dnsdistdist/m4/dnsdist_enable_dnscrypt.m4 @@ -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 index 0000000000..8f01a82241 --- /dev/null +++ b/pdns/dnsdistdist/test-dnscrypt_cc.cc @@ -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 index 0000000000..2b2c1255b3 --- /dev/null +++ b/pdns/test-dnscrypt_cc.cc @@ -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 + +#include "dnscrypt.hh" +#include "dnsname.hh" +#include "dnsparser.hh" +#include "dnswriter.hh" +#include + +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 plainQuery; + DNSPacketWriter pw(plainQuery, name, QType::TXT, QClass::IN, 0); + pw.getHeader()->rd = 0; + uint16_t len = plainQuery.size(); + + std::shared_ptr query = std::make_shared(); + 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 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 plainQuery; + DNSPacketWriter pw(plainQuery, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 0; + uint16_t len = plainQuery.size(); + + std::shared_ptr query = std::make_shared(); + 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 plainQuery; + DNSPacketWriter pw(plainQuery, name, QType::TXT, QClass::IN, 0); + pw.getHeader()->rd = 0; + uint16_t len = plainQuery.size(); + + std::shared_ptr query = std::make_shared(); + 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 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 query = std::make_shared(); + 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 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 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 query = std::make_shared(); + 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 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 query = std::make_shared(); + 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();