• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PowerDNS / pdns / 19741624072

27 Nov 2025 03:45PM UTC coverage: 73.086% (+0.02%) from 73.065%
19741624072

Pull #16570

github

web-flow
Merge 08a2cdb1d into f94a3f63f
Pull Request #16570: rec: rewrite all unwrap calls in web.rs

38523 of 63408 branches covered (60.75%)

Branch coverage included in aggregate %.

128044 of 164496 relevant lines covered (77.84%)

6531485.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.29
/pdns/dnsdistdist/dnscrypt.cc
1
/*
2
 * This file is part of PowerDNS or dnsdist.
3
 * Copyright -- PowerDNS.COM B.V. and its contributors
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of version 2 of the GNU General Public License as
7
 * published by the Free Software Foundation.
8
 *
9
 * In addition, for the avoidance of any doubt, permission is granted to
10
 * link this program with OpenSSL and to (re)distribute the binaries
11
 * produced as the result of such linking.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
 */
22
#include "config.h"
23
#ifdef HAVE_DNSCRYPT
24
#include <fstream>
25
#include <boost/format.hpp>
26
#include "dolog.hh"
27
#include "dnscrypt.hh"
28
#include "dnsdist-dnsparser.hh"
29
#include "dnswriter.hh"
30

31
DNSCryptPrivateKey::DNSCryptPrivateKey()
32
{
76✔
33
  sodium_memzero(key.data(), key.size());
76✔
34
  sodium_mlock(key.data(), key.size());
76✔
35
}
76✔
36

37
void DNSCryptPrivateKey::loadFromFile(const std::string& keyFile)
38
{
12✔
39
  ifstream file(keyFile);
12✔
40
  sodium_memzero(key.data(), key.size());
12✔
41
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
42
  file.read(reinterpret_cast<char*>(key.data()), static_cast<std::streamsize>(key.size()));
12✔
43

44
  if (file.fail()) {
12!
45
    sodium_memzero(key.data(), key.size());
×
46
    file.close();
×
47
    throw std::runtime_error("Invalid DNSCrypt key file " + keyFile);
×
48
  }
×
49

50
  file.close();
12✔
51
}
12✔
52

53
void DNSCryptPrivateKey::saveToFile(const std::string& keyFile) const
54
{
8✔
55
  ofstream file(keyFile);
8✔
56
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
57
  file.write(reinterpret_cast<const char*>(key.data()), static_cast<std::streamsize>(key.size()));
8✔
58
  file.close();
8✔
59
}
8✔
60

61
DNSCryptPrivateKey::~DNSCryptPrivateKey()
62
{
62✔
63
  sodium_munlock(key.data(), key.size());
62✔
64
}
62✔
65

66
DNSCryptExchangeVersion DNSCryptQuery::getVersion() const
67
{
76✔
68
  if (d_pair == nullptr) {
76!
69
    throw std::runtime_error("Unable to determine the version of a DNSCrypt query if there is not associated cert");
×
70
  }
×
71

72
  return DNSCryptContext::getExchangeVersion(d_pair->cert);
76✔
73
}
76✔
74

75
#ifdef HAVE_CRYPTO_BOX_EASY_AFTERNM
76
DNSCryptQuery::~DNSCryptQuery()
77
{
71✔
78
  if (d_sharedKeyComputed) {
71✔
79
    sodium_munlock(d_sharedKey.data(), d_sharedKey.size());
40✔
80
  }
40✔
81
}
71✔
82

83
int DNSCryptQuery::computeSharedKey()
84
{
76✔
85
  int res = 0;
76✔
86
  if (d_sharedKeyComputed) {
76✔
87
    return res;
36✔
88
  }
36✔
89
  if (d_pair == nullptr) {
40!
90
    throw std::runtime_error("Asked to compute a DNSCrypt shared key without the certificate key set");
×
91
  }
×
92

93
  const DNSCryptExchangeVersion version = DNSCryptContext::getExchangeVersion(d_pair->cert);
40✔
94

95
  sodium_mlock(d_sharedKey.data(), d_sharedKey.size());
40✔
96

97
  if (version == DNSCryptExchangeVersion::VERSION1) {
40!
98
    res = crypto_box_beforenm(d_sharedKey.data(),
40✔
99
                              d_header.clientPK.data(),
40✔
100
                              d_pair->privateKey.key.data());
40✔
101
  }
40✔
102
  else if (version == DNSCryptExchangeVersion::VERSION2) {
×
103
#ifdef HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY
×
104
    res = crypto_box_curve25519xchacha20poly1305_beforenm(d_sharedKey.data(),
×
105
                                                          d_header.clientPK.data(),
×
106
                                                          d_pair->privateKey.key.data());
×
107
#else /* HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY */
108
    res = -1;
109
#endif /* HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY */
110
  }
×
111
  else {
×
112
    res = -1;
×
113
  }
×
114

115
  if (res != 0) {
40!
116
    sodium_munlock(d_sharedKey.data(), d_sharedKey.size());
×
117
    return res;
×
118
  }
×
119

120
  d_sharedKeyComputed = true;
40✔
121
  return res;
40✔
122
}
40✔
123
#else
124
DNSCryptQuery::~DNSCryptQuery() = default;
125
#endif /* HAVE_CRYPTO_BOX_EASY_AFTERNM */
126

127
DNSCryptContext::~DNSCryptContext() = default;
14✔
128

129
DNSCryptContext::DNSCryptContext(const std::string& pName, const std::vector<CertKeyPaths>& certKeys) :
130
  d_certKeyPaths(certKeys), providerName(pName)
