#include "config.h" #include #include #ifdef HAVE_TIME_H #include #endif #include #include #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 /** * \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; isigned_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; jlocal_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; }