1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
|
#include "config.h"
#include <stdlib.h>
#include <fcntl.h>
#ifdef HAVE_TIME_H
#include <time.h>
#endif
#include <sys/time.h>
#include <sys/types.h>
#include "sldns/sbuffer.h"
#include "util/config_file.h"
#include "util/net_help.h"
#include "util/netevent.h"
#include "util/log.h"
#include "dnscrypt/cert.h"
#include "dnscrypt/dnscrypt.h"
#include <ctype.h>
/**
* \file
* dnscrypt functions for encrypting DNS packets.
*/
#define DNSCRYPT_QUERY_BOX_OFFSET \
(DNSCRYPT_MAGIC_HEADER_LEN + crypto_box_PUBLICKEYBYTES + crypto_box_HALF_NONCEBYTES)
// 8 bytes: magic header (CERT_MAGIC_HEADER)
// 12 bytes: the client's nonce
// 12 bytes: server nonce extension
// 16 bytes: Poly1305 MAC (crypto_box_ZEROBYTES - crypto_box_BOXZEROBYTES)
#define DNSCRYPT_REPLY_BOX_OFFSET \
(DNSCRYPT_MAGIC_HEADER_LEN + crypto_box_HALF_NONCEBYTES + crypto_box_HALF_NONCEBYTES)
/**
* Decrypt a query using the keypair that was found using dnsc_find_keypair.
* The client nonce will be extracted from the encrypted query and stored in
* client_nonce, a shared secret will be computed and stored in nmkey and the
* buffer will be decrypted inplace.
* \param[in] keypair the keypair that matches this encrypted query.
* \param[in] client_nonce where the client nonce will be stored.
* \param[in] nmkey where the shared secret key will be written.
* \param[in] buffer the encrypted buffer.
* \return 0 on success.
*/
static int
dnscrypt_server_uncurve(const KeyPair *keypair,
uint8_t client_nonce[crypto_box_HALF_NONCEBYTES],
uint8_t nmkey[crypto_box_BEFORENMBYTES],
struct sldns_buffer* buffer)
{
size_t len = sldns_buffer_limit(buffer);
uint8_t *const buf = sldns_buffer_begin(buffer);
uint8_t nonce[crypto_box_NONCEBYTES];
struct dnscrypt_query_header *query_header;
if (len <= DNSCRYPT_QUERY_HEADER_SIZE) {
return -1;
}
query_header = (struct dnscrypt_query_header *)buf;
memcpy(nmkey, query_header->publickey, crypto_box_PUBLICKEYBYTES);
if (crypto_box_beforenm(nmkey, nmkey, keypair->crypt_secretkey) != 0) {
return -1;
}
memcpy(nonce, query_header->nonce, crypto_box_HALF_NONCEBYTES);
memset(nonce + crypto_box_HALF_NONCEBYTES, 0, crypto_box_HALF_NONCEBYTES);
sldns_buffer_set_at(buffer,
DNSCRYPT_QUERY_BOX_OFFSET - crypto_box_BOXZEROBYTES,
0, crypto_box_BOXZEROBYTES);
if (crypto_box_open_afternm
(buf + DNSCRYPT_QUERY_BOX_OFFSET - crypto_box_BOXZEROBYTES,
buf + DNSCRYPT_QUERY_BOX_OFFSET - crypto_box_BOXZEROBYTES,
len - DNSCRYPT_QUERY_BOX_OFFSET + crypto_box_BOXZEROBYTES, nonce,
nmkey) != 0) {
return -1;
}
while (*sldns_buffer_at(buffer, --len) == 0)
;
if (*sldns_buffer_at(buffer, len) != 0x80) {
return -1;
}
memcpy(client_nonce, nonce, crypto_box_HALF_NONCEBYTES);
memmove(sldns_buffer_begin(buffer),
sldns_buffer_at(buffer, DNSCRYPT_QUERY_HEADER_SIZE),
len - DNSCRYPT_QUERY_HEADER_SIZE);
sldns_buffer_set_position(buffer, 0);
sldns_buffer_set_limit(buffer, len - DNSCRYPT_QUERY_HEADER_SIZE);
return 0;
}
/**
* Add random padding to a buffer, according to a client nonce.
* The length has to depend on the query in order to avoid reply attacks.
*
* @param buf a buffer
* @param len the initial size of the buffer
* @param max_len the maximum size
* @param nonce a nonce, made of the client nonce repeated twice
* @param secretkey
* @return the new size, after padding
*/
size_t
dnscrypt_pad(uint8_t *buf, const size_t len, const size_t max_len,
const uint8_t *nonce, const uint8_t *secretkey)
{
uint8_t *buf_padding_area = buf + len;
size_t padded_len;
uint32_t rnd;
// no padding
if (max_len < len + DNSCRYPT_MIN_PAD_LEN)
return len;
assert(nonce[crypto_box_HALF_NONCEBYTES] == nonce[0]);
crypto_stream((unsigned char *)&rnd, (unsigned long long)sizeof(rnd), nonce,
secretkey);
padded_len =
len + DNSCRYPT_MIN_PAD_LEN + rnd % (max_len - len -
DNSCRYPT_MIN_PAD_LEN + 1);
padded_len += DNSCRYPT_BLOCK_SIZE - padded_len % DNSCRYPT_BLOCK_SIZE;
if (padded_len > max_len)
padded_len = max_len;
memset(buf_padding_area, 0, padded_len - len);
*buf_padding_area = 0x80;
return padded_len;
}
uint64_t
dnscrypt_hrtime(void)
{
struct timeval tv;
uint64_t ts = (uint64_t)0U;
int ret;
ret = gettimeofday(&tv, NULL);
if (ret == 0) {
ts = (uint64_t)tv.tv_sec * 1000000U + (uint64_t)tv.tv_usec;
} else {
log_err("gettimeofday: %s", strerror(errno));
}
return ts;
}
/**
* Add the server nonce part to once.
* The nonce is made half of client nonce and the seconf half of the server
* nonce, both of them of size crypto_box_HALF_NONCEBYTES.
* \param[in] nonce: a uint8_t* of size crypto_box_NONCEBYTES
*/
static void
add_server_nonce(uint8_t *nonce)
{
uint64_t ts;
uint64_t tsn;
uint32_t suffix;
ts = dnscrypt_hrtime();
// TODO? dnscrypt-wrapper does some logic with context->nonce_ts_last
// unclear if we really need it, so skipping it for now.
tsn = (ts << 10) | (randombytes_random() & 0x3ff);
#if (BYTE_ORDER == LITTLE_ENDIAN)
tsn =
(((uint64_t)htonl((uint32_t)tsn)) << 32) | htonl((uint32_t)(tsn >> 32));
#endif
memcpy(nonce + crypto_box_HALF_NONCEBYTES, &tsn, 8);
suffix = randombytes_random();
memcpy(nonce + crypto_box_HALF_NONCEBYTES + 8, &suffix, 4);
}
/**
* Encrypt a reply using the keypair that was used with the query.
* The client nonce will be extracted from the encrypted query and stored in
* The buffer will be encrypted inplace.
* \param[in] keypair the keypair that matches this encrypted query.
* \param[in] client_nonce client nonce used during the query
* \param[in] nmkey shared secret key used during the query.
* \param[in] buffer the buffer where to encrypt the reply.
* \param[in] udp if whether or not it is a UDP query.
* \param[in] max_udp_size configured max udp size.
* \return 0 on success.
*/
static int
dnscrypt_server_curve(const KeyPair *keypair,
uint8_t client_nonce[crypto_box_HALF_NONCEBYTES],
uint8_t nmkey[crypto_box_BEFORENMBYTES],
struct sldns_buffer* buffer,
uint8_t udp,
size_t max_udp_size)
{
size_t dns_reply_len = sldns_buffer_limit(buffer);
size_t max_len = dns_reply_len + DNSCRYPT_MAX_PADDING + DNSCRYPT_REPLY_HEADER_SIZE;
size_t max_reply_size = max_udp_size - 20U - 8U;
uint8_t nonce[crypto_box_NONCEBYTES];
uint8_t *boxed;
uint8_t *const buf = sldns_buffer_begin(buffer);
size_t len = sldns_buffer_limit(buffer);
if(udp){
if (max_len > max_reply_size)
max_len = max_reply_size;
}
memcpy(nonce, client_nonce, crypto_box_HALF_NONCEBYTES);
memcpy(nonce + crypto_box_HALF_NONCEBYTES, client_nonce,
crypto_box_HALF_NONCEBYTES);
boxed = buf + DNSCRYPT_REPLY_BOX_OFFSET;
memmove(boxed + crypto_box_MACBYTES, buf, len);
len = dnscrypt_pad(boxed + crypto_box_MACBYTES, len,
max_len - DNSCRYPT_REPLY_HEADER_SIZE, nonce,
keypair->crypt_secretkey);
sldns_buffer_set_at(buffer,
DNSCRYPT_REPLY_BOX_OFFSET - crypto_box_BOXZEROBYTES,
0, crypto_box_ZEROBYTES);
// add server nonce extension
add_server_nonce(nonce);
if (crypto_box_afternm
(boxed - crypto_box_BOXZEROBYTES, boxed - crypto_box_BOXZEROBYTES,
len + crypto_box_ZEROBYTES, nonce, nmkey) != 0) {
return -1;
}
sldns_buffer_write_at(buffer, 0, DNSCRYPT_MAGIC_RESPONSE, DNSCRYPT_MAGIC_HEADER_LEN);
sldns_buffer_write_at(buffer, DNSCRYPT_MAGIC_HEADER_LEN, nonce, crypto_box_NONCEBYTES);
sldns_buffer_set_limit(buffer, len + DNSCRYPT_REPLY_HEADER_SIZE);
return 0;
}
/**
* Read the content of fname into buf.
* \param[in] fname name of the file to read.
* \param[in] buf the buffer in which to read the content of the file.
* \param[in] count number of bytes to read.
* \return 0 on success.
*/
static int
dnsc_read_from_file(char *fname, char *buf, size_t count)
{
int fd;
fd = open(fname, O_RDONLY);
if (fd == -1) {
return -1;
}
if (read(fd, buf, count) != (ssize_t)count) {
close(fd);
return -2;
}
close(fd);
return 0;
}
/**
* Parse certificates files provided by the configuration and load them into
* dnsc_env.
* \param[in] env the dnsc_env structure to load the certs into.
* \param[in] cfg the configuration.
* \return the number of certificates loaded.
*/
static int
dnsc_parse_certs(struct dnsc_env *env, struct config_file *cfg)
{
struct config_strlist *head;
size_t signed_cert_id;
env->signed_certs_count = 0U;
for (head = cfg->dnscrypt_provider_cert; head; head = head->next) {
env->signed_certs_count++;
}
env->signed_certs = sodium_allocarray(env->signed_certs_count,
sizeof *env->signed_certs);
signed_cert_id = 0U;
for(head = cfg->dnscrypt_provider_cert; head; head = head->next, signed_cert_id++) {
if(dnsc_read_from_file(
head->str,
(char *)(env->signed_certs + signed_cert_id),
sizeof(struct SignedCert)) != 0) {
fatal_exit("dnsc_parse_certs: failed to load %s: %s", head->str, strerror(errno));
}
verbose(VERB_OPS, "Loaded cert %s", head->str);
}
return signed_cert_id;
}
/**
* Helper function to convert a binary key into a printable fingerprint.
* \param[in] fingerprint the buffer in which to write the printable key.
* \param[in] key the key to convert.
*/
void
dnsc_key_to_fingerprint(char fingerprint[80U], const uint8_t * const key)
{
const size_t fingerprint_size = 80U;
size_t fingerprint_pos = (size_t) 0U;
size_t key_pos = (size_t) 0U;
for (;;) {
assert(fingerprint_size > fingerprint_pos);
snprintf(&fingerprint[fingerprint_pos],
fingerprint_size - fingerprint_pos, "%02X%02X",
key[key_pos], key[key_pos + 1U]);
key_pos += 2U;
if (key_pos >= crypto_box_PUBLICKEYBYTES) {
break;
}
fingerprint[fingerprint_pos + 4U] = ':';
fingerprint_pos += 5U;
}
}
/**
* Find the keypair matching a DNSCrypt query.
* \param[in] dnscenv The DNSCrypt enviroment, which contains the list of keys
* supported by the server.
* \param[in] buffer The encrypted DNS query.
* \return a KeyPair * if we found a key pair matching the query, NULL otherwise.
*/
static const KeyPair *
dnsc_find_keypair(struct dnsc_env* dnscenv, struct sldns_buffer* buffer)
{
const KeyPair *keypairs = dnscenv->keypairs;
struct dnscrypt_query_header *dnscrypt_header;
size_t i;
if (sldns_buffer_limit(buffer) < DNSCRYPT_QUERY_HEADER_SIZE) {
return NULL;
}
dnscrypt_header = (struct dnscrypt_query_header *)sldns_buffer_begin(buffer);
for (i = 0U; i < dnscenv->keypairs_count; i++) {
if (memcmp(keypairs[i].crypt_publickey, dnscrypt_header->magic_query,
DNSCRYPT_MAGIC_HEADER_LEN) == 0) {
return &keypairs[i];
}
}
return NULL;
}
/**
* Insert local-zone and local-data into configuration.
* In order to be able to serve certs over TXT, we can reuse the local-zone and
* local-data config option. The zone and qname are infered from the
* provider_name and the content of the TXT record from the certificate content.
* returns the number of certtificate TXT record that were loaded.
* < 0 in case of error.
*/
static int
dnsc_load_local_data(struct dnsc_env* dnscenv, struct config_file *cfg)
{
size_t i, j;
// Insert 'local-zone: "2.dnscrypt-cert.example.com" deny'
if(!cfg_str2list_insert(&cfg->local_zones,
strdup(dnscenv->provider_name),
strdup("deny"))) {
log_err("Could not load dnscrypt local-zone: %s deny",
dnscenv->provider_name);
return -1;
}
// Add local data entry of type:
// 2.dnscrypt-cert.example.com 86400 IN TXT "DNSC......"
for(i=0; i<dnscenv->signed_certs_count; i++) {
const char *ttl_class_type = " 86400 IN TXT \"";
struct SignedCert *cert = dnscenv->signed_certs + i;
uint16_t rrlen = strlen(dnscenv->provider_name) +
strlen(ttl_class_type) +
4 * sizeof(struct SignedCert) + // worst case scenario
1 + // trailing double quote
1;
char *rr = malloc(rrlen);
if(!rr) {
log_err("Could not allocate memory");
return -2;
}
snprintf(rr, rrlen - 1, "%s 86400 IN TXT \"", dnscenv->provider_name);
for(j=0; j<sizeof(struct SignedCert); j++) {
int c = (int)*((const uint8_t *) cert + j);
if (isprint(c) && c != '"' && c != '\\') {
snprintf(rr + strlen(rr), rrlen - 1 - strlen(rr), "%c", c);
} else {
snprintf(rr + strlen(rr), rrlen - 1 - strlen(rr), "\\%03d", c);
}
}
snprintf(rr + strlen(rr), rrlen - 1 - strlen(rr), "\"");
cfg_strlist_insert(&cfg->local_data, strdup(rr));
free(rr);
}
return dnscenv->signed_certs_count;
}
/**
* Parse the secret key files from `dnscrypt-secret-key` config and populates
* a list of secret/public keys supported by dnscrypt listener.
* \param[in] env The dnsc_env structure which will hold the keypairs.
* \param[in] cfg The config with the secret key file paths.
*/
static int
dnsc_parse_keys(struct dnsc_env *env, struct config_file *cfg)
{
struct config_strlist *head;
size_t keypair_id;
env->keypairs_count = 0U;
for (head = cfg->dnscrypt_secret_key; head; head = head->next) {
env->keypairs_count++;
}
env->keypairs = sodium_allocarray(env->keypairs_count,
sizeof *env->keypairs);
keypair_id = 0U;
for(head = cfg->dnscrypt_secret_key; head; head = head->next, keypair_id++) {
char fingerprint[80];
if(dnsc_read_from_file(
head->str,
(char *)(env->keypairs[keypair_id].crypt_secretkey),
crypto_box_SECRETKEYBYTES) != 0) {
fatal_exit("dnsc_parse_keys: failed to load %s: %s", head->str, strerror(errno));
}
verbose(VERB_OPS, "Loaded key %s", head->str);
if (crypto_scalarmult_base(env->keypairs[keypair_id].crypt_publickey,
env->keypairs[keypair_id].crypt_secretkey) != 0) {
fatal_exit("dnsc_parse_keys: could not generate public key from %s", head->str);
}
dnsc_key_to_fingerprint(fingerprint, env->keypairs[keypair_id].crypt_publickey);
verbose(VERB_OPS, "Crypt public key fingerprint for %s: %s", head->str, fingerprint);
}
return keypair_id;
}
/**
* #########################################################
* ############# Publicly accessible functions #############
* #########################################################
*/
int
dnsc_handle_curved_request(struct dnsc_env* dnscenv,
struct comm_reply* repinfo)
{
struct comm_point* c = repinfo->c;
repinfo->is_dnscrypted = 0;
if( !c->dnscrypt ) {
return 1;
}
// Attempt to decrypt the query. If it is not crypted, we may still need
// to serve the certificate.
verbose(VERB_ALGO, "handle request called on DNSCrypt socket");
if ((repinfo->keypair = dnsc_find_keypair(dnscenv, c->buffer)) != NULL) {
if(dnscrypt_server_uncurve(repinfo->keypair,
repinfo->client_nonce,
repinfo->nmkey,
c->buffer) != 0){
verbose(VERB_ALGO, "dnscrypt: Failed to uncurve");
comm_point_drop_reply(repinfo);
return 0;
}
repinfo->is_dnscrypted = 1;
sldns_buffer_rewind(c->buffer);
}
return 1;
}
int
dnsc_handle_uncurved_request(struct comm_reply *repinfo)
{
if(!repinfo->c->dnscrypt) {
return 1;
}
sldns_buffer_copy(repinfo->c->dnscrypt_buffer, repinfo->c->buffer);
if(!repinfo->is_dnscrypted) {
return 1;
}
if(dnscrypt_server_curve(repinfo->keypair,
repinfo->client_nonce,
repinfo->nmkey,
repinfo->c->dnscrypt_buffer,
repinfo->c->type == comm_udp,
repinfo->max_udp_size) != 0){
verbose(VERB_ALGO, "dnscrypt: Failed to curve cached missed answer");
comm_point_drop_reply(repinfo);
return 0;
}
return 1;
}
struct dnsc_env *
dnsc_create(void)
{
struct dnsc_env *env;
if (sodium_init() == -1) {
fatal_exit("dnsc_create: could not initialize libsodium.");
}
env = (struct dnsc_env *) calloc(1, sizeof(struct dnsc_env));
return env;
}
int
dnsc_apply_cfg(struct dnsc_env *env, struct config_file *cfg)
{
if(dnsc_parse_certs(env, cfg) <= 0) {
fatal_exit("dnsc_apply_cfg: no cert file loaded");
}
if(dnsc_parse_keys(env, cfg) <= 0) {
fatal_exit("dnsc_apply_cfg: no key file loaded");
}
randombytes_buf(env->hash_key, sizeof env->hash_key);
env->provider_name = cfg->dnscrypt_provider;
if(dnsc_load_local_data(env, cfg) <= 0) {
fatal_exit("dnsc_apply_cfg: could not load local data");
}
return 0;
}
|