10✔
131
{
10✔
132
  reloadCertificates();
10✔
133
}
10✔
134

135
DNSCryptContext::DNSCryptContext(const std::string& pName, const DNSCryptCert& certificate, const DNSCryptPrivateKey& pKey) :
136
  providerName(pName)
14✔
137
{
14✔
138
  addNewCertificate(certificate, pKey);
14✔
139
}
14✔
140

141
void DNSCryptContext::generateProviderKeys(DNSCryptCertSignedData::ResolverPublicKeyType& publicKey, DNSCryptCertSignedData::ResolverPrivateKeyType& privateKey)
142
{
14✔
143
  int res = crypto_sign_ed25519_keypair(publicKey.data(), privateKey.data());
14✔
144

145
  if (res != 0) {
14!
146
    throw std::runtime_error("Error generating DNSCrypt provider keys");
×
147
  }
×
148
}
14✔
149

150
std::string DNSCryptContext::getProviderFingerprint(const DNSCryptCertSignedData::ResolverPublicKeyType& publicKey)
151
{
×
152
  boost::format fmt("%02X%02X");
×
153
  ostringstream ret;
×
154

155
  for (size_t idx = 0; idx < DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE; idx += 2) {
×
156
    ret << (fmt % static_cast<int>(publicKey.at(idx)) % static_cast<int>(publicKey.at(idx + 1)));
×
157
    if (idx < (DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE - 2)) {
×
158
      ret << ":";
×
159
    }
×
160
  }
×
161

162
  return ret.str();
×
163
}
×
164

165
void DNSCryptContext::setExchangeVersion(const DNSCryptExchangeVersion& version, DNSCryptCert::ESVersionType& esVersion)
166
{
34✔
167
  esVersion.at(0) = 0x00;
34✔
168

169
  if (version == DNSCryptExchangeVersion::VERSION1) {
34!
170
    esVersion.at(1) = {0x01};
34✔
171
  }
34✔
172
  else if (version == DNSCryptExchangeVersion::VERSION2) {
×
173
    esVersion.at(1) = {0x02};
×
174
  }
×
175
  else {
×
176
    throw std::runtime_error("Unknown DNSCrypt exchange version");
×
177
  }
×
178
}
34✔
179

180
DNSCryptExchangeVersion DNSCryptContext::getExchangeVersion(const DNSCryptCert::ESVersionType& esVersion)
181
{
122✔
182
  if (esVersion.at(0) != 0x00) {
122!
183
    throw std::runtime_error("Unknown DNSCrypt exchange version");
×
184
  }
×
185

186
  if (esVersion.at(1) == 0x01) {
122!
187
    return DNSCryptExchangeVersion::VERSION1;
122✔
188
  }
122✔
189
  if (esVersion.at(1) == 0x02) {
×
190
    return DNSCryptExchangeVersion::VERSION2;
×
191
  }
×
192

193
  throw std::runtime_error("Unknown DNSCrypt exchange version");
×
194
}
×
195

196
DNSCryptExchangeVersion DNSCryptContext::getExchangeVersion(const DNSCryptCert& cert)
197
{
122✔
198
  return getExchangeVersion(cert.esVersion);
122✔
199
}
122✔
200

201
void DNSCryptContext::generateCertificate(uint32_t serial, time_t begin, time_t end, const DNSCryptExchangeVersion& version, const DNSCryptCertSignedData::ResolverPrivateKeyType& providerPrivateKey, DNSCryptPrivateKey& privateKey, DNSCryptCert& cert)
202
{
34✔
203
  setExchangeVersion(version, cert.esVersion);
34✔
204
  DNSCryptPublicKeyType pubKey;
34✔
205
  generateResolverKeyPair(privateKey, pubKey);
34✔
206

207
  cert.magic = DNSCRYPT_CERT_MAGIC_VALUE;
34✔
208
  cert.protocolMinorVersion = DNSCRYPT_CERT_PROTOCOL_MINOR_VERSION_VALUE;
34✔
209
  memcpy(cert.signedData.clientMagic.data(), pubKey.data(), cert.signedData.clientMagic.size());
34✔
210
  memcpy(cert.signedData.resolverPK.data(), pubKey.data(), cert.signedData.resolverPK.size());
34✔
211
  cert.signedData.serial = htonl(serial);
34✔
212
  // coverity[store_truncates_time_t]
213
  cert.signedData.tsStart = htonl(static_cast<uint32_t>(begin));
34✔
214
  // coverity[store_truncates_time_t]
215
  cert.signedData.tsEnd = htonl(static_cast<uint32_t>(end));
34✔
216

217
  unsigned long long signatureSize = 0;
34✔
218

219
  int res = crypto_sign_ed25519(cert.signature.data(),
34✔
220
                                &signatureSize,
34✔
221
                                // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
222
                                reinterpret_cast<unsigned char*>(&cert.signedData),
34✔
223
                                sizeof(cert.signedData),
34✔
224
                                providerPrivateKey.data());
34✔
225

226
  if (res != 0 || signatureSize != (sizeof(DNSCryptCertSignedData) + DNSCRYPT_SIGNATURE_SIZE)) {
34!
227
    throw std::runtime_error("Error generating DNSCrypt certificate");
×
228
  }
×
229
}
34✔
230

