diff options
Diffstat (limited to 'dnscrypt/dnscrypt.c')
-rw-r--r-- | dnscrypt/dnscrypt.c | 303 |
1 files changed, 256 insertions, 47 deletions
diff --git a/dnscrypt/dnscrypt.c b/dnscrypt/dnscrypt.c index 9e858c3fb061..db054df9d680 100644 --- a/dnscrypt/dnscrypt.c +++ b/dnscrypt/dnscrypt.c @@ -12,6 +12,8 @@ #include "util/net_help.h" #include "util/netevent.h" #include "util/log.h" +#include "util/storage/slabhash.h" +#include "util/storage/lookup3.h" #include "dnscrypt/cert.h" #include "dnscrypt/dnscrypt.h" @@ -19,13 +21,15 @@ #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) + (DNSCRYPT_MAGIC_HEADER_LEN + crypto_box_PUBLICKEYBYTES + \ + crypto_box_HALF_NONCEBYTES) // 8 bytes: magic header (CERT_MAGIC_HEADER) // 12 bytes: the client's nonce @@ -33,13 +37,110 @@ // 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) + (DNSCRYPT_MAGIC_HEADER_LEN + crypto_box_HALF_NONCEBYTES + \ + crypto_box_HALF_NONCEBYTES) + + +/** + * Shared secret cache key length. + * secret key. + * 1 byte: ES_VERSION[1] + * 32 bytes: client crypto_box_PUBLICKEYBYTES + * 32 bytes: server crypto_box_SECRETKEYBYTES + */ +#define DNSCRYPT_SHARED_SECRET_KEY_LENGTH \ + (1 + crypto_box_PUBLICKEYBYTES + crypto_box_SECRETKEYBYTES) + + +struct shared_secret_cache_key { + /** the hash table key */ + uint8_t key[DNSCRYPT_SHARED_SECRET_KEY_LENGTH]; + /** the hash table entry, data is uint8_t pointer of size crypto_box_BEFORENMBYTES which contains the shared secret. */ + struct lruhash_entry entry; +}; + + +/** + * Generate a key suitable to find shared secret in slabhash. + * \param[in] key: a uint8_t pointer of size DNSCRYPT_SHARED_SECRET_KEY_LENGTH + * \param[in] esversion: The es version least significant byte. + * \param[in] pk: The public key of the client. uint8_t pointer of size + * crypto_box_PUBLICKEYBYTES. + * \param[in] sk: The secret key of the server matching the magic query number. + * uint8_t pointer of size crypto_box_SECRETKEYBYTES. + * \return the hash of the key. + */ +static uint32_t +dnsc_shared_secrets_cache_key(uint8_t* key, + uint8_t esversion, + uint8_t* pk, + uint8_t* sk) +{ + key[0] = esversion; + memcpy(key + 1, pk, crypto_box_PUBLICKEYBYTES); + memcpy(key + 1 + crypto_box_PUBLICKEYBYTES, sk, crypto_box_SECRETKEYBYTES); + return hashlittle(key, DNSCRYPT_SHARED_SECRET_KEY_LENGTH, 0); +} + +/** + * Inserts a shared secret into the shared_secrets_cache slabhash. + * The shared secret is copied so the caller can use it freely without caring + * about the cache entry being evicted or not. + * \param[in] cache: the slabhash in which to look for the key. + * \param[in] key: a uint8_t pointer of size DNSCRYPT_SHARED_SECRET_KEY_LENGTH + * which contains the key of the shared secret. + * \param[in] hash: the hash of the key. + * \param[in] nmkey: a uint8_t pointer of size crypto_box_BEFORENMBYTES which + * contains the shared secret. + */ +static void +dnsc_shared_secret_cache_insert(struct slabhash *cache, + uint8_t key[DNSCRYPT_SHARED_SECRET_KEY_LENGTH], + uint32_t hash, + uint8_t nmkey[crypto_box_BEFORENMBYTES]) +{ + struct shared_secret_cache_key* k = + (struct shared_secret_cache_key*)calloc(1, sizeof(*k)); + uint8_t* d = malloc(crypto_box_BEFORENMBYTES); + if(!k || !d) { + free(k); + free(d); + return; + } + memcpy(d, nmkey, crypto_box_BEFORENMBYTES); + lock_rw_init(&k->entry.lock); + memcpy(k->key, key, DNSCRYPT_SHARED_SECRET_KEY_LENGTH); + k->entry.hash = hash; + k->entry.key = k; + k->entry.data = d; + slabhash_insert(cache, + hash, &k->entry, + d, + NULL); +} + +/** + * Lookup a record in shared_secrets_cache. + * \param[in] cache: a pointer to shared_secrets_cache slabhash. + * \param[in] key: a uint8_t pointer of size DNSCRYPT_SHARED_SECRET_KEY_LENGTH + * containing the key to look for. + * \param[in] hash: a hash of the key. + * \return a pointer to the locked cache entry or NULL on failure. + */ +static struct lruhash_entry* +dnsc_shared_secrets_lookup(struct slabhash* cache, + uint8_t key[DNSCRYPT_SHARED_SECRET_KEY_LENGTH], + uint32_t hash) +{ + return slabhash_lookup(cache, hash, key, 0); +} /** * Decrypt a query using the dnsccert that was found using dnsc_find_cert. * 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] env the dnscrypt environment. * \param[in] cert the cert 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. @@ -47,7 +148,8 @@ * \return 0 on success. */ static int -dnscrypt_server_uncurve(const dnsccert *cert, +dnscrypt_server_uncurve(struct dnsc_env* env, + const dnsccert *cert, uint8_t client_nonce[crypto_box_HALF_NONCEBYTES], uint8_t nmkey[crypto_box_BEFORENMBYTES], struct sldns_buffer* buffer) @@ -56,27 +158,55 @@ dnscrypt_server_uncurve(const dnsccert *cert, uint8_t *const buf = sldns_buffer_begin(buffer); uint8_t nonce[crypto_box_NONCEBYTES]; struct dnscrypt_query_header *query_header; + // shared secret cache + uint8_t key[DNSCRYPT_SHARED_SECRET_KEY_LENGTH]; + struct lruhash_entry* entry; + uint32_t hash; if (len <= DNSCRYPT_QUERY_HEADER_SIZE) { return -1; } query_header = (struct dnscrypt_query_header *)buf; - memcpy(nmkey, query_header->publickey, crypto_box_PUBLICKEYBYTES); - if(cert->es_version[1] == 2) { + hash = dnsc_shared_secrets_cache_key(key, + cert->es_version[1], + query_header->publickey, + cert->keypair->crypt_secretkey); + entry = dnsc_shared_secrets_lookup(env->shared_secrets_cache, + key, + hash); + + if(!entry) { + lock_basic_lock(&env->shared_secrets_cache_lock); + env->num_query_dnscrypt_secret_missed_cache++; + lock_basic_unlock(&env->shared_secrets_cache_lock); + if(cert->es_version[1] == 2) { #ifdef USE_DNSCRYPT_XCHACHA20 - if (crypto_box_curve25519xchacha20poly1305_beforenm( - nmkey, nmkey, cert->keypair->crypt_secretkey) != 0) { - return -1; - } + if (crypto_box_curve25519xchacha20poly1305_beforenm( + nmkey, query_header->publickey, + cert->keypair->crypt_secretkey) != 0) { + return -1; + } #else - return -1; + return -1; #endif } else { - if (crypto_box_beforenm(nmkey, nmkey, cert->keypair->crypt_secretkey) != 0) { + if (crypto_box_beforenm(nmkey, + query_header->publickey, + cert->keypair->crypt_secretkey) != 0) { return -1; } } + // Cache the shared secret we just computed. + dnsc_shared_secret_cache_insert(env->shared_secrets_cache, + key, + hash, + nmkey); + } else { + /* copy shared secret and unlock entry */ + memcpy(nmkey, entry->data, crypto_box_BEFORENMBYTES); + lock_rw_unlock(&entry->lock); + } memcpy(nonce, query_header->nonce, crypto_box_HALF_NONCEBYTES); memset(nonce + crypto_box_HALF_NONCEBYTES, 0, crypto_box_HALF_NONCEBYTES); @@ -106,7 +236,7 @@ dnscrypt_server_uncurve(const dnsccert *cert, len -= DNSCRYPT_QUERY_HEADER_SIZE; while (*sldns_buffer_at(buffer, --len) == 0) - ; + ; if (*sldns_buffer_at(buffer, len) != 0x80) { return -1; @@ -172,7 +302,7 @@ dnscrypt_hrtime(void) if (ret == 0) { ts = (uint64_t)tv.tv_sec * 1000000U + (uint64_t)tv.tv_usec; } else { - log_err("gettimeofday: %s", strerror(errno)); + log_err("gettimeofday: %s", strerror(errno)); } return ts; } @@ -223,7 +353,8 @@ dnscrypt_server_curve(const dnsccert *cert, 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_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; @@ -268,8 +399,14 @@ dnscrypt_server_curve(const dnsccert *cert, } } - 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_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; } @@ -284,17 +421,17 @@ dnscrypt_server_curve(const dnsccert *cert, 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; + 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; } /** @@ -308,12 +445,12 @@ dnsc_read_from_file(char *fname, char *buf, size_t count) static char * dnsc_chroot_path(struct config_file *cfg, char *path) { - char *nm; - nm = path; - if(cfg->chrootdir && cfg->chrootdir[0] && strncmp(nm, - cfg->chrootdir, strlen(cfg->chrootdir)) == 0) - nm += strlen(cfg->chrootdir); - return nm; + char *nm; + nm = path; + if(cfg->chrootdir && cfg->chrootdir[0] && strncmp(nm, + cfg->chrootdir, strlen(cfg->chrootdir)) == 0) + nm += strlen(cfg->chrootdir); + return nm; } /** @@ -379,7 +516,7 @@ dnsc_key_to_fingerprint(char fingerprint[80U], const uint8_t * const key) /** * Find the cert matching a DNSCrypt query. - * \param[in] dnscenv The DNSCrypt enviroment, which contains the list of certs + * \param[in] dnscenv The DNSCrypt environment, which contains the list of certs * supported by the server. * \param[in] buffer The encrypted DNS query. * \return a dnsccert * if we found a cert matching the magic_number of the @@ -450,6 +587,7 @@ dnsc_load_local_data(struct dnsc_env* dnscenv, struct config_file *cfg) snprintf(rr + strlen(rr), rrlen - 1 - strlen(rr), "\\%03d", c); } } + verbose(VERB_OPS, "DNSCrypt: adding local data to config: %s", rr); snprintf(rr + strlen(rr), rrlen - 1 - strlen(rr), "\""); cfg_strlist_insert(&cfg->local_data, strdup(rr)); free(rr); @@ -502,7 +640,7 @@ dnsc_parse_keys(struct dnsc_env *env, struct config_file *cfg) env->keypairs = sodium_allocarray(env->keypairs_count, sizeof *env->keypairs); - env->certs = sodium_allocarray(env->signed_certs_count, + env->certs = sodium_allocarray(env->signed_certs_count, sizeof *env->certs); cert_id = 0U; @@ -584,7 +722,8 @@ dnsc_handle_curved_request(struct dnsc_env* dnscenv, // to serve the certificate. verbose(VERB_ALGO, "handle request called on DNSCrypt socket"); if ((repinfo->dnsc_cert = dnsc_find_cert(dnscenv, c->buffer)) != NULL) { - if(dnscrypt_server_uncurve(repinfo->dnsc_cert, + if(dnscrypt_server_uncurve(dnscenv, + repinfo->dnsc_cert, repinfo->client_nonce, repinfo->nmkey, c->buffer) != 0){ @@ -629,23 +768,93 @@ dnsc_create(void) fatal_exit("dnsc_create: could not initialize libsodium."); } env = (struct dnsc_env *) calloc(1, sizeof(struct dnsc_env)); + lock_basic_init(&env->shared_secrets_cache_lock); + lock_protect(&env->shared_secrets_cache_lock, + &env->num_query_dnscrypt_secret_missed_cache, + sizeof(env->num_query_dnscrypt_secret_missed_cache)); 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_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"); + } + env->shared_secrets_cache = slabhash_create( + cfg->dnscrypt_shared_secret_cache_slabs, + HASH_DEFAULT_STARTARRAY, + cfg->dnscrypt_shared_secret_cache_size, + dnsc_shared_secrets_sizefunc, + dnsc_shared_secrets_compfunc, + dnsc_shared_secrets_delkeyfunc, + dnsc_shared_secrets_deldatafunc, + NULL + ); + if(!env->shared_secrets_cache){ + fatal_exit("dnsc_apply_cfg: could not create shared secrets cache."); + } + return 0; +} - if(dnsc_load_local_data(env, cfg) <= 0) { - fatal_exit("dnsc_apply_cfg: could not load local data"); +void +dnsc_delete(struct dnsc_env *env) +{ + if(!env) { + return; } - return 0; + verbose(VERB_OPS, "DNSCrypt: Freeing environment."); + sodium_free(env->signed_certs); + sodium_free(env->certs); + sodium_free(env->keypairs); + slabhash_delete(env->shared_secrets_cache); + lock_basic_destroy(&env->shared_secrets_cache_lock); + free(env); +} + +/** + * ######################################################### + * ############# Shared secrets cache functions ############ + * ######################################################### + */ + +size_t +dnsc_shared_secrets_sizefunc(void *k, void* ATTR_UNUSED(d)) +{ + struct shared_secret_cache_key* ssk = (struct shared_secret_cache_key*)k; + size_t key_size = sizeof(struct shared_secret_cache_key) + + lock_get_mem(&ssk->entry.lock); + size_t data_size = crypto_box_BEFORENMBYTES; + (void)ssk; /* otherwise ssk is unused if no threading, or fixed locksize */ + return key_size + data_size; +} + +int +dnsc_shared_secrets_compfunc(void *m1, void *m2) +{ + return sodium_memcmp(m1, m2, DNSCRYPT_SHARED_SECRET_KEY_LENGTH); +} + +void +dnsc_shared_secrets_delkeyfunc(void *k, void* ATTR_UNUSED(arg)) +{ + struct shared_secret_cache_key* ssk = (struct shared_secret_cache_key*)k; + lock_rw_destroy(&ssk->entry.lock); + free(ssk); +} + +void +dnsc_shared_secrets_deldatafunc(void* d, void* ATTR_UNUSED(arg)) +{ + uint8_t* data = (uint8_t*)d; + free(data); } |