]>
Commit | Line | Data |
---|---|---|
12471842 PL |
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 | */ | |
11e1e08b RG |
22 | #include "config.h" |
23 | #ifdef HAVE_DNSCRYPT | |
24 | #include <fstream> | |
25 | #include "dolog.hh" | |
26 | #include "dnscrypt.hh" | |
27 | #include "dnswriter.hh" | |
28 | ||
43234e76 | 29 | DNSCryptPrivateKey::DNSCryptPrivateKey() |
11e1e08b RG |
30 | { |
31 | sodium_memzero(key, sizeof(key)); | |
32 | sodium_mlock(key, sizeof(key)); | |
33 | } | |
34 | ||
43234e76 | 35 | void DNSCryptPrivateKey::loadFromFile(const std::string& keyFile) |
11e1e08b RG |
36 | { |
37 | ifstream file(keyFile); | |
38 | sodium_memzero(key, sizeof(key)); | |
39 | file.read((char*) key, sizeof(key)); | |
40 | ||
41 | if (file.fail()) { | |
42 | sodium_memzero(key, sizeof(key)); | |
43 | file.close(); | |
44 | throw std::runtime_error("Invalid DNSCrypt key file " + keyFile); | |
45 | } | |
46 | ||
47 | file.close(); | |
48 | } | |
49 | ||
43234e76 | 50 | void DNSCryptPrivateKey::saveToFile(const std::string& keyFile) const |
11e1e08b RG |
51 | { |
52 | ofstream file(keyFile); | |
53 | file.write((char*) key, sizeof(key)); | |
54 | file.close(); | |
55 | } | |
56 | ||
43234e76 | 57 | DNSCryptPrivateKey::~DNSCryptPrivateKey() |
11e1e08b | 58 | { |
11e1e08b RG |
59 | sodium_munlock(key, sizeof(key)); |
60 | } | |
61 | ||
43234e76 RG |
62 | DNSCryptExchangeVersion DNSCryptQuery::getVersion() const |
63 | { | |
64 | if (d_pair == nullptr) { | |
65 | throw std::runtime_error("Unable to determine the version of a DNSCrypt query if there is not associated cert"); | |
66 | } | |
67 | ||
68 | return DNSCryptContext::getExchangeVersion(d_pair->cert); | |
69 | } | |
70 | ||
00ffb1c2 | 71 | #ifdef HAVE_CRYPTO_BOX_EASY_AFTERNM |
43234e76 | 72 | DNSCryptQuery::~DNSCryptQuery() |
332fdc5f | 73 | { |
43234e76 RG |
74 | if (d_sharedKeyComputed) { |
75 | sodium_munlock(d_sharedKey, sizeof(d_sharedKey)); | |
8089cbe5 | 76 | } |
332fdc5f RG |
77 | } |
78 | ||
43234e76 | 79 | int DNSCryptQuery::computeSharedKey() |
332fdc5f | 80 | { |
43234e76 RG |
81 | assert(d_pair != nullptr); |
82 | ||
8089cbe5 RG |
83 | int res = 0; |
84 | ||
43234e76 | 85 | if (d_sharedKeyComputed) { |
8089cbe5 RG |
86 | return res; |
87 | } | |
88 | ||
43234e76 RG |
89 | const DNSCryptExchangeVersion version = DNSCryptContext::getExchangeVersion(d_pair->cert); |
90 | ||
91 | sodium_mlock(d_sharedKey, sizeof(d_sharedKey)); | |
92 | ||
93 | if (version == DNSCryptExchangeVersion::VERSION1) { | |
94 | res = crypto_box_beforenm(d_sharedKey, | |
95 | d_header.clientPK, | |
96 | d_pair->privateKey.key); | |
97 | } | |
98 | else if (version == DNSCryptExchangeVersion::VERSION2) { | |
99 | #ifdef HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY | |
100 | res = crypto_box_curve25519xchacha20poly1305_beforenm(d_sharedKey, | |
101 | d_header.clientPK, | |
102 | d_pair->privateKey.key); | |
103 | #else /* HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY */ | |
104 | res = -1; | |
105 | #endif /* HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY */ | |
106 | } | |
107 | else { | |
108 | res = -1; | |
109 | } | |
8089cbe5 RG |
110 | |
111 | if (res != 0) { | |
43234e76 | 112 | sodium_munlock(d_sharedKey, sizeof(d_sharedKey)); |
8089cbe5 RG |
113 | return res; |
114 | } | |
115 | ||
43234e76 | 116 | d_sharedKeyComputed = true; |
8089cbe5 | 117 | return res; |
332fdc5f | 118 | } |
00ffb1c2 | 119 | #else |
43234e76 | 120 | DNSCryptQuery::~DNSCryptQuery() |
00ffb1c2 RG |
121 | { |
122 | } | |
123 | #endif /* HAVE_CRYPTO_BOX_EASY_AFTERNM */ | |
8089cbe5 | 124 | |
040793d4 OM |
125 | |
126 | DNSCryptContext::~DNSCryptContext() { | |
040793d4 OM |
127 | } |
128 | ||
37b6d73d | 129 | DNSCryptContext::DNSCryptContext(const std::string& pName, const std::vector<CertKeyPaths>& certKeys): d_certKeyPaths(certKeys), providerName(pName) |
43234e76 | 130 | { |
37b6d73d | 131 | reloadCertificates(); |
43234e76 RG |
132 | } |
133 | ||
134 | DNSCryptContext::DNSCryptContext(const std::string& pName, const DNSCryptCert& certificate, const DNSCryptPrivateKey& pKey): providerName(pName) | |
135 | { | |
43234e76 RG |
136 | addNewCertificate(certificate, pKey); |
137 | } | |
138 | ||
139 | void DNSCryptContext::generateProviderKeys(unsigned char publicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE], unsigned char privateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE]) | |
11e1e08b RG |
140 | { |
141 | int res = crypto_sign_ed25519_keypair(publicKey, privateKey); | |
142 | ||
143 | if (res != 0) { | |
144 | throw std::runtime_error("Error generating DNSCrypt provider keys"); | |
145 | } | |
146 | } | |
147 | ||
43234e76 | 148 | std::string DNSCryptContext::getProviderFingerprint(unsigned char publicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE]) |
11e1e08b RG |
149 | { |
150 | boost::format fmt("%02X%02X"); | |
151 | ostringstream ret; | |
152 | ||
153 | for (size_t idx = 0; idx < DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE; idx += 2) | |
154 | { | |
155 | ret << (fmt % static_cast<int>(publicKey[idx]) % static_cast<int>(publicKey[idx+1])); | |
156 | if (idx < (DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE - 2)) { | |
157 | ret << ":"; | |
158 | } | |
159 | } | |
160 | ||
161 | return ret.str(); | |
162 | } | |
163 | ||
43234e76 RG |
164 | void DNSCryptContext::setExchangeVersion(const DNSCryptExchangeVersion& version, unsigned char esVersion[sizeof(DNSCryptCert::esVersion)]) |
165 | { | |
166 | esVersion[0] = 0x00; | |
167 | ||
168 | if (version == DNSCryptExchangeVersion::VERSION1) { | |
169 | esVersion[1] = { 0x01 }; | |
170 | } | |
171 | else if (version == DNSCryptExchangeVersion::VERSION2) { | |
172 | esVersion[1] = { 0x02 }; | |
173 | } | |
174 | else { | |
175 | throw std::runtime_error("Unknown DNSCrypt exchange version"); | |
176 | } | |
177 | } | |
178 | ||
179 | DNSCryptExchangeVersion DNSCryptContext::getExchangeVersion(const unsigned char esVersion[sizeof(DNSCryptCert::esVersion)]) | |
180 | { | |
181 | if (esVersion[0] != 0x00) { | |
182 | throw std::runtime_error("Unknown DNSCrypt exchange version"); | |
183 | } | |
184 | ||
185 | if (esVersion[1] == 0x01) { | |
186 | return DNSCryptExchangeVersion::VERSION1; | |
187 | } | |
188 | else if (esVersion[1] == 0x02) { | |
189 | return DNSCryptExchangeVersion::VERSION2; | |
190 | } | |
191 | ||
192 | throw std::runtime_error("Unknown DNSCrypt exchange version"); | |
193 | } | |
194 | ||
195 | DNSCryptExchangeVersion DNSCryptContext::getExchangeVersion(const DNSCryptCert& cert) | |
196 | { | |
197 | return getExchangeVersion(cert.esVersion); | |
198 | } | |
199 | ||
200 | ||
201 | 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) | |
11e1e08b RG |
202 | { |
203 | unsigned char magic[DNSCRYPT_CERT_MAGIC_SIZE] = DNSCRYPT_CERT_MAGIC_VALUE; | |
11e1e08b RG |
204 | unsigned char protocolMinorVersion[] = DNSCRYPT_CERT_PROTOCOL_MINOR_VERSION_VALUE; |
205 | unsigned char pubK[DNSCRYPT_PUBLIC_KEY_SIZE]; | |
43234e76 RG |
206 | unsigned char esVersion[sizeof(DNSCryptCert::esVersion)]; |
207 | setExchangeVersion(version, esVersion); | |
208 | ||
11e1e08b RG |
209 | generateResolverKeyPair(privateKey, pubK); |
210 | ||
211 | memcpy(cert.magic, magic, sizeof(magic)); | |
212 | memcpy(cert.esVersion, esVersion, sizeof(esVersion)); | |
213 | memcpy(cert.protocolMinorVersion, protocolMinorVersion, sizeof(protocolMinorVersion)); | |
214 | memcpy(cert.signedData.resolverPK, pubK, sizeof(cert.signedData.resolverPK)); | |
215 | memcpy(cert.signedData.clientMagic, pubK, sizeof(cert.signedData.clientMagic)); | |
b7bd0317 | 216 | cert.signedData.serial = htonl(serial); |
11e1e08b RG |
217 | cert.signedData.tsStart = htonl((uint32_t) begin); |
218 | cert.signedData.tsEnd = htonl((uint32_t) end); | |
219 | ||
220 | unsigned long long signatureSize = 0; | |
221 | ||
222 | int res = crypto_sign_ed25519(cert.signature, | |
223 | &signatureSize, | |
224 | (unsigned char*) &cert.signedData, | |
225 | sizeof(cert.signedData), | |
226 | providerPrivateKey); | |
227 | ||
228 | if (res == 0) { | |
43234e76 | 229 | assert(signatureSize == sizeof(DNSCryptCertSignedData) + DNSCRYPT_SIGNATURE_SIZE); |
11e1e08b RG |
230 | } |
231 | else { | |
232 | throw std::runtime_error("Error generating DNSCrypt certificate"); | |
233 | } | |
234 | } | |
235 | ||
43234e76 | 236 | void DNSCryptContext::loadCertFromFile(const std::string&filename, DNSCryptCert& dest) |
11e1e08b RG |
237 | { |
238 | ifstream file(filename); | |
239 | file.read((char *) &dest, sizeof(dest)); | |
240 | ||
241 | if (file.fail()) | |
242 | throw std::runtime_error("Invalid dnscrypt certificate file " + filename); | |
243 | ||
244 | file.close(); | |
245 | } | |
246 | ||
43234e76 | 247 | void DNSCryptContext::saveCertFromFile(const DNSCryptCert& cert, const std::string&filename) |
11e1e08b RG |
248 | { |
249 | ofstream file(filename); | |
250 | file.write((char *) &cert, sizeof(cert)); | |
251 | file.close(); | |
252 | } | |
253 | ||
43234e76 | 254 | void DNSCryptContext::generateResolverKeyPair(DNSCryptPrivateKey& privK, unsigned char pubK[DNSCRYPT_PUBLIC_KEY_SIZE]) |
11e1e08b RG |
255 | { |
256 | int res = crypto_box_keypair(pubK, privK.key); | |
257 | ||
258 | if (res != 0) { | |
259 | throw std::runtime_error("Error generating DNSCrypt resolver keys"); | |
260 | } | |
261 | } | |
262 | ||
43234e76 | 263 | void DNSCryptContext::computePublicKeyFromPrivate(const DNSCryptPrivateKey& privK, unsigned char* pubK) |
11e1e08b RG |
264 | { |
265 | int res = crypto_scalarmult_base(pubK, | |
266 | privK.key); | |
267 | ||
268 | if (res != 0) { | |
269 | throw std::runtime_error("Error computing dnscrypt public key from the private one"); | |
270 | } | |
271 | } | |
272 | ||
43234e76 | 273 | std::string DNSCryptContext::certificateDateToStr(uint32_t date) |
11e1e08b RG |
274 | { |
275 | char buf[20]; | |
43234e76 | 276 | time_t tdate = static_cast<time_t>(ntohl(date)); |
11e1e08b RG |
277 | struct tm date_tm; |
278 | ||
279 | localtime_r(&tdate, &date_tm); | |
280 | strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &date_tm); | |
281 | ||
282 | return string(buf); | |
283 | } | |
284 | ||
37b6d73d | 285 | void DNSCryptContext::addNewCertificate(std::shared_ptr<DNSCryptCertificatePair>& newCert, bool reload) |
11e1e08b | 286 | { |
43234e76 RG |
287 | WriteLock w(&d_lock); |
288 | ||
37b6d73d RG |
289 | for (auto pair : d_certs) { |
290 | if (pair->cert.getSerial() == newCert->cert.getSerial()) { | |
bcc62bfb RG |
291 | if (reload) { |
292 | /* on reload we just assume that this is the same certificate */ | |
293 | return; | |
294 | } | |
295 | else { | |
296 | throw std::runtime_error("Error adding a new certificate: we already have a certificate with the same serial"); | |
297 | } | |
43234e76 RG |
298 | } |
299 | } | |
300 | ||
37b6d73d RG |
301 | d_certs.push_back(newCert); |
302 | } | |
303 | ||
304 | void DNSCryptContext::addNewCertificate(const DNSCryptCert& newCert, const DNSCryptPrivateKey& newKey, bool active, bool reload) | |
305 | { | |
43234e76 RG |
306 | auto pair = std::make_shared<DNSCryptCertificatePair>(); |
307 | pair->cert = newCert; | |
308 | pair->privateKey = newKey; | |
309 | computePublicKeyFromPrivate(pair->privateKey, pair->publicKey); | |
310 | pair->active = active; | |
37b6d73d RG |
311 | |
312 | addNewCertificate(pair, reload); | |
11e1e08b RG |
313 | } |
314 | ||
37b6d73d | 315 | std::shared_ptr<DNSCryptCertificatePair> DNSCryptContext::loadCertificatePair(const std::string& certFile, const std::string& keyFile) |
11e1e08b | 316 | { |
37b6d73d RG |
317 | auto pair = std::make_shared<DNSCryptCertificatePair>(); |
318 | loadCertFromFile(certFile, pair->cert); | |
319 | pair->privateKey.loadFromFile(keyFile); | |
320 | pair->active = true; | |
321 | computePublicKeyFromPrivate(pair->privateKey, pair->publicKey); | |
322 | return pair; | |
323 | } | |
43234e76 | 324 | |
37b6d73d RG |
325 | void DNSCryptContext::loadNewCertificate(const std::string& certFile, const std::string& keyFile, bool active, bool reload) |
326 | { | |
327 | auto newPair = DNSCryptContext::loadCertificatePair(certFile, keyFile); | |
328 | newPair->active = active; | |
329 | addNewCertificate(newPair, reload); | |
330 | d_certKeyPaths.push_back({certFile, keyFile}); | |
bcc62bfb RG |
331 | } |
332 | ||
37b6d73d | 333 | void DNSCryptContext::reloadCertificates() |
bcc62bfb | 334 | { |
37b6d73d RG |
335 | std::vector<std::shared_ptr<DNSCryptCertificatePair>> newCerts; |
336 | for (const auto& pair : d_certKeyPaths) { | |
337 | newCerts.push_back(DNSCryptContext::loadCertificatePair(pair.cert, pair.key)); | |
338 | } | |
339 | ||
340 | { | |
341 | WriteLock w(&d_lock); | |
342 | d_certs = std::move(newCerts); | |
343 | } | |
43234e76 RG |
344 | } |
345 | ||
346 | void DNSCryptContext::markActive(uint32_t serial) | |
347 | { | |
348 | WriteLock w(&d_lock); | |
349 | ||
37b6d73d | 350 | for (auto pair : d_certs) { |
43234e76 RG |
351 | if (pair->active == false && pair->cert.getSerial() == serial) { |
352 | pair->active = true; | |
353 | return; | |
354 | } | |
355 | } | |
356 | throw std::runtime_error("No inactive certificate found with this serial"); | |
357 | } | |
358 | ||
359 | void DNSCryptContext::markInactive(uint32_t serial) | |
360 | { | |
361 | WriteLock w(&d_lock); | |
362 | ||
37b6d73d | 363 | for (auto pair : d_certs) { |
43234e76 RG |
364 | if (pair->active == true && pair->cert.getSerial() == serial) { |
365 | pair->active = false; | |
366 | return; | |
367 | } | |
368 | } | |
369 | throw std::runtime_error("No active certificate found with this serial"); | |
11e1e08b RG |
370 | } |
371 | ||
43234e76 | 372 | void DNSCryptContext::removeInactiveCertificate(uint32_t serial) |
11e1e08b | 373 | { |
43234e76 RG |
374 | WriteLock w(&d_lock); |
375 | ||
37b6d73d | 376 | for (auto it = d_certs.begin(); it != d_certs.end(); ) { |
43234e76 | 377 | if ((*it)->active == false && (*it)->cert.getSerial() == serial) { |
37b6d73d | 378 | it = d_certs.erase(it); |
43234e76 RG |
379 | return; |
380 | } else { | |
381 | it++; | |
382 | } | |
383 | } | |
384 | throw std::runtime_error("No inactive certificate found with this serial"); | |
385 | } | |
386 | ||
387 | bool DNSCryptQuery::parsePlaintextQuery(const char * packet, uint16_t packetSize) | |
388 | { | |
389 | assert(d_ctx != nullptr); | |
390 | ||
11e1e08b | 391 | if (packetSize < sizeof(dnsheader)) { |
43234e76 | 392 | return false; |
11e1e08b RG |
393 | } |
394 | ||
43234e76 | 395 | const struct dnsheader * dh = reinterpret_cast<const struct dnsheader *>(packet); |
11e1e08b | 396 | if (dh->qr || ntohs(dh->qdcount) != 1 || dh->ancount != 0 || dh->nscount != 0 || dh->opcode != Opcode::Query) |
43234e76 | 397 | return false; |
11e1e08b RG |
398 | |
399 | unsigned int consumed; | |
400 | uint16_t qtype, qclass; | |
401 | DNSName qname(packet, packetSize, sizeof(dnsheader), false, &qtype, &qclass, &consumed); | |
bd910269 | 402 | if ((packetSize - sizeof(dnsheader)) < (consumed + sizeof(qtype) + sizeof(qclass))) |
43234e76 | 403 | return false; |
11e1e08b RG |
404 | |
405 | if (qtype != QType::TXT || qclass != QClass::IN) | |
43234e76 | 406 | return false; |
11e1e08b | 407 | |
43234e76 RG |
408 | if (qname != d_ctx->getProviderName()) |
409 | return false; | |
410 | ||
411 | d_qname = qname; | |
412 | d_id = dh->id; | |
413 | d_valid = true; | |
11e1e08b | 414 | |
43234e76 | 415 | return true; |
11e1e08b RG |
416 | } |
417 | ||
43234e76 | 418 | void DNSCryptContext::getCertificateResponse(time_t now, const DNSName& qname, uint16_t qid, std::vector<uint8_t>& response) |
11e1e08b | 419 | { |
43234e76 | 420 | DNSPacketWriter pw(response, qname, QType::TXT, QClass::IN, Opcode::Query); |
11e1e08b | 421 | struct dnsheader * dh = pw.getHeader(); |
43234e76 | 422 | dh->id = qid; |
11e1e08b RG |
423 | dh->qr = true; |
424 | dh->rcode = RCode::NoError; | |
11e1e08b | 425 | |
43234e76 | 426 | ReadLock r(&d_lock); |
ba8ade8e | 427 | for (const auto& pair : d_certs) { |
43234e76 RG |
428 | if (!pair->active || !pair->cert.isValid(now)) { |
429 | continue; | |
430 | } | |
11e1e08b | 431 | |
43234e76 RG |
432 | pw.startRecord(qname, QType::TXT, (DNSCRYPT_CERTIFICATE_RESPONSE_TTL), QClass::IN, DNSResourceRecord::ANSWER, true); |
433 | std::string scert; | |
434 | uint8_t certSize = sizeof(pair->cert); | |
435 | scert.assign((const char*) &certSize, sizeof(certSize)); | |
436 | scert.append((const char*) &pair->cert, certSize); | |
11e1e08b | 437 | |
43234e76 RG |
438 | pw.xfrBlob(scert); |
439 | pw.commit(); | |
11e1e08b | 440 | } |
43234e76 | 441 | } |
11e1e08b | 442 | |
43234e76 RG |
443 | bool DNSCryptContext::magicMatchesAPublicKey(DNSCryptQuery& query, time_t now) |
444 | { | |
445 | const unsigned char* magic = query.getClientMagic(); | |
446 | ||
447 | ReadLock r(&d_lock); | |
37b6d73d | 448 | for (const auto& pair : d_certs) { |
43234e76 RG |
449 | if (pair->cert.isValid(now) && memcmp(magic, pair->cert.signedData.clientMagic, DNSCRYPT_CLIENT_MAGIC_SIZE) == 0) { |
450 | query.setCertificatePair(pair); | |
451 | return true; | |
452 | } | |
11e1e08b RG |
453 | } |
454 | ||
455 | return false; | |
456 | } | |
457 | ||
43234e76 | 458 | bool DNSCryptQuery::isEncryptedQuery(const char * packet, uint16_t packetSize, bool tcp, time_t now) |
11e1e08b | 459 | { |
43234e76 | 460 | assert(d_ctx != nullptr); |
11e1e08b | 461 | |
43234e76 RG |
462 | d_encrypted = false; |
463 | ||
464 | if (packetSize < sizeof(DNSCryptQueryHeader)) { | |
465 | return false; | |
11e1e08b RG |
466 | } |
467 | ||
43234e76 RG |
468 | if (!tcp && packetSize < DNSCryptQuery::s_minUDPLength) { |
469 | return false; | |
11e1e08b RG |
470 | } |
471 | ||
43234e76 | 472 | const struct DNSCryptQueryHeader* header = reinterpret_cast<const struct DNSCryptQueryHeader*>(packet); |
11e1e08b | 473 | |
43234e76 | 474 | d_header = *header; |
11e1e08b | 475 | |
43234e76 RG |
476 | if (!d_ctx->magicMatchesAPublicKey(*this, now)) { |
477 | return false; | |
11e1e08b RG |
478 | } |
479 | ||
43234e76 RG |
480 | d_encrypted = true; |
481 | ||
482 | return true; | |
11e1e08b RG |
483 | } |
484 | ||
43234e76 | 485 | void DNSCryptQuery::getDecrypted(bool tcp, char* packet, uint16_t packetSize, uint16_t* decryptedQueryLen) |
11e1e08b | 486 | { |
43234e76 RG |
487 | assert(decryptedQueryLen != nullptr); |
488 | assert(d_encrypted); | |
489 | assert(d_pair != nullptr); | |
490 | assert(d_valid == false); | |
11e1e08b RG |
491 | |
492 | #ifdef DNSCRYPT_STRICT_PADDING_LENGTH | |
43234e76 RG |
493 | if (tcp && ((packetSize - sizeof(DNSCryptQueryHeader)) % DNSCRYPT_PADDED_BLOCK_SIZE) != 0) { |
494 | vinfolog("Dropping encrypted query with invalid size of %d (should be a multiple of %d)", (packetSize - sizeof(DNSCryptQueryHeader)), DNSCRYPT_PADDED_BLOCK_SIZE); | |
11e1e08b RG |
495 | return; |
496 | } | |
497 | #endif | |
498 | ||
499 | unsigned char nonce[DNSCRYPT_NONCE_SIZE]; | |
43234e76 | 500 | static_assert(sizeof(nonce) == (2* sizeof(d_header.clientNonce)), "Nonce should be larger than clientNonce (half)"); |
ef2ea4bf | 501 | static_assert(sizeof(d_header.clientPK) == DNSCRYPT_PUBLIC_KEY_SIZE, "Client Public key size is not right"); |
43234e76 | 502 | static_assert(sizeof(d_pair->privateKey.key) == DNSCRYPT_PRIVATE_KEY_SIZE, "Private key size is not right"); |
11e1e08b | 503 | |
43234e76 RG |
504 | memcpy(nonce, &d_header.clientNonce, sizeof(d_header.clientNonce)); |
505 | memset(nonce + sizeof(d_header.clientNonce), 0, sizeof(nonce) - sizeof(d_header.clientNonce)); | |
11e1e08b | 506 | |
00ffb1c2 | 507 | #ifdef HAVE_CRYPTO_BOX_EASY_AFTERNM |
43234e76 | 508 | int res = computeSharedKey(); |
8089cbe5 RG |
509 | if (res != 0) { |
510 | vinfolog("Dropping encrypted query we can't compute the shared key for"); | |
511 | return; | |
332fdc5f RG |
512 | } |
513 | ||
43234e76 RG |
514 | const DNSCryptExchangeVersion version = getVersion(); |
515 | ||
516 | if (version == DNSCryptExchangeVersion::VERSION1) { | |
517 | res = crypto_box_open_easy_afternm(reinterpret_cast<unsigned char*>(packet), | |
518 | reinterpret_cast<unsigned char*>(packet + sizeof(DNSCryptQueryHeader)), | |
519 | packetSize - sizeof(DNSCryptQueryHeader), | |
520 | nonce, | |
521 | d_sharedKey); | |
522 | } | |
523 | else if (version == DNSCryptExchangeVersion::VERSION2) { | |
524 | #ifdef HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY | |
525 | res = crypto_box_curve25519xchacha20poly1305_open_easy_afternm(reinterpret_cast<unsigned char*>(packet), | |
526 | reinterpret_cast<unsigned char*>(packet + sizeof(DNSCryptQueryHeader)), | |
527 | packetSize - sizeof(DNSCryptQueryHeader), | |
528 | nonce, | |
529 | d_sharedKey); | |
530 | #else /* HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY */ | |
531 | res = -1; | |
532 | #endif /* HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY */ | |
533 | } else { | |
534 | res = -1; | |
535 | } | |
536 | ||
537 | #else /* HAVE_CRYPTO_BOX_EASY_AFTERNM */ | |
538 | int res = crypto_box_open_easy(reinterpret_cast<unsigned char*>(packet), | |
539 | reinterpret_cast<unsigned char*>(packet + sizeof(DNSCryptQueryHeader)), | |
540 | packetSize - sizeof(DNSCryptQueryHeader), | |
00ffb1c2 | 541 | nonce, |
43234e76 RG |
542 | d_header.clientPK, |
543 | d_pair->privateKey.key); | |
00ffb1c2 | 544 | #endif /* HAVE_CRYPTO_BOX_EASY_AFTERNM */ |
11e1e08b RG |
545 | |
546 | if (res != 0) { | |
547 | vinfolog("Dropping encrypted query we can't decrypt"); | |
548 | return; | |
549 | } | |
550 | ||
43234e76 | 551 | *decryptedQueryLen = packetSize - sizeof(DNSCryptQueryHeader) - DNSCRYPT_MAC_SIZE; |
11e1e08b RG |
552 | uint16_t pos = *decryptedQueryLen; |
553 | assert(pos < packetSize); | |
43234e76 | 554 | d_paddedLen = *decryptedQueryLen; |
11e1e08b RG |
555 | |
556 | while(pos > 0 && packet[pos - 1] == 0) pos--; | |
557 | ||
43234e76 | 558 | if (pos == 0 || static_cast<uint8_t>(packet[pos - 1]) != 0x80) { |
11e1e08b RG |
559 | vinfolog("Dropping encrypted query with invalid padding value"); |
560 | return; | |
561 | } | |
562 | ||
563 | pos--; | |
564 | ||
565 | size_t paddingLen = *decryptedQueryLen - pos; | |
566 | *decryptedQueryLen = pos; | |
567 | ||
568 | if (tcp && paddingLen > DNSCRYPT_MAX_TCP_PADDING_SIZE) { | |
569 | vinfolog("Dropping encrypted query with too long padding size"); | |
570 | return; | |
571 | } | |
572 | ||
43234e76 RG |
573 | d_len = pos; |
574 | d_valid = true; | |
575 | } | |
11e1e08b | 576 | |
43234e76 RG |
577 | void DNSCryptQuery::getCertificateResponse(time_t now, std::vector<uint8_t>& response) const |
578 | { | |
579 | assert(d_ctx != nullptr); | |
580 | d_ctx->getCertificateResponse(now, d_qname, d_id, response); | |
11e1e08b RG |
581 | } |
582 | ||
43234e76 | 583 | void DNSCryptQuery::parsePacket(char* packet, uint16_t packetSize, bool tcp, uint16_t* decryptedQueryLen, time_t now) |
11e1e08b | 584 | { |
43234e76 RG |
585 | assert(packet != nullptr); |
586 | assert(decryptedQueryLen != nullptr); | |
11e1e08b | 587 | |
43234e76 | 588 | d_valid = false; |
11e1e08b RG |
589 | |
590 | /* might be a plaintext certificate request or an authenticated request */ | |
43234e76 RG |
591 | if (isEncryptedQuery(packet, packetSize, tcp, now)) { |
592 | getDecrypted(tcp, packet, packetSize, decryptedQueryLen); | |
11e1e08b RG |
593 | } |
594 | else { | |
43234e76 | 595 | parsePlaintextQuery(packet, packetSize); |
11e1e08b RG |
596 | } |
597 | } | |
598 | ||
43234e76 | 599 | void DNSCryptQuery::fillServerNonce(unsigned char* nonce) const |
11e1e08b | 600 | { |
43234e76 | 601 | uint32_t* dest = reinterpret_cast<uint32_t*>(nonce); |
11e1e08b RG |
602 | static const size_t nonceSize = DNSCRYPT_NONCE_SIZE / 2; |
603 | ||
604 | for (size_t pos = 0; pos < (nonceSize / sizeof(*dest)); pos++) | |
605 | { | |
606 | const uint32_t value = randombytes_random(); | |
607 | memcpy(dest + pos, &value, sizeof(value)); | |
608 | } | |
609 | } | |
610 | ||
611 | /* | |
612 | "The length of <resolver-response-pad> must be between 0 and 256 bytes, | |
613 | and must be constant for a given (<resolver-sk>, <client-nonce>) tuple." | |
614 | */ | |
43234e76 | 615 | uint16_t DNSCryptQuery::computePaddingSize(uint16_t unpaddedLen, size_t maxLen) const |
11e1e08b | 616 | { |
43234e76 | 617 | size_t paddedSize = 0; |
11e1e08b RG |
618 | uint16_t result = 0; |
619 | uint32_t rnd = 0; | |
43234e76 RG |
620 | assert(d_header.clientNonce); |
621 | assert(d_pair != nullptr); | |
622 | ||
11e1e08b | 623 | unsigned char nonce[DNSCRYPT_NONCE_SIZE]; |
43234e76 RG |
624 | memcpy(nonce, d_header.clientNonce, (DNSCRYPT_NONCE_SIZE / 2)); |
625 | memcpy(&(nonce[DNSCRYPT_NONCE_SIZE / 2]), d_header.clientNonce, (DNSCRYPT_NONCE_SIZE / 2)); | |
626 | crypto_stream((unsigned char*) &rnd, sizeof(rnd), nonce, d_pair->privateKey.key); | |
11e1e08b | 627 | |
43234e76 RG |
628 | paddedSize = unpaddedLen + rnd % (maxLen - unpaddedLen + 1); |
629 | paddedSize += DNSCRYPT_PADDED_BLOCK_SIZE - (paddedSize % DNSCRYPT_PADDED_BLOCK_SIZE); | |
11e1e08b | 630 | |
43234e76 RG |
631 | if (paddedSize > maxLen) |
632 | paddedSize = maxLen; | |
11e1e08b | 633 | |
43234e76 | 634 | result = paddedSize - unpaddedLen; |
11e1e08b RG |
635 | |
636 | return result; | |
637 | } | |
638 | ||
43234e76 | 639 | int DNSCryptQuery::encryptResponse(char* response, uint16_t responseLen, uint16_t responseSize, bool tcp, uint16_t* encryptedResponseLen) |
11e1e08b | 640 | { |
43234e76 RG |
641 | struct DNSCryptResponseHeader responseHeader; |
642 | assert(response != nullptr); | |
11e1e08b RG |
643 | assert(responseLen > 0); |
644 | assert(responseSize >= responseLen); | |
43234e76 RG |
645 | assert(encryptedResponseLen != nullptr); |
646 | assert(d_encrypted == true); | |
647 | assert(d_pair != nullptr); | |
11e1e08b | 648 | |
43234e76 RG |
649 | if (!tcp && d_paddedLen < responseLen) { |
650 | struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(response); | |
bd64cc44 RG |
651 | size_t questionSize = 0; |
652 | ||
653 | if (responseLen > sizeof(dnsheader)) { | |
654 | unsigned int consumed = 0; | |
43234e76 | 655 | DNSName tempQName(response, responseLen, sizeof(dnsheader), false, 0, 0, &consumed); |
bd64cc44 RG |
656 | if (consumed > 0) { |
657 | questionSize = consumed + DNS_TYPE_SIZE + DNS_CLASS_SIZE; | |
658 | } | |
659 | } | |
660 | ||
661 | responseLen = sizeof(dnsheader) + questionSize; | |
662 | ||
43234e76 RG |
663 | if (responseLen > d_paddedLen) { |
664 | responseLen = d_paddedLen; | |
bd64cc44 RG |
665 | } |
666 | dh->ancount = dh->arcount = dh->nscount = 0; | |
11e1e08b RG |
667 | dh->tc = 1; |
668 | } | |
669 | ||
43234e76 | 670 | size_t requiredSize = sizeof(responseHeader) + DNSCRYPT_MAC_SIZE + responseLen; |
11e1e08b | 671 | size_t maxSize = (responseSize > (requiredSize + DNSCRYPT_MAX_RESPONSE_PADDING_SIZE)) ? (requiredSize + DNSCRYPT_MAX_RESPONSE_PADDING_SIZE) : responseSize; |
43234e76 | 672 | uint16_t paddingSize = computePaddingSize(requiredSize, maxSize); |
11e1e08b RG |
673 | requiredSize += paddingSize; |
674 | ||
675 | if (requiredSize > responseSize) | |
676 | return ENOBUFS; | |
677 | ||
43234e76 RG |
678 | memcpy(&responseHeader.nonce, &d_header.clientNonce, sizeof d_header.clientNonce); |
679 | fillServerNonce(&(responseHeader.nonce[sizeof(d_header.clientNonce)])); | |
11e1e08b RG |
680 | |
681 | /* moving the existing response after the header + MAC */ | |
43234e76 | 682 | memmove(response + sizeof(responseHeader) + DNSCRYPT_MAC_SIZE, response, responseLen); |
11e1e08b RG |
683 | |
684 | uint16_t pos = 0; | |
685 | /* copying header */ | |
43234e76 RG |
686 | memcpy(response + pos, &responseHeader, sizeof(responseHeader)); |
687 | pos += sizeof(responseHeader); | |
11e1e08b RG |
688 | /* setting MAC bytes to 0 */ |
689 | memset(response + pos, 0, DNSCRYPT_MAC_SIZE); | |
690 | pos += DNSCRYPT_MAC_SIZE; | |
691 | uint16_t toEncryptPos = pos; | |
692 | /* skipping response */ | |
693 | pos += responseLen; | |
694 | /* padding */ | |
43234e76 | 695 | response[pos] = static_cast<uint8_t>(0x80); |
11e1e08b RG |
696 | pos++; |
697 | memset(response + pos, 0, paddingSize - 1); | |
698 | pos += (paddingSize - 1); | |
332fdc5f | 699 | |
11e1e08b | 700 | /* encrypting */ |
00ffb1c2 | 701 | #ifdef HAVE_CRYPTO_BOX_EASY_AFTERNM |
43234e76 | 702 | int res = computeSharedKey(); |
8089cbe5 RG |
703 | if (res != 0) { |
704 | return res; | |
332fdc5f RG |
705 | } |
706 | ||
43234e76 RG |
707 | const DNSCryptExchangeVersion version = getVersion(); |
708 | ||
709 | if (version == DNSCryptExchangeVersion::VERSION1) { | |
710 | res = crypto_box_easy_afternm(reinterpret_cast<unsigned char*>(response + sizeof(responseHeader)), | |
711 | reinterpret_cast<unsigned char*>(response + toEncryptPos), | |
712 | responseLen + paddingSize, | |
713 | responseHeader.nonce, | |
714 | d_sharedKey); | |
715 | } | |
716 | else if (version == DNSCryptExchangeVersion::VERSION2) { | |
717 | #ifdef HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY | |
718 | res = crypto_box_curve25519xchacha20poly1305_easy_afternm(reinterpret_cast<unsigned char*>(response + sizeof(responseHeader)), | |
719 | reinterpret_cast<unsigned char*>(response + toEncryptPos), | |
720 | responseLen + paddingSize, | |
721 | responseHeader.nonce, | |
722 | d_sharedKey); | |
723 | #else /* HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY */ | |
724 | res = -1; | |
725 | #endif /* HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY */ | |
726 | } | |
727 | else { | |
728 | res = -1; | |
729 | } | |
00ffb1c2 | 730 | #else |
43234e76 RG |
731 | int res = crypto_box_easy(reinterpret_cast<unsigned char*>(response + sizeof(responseHeader)), |
732 | reinterpret_cast<unsigned char*>(response + toEncryptPos), | |
00ffb1c2 | 733 | responseLen + paddingSize, |
43234e76 RG |
734 | responseHeader.nonce, |
735 | d_header.clientPK, | |
736 | d_pair->privateKey.key); | |
00ffb1c2 | 737 | #endif /* HAVE_CRYPTO_BOX_EASY_AFTERNM */ |
11e1e08b RG |
738 | |
739 | if (res == 0) { | |
740 | assert(pos == requiredSize); | |
741 | *encryptedResponseLen = requiredSize; | |
742 | } | |
743 | ||
744 | return res; | |
745 | } | |
746 | ||
f3b1a1ef | 747 | 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 |
11e1e08b | 748 | { |
43234e76 | 749 | assert(query != nullptr); |
11e1e08b RG |
750 | assert(queryLen > 0); |
751 | assert(querySize >= queryLen); | |
43234e76 RG |
752 | assert(encryptedResponseLen != nullptr); |
753 | assert(cert != nullptr); | |
754 | ||
11e1e08b | 755 | unsigned char nonce[DNSCRYPT_NONCE_SIZE]; |
43234e76 | 756 | size_t requiredSize = sizeof(DNSCryptQueryHeader) + DNSCRYPT_MAC_SIZE + queryLen; |
11e1e08b RG |
757 | /* this is not optimal, we should compute a random padding size, multiple of DNSCRYPT_PADDED_BLOCK_SIZE, |
758 | DNSCRYPT_PADDED_BLOCK_SIZE <= padding size <= 4096? */ | |
759 | uint16_t paddingSize = DNSCRYPT_PADDED_BLOCK_SIZE - (queryLen % DNSCRYPT_PADDED_BLOCK_SIZE); | |
760 | requiredSize += paddingSize; | |
761 | ||
43234e76 RG |
762 | if (!tcp && requiredSize < DNSCryptQuery::s_minUDPLength) { |
763 | paddingSize += (DNSCryptQuery::s_minUDPLength - requiredSize); | |
764 | requiredSize = DNSCryptQuery::s_minUDPLength; | |
11e1e08b RG |
765 | } |
766 | ||
767 | if (requiredSize > querySize) | |
768 | return ENOBUFS; | |
769 | ||
770 | /* moving the existing query after the header + MAC */ | |
43234e76 | 771 | memmove(query + sizeof(DNSCryptQueryHeader) + DNSCRYPT_MAC_SIZE, query, queryLen); |
11e1e08b RG |
772 | |
773 | size_t pos = 0; | |
774 | /* client magic */ | |
43234e76 RG |
775 | memcpy(query + pos, cert->signedData.clientMagic, sizeof(cert->signedData.clientMagic)); |
776 | pos += sizeof(cert->signedData.clientMagic); | |
11e1e08b RG |
777 | |
778 | /* client PK */ | |
779 | memcpy(query + pos, clientPublicKey, DNSCRYPT_PUBLIC_KEY_SIZE); | |
780 | pos += DNSCRYPT_PUBLIC_KEY_SIZE; | |
781 | ||
782 | /* client nonce */ | |
783 | memcpy(query + pos, clientNonce, DNSCRYPT_NONCE_SIZE / 2); | |
784 | pos += DNSCRYPT_NONCE_SIZE / 2; | |
785 | size_t encryptedPos = pos; | |
786 | ||
787 | /* clear the MAC bytes */ | |
788 | memset(query + pos, 0, DNSCRYPT_MAC_SIZE); | |
789 | pos += DNSCRYPT_MAC_SIZE; | |
790 | ||
791 | /* skipping data */ | |
792 | pos += queryLen; | |
793 | ||
794 | /* padding */ | |
43234e76 | 795 | query[pos] = static_cast<uint8_t>(0x80); |
11e1e08b RG |
796 | pos++; |
797 | memset(query + pos, 0, paddingSize - 1); | |
798 | pos += paddingSize - 1; | |
799 | ||
800 | memcpy(nonce, clientNonce, DNSCRYPT_NONCE_SIZE / 2); | |
801 | memset(nonce + (DNSCRYPT_NONCE_SIZE / 2), 0, DNSCRYPT_NONCE_SIZE / 2); | |
802 | ||
43234e76 RG |
803 | const DNSCryptExchangeVersion version = getExchangeVersion(*cert); |
804 | int res = -1; | |
805 | ||
806 | if (version == DNSCryptExchangeVersion::VERSION1) { | |
807 | res = crypto_box_easy(reinterpret_cast<unsigned char*>(query + encryptedPos), | |
808 | reinterpret_cast<unsigned char*>(query + encryptedPos + DNSCRYPT_MAC_SIZE), | |
809 | queryLen + paddingSize, | |
810 | nonce, | |
811 | cert->signedData.resolverPK, | |
812 | clientPrivateKey.key); | |
813 | } | |
814 | else if (version == DNSCryptExchangeVersion::VERSION2) { | |
815 | #ifdef HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY | |
816 | res = crypto_box_curve25519xchacha20poly1305_easy(reinterpret_cast<unsigned char*>(query + encryptedPos), | |
817 | reinterpret_cast<unsigned char*>(query + encryptedPos + DNSCRYPT_MAC_SIZE), | |
818 | queryLen + paddingSize, | |
819 | nonce, | |
820 | cert->signedData.resolverPK, | |
821 | clientPrivateKey.key); | |
822 | #endif /* HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY */ | |
823 | } | |
824 | else { | |
825 | throw std::runtime_error("Unknown DNSCrypt exchange version"); | |
826 | } | |
11e1e08b RG |
827 | |
828 | if (res == 0) { | |
829 | assert(pos == requiredSize); | |
830 | *encryptedResponseLen = requiredSize; | |
831 | } | |
832 | ||
833 | return res; | |
834 | } | |
835 | ||
43234e76 | 836 | bool generateDNSCryptCertificate(const std::string& providerPrivateKeyFile, uint32_t serial, time_t begin, time_t end, DNSCryptExchangeVersion version, DNSCryptCert& certOut, DNSCryptPrivateKey& keyOut) |
6bb38cd6 RG |
837 | { |
838 | bool success = false; | |
839 | unsigned char providerPrivateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE]; | |
840 | sodium_mlock(providerPrivateKey, sizeof(providerPrivateKey)); | |
841 | sodium_memzero(providerPrivateKey, sizeof(providerPrivateKey)); | |
842 | ||
843 | try { | |
844 | ifstream providerKStream(providerPrivateKeyFile); | |
845 | providerKStream.read((char*) providerPrivateKey, sizeof(providerPrivateKey)); | |
846 | if (providerKStream.fail()) { | |
847 | providerKStream.close(); | |
848 | throw std::runtime_error("Invalid DNSCrypt provider key file " + providerPrivateKeyFile); | |
849 | } | |
850 | ||
43234e76 | 851 | DNSCryptContext::generateCertificate(serial, begin, end, version, providerPrivateKey, keyOut, certOut); |
6bb38cd6 RG |
852 | success = true; |
853 | } | |
854 | catch(const std::exception& e) { | |
855 | errlog(e.what()); | |
856 | } | |
857 | ||
858 | sodium_memzero(providerPrivateKey, sizeof(providerPrivateKey)); | |
859 | sodium_munlock(providerPrivateKey, sizeof(providerPrivateKey)); | |
860 | return success; | |
861 | } | |
862 | ||
11e1e08b | 863 | #endif |