231
void DNSCryptContext::loadCertFromFile(const std::string& filename, DNSCryptCert& dest)
232
{
12✔
233
  ifstream file(filename);
12✔
234
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
235
  file.read(reinterpret_cast<char*>(&dest), sizeof(dest));
12✔
236

237
  if (file.fail()) {
12!
238
    throw std::runtime_error("Invalid dnscrypt certificate file " + filename);
×
239
  }
×
240

241
  file.close();
12✔
242
}
12✔
243

244
void DNSCryptContext::saveCertFromFile(const DNSCryptCert& cert, const std::string& filename)
245
{
8✔
246
  ofstream file(filename);
8✔
247
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
248
  file.write(reinterpret_cast<const char*>(&cert), sizeof(cert));
8✔
249
  file.close();
8✔
250
}
8✔
251

252
void DNSCryptContext::generateResolverKeyPair(DNSCryptPrivateKey& privK, DNSCryptPublicKeyType& pubK)
253
{
42✔
254
  int res = crypto_box_keypair(pubK.data(), privK.key.data());
42✔
255

256
  if (res != 0) {
42!
257
    throw std::runtime_error("Error generating DNSCrypt resolver keys");
×
258
  }
×
259
}
42✔
260

261
void DNSCryptContext::computePublicKeyFromPrivate(const DNSCryptPrivateKey& privK, DNSCryptCertificatePair::PublicKeyType& pubK)
262
{
38✔
263
  int res = crypto_scalarmult_base(pubK.data(),
38✔
264
                                   privK.key.data());
38✔
265

266
  if (res != 0) {
38!
267
    throw std::runtime_error("Error computing dnscrypt public key from the private one");
×
268
  }
×
269
}
38✔
270

271
std::string DNSCryptContext::certificateDateToStr(uint32_t date)
272
{
×
273
  std::string result;
×
274
  auto tdate = static_cast<time_t>(ntohl(date));
×
275
  tm date_tm{};
×
276
  localtime_r(&tdate, &date_tm);
×
277
  result.resize(20);
×
278
  auto got = strftime(result.data(), result.size(), "%Y-%m-%d %H:%M:%S", &date_tm);
×
279
  result.resize(got);
×
280
  return result;
×
281
}
×
282

283
void DNSCryptContext::addNewCertificate(std::shared_ptr<DNSCryptCertificatePair>& newCert, bool reload)
284
{
28✔
285
  auto certs = d_certs.write_lock();
28✔
286

287
  for (const auto& pair : *certs) {
36✔
288
    if (pair->cert.getSerial() == newCert->cert.getSerial()) {
22!
289
      if (reload) {
×
290
        /* on reload we just assume that this is the same certificate */
291
        return;
×
292
      }
×
293
      throw std::runtime_error("Error adding a new certificate: we already have a certificate with the same serial");
×
294
    }
×
295
  }
22✔
296

297
  certs->push_back(newCert);
28✔
298
}
28✔
299

300
void DNSCryptContext::addNewCertificate(const DNSCryptCert& newCert, const DNSCryptPrivateKey& newKey, bool active, bool reload)
301
{
26✔
302
  auto pair = std::make_shared<DNSCryptCertificatePair>();
26✔
303
  pair->cert = newCert;
26✔
304
  pair->privateKey = newKey;
26✔
305
  computePublicKeyFromPrivate(pair->privateKey, pair->publicKey);
26✔
306
  pair->active = active;
26✔
307

308
  addNewCertificate(pair, reload);
26✔
309
}
26✔
310

311
std::shared_ptr<DNSCryptCertificatePair> DNSCryptContext::loadCertificatePair(const std::string& certFile, const std::string& keyFile)
312
{
12✔
313
  auto pair = std::make_shared<DNSCryptCertificatePair>();
12✔
314
  loadCertFromFile(certFile, pair->cert);
12✔
315
  pair->privateKey.loadFromFile(keyFile);
12✔
316
  pair->active = true;
12✔
317
  computePublicKeyFromPrivate(pair->privateKey, pair->publicKey);
12✔
318
  return pair;
12✔
319
}
12✔
320

321
void DNSCryptContext::loadNewCertificate(const std::string& certFile, const std::string& keyFile, bool active, bool reload)
322
{
2✔
323
  auto newPair = DNSCryptContext::loadCertificatePair(certFile, keyFile);
2✔
324
  newPair->active = active;
2✔
325
  addNewCertificate(newPair, reload);
2✔
326
  d_certKeyPaths.write_lock()->push_back({certFile, keyFile});
2✔
327
}
2✔
328

329
void DNSCryptContext::reloadCertificates()
330
{
10✔
331
  std::vector<std::shared_ptr<DNSCryptCertificatePair>> newCerts;
10✔
332
  {
10✔
333
    auto paths = d_certKeyPaths.read_lock();
10✔
334
    newCerts.reserve(paths->size());
10✔
335
    for (const auto& pair : *paths) {
10✔
336
      newCerts.push_back(DNSCryptContext::loadCertificatePair(pair.cert, pair.key));
10✔
337
    }
10✔
338
  }
10✔
339

340
  {
10✔
341
    *(d_certs.write_lock()) = std::move(newCerts);
10✔
342
  }
10✔
343
}
10✔
344

345
std::vector<std::shared_ptr<DNSCryptCertificatePair>> DNSCryptContext::getCertificates()
346
{
8✔
347
  std::vector<std::shared_ptr<DNSCryptCertificatePair>> ret = *(d_certs.read_lock());
8✔
348
  return ret;
8✔
349
};
8✔
350

