mirror of
https://github.com/NLnetLabs/unbound.git
synced 2025-12-20 23:00:56 -05:00
- Fix #1415: [dnscrypt] shared secret cache, patch from
Manu Bretelle. git-svn-id: file:///svn/unbound/trunk@4312 be551aaa-1e26-0410-a405-d3ace91eadb9
This commit is contained in:
parent
cd46a535cd
commit
e3cc298ffd
4 changed files with 270 additions and 47 deletions
|
|
@ -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 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);
|
||||
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) {
|
||||
nmkey, query_header->publickey,
|
||||
cert->keypair->crypt_secretkey) != 0) {
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
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);
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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){
|
||||
|
|
@ -648,6 +783,19 @@ dnsc_apply_cfg(struct dnsc_env *env, struct config_file *cfg)
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue