]> git.ipfire.org Git - thirdparty/pdns.git/blobdiff - pdns/dnscrypt.cc
Merge pull request #8223 from PowerDNS/omoerbeek-patch-1
[thirdparty/pdns.git] / pdns / dnscrypt.cc
index 30ce1e9ad02e48ba48addc7076b1b6bff599b979..c18d29e2c5e5a14bcc775399b7f254eec17a44ba 100644 (file)
 #include "dolog.hh"
 #include "dnscrypt.hh"
 #include "dnswriter.hh"
+#include "lock.hh"
 
-DnsCryptPrivateKey::DnsCryptPrivateKey()
+DNSCryptPrivateKey::DNSCryptPrivateKey()
 {
   sodium_memzero(key, sizeof(key));
   sodium_mlock(key, sizeof(key));
 }
 
-void DnsCryptPrivateKey::loadFromFile(const std::string& keyFile)
+void DNSCryptPrivateKey::loadFromFile(const std::string& keyFile)
 {
   ifstream file(keyFile);
   sodium_memzero(key, sizeof(key));
@@ -47,54 +48,96 @@ void DnsCryptPrivateKey::loadFromFile(const std::string& keyFile)
   file.close();
 }
 
-void DnsCryptPrivateKey::saveToFile(const std::string& keyFile) const
+void DNSCryptPrivateKey::saveToFile(const std::string& keyFile) const
 {
   ofstream file(keyFile);
   file.write((char*) key, sizeof(key));
   file.close();
 }
 
-DnsCryptPrivateKey::~DnsCryptPrivateKey()
+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()
+DNSCryptQuery::~DNSCryptQuery()
 {
-  if (sharedKeyComputed) {
-    sodium_munlock(sharedKey, sizeof(sharedKey));
+  if (d_sharedKeyComputed) {
+    sodium_munlock(d_sharedKey, sizeof(d_sharedKey));
   }
 }
 
-int DnsCryptQuery::computeSharedKey(const DnsCryptPrivateKey& privateKey)
+int DNSCryptQuery::computeSharedKey()
 {
+  assert(d_pair != nullptr);
+
   int res = 0;
 
-  if (sharedKeyComputed) {
+  if (d_sharedKeyComputed) {
     return res;
   }
 
-  sodium_mlock(sharedKey, sizeof(sharedKey));
-  res = crypto_box_beforenm(sharedKey,
-                            header.clientPK,
-                            privateKey.key);
+  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(sharedKey, sizeof(sharedKey));
+    sodium_munlock(d_sharedKey, sizeof(d_sharedKey));
     return res;
   }
 
-  sharedKeyComputed = true;
+  d_sharedKeyComputed = true;
   return res;
 }
 #else
-DnsCryptQuery::~DnsCryptQuery()
+DNSCryptQuery::~DNSCryptQuery()
 {
 }
 #endif /* HAVE_CRYPTO_BOX_EASY_AFTERNM */
 
-void DnsCryptContext::generateProviderKeys(unsigned char publicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE], unsigned char privateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE])
+DNSCryptContext::DNSCryptContext(const std::string& pName, const std::vector<CertKeyPaths>& certKeys): d_certKeyPaths(certKeys), providerName(pName)
+{
+  pthread_rwlock_init(&d_lock, 0);
+
+  reloadCertificates();
+}
+
+DNSCryptContext::DNSCryptContext(const std::string& pName, const DNSCryptCert& certificate, const DNSCryptPrivateKey& pKey): providerName(pName)
+{
+  pthread_rwlock_init(&d_lock, 0);
+
+  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);
 
@@ -103,7 +146,7 @@ void DnsCryptContext::generateProviderKeys(unsigned char publicKey[DNSCRYPT_PROV
   }
 }
 