351
void DNSCryptContext::markActive(uint32_t serial)
352
{
×
353
  for (const auto& pair : *d_certs.write_lock()) {
×
354
    if (!pair->active && pair->cert.getSerial() == serial) {
×
355
      pair->active = true;
×
356
      return;
×
357
    }
×
358
  }
×
359
  throw std::runtime_error("No inactive certificate found with this serial");
×
360
}
×
361

362
void DNSCryptContext::markInactive(uint32_t serial)
363
{
10✔
364
  for (const auto& pair : *d_certs.write_lock()) {
16!
365
    if (pair->active && pair->cert.getSerial() == serial) {
16!
366
      pair->active = false;
10✔
367
      return;
10✔
368
    }
10✔
369
  }
16✔
370
  throw std::runtime_error("No active certificate found with this serial");
×
371
}
10✔
372

373
void DNSCryptContext::removeInactiveCertificate(uint32_t serial)
374
{
8✔
375
  auto certs = d_certs.write_lock();
8✔
376

377
  for (auto it = certs->begin(); it != certs->end();) {
8!
378
    if (!(*it)->active && (*it)->cert.getSerial() == serial) {
8!
379
      it = certs->erase(it);
8✔
380
      return;
8✔
381
    }
8✔
382
    it++;
×
383
  }
×
384
  throw std::runtime_error("No inactive certificate found with this serial");
×
385
}
8✔
386

387
bool DNSCryptQuery::parsePlaintextQuery(const PacketBuffer& packet)
388
{
31✔
389
  if (packet.size() < sizeof(dnsheader)) {
31!
390
    return false;
×
391
  }
×
392

393
  const dnsheader_aligned dnsHeader(packet.data());
31✔
394
  if (dnsHeader->qr || ntohs(dnsHeader->qdcount) != 1 || dnsHeader->ancount != 0 || dnsHeader->nscount != 0 || static_cast<uint8_t>(dnsHeader->opcode) != Opcode::Query) {
31!
395
    return false;
4✔
396
  }
4✔
397

398
  unsigned int qnameWireLength{0};
27✔
399
  uint16_t qtype{0};
27✔
400
  uint16_t qclass{0};
27✔
401
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
402
  DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &qnameWireLength);
27✔
403
  if ((packet.size() - sizeof(dnsheader)) < (qnameWireLength + sizeof(qtype) + sizeof(qclass))) {
27!
404
    return false;
×
405
  }
×
406

407
  if (qtype != QType::TXT || qclass != QClass::IN) {
27!
408
    return false;
2✔
409
  }
2✔
410

411
  if (d_ctx == nullptr || qname != d_ctx->getProviderName()) {
25!
412
    return false;
2✔
413
  }
2✔
414

415
  d_qname = std::move(qname);
23✔
416
  d_id = dnsHeader->id;
23✔
417
  d_valid = true;
23✔
418

419
  return true;
23✔
420
}
25✔
421

422
void DNSCryptContext::getCertificateResponse(time_t now, const DNSName& qname, uint16_t qid, PacketBuffer& response)
423
{
23✔
424
  GenericDNSPacketWriter<PacketBuffer> packetWriter(response, qname, QType::TXT, QClass::IN, Opcode::Query);
23✔
425
  struct dnsheader* dnsHeader = packetWriter.getHeader();
23✔
426
  dnsHeader->id = qid;
23✔
427
  dnsHeader->qr = true;
23✔
428
  dnsHeader->rcode = RCode::NoError;
23✔
429

430
  auto certs = d_certs.read_lock();
23✔
431
  for (const auto& pair : *certs) {
31✔
432
    if (!pair->active || !pair->cert.isValid(now)) {
31!
433
      continue;
×
434
    }
×
435

436
    packetWriter.startRecord(qname, QType::TXT, (DNSCRYPT_CERTIFICATE_RESPONSE_TTL), QClass::IN, DNSResourceRecord::ANSWER, true);
31✔
437
    std::string scert;
31✔
438
    uint8_t certSize = sizeof(pair->cert);
31✔
439
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
440
    scert.assign(reinterpret_cast<const char*>(&certSize), sizeof(certSize));
31✔
441
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
442
    scert.append(reinterpret_cast<const char*>(&pair->cert), certSize);
31✔
443

444
    packetWriter.xfrBlob(scert);
31✔
445
    packetWriter.commit();
31✔
446
  }
31✔
447
}
23✔
448

449
bool DNSCryptContext::magicMatchesAPublicKey(DNSCryptQuery& query, time_t now)
450
{
44✔
451
  const auto& magic = query.getClientMagic();
44✔
452

453
  auto certs = d_certs.read_lock();
44✔
454
  for (const auto& pair : *certs) {
60✔
455
    if (pair->cert.isValid(now) && magic == pair->cert.signedData.clientMagic) {
60!
456
      query.setCertificatePair(pair);
40✔
457
      return true;
40✔
458
    }
40✔
459
  }
60✔
460

461
  return false;
4✔
462
}
44✔
463

464
bool DNSCryptQuery::isEncryptedQuery(const PacketBuffer& packet, bool tcp, time_t now)
465
{
71✔
466
  d_encrypted = false;
71✔
467

468
  if (packet.size() < sizeof(DNSCryptQueryHeader)) {
71✔
469
    return false;
27✔
470
  }
27✔
471

472
  if (!tcp && packet.size() < DNSCryptQuery::s_minUDPLength) {
44!
473
    return false;
×
474
  }
×
475

476
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
477
  const auto* header = reinterpret_cast<const DNSCryptQueryHeader*>(packet.data());
44✔
478

479
  d_header = *header;
44✔
480

481
  if (d_ctx == nullptr || !d_ctx->magicMatchesAPublicKey(*this, now)) {
44!
482
    return false;
4✔
483
  }
4✔
484

485
  d_encrypted = true;
40✔
486

487
  return true;
40✔
488
}
44✔
489

