]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- Fix #1415: [dnscrypt] shared secret cache, patch from
authorWouter Wijngaards <wouter@nlnetlabs.nl>
Mon, 28 Aug 2017 10:55:41 +0000 (10:55 +0000)
committerWouter Wijngaards <wouter@nlnetlabs.nl>
Mon, 28 Aug 2017 10:55:41 +0000 (10:55 +0000)
  Manu Bretelle.

git-svn-id: file:///svn/unbound/trunk@4312 be551aaa-1e26-0410-a405-d3ace91eadb9

dnscrypt/dnscrypt.c
dnscrypt/dnscrypt.h
doc/Changelog
util/fptr_wlist.c

index 51308e3750c268ae5dac171396e2bdb33a6243d5..3bf89475a864e3d5801c324764d6f012ceea9a47 100644 (file)
@@ -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"
 
 #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
 // 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.
  * \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);
+}
index 189dca54edc7e92bd8849d296d4919de3f1a6eca..0575d45a73fa74d12d37719150c406d74563da6e 100644 (file)
@@ -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
index dfec319a8fdb165abdf454cfbe2bc08a0fe5757f..89602d30d392963d08d6bedcf5a2a1ef5fd0a314 100644 (file)
@@ -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.
index 2797d1fe84494b716cf9f54a6eaedffd44b8867b..81ef487c5877e44a77b3c05531be646e256e5cab 100644 (file)
@@ -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;
 }