-std::string DnsCryptContext::getProviderFingerprint(unsigned char publicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE])
+std::string DNSCryptContext::getProviderFingerprint(unsigned char publicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE])
 {
   boost::format fmt("%02X%02X");
   ostringstream ret;
@@ -119,12 +162,51 @@ std::string DnsCryptContext::getProviderFingerprint(unsigned char publicKey[DNSC
   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)
+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 esVersion[] = DNSCRYPT_CERT_ES_VERSION_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));
@@ -132,7 +214,7 @@ void DnsCryptContext::generateCertificate(uint32_t serial, time_t begin, time_t
   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.serial = htonl(serial);
   cert.signedData.tsStart = htonl((uint32_t) begin);
   cert.signedData.tsEnd = htonl((uint32_t) end);
 
@@ -145,14 +227,14 @@ void DnsCryptContext::generateCertificate(uint32_t serial, time_t begin, time_t
                                 providerPrivateKey);
 
   if (res == 0) {
-    assert(signatureSize == sizeof(DnsCryptCertSignedData) + DNSCRYPT_SIGNATURE_SIZE);
+    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)
+void DNSCryptContext::loadCertFromFile(const std::string&filename, DNSCryptCert& dest)
 {
   ifstream file(filename);
   file.read((char *) &dest, sizeof(dest));
@@ -163,14 +245,14 @@ void DnsCryptContext::loadCertFromFile(const std::string&filename, DnsCryptCert&
   file.close();
 }
 
-void DnsCryptContext::saveCertFromFile(const DnsCryptCert& cert, const std::string&filename)
+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])
+void DNSCryptContext::generateResolverKeyPair(DNSCryptPrivateKey& privK, unsigned char pubK[DNSCRYPT_PUBLIC_KEY_SIZE])
 {
   int res = crypto_box_keypair(pubK, privK.key);
 
@@ -179,7 +261,7 @@ void DnsCryptContext::generateResolverKeyPair(DnsCryptPrivateKey& privK, unsigne
   }
 }
 
-void DnsCryptContext::computePublicKeyFromPrivate(const DnsCryptPrivateKey& privK, unsigned char* pubK)
+void DNSCryptContext::computePublicKeyFromPrivate(const DNSCryptPrivateKey& privK, unsigned char* pubK)
 {
   int res = crypto_scalarmult_base(pubK,
                                    privK.key);
@@ -189,10 +271,10 @@ void DnsCryptContext::computePublicKeyFromPrivate(const DnsCryptPrivateKey& priv
   }
 }
 
-std::string DnsCryptContext::certificateDateToStr(uint32_t date)
+std::string DNSCryptContext::certificateDateToStr(uint32_t date)
 {
   char buf[20];
-  time_t tdate = (time_t) ntohl(date);
+  time_t tdate = static_cast<time_t>(ntohl(date));
   struct tm date_tm;
 
   localtime_r(&tdate, &date_tm);
@@ -201,150 +283,265 @@ std::string DnsCryptContext::certificateDateToStr(uint32_t date)
   return string(buf);
 }
 