490
void DNSCryptQuery::getDecrypted(bool tcp, PacketBuffer& packet)
491
{
40✔
492
  if (!d_encrypted || d_valid || d_pair == nullptr) {
40!
493
    throw std::runtime_error("Trying to decrypt a DNSCrypt query in an invalid state");
×
494
  }
×
495

496
#ifdef DNSCRYPT_STRICT_PADDING_LENGTH
497
  if (tcp && ((packet.size() - sizeof(DNSCryptQueryHeader)) % DNSCRYPT_PADDED_BLOCK_SIZE) != 0) {
498
    vinfolog("Dropping encrypted query with invalid size of %d (should be a multiple of %d)", (packet.size() - sizeof(DNSCryptQueryHeader)), DNSCRYPT_PADDED_BLOCK_SIZE);
499
    return;
500
  }
501
#endif
502

503
  DNSCryptNonceType nonce;
40✔
504
  memcpy(nonce.data(), d_header.clientNonce.data(), d_header.clientNonce.size());
40✔
505
  memset(&nonce.at(d_header.clientNonce.size()), 0, nonce.size() - d_header.clientNonce.size());
40✔
506

507
#ifdef HAVE_CRYPTO_BOX_EASY_AFTERNM
40✔
508
  int res = computeSharedKey();
40✔
509
  if (res != 0) {
40!
510
    vinfolog("Dropping encrypted query we can't compute the shared key for");
×
511
    return;
×
512
  }
×
513

514
  const DNSCryptExchangeVersion version = getVersion();
40✔
515

516
  if (version == DNSCryptExchangeVersion::VERSION1) {
40!
517
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
518
    res = crypto_box_open_easy_afternm(reinterpret_cast<unsigned char*>(packet.data()),
40✔
519
                                       // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
520
                                       reinterpret_cast<unsigned char*>(&packet.at(sizeof(DNSCryptQueryHeader))),
40✔
521
                                       packet.size() - sizeof(DNSCryptQueryHeader),
40✔
522
                                       nonce.data(),
40✔
523
                                       d_sharedKey.data());
40✔
524
  }
40✔
525
  else if (version == DNSCryptExchangeVersion::VERSION2) {
×
526
#ifdef HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY
×
527
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
528
    res = crypto_box_curve25519xchacha20poly1305_open_easy_afternm(reinterpret_cast<unsigned char*>(packet.data()),
×
529
                                                                   // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
530
                                                                   reinterpret_cast<unsigned char*>(&packet.at(sizeof(DNSCryptQueryHeader))),
×
531
                                                                   packet.size() - sizeof(DNSCryptQueryHeader),
×
532
                                                                   nonce.data(),
×
533
                                                                   d_sharedKey.data());
×
534
#else /* HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY */
535
    res = -1;
536
#endif /* HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY */
537
  }
×
538
  else {
×
539
    res = -1;
×
540
  }
×
541

542
#else /* HAVE_CRYPTO_BOX_EASY_AFTERNM */
543
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
544
  int res = crypto_box_open_easy(reinterpret_cast<unsigned char*>(packet.data()),
545
                                 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
546
                                 reinterpret_cast<unsigned char*>(&packet.at(sizeof(DNSCryptQueryHeader))),
547
                                 packet.size() - sizeof(DNSCryptQueryHeader),
548
                                 nonce.data(),
549
                                 d_header.clientPK.data(),
550
                                 d_pair->privateKey.key.data());
551
#endif /* HAVE_CRYPTO_BOX_EASY_AFTERNM */
552

553
  if (res != 0) {
40!
554
    vinfolog("Dropping encrypted query we can't decrypt");
×
555
    return;
×
556
  }
×
557

558
  uint16_t decryptedQueryLen = packet.size() - sizeof(DNSCryptQueryHeader) - DNSCRYPT_MAC_SIZE;
40✔
559
  uint16_t pos = decryptedQueryLen;
40✔
560
  if (pos >= packet.size()) {
40!
561
    vinfolog("Dropping encrypted query we can't decrypt (invalid position)");
×
562
    return;
×
563
  }
×
564

565
  d_paddedLen = decryptedQueryLen;
40✔
566

567
  while (pos > 0 && packet.at(pos - 1) == 0) {
3,892!
568
    pos--;
3,852✔
569
  }
3,852✔
570

571
  if (pos == 0 || packet.at(pos - 1) != 0x80) {
40!
572
    vinfolog("Dropping encrypted query with invalid padding value");
×
573
    return;
×
574
  }
×
575

576
  pos--;
40✔
577

578
  size_t paddingLen = decryptedQueryLen - pos;
40✔
579
  packet.resize(pos);
40✔
580

581
  if (tcp && paddingLen > DNSCRYPT_MAX_TCP_PADDING_SIZE) {
40!
582
    vinfolog("Dropping encrypted query with too long padding size");
×
583
    return;
×
584
  }
×
585

586
  d_len = pos;
40✔
587
  d_valid = true;
40✔
588
}
40✔
589

590
void DNSCryptQuery::getCertificateResponse(time_t now, PacketBuffer& response) const
591
{
23✔
592
  if (d_ctx == nullptr) {
23!
593
    throw std::runtime_error("Trying to get a certificate response from a DNSCrypt query lacking context");
×
594
  }
×
595
  d_ctx->getCertificateResponse(now, d_qname, d_id, response);
23✔
596
}
23✔
597

