diff --git a/dnscrypt/dnscrypt.c b/dnscrypt/dnscrypt.c index 51308e375..3bf89475a 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 + /** * \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 struct reply_info* */ + 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,52 @@ 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) { + 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 +233,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 +299,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 +350,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 +396,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 +418,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 +442,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 +513,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 @@ -585,7 +719,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){ @@ -636,19 +771,32 @@ dnsc_create(void) 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"); - } - return 0; + 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->msg_cache_slabs, + HASH_DEFAULT_STARTARRAY, + cfg->msg_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; } void @@ -661,5 +809,43 @@ dnsc_delete(struct dnsc_env *env) sodium_free(env->signed_certs); sodium_free(env->certs); sodium_free(env->keypairs); + slabhash_delete(env->shared_secrets_cache); free(env); } + +/** + * ######################################################### + * ############# Shared secrets cache functions ############ + * ######################################################### + */ + +size_t +dnsc_shared_secrets_sizefunc(void *k, void *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; + 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); +} diff --git a/dnscrypt/dnscrypt.h b/dnscrypt/dnscrypt.h index 189dca54e..0575d45a7 100644 --- a/dnscrypt/dnscrypt.h +++ b/dnscrypt/dnscrypt.h @@ -38,6 +38,7 @@ struct sldns_buffer; struct config_file; struct comm_reply; +struct slabhash; typedef struct KeyPair_ { uint8_t crypt_publickey[crypto_box_PUBLICKEYBYTES]; @@ -61,6 +62,7 @@ struct dnsc_env { uint64_t nonce_ts_last; unsigned char hash_key[crypto_shorthash_KEYBYTES]; char * provider_name; + struct slabhash *shared_secrets_cache; }; struct dnscrypt_query_header { @@ -111,5 +113,26 @@ int dnsc_handle_curved_request(struct dnsc_env* dnscenv, */ int dnsc_handle_uncurved_request(struct comm_reply *repinfo); + +/** + * Computes the size of the shared secret cache entry. + */ +size_t dnsc_shared_secrets_sizefunc(void *k, void *d); + +/** + * Compares two shared secret cache keys. + */ +int dnsc_shared_secrets_compfunc(void *m1, void *m2); + +/** + * Function to delete a shared secret cache key. + */ +void dnsc_shared_secrets_delkeyfunc(void *k, void* arg); + +/** + * Function to delete a share secret cache value. + */ +void dnsc_shared_secrets_deldatafunc(void* d, void* arg); + #endif /* USE_DNSCRYPT */ #endif diff --git a/doc/Changelog b/doc/Changelog index dfec319a8..89602d30d 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,6 +1,8 @@ 28 August 2017: Wouter - Fix #1415: patch to free dnscrypt environment on reload. - iana portlist update + - Fix #1415: [dnscrypt] shared secret cache, patch from + Manu Bretelle. 23 August 2017: Wouter - Fix #1407: Add ECS options check to unbound-checkconf. diff --git a/util/fptr_wlist.c b/util/fptr_wlist.c index 2797d1fe8..81ef487c5 100644 --- a/util/fptr_wlist.c +++ b/util/fptr_wlist.c @@ -230,6 +230,9 @@ fptr_whitelist_hash_sizefunc(lruhash_sizefunc_type fptr) else if(fptr == &test_slabhash_sizefunc) return 1; #ifdef CLIENT_SUBNET else if(fptr == &msg_cache_sizefunc) return 1; +#endif +#ifdef USE_DNSCRYPT + else if(fptr == &dnsc_shared_secrets_sizefunc) return 1; #endif return 0; } @@ -244,6 +247,9 @@ fptr_whitelist_hash_compfunc(lruhash_compfunc_type fptr) else if(fptr == &rate_compfunc) return 1; else if(fptr == &ip_rate_compfunc) return 1; else if(fptr == &test_slabhash_compfunc) return 1; +#ifdef USE_DNSCRYPT + else if(fptr == &dnsc_shared_secrets_compfunc) return 1; +#endif return 0; } @@ -257,6 +263,9 @@ fptr_whitelist_hash_delkeyfunc(lruhash_delkeyfunc_type fptr) else if(fptr == &rate_delkeyfunc) return 1; else if(fptr == &ip_rate_delkeyfunc) return 1; else if(fptr == &test_slabhash_delkey) return 1; +#ifdef USE_DNSCRYPT + else if(fptr == &dnsc_shared_secrets_delkeyfunc) return 1; +#endif return 0; } @@ -271,6 +280,9 @@ fptr_whitelist_hash_deldatafunc(lruhash_deldatafunc_type fptr) else if(fptr == &test_slabhash_deldata) return 1; #ifdef CLIENT_SUBNET else if(fptr == &subnet_data_delete) return 1; +#endif +#ifdef USE_DNSCRYPT + else if(fptr == &dnsc_shared_secrets_deldatafunc) return 1; #endif return 0; }