-void DnsCryptContext::setNewCertificate(const DnsCryptCert& newCert, const DnsCryptPrivateKey& newKey)
+void DNSCryptContext::addNewCertificate(std::shared_ptr<DNSCryptCertificatePair>& newCert, bool reload)
+{
+  WriteLock w(&d_lock);
+
+  for (auto pair : d_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");
+      }
+    }
+  }
+
+  d_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.push_back({certFile, keyFile});
+}
+
+void DNSCryptContext::reloadCertificates()
+{
+  std::vector<std::shared_ptr<DNSCryptCertificatePair>> newCerts;
+  for (const auto& pair : d_certKeyPaths) {
+    newCerts.push_back(DNSCryptContext::loadCertificatePair(pair.cert, pair.key));
+  }
+
+  {
+    WriteLock w(&d_lock);
+    d_certs = std::move(newCerts);
+  }
+}
+
+void DNSCryptContext::markActive(uint32_t serial)
 {
-  // XXX TODO: this could use a lock
-  oldPrivateKey = privateKey;
-  oldCert = cert;
-  hasOldCert = true;
-  privateKey = newKey;
-  cert = newCert;
+  WriteLock w(&d_lock);
+
+  for (auto pair : d_certs) {
+    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::loadNewCertificate(const std::string& certFile, const std::string& keyFile)
+void DNSCryptContext::markInactive(uint32_t serial)
 {
-  DnsCryptCert newCert;
-  DnsCryptPrivateKey newPrivateKey;
+  WriteLock w(&d_lock);
 
-  loadCertFromFile(certFile, newCert);
-  newPrivateKey.loadFromFile(keyFile);
-  setNewCertificate(newCert, newPrivateKey);
+  for (auto pair : d_certs) {
+    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)
+{
+  WriteLock w(&d_lock);
+
+  for (auto it = d_certs.begin(); it != d_certs.end(); ) {
+    if ((*it)->active == false && (*it)->cert.getSerial() == serial) {
+      it = d_certs.erase(it);
+      return;
+    } else {
+      it++;
+    }
+  }
+  throw std::runtime_error("No inactive certificate found with this serial");
 }
 
-void DnsCryptContext::parsePlaintextQuery(const char * packet, uint16_t packetSize, std::shared_ptr<DnsCryptQuery> query) const
+bool DNSCryptQuery::parsePlaintextQuery(const char * packet, uint16_t packetSize)
 {
+  assert(d_ctx != nullptr);
+
   if (packetSize < sizeof(dnsheader)) {
-    return;
+    return false;
   }
 
-  struct dnsheader * dh = (struct dnsheader *) packet;
+  const struct dnsheader * dh = reinterpret_cast<const struct dnsheader *>(packet);
   if (dh->qr || ntohs(dh->qdcount) != 1 || dh->ancount != 0 || dh->nscount != 0 || dh->opcode != Opcode::Query)
-    return;
+    return false;
 
   unsigned int consumed;
   uint16_t qtype, qclass;
   DNSName qname(packet, packetSize, sizeof(dnsheader), false, &qtype, &qclass, &consumed);
   if ((packetSize - sizeof(dnsheader)) < (consumed + sizeof(qtype) + sizeof(qclass)))
-    return;
+    return false;
 
   if (qtype != QType::TXT || qclass != QClass::IN)
-    return;
+    return false;
 
-  if (qname != DNSName(providerName))
-    return;
+  if (qname != d_ctx->getProviderName())
+    return false;
+
+  d_qname = qname;
+  d_id = dh->id;
+  d_valid = true;
 
-  query->qname = qname;
-  query->id = dh->id;
-  query->valid = true;
+  return true;
 }
 
-void DnsCryptContext::getCertificateResponse(const std::shared_ptr<DnsCryptQuery> query, vector<uint8_t>& response) const
+void DNSCryptContext::getCertificateResponse(time_t now, const DNSName& qname, uint16_t qid, std::vector<uint8_t>& response)
 {
-  DNSPacketWriter pw(response, query->qname, QType::TXT, QClass::IN, Opcode::Query);
+  DNSPacketWriter pw(response, qname, QType::TXT, QClass::IN, Opcode::Query);
   struct dnsheader * dh = pw.getHeader();
-  dh->id = query->id;
+  dh->id = qid;
   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();
-}
+  ReadLock r(&d_lock);
+  for (const auto pair : d_certs) {
+    if (!pair->active || !pair->cert.isValid(now)) {
+      continue;
+    }
 
-bool DnsCryptContext::magicMatchesPublicKey(std::shared_ptr<DnsCryptQuery> query) const
-{
-  const unsigned char* magic = query->header.clientMagic;
+    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);
 
-  if (memcmp(magic, cert.signedData.clientMagic, DNSCRYPT_CLIENT_MAGIC_SIZE) == 0) {
-    return true;
+    pw.xfrBlob(scert);
+    pw.commit();
   }
+}
+
+bool DNSCryptContext::magicMatchesAPublicKey(DNSCryptQuery& query, time_t now)
+{
+  const unsigned char* magic = query.getClientMagic();
 
-  if (hasOldCert == true &&
-      memcmp(magic, oldCert.signedData.clientMagic, DNSCRYPT_CLIENT_MAGIC_SIZE) == 0) {
-    query->useOldCert = true;
-    return true;
+  ReadLock r(&d_lock);
+  for (const auto& pair : d_certs) {
+    if (pair->cert.isValid(now) && memcmp(magic, pair->cert.signedData.clientMagic, DNSCRYPT_CLIENT_MAGIC_SIZE) == 0) {
+      query.setCertificatePair(pair);
+      return true;
+    }
   }
 
   return false;
 }
 
-void DnsCryptContext::isQueryEncrypted(const char * packet, uint16_t packetSize, std::shared_ptr<DnsCryptQuery> query, bool tcp) const
+bool DNSCryptQuery::isEncryptedQuery(const char * packet, uint16_t packetSize, bool tcp, time_t now)
 {
-  query->encrypted = false;
+  assert(d_ctx != nullptr);
 
-  if (packetSize < sizeof(DnsCryptQueryHeader)) {
-    return;
+  d_encrypted = false;
+
+  if (packetSize < sizeof(DNSCryptQueryHeader)) {
+    return false;
   }
 
-  if (!tcp && packetSize < DnsCryptQuery::minUDPLength) {
-    return;
+  if (!tcp && packetSize < DNSCryptQuery::s_minUDPLength) {
+    return false;
   }
 
-  struct DnsCryptQueryHeader* header = (struct DnsCryptQueryHeader*) packet;
+  const struct DNSCryptQueryHeader* header = reinterpret_cast<const struct DNSCryptQueryHeader*>(packet);
 
-  query->header = *(header);
+  d_header = *header;
 
-  if (!magicMatchesPublicKey(query)) {
-    return;
+  if (!d_ctx->magicMatchesAPublicKey(*this, now)) {
+    return false;
   }
 
-  query->encrypted = true;
+  d_encrypted = true;
+
+  return true;
 }
 
-void DnsCryptContext::getDecryptedQuery(std::shared_ptr<DnsCryptQuery> query, bool tcp, char* packet, uint16_t packetSize, uint16_t* decryptedQueryLen) const
+void DNSCryptQuery::getDecrypted(bool tcp, char* packet, uint16_t packetSize, uint16_t* decryptedQueryLen)
 {
-  assert(decryptedQueryLen != NULL);
-  assert(query->encrypted);
-  assert(query->valid == false);
+  assert(decryptedQueryLen != nullptr);
+  assert(d_encrypted);
+  assert(d_pair != nullptr);
+  assert(d_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);
+  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");
+  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 Publick key size is not right");
+  static_assert(sizeof(d_pair->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));
+  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 = query->computeSharedKey(query->useOldCert ? oldPrivateKey : privateKey);
+  int res = computeSharedKey();
   if (res != 0) {
     vinfolog("Dropping encrypted query we can't compute the shared key for");
     return;
   }
 
-  res = crypto_box_open_easy_afternm((unsigned char*) packet,
-                                     (unsigned char*) packet + sizeof(DnsCryptQueryHeader),
-                                     packetSize - sizeof(DnsCryptQueryHeader),
-                                     nonce,
-                                     query->sharedKey);
-#else
-  int res = crypto_box_open_easy((unsigned char*) packet,
-                                 (unsigned char*) packet + sizeof(DnsCryptQueryHeader),
-                                 packetSize - sizeof(DnsCryptQueryHeader),
+  const DNSCryptExchangeVersion version = getVersion();
+
+  if (version == DNSCryptExchangeVersion::VERSION1) {
+    res = crypto_box_open_easy_afternm(reinterpret_cast<unsigned char*>(packet),
+                                       reinterpret_cast<unsigned char*>(packet + sizeof(DNSCryptQueryHeader)),
+                                       packetSize - 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),
+                                                                   reinterpret_cast<unsigned char*>(packet + sizeof(DNSCryptQueryHeader)),
+                                                                   packetSize - 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),
+                                 reinterpret_cast<unsigned char*>(packet + sizeof(DNSCryptQueryHeader)),
+                                 packetSize - sizeof(DNSCryptQueryHeader),
                                  nonce,
-                                 query->header.clientPK,
-                                 query->useOldCert ? oldPrivateKey.key : privateKey.key);
+                                 d_header.clientPK,
+                                 d_pair->privateKey.key);
 #endif /* HAVE_CRYPTO_BOX_EASY_AFTERNM */
 
   if (res != 0) {
@@ -352,14 +549,14 @@ void DnsCryptContext::getDecryptedQuery(std::shared_ptr<DnsCryptQuery> query, bo
     return;
   }
 
-  *decryptedQueryLen = packetSize - sizeof(DnsCryptQueryHeader) - DNSCRYPT_MAC_SIZE;
+  *decryptedQueryLen = packetSize - sizeof(DNSCryptQueryHeader) - DNSCRYPT_MAC_SIZE;
   uint16_t pos = *decryptedQueryLen;
   assert(pos < packetSize);
-  query->paddedLen = *decryptedQueryLen;
+  d_paddedLen = *decryptedQueryLen;
 
   while(pos > 0 && packet[pos - 1] == 0) pos--;
 
-  if (pos == 0 || ((uint8_t) packet[pos - 1]) != 0x80) {
+  if (pos == 0 || static_cast<uint8_t>(packet[pos - 1]) != 0x80) {
     vinfolog("Dropping encrypted query with invalid padding value");
     return;
   }
@@ -374,32 +571,35 @@ void DnsCryptContext::getDecryptedQuery(std::shared_ptr<DnsCryptQuery> query, bo
     return;
   }
 
-  query->len = pos;
+  d_len = pos;
+  d_valid = true;
+}
 
-  query->valid = true;
+void DNSCryptQuery::getCertificateResponse(time_t now, std::vector<uint8_t>& response) const
+{
+  assert(d_ctx != nullptr);
+  d_ctx->getCertificateResponse(now, d_qname, d_id, response);
 }
 
-void DnsCryptContext::parsePacket(char* packet, uint16_t packetSize, std::shared_ptr<DnsCryptQuery> query, bool tcp, uint16_t* decryptedQueryLen) const
+void DNSCryptQuery::parsePacket(char* packet, uint16_t packetSize, bool tcp, uint16_t* decryptedQueryLen, time_t now)
 {
-  assert(packet != NULL);
-  assert(decryptedQueryLen != NULL);
+  assert(packet != nullptr);
+  assert(decryptedQueryLen != nullptr);
 
-  query->valid = false;
+  d_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);
+  if (isEncryptedQuery(packet, packetSize, tcp, now)) {
+    getDecrypted(tcp, packet, packetSize, decryptedQueryLen);
   }
   else {
-    parsePlaintextQuery(packet, packetSize, query);
+    parsePlaintextQuery(packet, packetSize);
   }
 }
 
-void DnsCryptContext::fillServerNonce(unsigned char* nonce) const
+void DNSCryptQuery::fillServerNonce(unsigned char* nonce) const
 {
-  uint32_t* dest = (uint32_t*) nonce;
+  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++)
@@ -413,44 +613,47 @@ void DnsCryptContext::fillServerNonce(unsigned char* nonce) const
    "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
+uint16_t DNSCryptQuery::computePaddingSize(uint16_t unpaddedLen, size_t maxLen) const
 {
-  size_t paddedLen = 0;
+  size_t paddedSize = 0;
   uint16_t result = 0;
   uint32_t rnd = 0;
-  assert(clientNonce != NULL);
+  assert(d_header.clientNonce);
+  assert(d_pair != nullptr);
+
   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);
+  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);
 
-  paddedLen = unpaddedLen + rnd % (maxLen - unpaddedLen + 1);
-  paddedLen += DNSCRYPT_PADDED_BLOCK_SIZE - (paddedLen % DNSCRYPT_PADDED_BLOCK_SIZE);
+  paddedSize = unpaddedLen + rnd % (maxLen - unpaddedLen + 1);
+  paddedSize += DNSCRYPT_PADDED_BLOCK_SIZE - (paddedSize % DNSCRYPT_PADDED_BLOCK_SIZE);
 
-  if (paddedLen > maxLen)
-    paddedLen = maxLen;
+  if (paddedSize > maxLen)
+    paddedSize = maxLen;
 
-  result = paddedLen - unpaddedLen;
+  result = paddedSize - 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
+int DNSCryptQuery::encryptResponse(char* response, uint16_t responseLen, uint16_t responseSize, bool tcp, uint16_t* encryptedResponseLen)
 {
-  struct DnsCryptResponseHeader header;
-  assert(response != NULL);
+  struct DNSCryptResponseHeader responseHeader;
+  assert(response != nullptr);
   assert(responseLen > 0);
   assert(responseSize >= responseLen);
-  assert(encryptedResponseLen != NULL);
-  assert(query->encrypted == true);
+  assert(encryptedResponseLen != nullptr);
+  assert(d_encrypted == true);
+  assert(d_pair != nullptr);
 
-  if (!tcp && query->paddedLen < responseLen) {
-    struct dnsheader* dh = (struct dnsheader*) response;
+  if (!tcp && d_paddedLen < responseLen) {
+    struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(response);
     size_t questionSize = 0;
 
     if (responseLen > sizeof(dnsheader)) {
       unsigned int consumed = 0;
-      DNSName qname(response, responseLen, sizeof(dnsheader), false, 0, 0, &consumed);
+      DNSName tempQName(response, responseLen, sizeof(dnsheader), false, 0, 0, &consumed);
       if (consumed > 0) {
         questionSize = consumed + DNS_TYPE_SIZE + DNS_CLASS_SIZE;
       }
@@ -458,31 +661,31 @@ int DnsCryptContext::encryptResponse(char* response, uint16_t responseLen, uint1
 
     responseLen = sizeof(dnsheader) + questionSize;
 
-    if (responseLen > query->paddedLen) {
-      responseLen = query->paddedLen;
+    if (responseLen > d_paddedLen) {
+      responseLen = d_paddedLen;
     }
     dh->ancount = dh->arcount = dh->nscount = 0;
     dh->tc = 1;
   }
 
-  size_t requiredSize = sizeof(header) + DNSCRYPT_MAC_SIZE + responseLen;
+  size_t requiredSize = sizeof(responseHeader) + 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);
+  uint16_t paddingSize = computePaddingSize(requiredSize, maxSize);
   requiredSize += paddingSize;
 
   if (requiredSize > responseSize)
     return ENOBUFS;
 
-  memcpy(&header.nonce, &query->header.clientNonce, sizeof query->header.clientNonce);
-  fillServerNonce(&(header.nonce[sizeof(query->header.clientNonce)]));
+  memcpy(&responseHeader.nonce, &d_header.clientNonce, sizeof d_header.clientNonce);
+  fillServerNonce(&(responseHeader.nonce[sizeof(d_header.clientNonce)]));
 
   /* moving the existing response after the header + MAC */
-  memmove(response + sizeof(header) + DNSCRYPT_MAC_SIZE, response, responseLen);
+  memmove(response + sizeof(responseHeader) + DNSCRYPT_MAC_SIZE, response, responseLen);
 
   uint16_t pos = 0;
   /* copying header */
-  memcpy(response + pos, &header, sizeof(header));
-  pos += sizeof(header);
+  memcpy(response + pos, &responseHeader, sizeof(responseHeader));
+  pos += sizeof(responseHeader);
   /* setting MAC bytes to 0 */
   memset(response + pos, 0, DNSCRYPT_MAC_SIZE);
   pos += DNSCRYPT_MAC_SIZE;
@@ -490,30 +693,48 @@ int DnsCryptContext::encryptResponse(char* response, uint16_t responseLen, uint1
   /* skipping response */
   pos += responseLen;
   /* padding */
-  response[pos] = (uint8_t) 0x80;
+  response[pos] = static_cast<uint8_t>(0x80);
   pos++;
   memset(response + pos, 0, paddingSize - 1);
   pos += (paddingSize - 1);
 
   /* encrypting */
 #ifdef HAVE_CRYPTO_BOX_EASY_AFTERNM
-  int res = query->computeSharedKey(query->useOldCert ? oldPrivateKey : privateKey);
+  int res = computeSharedKey();
   if (res != 0) {
     return res;
   }
 
-  res = crypto_box_easy_afternm((unsigned char*) (response + sizeof(header)),
-                                (unsigned char*) (response + toEncryptPos),
-                                responseLen + paddingSize,
-                                header.nonce,
-                                query->sharedKey);
+  const DNSCryptExchangeVersion version = getVersion();
+
+  if (version == DNSCryptExchangeVersion::VERSION1) {
+    res = crypto_box_easy_afternm(reinterpret_cast<unsigned char*>(response + sizeof(responseHeader)),
+                                  reinterpret_cast<unsigned char*>(response + 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 + sizeof(responseHeader)),
+                                                              reinterpret_cast<unsigned char*>(response + 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((unsigned char*) (response + sizeof(header)),
-                            (unsigned char*) (response + toEncryptPos),
+  int res = crypto_box_easy(reinterpret_cast<unsigned char*>(response + sizeof(responseHeader)),
+                            reinterpret_cast<unsigned char*>(response + toEncryptPos),
                             responseLen + paddingSize,
-                            header.nonce,
-                            query->header.clientPK,
-                            query->useOldCert ? oldPrivateKey.key : privateKey.key);
+                            responseHeader.nonce,
+                            d_header.clientPK,
+                            d_pair->privateKey.key);
 #endif /* HAVE_CRYPTO_BOX_EASY_AFTERNM */
 
   if (res == 0) {
@@ -524,34 +745,36 @@ int DnsCryptContext::encryptResponse(char* response, uint16_t responseLen, uint1
   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
+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 std::shared_ptr<DNSCryptCert>& cert) const
 {
-  assert(query != NULL);
+  assert(query != nullptr);
   assert(queryLen > 0);
   assert(querySize >= queryLen);
-  assert(encryptedResponseLen != NULL);
+  assert(encryptedResponseLen != nullptr);
+  assert(cert != nullptr);
+
   unsigned char nonce[DNSCRYPT_NONCE_SIZE];
-  size_t requiredSize = sizeof(DnsCryptQueryHeader) + DNSCRYPT_MAC_SIZE + queryLen;
+  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 (!tcp && requiredSize < DNSCryptQuery::s_minUDPLength) {
+    paddingSize += (DNSCryptQuery::s_minUDPLength - requiredSize);
+    requiredSize = DNSCryptQuery::s_minUDPLength;
   }
 
   if (requiredSize > querySize)
     return ENOBUFS;
 
   /* moving the existing query after the header + MAC */
-  memmove(query + sizeof(DnsCryptQueryHeader) + DNSCRYPT_MAC_SIZE, query, queryLen);
+  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);
+  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);
@@ -570,7 +793,7 @@ int DnsCryptContext::encryptQuery(char* query, uint16_t queryLen, uint16_t query
   pos += queryLen;
 
   /* padding */
-  query[pos] = (uint8_t) 0x80;
+  query[pos] = static_cast<uint8_t>(0x80);
   pos++;
   memset(query + pos, 0, paddingSize - 1);
   pos += paddingSize - 1;
@@ -578,12 +801,30 @@ int DnsCryptContext::encryptQuery(char* query, uint16_t queryLen, uint16_t query
   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);
+  const DNSCryptExchangeVersion version = getExchangeVersion(*cert);
+  int res = -1;
+
+  if (version == DNSCryptExchangeVersion::VERSION1) {
+    res = crypto_box_easy(reinterpret_cast<unsigned char*>(query + encryptedPos),
+                          reinterpret_cast<unsigned char*>(query + 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*>(query + encryptedPos),
+                                                      reinterpret_cast<unsigned char*>(query + 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);
@@ -593,4 +834,31 @@ int DnsCryptContext::encryptQuery(char* query, uint16_t queryLen, uint16_t query
   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