598
void DNSCryptQuery::parsePacket(PacketBuffer& packet, bool tcp, time_t now)
599
{
71✔
600
  d_valid = false;
71✔
601

602
  /* might be a plaintext certificate request or an authenticated request */
603
  if (isEncryptedQuery(packet, tcp, now)) {
71✔
604
    getDecrypted(tcp, packet);
40✔
605
  }
40✔
606
  else {
31✔
607
    parsePlaintextQuery(packet);
31✔
608
  }
31✔
609
}
71✔
610

611
void DNSCryptQuery::fillServerNonce(DNSCryptNonceType& nonce)
612
{
36✔
613
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
614
  auto* dest = reinterpret_cast<uint32_t*>(&nonce.at(DNSCRYPT_NONCE_SIZE / 2));
36✔
615
  static const size_t nonceSize = DNSCRYPT_NONCE_SIZE / 2;
36✔
616

617
  for (size_t pos = 0; pos < (nonceSize / sizeof(*dest)); pos++) {
144✔
618
    const uint32_t value = randombytes_random();
108✔
619
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): sorry
620
    memcpy(dest + pos, &value, sizeof(value));
108✔
621
  }
108✔
622
}
36✔
623

624
/*
625
   "The length of <resolver-response-pad> must be between 0 and 256 bytes,
626
   and must be constant for a given (<resolver-sk>, <client-nonce>) tuple."
627
*/
628
uint16_t DNSCryptQuery::computePaddingSize(uint16_t unpaddedLen, size_t maxLen) const
629
{
36✔
630
  size_t paddedSize = 0;
36✔
631
  uint16_t result = 0;
36✔
632
  uint32_t rnd = 0;
36✔
633
  if (d_pair == nullptr) {
36!
634
    throw std::runtime_error("Trying to compute the padding size from an invalid DNSCrypt query");
×
635
  }
×
636

637
  DNSCryptNonceType nonce;
36✔
638
  memcpy(nonce.data(), d_header.clientNonce.data(), d_header.clientNonce.size());
36✔
639
  memcpy(&(nonce.at(d_header.clientNonce.size())), d_header.clientNonce.data(), d_header.clientNonce.size());
36✔
640
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
641
  crypto_stream(reinterpret_cast<unsigned char*>(&rnd), sizeof(rnd), nonce.data(), d_pair->privateKey.key.data());
36✔
642

643
  paddedSize = unpaddedLen + rnd % (maxLen - unpaddedLen + 1);
36✔
644
  paddedSize += DNSCRYPT_PADDED_BLOCK_SIZE - (paddedSize % DNSCRYPT_PADDED_BLOCK_SIZE);
36✔
645

646
  if (paddedSize > maxLen) {
36✔
647
    paddedSize = maxLen;
5✔
648
  }
5✔
649

650
  result = paddedSize - unpaddedLen;
36✔
651

652
  return result;
36✔
653
}
36✔
654

