summaryrefslogtreecommitdiffstats
path: root/dnscrypt.cc
diff options
context:
space:
mode:
Diffstat (limited to 'dnscrypt.cc')
-rw-r--r--dnscrypt.cc871
1 files changed, 871 insertions, 0 deletions
diff --git a/dnscrypt.cc b/dnscrypt.cc
new file mode 100644
index 0000000..6db8613
--- /dev/null
+++ b/dnscrypt.cc
@@ -0,0 +1,871 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "config.h"
+#ifdef HAVE_DNSCRYPT
+#include <fstream>
+#include <boost/format.hpp>
+#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(reinterpret_cast<const char*>(key), sizeof(key));
+ file.close();
+}
+
+DNSCryptPrivateKey::~DNSCryptPrivateKey()
+{
+ sodium_munlock(key, sizeof(key));
+}
+
+DNSCryptExchangeVersion DNSCryptQuery::getVersion() const
+{
+ if (d_pair == nullptr) {
+ throw std::runtime_error("Unable to determine the version of a DNSCrypt query if there is not associated cert");
+ }
+
+ return DNSCryptContext::getExchangeVersion(d_pair->cert);
+}
+
+#ifdef HAVE_CRYPTO_BOX_EASY_AFTERNM
+DNSCryptQuery::~DNSCryptQuery()
+{
+ if (d_sharedKeyComputed) {
+ sodium_munlock(d_sharedKey, sizeof(d_sharedKey));
+ }
+}
+
+int DNSCryptQuery::computeSharedKey()
+{
+ assert(d_pair != nullptr);
+
+ int res = 0;
+
+ if (d_sharedKeyComputed) {
+ return res;
+ }
+
+ const DNSCryptExchangeVersion version = DNSCryptContext::getExchangeVersion(d_pair->cert);
+
+ sodium_mlock(d_sharedKey, sizeof(d_sharedKey));
+
+ if (version == DNSCryptExchangeVersion::VERSION1) {
+ res = crypto_box_beforenm(d_sharedKey,
+ d_header.clientPK,
+ d_pair->privateKey.key);
+ }
+ else if (version == DNSCryptExchangeVersion::VERSION2) {
+#ifdef HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY
+ res = crypto_box_curve25519xchacha20poly1305_beforenm(d_sharedKey,
+ d_header.clientPK,
+ d_pair->privateKey.key);
+#else /* HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY */
+ res = -1;
+#endif /* HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY */
+ }
+ else {
+ res = -1;
+ }
+
+ if (res != 0) {
+ sodium_munlock(d_sharedKey, sizeof(d_sharedKey));
+ return res;
+ }
+
+ d_sharedKeyComputed = true;
+ return res;
+}
+#else
+DNSCryptQuery::~DNSCryptQuery()
+{
+}
+#endif /* HAVE_CRYPTO_BOX_EASY_AFTERNM */
+
+
+DNSCryptContext::~DNSCryptContext() {
+}
+
+DNSCryptContext::DNSCryptContext(const std::string& pName, const std::vector<CertKeyPaths>& certKeys): d_certKeyPaths(certKeys), providerName(pName)
+{
+ reloadCertificates();
+}
+
+DNSCryptContext::DNSCryptContext(const std::string& pName, const DNSCryptCert& certificate, const DNSCryptPrivateKey& pKey): providerName(pName)
+{
+ addNewCertificate(certificate, pKey);
+}
+
+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::setExchangeVersion(const DNSCryptExchangeVersion& version, unsigned char esVersion[sizeof(DNSCryptCert::esVersion)])
+{
+ esVersion[0] = 0x00;
+
+ if (version == DNSCryptExchangeVersion::VERSION1) {
+ esVersion[1] = { 0x01 };
+ }
+ else if (version == DNSCryptExchangeVersion::VERSION2) {
+ esVersion[1] = { 0x02 };
+ }
+ else {
+ throw std::runtime_error("Unknown DNSCrypt exchange version");
+ }
+}
+
+DNSCryptExchangeVersion DNSCryptContext::getExchangeVersion(const unsigned char esVersion[sizeof(DNSCryptCert::esVersion)])
+{
+ if (esVersion[0] != 0x00) {
+ throw std::runtime_error("Unknown DNSCrypt exchange version");
+ }
+
+ if (esVersion[1] == 0x01) {
+ return DNSCryptExchangeVersion::VERSION1;
+ }
+ else if (esVersion[1] == 0x02) {
+ return DNSCryptExchangeVersion::VERSION2;
+ }
+
+ throw std::runtime_error("Unknown DNSCrypt exchange version");
+}
+
+DNSCryptExchangeVersion DNSCryptContext::getExchangeVersion(const DNSCryptCert& cert)
+{
+ return getExchangeVersion(cert.esVersion);
+}
+
+
+void DNSCryptContext::generateCertificate(uint32_t serial, time_t begin, time_t end, const DNSCryptExchangeVersion& version, 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 protocolMinorVersion[] = DNSCRYPT_CERT_PROTOCOL_MINOR_VERSION_VALUE;
+ unsigned char pubK[DNSCRYPT_PUBLIC_KEY_SIZE];
+ unsigned char esVersion[sizeof(DNSCryptCert::esVersion)];
+ setExchangeVersion(version, esVersion);
+
+ 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 = htonl(serial);
+ // coverity[store_truncates_time_t]
+ cert.signedData.tsStart = htonl((uint32_t) begin);
+ // coverity[store_truncates_time_t]
+ 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(reinterpret_cast<const 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 = static_cast<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::addNewCertificate(std::shared_ptr<DNSCryptCertificatePair>& newCert, bool reload)
+{
+ auto certs = d_certs.write_lock();
+
+ for (const auto& pair : *certs) {
+ if (pair->cert.getSerial() == newCert->cert.getSerial()) {
+ if (reload) {
+ /* on reload we just assume that this is the same certificate */
+ return;
+ }
+ else {
+ throw std::runtime_error("Error adding a new certificate: we already have a certificate with the same serial");
+ }
+ }
+ }
+
+ certs->push_back(newCert);
+}
+
+void DNSCryptContext::addNewCertificate(const DNSCryptCert& newCert, const DNSCryptPrivateKey& newKey, bool active, bool reload)
+{
+ auto pair = std::make_shared<DNSCryptCertificatePair>();
+ pair->cert = newCert;
+ pair->privateKey = newKey;
+ computePublicKeyFromPrivate(pair->privateKey, pair->publicKey);
+ pair->active = active;
+
+ addNewCertificate(pair, reload);
+}
+
+std::shared_ptr<DNSCryptCertificatePair> DNSCryptContext::loadCertificatePair(const std::string& certFile, const std::string& keyFile)
+{
+ auto pair = std::make_shared<DNSCryptCertificatePair>();
+ loadCertFromFile(certFile, pair->cert);
+ pair->privateKey.loadFromFile(keyFile);
+ pair->active = true;
+ computePublicKeyFromPrivate(pair->privateKey, pair->publicKey);
+ return pair;
+}
+
+void DNSCryptContext::loadNewCertificate(const std::string& certFile, const std::string& keyFile, bool active, bool reload)
+{
+ auto newPair = DNSCryptContext::loadCertificatePair(certFile, keyFile);
+ newPair->active = active;
+ addNewCertificate(newPair, reload);
+ d_certKeyPaths.write_lock()->push_back({certFile, keyFile});
+}
+
+void DNSCryptContext::reloadCertificates()
+{
+ std::vector<std::shared_ptr<DNSCryptCertificatePair>> newCerts;
+ {
+ auto paths = d_certKeyPaths.read_lock();
+ newCerts.reserve(paths->size());
+ for (const auto& pair : *paths) {
+ newCerts.push_back(DNSCryptContext::loadCertificatePair(pair.cert, pair.key));
+ }
+ }
+
+ {
+ *(d_certs.write_lock()) = std::move(newCerts);
+ }
+}
+
+std::vector<std::shared_ptr<DNSCryptCertificatePair>> DNSCryptContext::getCertificates() {
+ std::vector<std::shared_ptr<DNSCryptCertificatePair>> ret = *(d_certs.read_lock());
+ return ret;
+};
+
+void DNSCryptContext::markActive(uint32_t serial)
+{
+ for (const auto& pair : *d_certs.write_lock()) {
+ if (pair->active == false && pair->cert.getSerial() == serial) {
+ pair->active = true;
+ return;
+ }
+ }
+ throw std::runtime_error("No inactive certificate found with this serial");
+}
+
+void DNSCryptContext::markInactive(uint32_t serial)
+{
+ for (const auto& pair : *d_certs.write_lock()) {
+ if (pair->active == true && pair->cert.getSerial() == serial) {
+ pair->active = false;
+ return;
+ }
+ }
+ throw std::runtime_error("No active certificate found with this serial");
+}
+
+void DNSCryptContext::removeInactiveCertificate(uint32_t serial)
+{
+ auto certs = d_certs.write_lock();
+
+ for (auto it = certs->begin(); it != certs->end(); ) {
+ if ((*it)->active == false && (*it)->cert.getSerial() == serial) {
+ it = certs->erase(it);
+ return;
+ } else {
+ it++;
+ }
+ }
+ throw std::runtime_error("No inactive certificate found with this serial");
+}
+
+bool DNSCryptQuery::parsePlaintextQuery(const PacketBuffer& packet)
+{
+ assert(d_ctx != nullptr);
+
+ if (packet.size() < sizeof(dnsheader)) {
+ return false;
+ }
+
+ const struct dnsheader * dh = reinterpret_cast<const struct dnsheader *>(packet.data());
+ if (dh->qr || ntohs(dh->qdcount) != 1 || dh->ancount != 0 || dh->nscount != 0 || dh->opcode != Opcode::Query)
+ return false;
+
+ unsigned int qnameWireLength;
+ uint16_t qtype, qclass;
+ DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &qnameWireLength);
+ if ((packet.size() - sizeof(dnsheader)) < (qnameWireLength + sizeof(qtype) + sizeof(qclass))) {
+ return false;
+ }
+
+ if (qtype != QType::TXT || qclass != QClass::IN) {
+ return false;
+ }
+
+ if (qname != d_ctx->getProviderName()) {
+ return false;
+ }
+
+ d_qname = qname;
+ d_id = dh->id;
+ d_valid = true;
+
+ return true;
+}
+
+void DNSCryptContext::getCertificateResponse(time_t now, const DNSName& qname, uint16_t qid, PacketBuffer& response)
+{
+ GenericDNSPacketWriter<PacketBuffer> pw(response, qname, QType::TXT, QClass::IN, Opcode::Query);
+ struct dnsheader * dh = pw.getHeader();
+ dh->id = qid;
+ dh->qr = true;
+ dh->rcode = RCode::NoError;
+
+ auto certs = d_certs.read_lock();
+ for (const auto& pair : *certs) {
+ if (!pair->active || !pair->cert.isValid(now)) {
+ continue;
+ }
+
+ pw.startRecord(qname, QType::TXT, (DNSCRYPT_CERTIFICATE_RESPONSE_TTL), QClass::IN, DNSResourceRecord::ANSWER, true);
+ std::string scert;
+ uint8_t certSize = sizeof(pair->cert);
+ scert.assign((const char*) &certSize, sizeof(certSize));
+ scert.append((const char*) &pair->cert, certSize);
+
+ pw.xfrBlob(scert);
+ pw.commit();
+ }
+}
+
+bool DNSCryptContext::magicMatchesAPublicKey(DNSCryptQuery& query, time_t now)
+{
+ const unsigned char* magic = query.getClientMagic();
+
+ auto certs = d_certs.read_lock();
+ for (const auto& pair : *certs) {
+ if (pair->cert.isValid(now) && memcmp(magic, pair->cert.signedData.clientMagic, DNSCRYPT_CLIENT_MAGIC_SIZE) == 0) {
+ query.setCertificatePair(pair);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool DNSCryptQuery::isEncryptedQuery(const PacketBuffer& packet, bool tcp, time_t now)
+{
+ assert(d_ctx != nullptr);
+
+ d_encrypted = false;
+
+ if (packet.size() < sizeof(DNSCryptQueryHeader)) {
+ return false;
+ }
+
+ if (!tcp && packet.size() < DNSCryptQuery::s_minUDPLength) {
+ return false;
+ }
+
+ const struct DNSCryptQueryHeader* header = reinterpret_cast<const struct DNSCryptQueryHeader*>(packet.data());
+
+ d_header = *header;
+
+ if (!d_ctx->magicMatchesAPublicKey(*this, now)) {
+ return false;
+ }
+
+ d_encrypted = true;
+
+ return true;
+}
+
+void DNSCryptQuery::getDecrypted(bool tcp, PacketBuffer& packet)
+{
+ assert(d_encrypted);
+ assert(d_pair != nullptr);
+ assert(d_valid == false);
+
+#ifdef DNSCRYPT_STRICT_PADDING_LENGTH
+ if (tcp && ((packet.size() - sizeof(DNSCryptQueryHeader)) % DNSCRYPT_PADDED_BLOCK_SIZE) != 0) {
+ vinfolog("Dropping encrypted query with invalid size of %d (should be a multiple of %d)", (packet.size() - sizeof(DNSCryptQueryHeader)), DNSCRYPT_PADDED_BLOCK_SIZE);
+ return;
+ }
+#endif
+
+ unsigned char nonce[DNSCRYPT_NONCE_SIZE];
+ static_assert(sizeof(nonce) == (2* sizeof(d_header.clientNonce)), "Nonce should be larger than clientNonce (half)");
+ static_assert(sizeof(d_header.clientPK) == DNSCRYPT_PUBLIC_KEY_SIZE, "Client Public key size is not right");
+ static_assert(sizeof(d_pair->privateKey.key) == DNSCRYPT_PRIVATE_KEY_SIZE, "Private key size is not right");
+
+ memcpy(nonce, &d_header.clientNonce, sizeof(d_header.clientNonce));
+ memset(nonce + sizeof(d_header.clientNonce), 0, sizeof(nonce) - sizeof(d_header.clientNonce));
+
+#ifdef HAVE_CRYPTO_BOX_EASY_AFTERNM
+ int res = computeSharedKey();
+ if (res != 0) {
+ vinfolog("Dropping encrypted query we can't compute the shared key for");
+ return;
+ }
+
+ const DNSCryptExchangeVersion version = getVersion();
+
+ if (version == DNSCryptExchangeVersion::VERSION1) {
+ res = crypto_box_open_easy_afternm(reinterpret_cast<unsigned char*>(packet.data()),
+ reinterpret_cast<unsigned char*>(&packet.at(sizeof(DNSCryptQueryHeader))),
+ packet.size() - sizeof(DNSCryptQueryHeader),
+ nonce,
+ d_sharedKey);
+ }
+ else if (version == DNSCryptExchangeVersion::VERSION2) {
+#ifdef HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY
+ res = crypto_box_curve25519xchacha20poly1305_open_easy_afternm(reinterpret_cast<unsigned char*>(packet.data()),
+ reinterpret_cast<unsigned char*>(&packet.at(sizeof(DNSCryptQueryHeader))),
+ packet.size() - sizeof(DNSCryptQueryHeader),
+ nonce,
+ d_sharedKey);
+#else /* HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY */
+ res = -1;
+#endif /* HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY */
+ } else {
+ res = -1;
+ }
+
+#else /* HAVE_CRYPTO_BOX_EASY_AFTERNM */
+ int res = crypto_box_open_easy(reinterpret_cast<unsigned char*>(packet.data()),
+ reinterpret_cast<unsigned char*>(&packet.at(sizeof(DNSCryptQueryHeader))),
+ packet.size() - sizeof(DNSCryptQueryHeader),
+ nonce,
+ d_header.clientPK,
+ d_pair->privateKey.key);
+#endif /* HAVE_CRYPTO_BOX_EASY_AFTERNM */
+
+ if (res != 0) {
+ vinfolog("Dropping encrypted query we can't decrypt");
+ return;
+ }
+
+ uint16_t decryptedQueryLen = packet.size() - sizeof(DNSCryptQueryHeader) - DNSCRYPT_MAC_SIZE;
+ uint16_t pos = decryptedQueryLen;
+ assert(pos < packet.size());
+ d_paddedLen = decryptedQueryLen;
+
+ while (pos > 0 && packet.at(pos - 1) == 0) pos--;
+
+ if (pos == 0 || packet.at(pos - 1) != 0x80) {
+ vinfolog("Dropping encrypted query with invalid padding value");
+ return;
+ }
+
+ pos--;
+
+ size_t paddingLen = decryptedQueryLen - pos;
+ packet.resize(pos);
+
+ if (tcp && paddingLen > DNSCRYPT_MAX_TCP_PADDING_SIZE) {
+ vinfolog("Dropping encrypted query with too long padding size");
+ return;
+ }
+
+ d_len = pos;
+ d_valid = true;
+}
+
+void DNSCryptQuery::getCertificateResponse(time_t now, PacketBuffer& response) const
+{
+ assert(d_ctx != nullptr);
+ d_ctx->getCertificateResponse(now, d_qname, d_id, response);
+}
+
+void DNSCryptQuery::parsePacket(PacketBuffer& packet, bool tcp, time_t now)
+{
+ d_valid = false;
+
+ /* might be a plaintext certificate request or an authenticated request */
+ if (isEncryptedQuery(packet, tcp, now)) {
+ getDecrypted(tcp, packet);
+ }
+ else {
+ parsePlaintextQuery(packet);
+ }
+}
+
+void DNSCryptQuery::fillServerNonce(unsigned char* nonce) const
+{
+ uint32_t* dest = reinterpret_cast<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 DNSCryptQuery::computePaddingSize(uint16_t unpaddedLen, size_t maxLen) const
+{
+ size_t paddedSize = 0;
+ uint16_t result = 0;
+ uint32_t rnd = 0;
+ assert(d_header.clientNonce);
+ assert(d_pair != nullptr);
+
+ unsigned char nonce[DNSCRYPT_NONCE_SIZE];
+ memcpy(nonce, d_header.clientNonce, (DNSCRYPT_NONCE_SIZE / 2));
+ memcpy(&(nonce[DNSCRYPT_NONCE_SIZE / 2]), d_header.clientNonce, (DNSCRYPT_NONCE_SIZE / 2));
+ crypto_stream((unsigned char*) &rnd, sizeof(rnd), nonce, d_pair->privateKey.key);
+
+ paddedSize = unpaddedLen + rnd % (maxLen - unpaddedLen + 1);
+ paddedSize += DNSCRYPT_PADDED_BLOCK_SIZE - (paddedSize % DNSCRYPT_PADDED_BLOCK_SIZE);
+
+ if (paddedSize > maxLen)
+ paddedSize = maxLen;
+
+ result = paddedSize - unpaddedLen;
+
+ return result;
+}
+
+int DNSCryptQuery::encryptResponse(PacketBuffer& response, size_t maxResponseSize, bool tcp)
+{
+ struct DNSCryptResponseHeader responseHeader;
+ assert(response.size() > 0);
+ assert(maxResponseSize >= response.size());
+ assert(d_encrypted == true);
+ assert(d_pair != nullptr);
+
+ /* a DNSCrypt UDP response can't be larger than the (padded) DNSCrypt query */
+ if (!tcp && d_paddedLen < response.size()) {
+ /* so we need to truncate it */
+ size_t questionSize = 0;
+
+ if (response.size() > sizeof(dnsheader)) {
+ unsigned int qnameWireLength = 0;
+ DNSName tempQName(reinterpret_cast<const char*>(response.data()), response.size(), sizeof(dnsheader), false, 0, 0, &qnameWireLength);
+ if (qnameWireLength > 0) {
+ questionSize = qnameWireLength + DNS_TYPE_SIZE + DNS_CLASS_SIZE;
+ }
+ }
+
+ response.resize(sizeof(dnsheader) + questionSize);
+
+ if (response.size() > d_paddedLen) {
+ /* that does not seem right but let's truncate even more */
+ response.resize(d_paddedLen);
+ }
+ struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(response.data());
+ dh->ancount = dh->arcount = dh->nscount = 0;
+ dh->tc = 1;
+ }
+
+ size_t requiredSize = sizeof(responseHeader) + DNSCRYPT_MAC_SIZE + response.size();
+ size_t maxSize = std::min(maxResponseSize, requiredSize + DNSCRYPT_MAX_RESPONSE_PADDING_SIZE);
+ uint16_t paddingSize = computePaddingSize(requiredSize, maxSize);
+ requiredSize += paddingSize;
+
+ if (requiredSize > maxResponseSize) {
+ return ENOBUFS;
+ }
+
+ memcpy(&responseHeader.nonce, &d_header.clientNonce, sizeof d_header.clientNonce);
+ fillServerNonce(&(responseHeader.nonce[sizeof(d_header.clientNonce)]));
+
+ size_t responseLen = response.size();
+ /* moving the existing response after the header + MAC */
+ response.resize(requiredSize);
+ std::copy_backward(response.begin(), response.begin() + responseLen, response.begin() + responseLen + sizeof(responseHeader) + DNSCRYPT_MAC_SIZE);
+
+ uint16_t pos = 0;
+ /* copying header */
+ memcpy(&response.at(pos), &responseHeader, sizeof(responseHeader));
+ pos += sizeof(responseHeader);
+ /* setting MAC bytes to 0 */
+ memset(&response.at(pos), 0, DNSCRYPT_MAC_SIZE);
+ pos += DNSCRYPT_MAC_SIZE;
+ uint16_t toEncryptPos = pos;
+ /* skipping response */
+ pos += responseLen;
+ /* padding */
+ response.at(pos) = static_cast<uint8_t>(0x80);
+ pos++;
+ memset(&response.at(pos), 0, paddingSize - 1);
+ pos += (paddingSize - 1);
+
+ /* encrypting */
+#ifdef HAVE_CRYPTO_BOX_EASY_AFTERNM
+ int res = computeSharedKey();
+ if (res != 0) {
+ return res;
+ }
+
+ const DNSCryptExchangeVersion version = getVersion();
+
+ if (version == DNSCryptExchangeVersion::VERSION1) {
+ res = crypto_box_easy_afternm(reinterpret_cast<unsigned char*>(&response.at(sizeof(responseHeader))),
+ reinterpret_cast<unsigned char*>(&response.at(toEncryptPos)),
+ responseLen + paddingSize,
+ responseHeader.nonce,
+ d_sharedKey);
+ }
+ else if (version == DNSCryptExchangeVersion::VERSION2) {
+#ifdef HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY
+ res = crypto_box_curve25519xchacha20poly1305_easy_afternm(reinterpret_cast<unsigned char*>(&response.at(sizeof(responseHeader))),
+ reinterpret_cast<unsigned char*>(&response.at(toEncryptPos)),
+ responseLen + paddingSize,
+ responseHeader.nonce,
+ d_sharedKey);
+#else /* HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY */
+ res = -1;
+#endif /* HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY */
+ }
+ else {
+ res = -1;
+ }
+#else
+ int res = crypto_box_easy(reinterpret_cast<unsigned char*>(&response.at(sizeof(responseHeader))),
+ reinterpret_cast<unsigned char*>(&response.at(toEncryptPos)),
+ responseLen + paddingSize,
+ responseHeader.nonce,
+ d_header.clientPK,
+ d_pair->privateKey.key);
+#endif /* HAVE_CRYPTO_BOX_EASY_AFTERNM */
+
+ if (res == 0) {
+ assert(pos == requiredSize);
+ }
+
+ return res;
+}
+
+int DNSCryptContext::encryptQuery(PacketBuffer& packet, size_t maximumSize, const unsigned char clientPublicKey[DNSCRYPT_PUBLIC_KEY_SIZE], const DNSCryptPrivateKey& clientPrivateKey, const unsigned char clientNonce[DNSCRYPT_NONCE_SIZE / 2], bool tcp, const std::shared_ptr<DNSCryptCert>& cert) const
+{
+ assert(packet.size() > 0);
+ assert(cert != nullptr);
+
+ size_t queryLen = packet.size();
+ 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::s_minUDPLength) {
+ paddingSize += (DNSCryptQuery::s_minUDPLength - requiredSize);
+ requiredSize = DNSCryptQuery::s_minUDPLength;
+ }
+
+ if (requiredSize > maximumSize) {
+ return ENOBUFS;
+ }
+
+ /* moving the existing query after the header + MAC */
+ packet.resize(requiredSize);
+ std::copy_backward(packet.begin(), packet.begin() + queryLen, packet.begin() + queryLen + sizeof(DNSCryptQueryHeader) + DNSCRYPT_MAC_SIZE);
+
+ size_t pos = 0;
+ /* client magic */
+ memcpy(&packet.at(pos), cert->signedData.clientMagic, sizeof(cert->signedData.clientMagic));
+ pos += sizeof(cert->signedData.clientMagic);
+
+ /* client PK */
+ memcpy(&packet.at(pos), clientPublicKey, DNSCRYPT_PUBLIC_KEY_SIZE);
+ pos += DNSCRYPT_PUBLIC_KEY_SIZE;
+
+ /* client nonce */
+ memcpy(&packet.at(pos), clientNonce, DNSCRYPT_NONCE_SIZE / 2);
+ pos += DNSCRYPT_NONCE_SIZE / 2;
+ size_t encryptedPos = pos;
+
+ /* clear the MAC bytes */
+ memset(&packet.at(pos), 0, DNSCRYPT_MAC_SIZE);
+ pos += DNSCRYPT_MAC_SIZE;
+
+ /* skipping data */
+ pos += queryLen;
+
+ /* padding */
+ packet.at(pos) = static_cast<uint8_t>(0x80);
+ pos++;
+ memset(&packet.at(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);
+
+ const DNSCryptExchangeVersion version = getExchangeVersion(*cert);
+ int res = -1;
+
+ if (version == DNSCryptExchangeVersion::VERSION1) {
+ res = crypto_box_easy(reinterpret_cast<unsigned char*>(&packet.at(encryptedPos)),
+ reinterpret_cast<unsigned char*>(&packet.at(encryptedPos + DNSCRYPT_MAC_SIZE)),
+ queryLen + paddingSize,
+ nonce,
+ cert->signedData.resolverPK,
+ clientPrivateKey.key);
+ }
+ else if (version == DNSCryptExchangeVersion::VERSION2) {
+#ifdef HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY
+ res = crypto_box_curve25519xchacha20poly1305_easy(reinterpret_cast<unsigned char*>(&packet.at(encryptedPos)),
+ reinterpret_cast<unsigned char*>(&packet.at(encryptedPos + DNSCRYPT_MAC_SIZE)),
+ queryLen + paddingSize,
+ nonce,
+ cert->signedData.resolverPK,
+ clientPrivateKey.key);
+#endif /* HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY */
+ }
+ else {
+ throw std::runtime_error("Unknown DNSCrypt exchange version");
+ }
+
+ if (res == 0) {
+ assert(pos == requiredSize);
+ }
+
+ return res;
+}
+
+bool generateDNSCryptCertificate(const std::string& providerPrivateKeyFile, uint32_t serial, time_t begin, time_t end, DNSCryptExchangeVersion version, DNSCryptCert& certOut, DNSCryptPrivateKey& keyOut)
+{
+ bool success = false;
+ unsigned char providerPrivateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE];
+ sodium_mlock(providerPrivateKey, sizeof(providerPrivateKey));
+ sodium_memzero(providerPrivateKey, sizeof(providerPrivateKey));
+
+ try {
+ 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, version, providerPrivateKey, keyOut, certOut);
+ success = true;
+ }
+ catch(const std::exception& e) {
+ errlog(e.what());
+ }
+
+ sodium_memzero(providerPrivateKey, sizeof(providerPrivateKey));
+ sodium_munlock(providerPrivateKey, sizeof(providerPrivateKey));
+ return success;
+}
+
+#endif