655
int DNSCryptQuery::encryptResponse(PacketBuffer& response, size_t maxResponseSize, bool tcp)
656
{
36✔
657
  if (response.empty() || response.size() > maxResponseSize || !d_encrypted || d_pair == nullptr) {
36!
658
    throw std::runtime_error("Trying to encrypt a DNSCrypt response from an invalid state");
×
659
  }
×
660

661
  DNSCryptResponseHeader responseHeader{};
36✔
662
  /* a DNSCrypt UDP response can't be larger than the (padded) DNSCrypt query */
663
  if (!tcp && d_paddedLen < response.size()) {
36✔
664
    /* so we need to truncate it */
665
    size_t questionSize = 0;
2✔
666

667
    if (response.size() > sizeof(dnsheader)) {
2!
668
      unsigned int qnameWireLength = 0;
2✔
669
      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
670
      DNSName tempQName(reinterpret_cast<const char*>(response.data()), response.size(), sizeof(dnsheader), false, nullptr, nullptr, &qnameWireLength);
2✔
671
      if (qnameWireLength > 0) {
2!
672
        questionSize = qnameWireLength + DNS_TYPE_SIZE + DNS_CLASS_SIZE;
2✔
673
      }
2✔
674
    }
2✔
675

676
    response.resize(sizeof(dnsheader) + questionSize);
2✔
677

678
    if (response.size() > d_paddedLen) {
2!
679
      /* that does not seem right but let's truncate even more */
680
      response.resize(d_paddedLen);
×
681
    }
×
682
    dnsdist::PacketMangling::editDNSHeaderFromPacket(response, [](dnsheader& header) {
2✔
683
      header.ancount = 0;
2✔
684
      header.arcount = 0;
2✔
685
      header.nscount = 0;
2✔
686
      header.tc = 1;
2✔
687
      return true;
2✔
688
    });
2✔
689
  }
2✔
690

691
  size_t requiredSize = sizeof(responseHeader) + DNSCRYPT_MAC_SIZE + response.size();
36✔
692
  size_t maxSize = std::min(maxResponseSize, requiredSize + DNSCRYPT_MAX_RESPONSE_PADDING_SIZE);
36✔
693
  uint16_t paddingSize = computePaddingSize(requiredSize, maxSize);
36✔
694
  requiredSize += paddingSize;
36✔
695

696
  if (requiredSize > maxResponseSize) {
36!
697
    return ENOBUFS;
×
698
  }
×
699

700
  memcpy(responseHeader.nonce.data(), d_header.clientNonce.data(), d_header.clientNonce.size());
36✔
701
  fillServerNonce(responseHeader.nonce);
36✔
702

703
  size_t responseLen = response.size();
36✔
704
  /* moving the existing response after the header + MAC */
705
  response.resize(requiredSize);
36✔
706
  std::copy_backward(response.begin(), response.begin() + static_cast<ssize_t>(responseLen), response.begin() + static_cast<ssize_t>(responseLen + sizeof(responseHeader) + DNSCRYPT_MAC_SIZE));
36✔
707

708
  uint16_t pos = 0;
36✔
709
  /* copying header */
710
  memcpy(&response.at(pos), &responseHeader, sizeof(responseHeader));
36✔
711
  pos += sizeof(responseHeader);
36✔
712
  /* setting MAC bytes to 0 */
713
  memset(&response.at(pos), 0, DNSCRYPT_MAC_SIZE);
36✔
714
  pos += DNSCRYPT_MAC_SIZE;
36✔
715
  uint16_t toEncryptPos = pos;
36✔
716
  /* skipping response */
717
  pos += responseLen;
36✔
718
  /* padding */
719
  response.at(pos) = static_cast<uint8_t>(0x80);
36✔
720
  pos++;
36✔
721
  memset(&response.at(pos), 0, paddingSize - 1);
36✔
722
  pos += (paddingSize - 1);
36✔
723

724
  /* encrypting */
725
#ifdef HAVE_CRYPTO_BOX_EASY_AFTERNM
36✔
726
  int res = computeSharedKey();
36✔
727
  if (res != 0) {
36!
728
    return res;
×
729
  }
×
730

731
  const DNSCryptExchangeVersion version = getVersion();
36✔
732

733
  if (version == DNSCryptExchangeVersion::VERSION1) {
36!
734
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
735
    res = crypto_box_easy_afternm(reinterpret_cast<unsigned char*>(&response.at(sizeof(responseHeader))),
36✔
736
                                  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
737
                                  reinterpret_cast<unsigned char*>(&response.at(toEncryptPos)),
36✔
738
                                  responseLen + paddingSize,
36✔
739
                                  responseHeader.nonce.data(),
36✔
740
                                  d_sharedKey.data());
36✔
741
  }
36✔
742
  else if (version == DNSCryptExchangeVersion::VERSION2) {
×
743
#ifdef HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY
×
744
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
745
    res = crypto_box_curve25519xchacha20poly1305_easy_afternm(reinterpret_cast<unsigned char*>(&response.at(sizeof(responseHeader))),
×
746
                                                              // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
747
                                                              reinterpret_cast<unsigned char*>(&response.at(toEncryptPos)),
×
748
                                                              responseLen + paddingSize,
×
749
                                                              responseHeader.nonce.data(),
×
750
                                                              d_sharedKey.data());
×
751
#else /* HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY */
752
    res = -1;
753
#endif /* HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY */
754
  }
×
755
  else {
×
756
    res = -1;
×
757
  }
×
758
#else
759
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
760
  int res = crypto_box_easy(reinterpret_cast<unsigned char*>(&response.at(sizeof(responseHeader))),
761
                            // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
762
                            reinterpret_cast<unsigned char*>(&response.at(toEncryptPos)),
763
                            responseLen + paddingSize,
764
                            responseHeader.nonce.data(),
765
                            d_header.clientPK.data(),
766
                            d_pair->privateKey.key.data());
767
#endif /* HAVE_CRYPTO_BOX_EASY_AFTERNM */
768

769
  if (res == 0) {
36!
770
    if (pos != requiredSize) {
36!
771
      throw std::runtime_error("Unexpected size for encrypted DNSCrypt response");
×
772
    }
×
773
  }
36✔
774

775
  return res;
36✔
776
}
36✔
777

778
int DNSCryptContext::encryptQuery(PacketBuffer& packet, size_t maximumSize, const DNSCryptCertificatePair::PublicKeyType& clientPublicKey, const DNSCryptPrivateKey& clientPrivateKey, const DNSCryptClientNonceType& clientNonce, bool tcp, const std::shared_ptr<DNSCryptCert>& cert)
779
{
8✔
780
  if (packet.empty() || cert == nullptr) {
8!
781
    throw std::runtime_error("Trying to encrypt a DNSCrypt query with an invalid state");
×
782
  }
×
783

784
  size_t queryLen = packet.size();
8✔
785
  DNSCryptNonceType nonce;
8✔
786
  size_t requiredSize = sizeof(DNSCryptQueryHeader) + DNSCRYPT_MAC_SIZE + queryLen;
8✔
787
  /* this is not optimal, we should compute a random padding size, multiple of DNSCRYPT_PADDED_BLOCK_SIZE,
788
     DNSCRYPT_PADDED_BLOCK_SIZE <= padding size <= 4096? */
789
  uint16_t paddingSize = DNSCRYPT_PADDED_BLOCK_SIZE - (queryLen % DNSCRYPT_PADDED_BLOCK_SIZE);
8✔
790
  requiredSize += paddingSize;
8✔
791

792
  if (!tcp && requiredSize < DNSCryptQuery::s_minUDPLength) {
8!
793
    paddingSize += (DNSCryptQuery::s_minUDPLength - requiredSize);
8✔
794
    requiredSize = DNSCryptQuery::s_minUDPLength;
8✔
795
  }
8✔
796

797
  if (requiredSize > maximumSize) {
8✔
798
    return ENOBUFS;
2✔
799
  }
2✔
800

801
  /* moving the existing query after the header + MAC */
802
  packet.resize(requiredSize);
6✔
803
  std::copy_backward(packet.begin(), packet.begin() + static_cast<ssize_t>(queryLen), packet.begin() + static_cast<ssize_t>(queryLen + sizeof(DNSCryptQueryHeader) + DNSCRYPT_MAC_SIZE));
6✔
804

805
  size_t pos = 0;
6✔
806
  /* client magic */
807
  memcpy(&packet.at(pos), cert->signedData.clientMagic.data(), sizeof(cert->signedData.clientMagic));
6✔
808
  pos += cert->signedData.clientMagic.size();
6✔
809

810
  /* client PK */
811
  memcpy(&packet.at(pos), clientPublicKey.data(), clientPublicKey.size());
6✔
812
  pos += DNSCRYPT_PUBLIC_KEY_SIZE;
6✔
813

814
  /* client nonce */
815
  memcpy(&packet.at(pos), clientNonce.data(), clientNonce.size());
6✔
816
  pos += clientNonce.size();
6✔
817
  size_t encryptedPos = pos;
6✔
818

819
  /* clear the MAC bytes */
820
  memset(&packet.at(pos), 0, DNSCRYPT_MAC_SIZE);
6✔
821
  pos += DNSCRYPT_MAC_SIZE;
6✔
822

823
  /* skipping data */
824
  pos += queryLen;
6✔
825

826
  /* padding */
827
  packet.at(pos) = static_cast<uint8_t>(0x80);
6✔
828
  pos++;
6✔
829
  memset(&packet.at(pos), 0, paddingSize - 1);
6✔
830
  pos += paddingSize - 1;
6✔
831

832
  memcpy(nonce.data(), clientNonce.data(), clientNonce.size());
6✔
833
  memset(&nonce.at(clientNonce.size()), 0, DNSCRYPT_NONCE_SIZE / 2);
6✔
834

835
  const DNSCryptExchangeVersion version = getExchangeVersion(*cert);
6✔
836
  int res = -1;
6✔
837

838
  if (version == DNSCryptExchangeVersion::VERSION1) {
6!
839
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
840
    res = crypto_box_easy(reinterpret_cast<unsigned char*>(&packet.at(encryptedPos)),
6✔
841
                          // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
842
                          reinterpret_cast<unsigned char*>(&packet.at(encryptedPos + DNSCRYPT_MAC_SIZE)),
6✔
843
                          queryLen + paddingSize,
6✔
844
                          nonce.data(),
6✔
845
                          cert->signedData.resolverPK.data(),
6✔
846
                          clientPrivateKey.key.data());
6✔
847
  }
6✔
848
  else if (version == DNSCryptExchangeVersion::VERSION2) {
×
849
#ifdef HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY
×
850
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
851
    res = crypto_box_curve25519xchacha20poly1305_easy(reinterpret_cast<unsigned char*>(&packet.at(encryptedPos)),
×
852
                                                      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
853
                                                      reinterpret_cast<unsigned char*>(&packet.at(encryptedPos + DNSCRYPT_MAC_SIZE)),
×
854
                                                      queryLen + paddingSize,
×
855
                                                      nonce.data(),
×
856
                                                      cert->signedData.resolverPK.data(),
×
857
                                                      clientPrivateKey.key.data());
×
858
#endif /* HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY */
×
859
  }
×
860
  else {
×
861
    throw std::runtime_error("Unknown DNSCrypt exchange version");
×
862
  }
×
863

864
  if (res == 0) {
6!
865
    if (pos != requiredSize) {
6!
866
      throw std::runtime_error("Unexpected size for encrypted DNSCrypt query");
×
867
    }
×
868
  }
6✔
869

870
  return res;
6✔
871
}
6✔
872

873
bool generateDNSCryptCertificate(const std::string& providerPrivateKeyFile, uint32_t serial, time_t begin, time_t end, DNSCryptExchangeVersion version, DNSCryptCert& certOut, DNSCryptPrivateKey& keyOut)
874
{
16✔
875
  bool success = false;
16✔
876
  DNSCryptCertSignedData::ResolverPrivateKeyType providerPrivateKey{};
16✔
877
  sodium_mlock(providerPrivateKey.data(), providerPrivateKey.size());
16✔
878
  sodium_memzero(providerPrivateKey.data(), providerPrivateKey.size());
16✔
879

880
  try {
16✔
881
    ifstream providerKStream(providerPrivateKeyFile);
16✔
882
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): this is the API we have
883
    providerKStream.read(reinterpret_cast<char*>(providerPrivateKey.data()), providerPrivateKey.size());
16✔
884
    if (providerKStream.fail()) {
16!
885
      providerKStream.close();
×
886
      throw std::runtime_error("Invalid DNSCrypt provider key file " + providerPrivateKeyFile);
×
887
    }
×
888

889
    DNSCryptContext::generateCertificate(serial, begin, end, version, providerPrivateKey, keyOut, certOut);
16✔
890
    success = true;
16✔
891
  }
16✔
892
  catch (const std::exception& e) {
16✔
893
    errlog(e.what());
×
894
  }
×
895

896
  sodium_memzero(providerPrivateKey.data(), providerPrivateKey.size());
16✔
897
  sodium_munlock(providerPrivateKey.data(), providerPrivateKey.size());
16✔
898
  return success;
16✔
899
}
16✔
900

901
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc