diff options
Diffstat (limited to 'contrib/unbound/dnscrypt/dnscrypt.c')
| -rw-r--r-- | contrib/unbound/dnscrypt/dnscrypt.c | 1110 | 
1 files changed, 1110 insertions, 0 deletions
diff --git a/contrib/unbound/dnscrypt/dnscrypt.c b/contrib/unbound/dnscrypt/dnscrypt.c new file mode 100644 index 000000000000..4902447fda01 --- /dev/null +++ b/contrib/unbound/dnscrypt/dnscrypt.c @@ -0,0 +1,1110 @@ + +#include "config.h" +#include <stdlib.h> +#include <fcntl.h> +#ifdef HAVE_TIME_H +#include <time.h> +#endif +#include <inttypes.h> +#include <sys/time.h> +#include <sys/types.h> +#include "sldns/sbuffer.h" +#include "util/config_file.h" +#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 "dnscrypt/dnscrypt_config.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) + +//  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) + + +/** + * 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; +}; + + +struct nonce_cache_key { +    /** the nonce used by the client */ +    uint8_t nonce[crypto_box_HALF_NONCEBYTES]; +    /** the client_magic used by the client, this is associated to 1 cert only */ +    uint8_t magic_query[DNSCRYPT_MAGIC_HEADER_LEN]; +    /** the client public key */ +    uint8_t client_publickey[crypto_box_PUBLICKEYBYTES]; +    /** the hash table entry, data is uint8_t */ +    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); +} + +/** + * Generate a key hash suitable to find a nonce in slabhash. + * \param[in] nonce: a uint8_t pointer of size crypto_box_HALF_NONCEBYTES + * \param[in] magic_query: a uint8_t pointer of size DNSCRYPT_MAGIC_HEADER_LEN + * \param[in] pk: The public key of the client. uint8_t pointer of size + * crypto_box_PUBLICKEYBYTES. + * \return the hash of the key. + */ +static uint32_t +dnsc_nonce_cache_key_hash(const uint8_t nonce[crypto_box_HALF_NONCEBYTES], +                          const uint8_t magic_query[DNSCRYPT_MAGIC_HEADER_LEN], +                          const uint8_t pk[crypto_box_PUBLICKEYBYTES]) +{ +    uint32_t h = 0; +    h = hashlittle(nonce, crypto_box_HALF_NONCEBYTES, h); +    h = hashlittle(magic_query, DNSCRYPT_MAGIC_HEADER_LEN, h); +    return hashlittle(pk, crypto_box_PUBLICKEYBYTES, h); +} + +/** + * Inserts a nonce, magic_query, pk tuple into the nonces_cache slabhash. + * \param[in] cache: the slabhash in which to look for the key. + * \param[in] nonce: a uint8_t pointer of size crypto_box_HALF_NONCEBYTES + * \param[in] magic_query: a uint8_t pointer of size DNSCRYPT_MAGIC_HEADER_LEN + * \param[in] pk: The public key of the client. uint8_t pointer of size + * crypto_box_PUBLICKEYBYTES. + * \param[in] hash: the hash of the key. + */ +static void +dnsc_nonce_cache_insert(struct slabhash *cache, +                        const uint8_t nonce[crypto_box_HALF_NONCEBYTES], +                        const uint8_t magic_query[DNSCRYPT_MAGIC_HEADER_LEN], +                        const uint8_t pk[crypto_box_PUBLICKEYBYTES], +                        uint32_t hash) +{ +    struct nonce_cache_key* k = +        (struct nonce_cache_key*)calloc(1, sizeof(*k)); +    if(!k) { +        free(k); +        return; +    } +    lock_rw_init(&k->entry.lock); +    memcpy(k->nonce, nonce, crypto_box_HALF_NONCEBYTES); +    memcpy(k->magic_query, magic_query, DNSCRYPT_MAGIC_HEADER_LEN); +    memcpy(k->client_publickey, pk, crypto_box_PUBLICKEYBYTES); +    k->entry.hash = hash; +    k->entry.key = k; +    k->entry.data = NULL; +    slabhash_insert(cache, +                    hash, &k->entry, +                    NULL, +                    NULL); +} + +/** + * Lookup a record in nonces_cache. + * \param[in] cache: the slabhash in which to look for the key. + * \param[in] nonce: a uint8_t pointer of size crypto_box_HALF_NONCEBYTES + * \param[in] magic_query: a uint8_t pointer of size DNSCRYPT_MAGIC_HEADER_LEN + * \param[in] pk: The public key of the client. uint8_t pointer of size + * crypto_box_PUBLICKEYBYTES. + * \param[in] hash: the hash of the key. + * \return a pointer to the locked cache entry or NULL on failure. + */ +static struct lruhash_entry* +dnsc_nonces_lookup(struct slabhash* cache, +                   const uint8_t nonce[crypto_box_HALF_NONCEBYTES], +                   const uint8_t magic_query[DNSCRYPT_MAGIC_HEADER_LEN], +                   const uint8_t pk[crypto_box_PUBLICKEYBYTES], +                   uint32_t hash) +{ +    struct nonce_cache_key k; +    memset(&k, 0, sizeof(k)); +    k.entry.hash = hash; +    memcpy(k.nonce, nonce, crypto_box_HALF_NONCEBYTES); +    memcpy(k.magic_query, magic_query, DNSCRYPT_MAGIC_HEADER_LEN); +    memcpy(k.client_publickey, pk, crypto_box_PUBLICKEYBYTES); + +    return slabhash_lookup(cache, hash, &k, 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. + * \param[in] buffer the encrypted buffer. + * \return 0 on success. + */ +static int +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) +{ +    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; +    // shared secret cache +    uint8_t key[DNSCRYPT_SHARED_SECRET_KEY_LENGTH]; +    struct lruhash_entry* entry; +    uint32_t hash; + +    uint32_t nonce_hash; + +    if (len <= DNSCRYPT_QUERY_HEADER_SIZE) { +        return -1; +    } + +    query_header = (struct dnscrypt_query_header *)buf; + +    /* Detect replay attacks */ +    nonce_hash = dnsc_nonce_cache_key_hash( +        query_header->nonce, +        cert->magic_query, +        query_header->publickey); + +    lock_basic_lock(&env->nonces_cache_lock); +    entry = dnsc_nonces_lookup( +        env->nonces_cache, +        query_header->nonce, +        cert->magic_query, +        query_header->publickey, +        nonce_hash); + +    if(entry) { +        lock_rw_unlock(&entry->lock); +        env->num_query_dnscrypt_replay++; +        lock_basic_unlock(&env->nonces_cache_lock); +        return -1; +    } + +    dnsc_nonce_cache_insert( +        env->nonces_cache, +        query_header->nonce, +        cert->magic_query, +        query_header->publickey, +        nonce_hash); +    lock_basic_unlock(&env->nonces_cache_lock); + +    /* Find existing shared secret */ +    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, query_header->publickey, +                        cert->keypair->crypt_secretkey) != 0) { +                return -1; +            } +#else +            return -1; +#endif +	} else { +	    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); + +    if(cert->es_version[1] == 2) { +#ifdef USE_DNSCRYPT_XCHACHA20 +        if (crypto_box_curve25519xchacha20poly1305_open_easy_afternm +                (buf, +                buf + DNSCRYPT_QUERY_BOX_OFFSET, +                len - DNSCRYPT_QUERY_BOX_OFFSET, nonce, +                nmkey) != 0) { +            return -1; +        } +#else +        return -1; +#endif +    } else { +        if (crypto_box_open_easy_afternm +            (buf, +             buf + DNSCRYPT_QUERY_BOX_OFFSET, +             len - DNSCRYPT_QUERY_BOX_OFFSET, nonce, +             nmkey) != 0) { +            return -1; +        } +    } + +    len -= DNSCRYPT_QUERY_HEADER_SIZE; + +    while (*sldns_buffer_at(buffer, --len) == 0) +        ; + +    if (*sldns_buffer_at(buffer, len) != 0x80) { +        return -1; +    } + +    memcpy(client_nonce, nonce, crypto_box_HALF_NONCEBYTES); + +    sldns_buffer_set_position(buffer, 0); +    sldns_buffer_set_limit(buffer, len); + +    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 second 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) +{ +    randombytes_buf(nonce + crypto_box_HALF_NONCEBYTES, 8/*tsn*/+4/*suffix*/); +} + +/** + * Encrypt a reply using the dnsccert 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] cert the dnsccert 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 dnsccert *cert, +                      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, +                       cert->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(cert->es_version[1] == 2) { +#ifdef USE_DNSCRYPT_XCHACHA20 +        if (crypto_box_curve25519xchacha20poly1305_easy_afternm +            (boxed, boxed + crypto_box_MACBYTES, len, nonce, nmkey) != 0) { +            return -1; +        } +#else +        return -1; +#endif +    } else { +        if (crypto_box_easy_afternm +            (boxed, boxed + crypto_box_MACBYTES, len, 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; +} + +/** + * Given an absolute path on the original root, returns the absolute path + * within the chroot. If chroot is disabled, the path is not modified. + * No char * is malloced so there is no need to free this. + * \param[in] cfg the configuration. + * \param[in] path the path from the original root. + * \return the path from inside the chroot. + */ +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; +} + +/** + * 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, *head2; +	size_t signed_cert_id; +	size_t rotated_cert_id; +	char *nm; + +	env->signed_certs_count = 0U; +	env->rotated_certs_count = 0U; +	for (head = cfg->dnscrypt_provider_cert; head; head = head->next) { +		env->signed_certs_count++; +	} +	for (head = cfg->dnscrypt_provider_cert_rotated; head; head = head->next) { +		env->rotated_certs_count++; +	} +	env->signed_certs = sodium_allocarray(env->signed_certs_count, +										  sizeof *env->signed_certs); + +	env->rotated_certs = sodium_allocarray(env->rotated_certs_count, +										  sizeof env->signed_certs); +	signed_cert_id = 0U; +	rotated_cert_id = 0U; +	for(head = cfg->dnscrypt_provider_cert; head; head = head->next, signed_cert_id++) { +		nm = dnsc_chroot_path(cfg, head->str); +		if(dnsc_read_from_file( +				nm, +				(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)); +		} +		for(head2 = cfg->dnscrypt_provider_cert_rotated; head2; head2 = head2->next) { +			if(strcmp(head->str, head2->str) == 0) { +				*(env->rotated_certs + rotated_cert_id) = env->signed_certs + signed_cert_id; +				rotated_cert_id++; +				verbose(VERB_OPS, "Cert %s is rotated and will not be distributed via DNS", head->str); +				break; +			} +		} +		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 cert matching a DNSCrypt query. + * \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 + * query, NULL otherwise. + */ +static const dnsccert * +dnsc_find_cert(struct dnsc_env* dnscenv, struct sldns_buffer* buffer) +{ +	const dnsccert *certs = dnscenv->certs; +	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->signed_certs_count; i++) { +		if (memcmp(certs[i].magic_query, dnscrypt_header->magic_query, +                   DNSCRYPT_MAGIC_HEADER_LEN) == 0) { +			return &certs[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 inferred from the + * provider_name and the content of the TXT record from the certificate content. + * returns the number of certificate 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; i<dnscenv->signed_certs_count; i++) { +        const char *ttl_class_type = " 86400 IN TXT \""; +        int rotated_cert = 0; +	uint32_t serial; +	uint16_t rrlen; +	char* rr; +        struct SignedCert *cert = dnscenv->signed_certs + i; +		// Check if the certificate is being rotated and should not be published +        for(j=0; j<dnscenv->rotated_certs_count; j++){ +            if(cert == dnscenv->rotated_certs[j]) { +                rotated_cert = 1; +                break; +            } +        } +		memcpy(&serial, cert->serial, sizeof serial); +		serial = htonl(serial); +        if(rotated_cert) { +            verbose(VERB_OPS, +                "DNSCrypt: not adding cert with serial #%" +                PRIu32 +                " to local-data as it is rotated", +                serial +            ); +            continue; +        } +	if((unsigned)strlen(dnscenv->provider_name) >= (unsigned)0xffff0000) { +		/* guard against integer overflow in rrlen calculation */ +		verbose(VERB_OPS, "cert #%" PRIu32 " is too long", serial); +		continue; +	} +        rrlen = strlen(dnscenv->provider_name) + +                         strlen(ttl_class_type) + +                         4 * sizeof(struct SignedCert) + // worst case scenario +                         1 + // trailing double quote +                         1; +        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; j<sizeof(struct SignedCert); j++) { +			int c = (int)*((const uint8_t *) cert + j); +            if (isprint(c) && c != '"' && c != '\\') { +                snprintf(rr + strlen(rr), rrlen - strlen(rr), "%c", c); +            } else { +                snprintf(rr + strlen(rr), rrlen - strlen(rr), "\\%03d", c); +            } +        } +        verbose(VERB_OPS, +			"DNSCrypt: adding cert with serial #%" +			PRIu32 +			" to local-data to config: %s", +			serial, rr +		); +        snprintf(rr + strlen(rr), rrlen - strlen(rr), "\""); +        cfg_strlist_insert(&cfg->local_data, strdup(rr)); +        free(rr); +    } +    return dnscenv->signed_certs_count; +} + +static const char * +key_get_es_version(uint8_t version[2]) +{ +    struct es_version { +        uint8_t es_version[2]; +        const char *name; +    }; + +    const int num_versions = 2; +    struct es_version es_versions[] = { +        {{0x00, 0x01}, "X25519-XSalsa20Poly1305"}, +        {{0x00, 0x02}, "X25519-XChacha20Poly1305"}, +    }; +    int i; +    for(i=0; i < num_versions; i++){ +        if(es_versions[i].es_version[0] == version[0] && +           es_versions[i].es_version[1] == version[1]){ +            return es_versions[i].name; +        } +    } +    return NULL; +} + + +/** + * Parse the secret key files from `dnscrypt-secret-key` config and populates + * a list of dnsccert with es_version, magic number and 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 cert_id, keypair_id; +	size_t c; +	char *nm; + +	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); +	env->certs = sodium_allocarray(env->signed_certs_count, +		sizeof *env->certs); + +	cert_id = 0U; +	keypair_id = 0U; +	for(head = cfg->dnscrypt_secret_key; head; head = head->next, keypair_id++) { +		char fingerprint[80]; +		int found_cert = 0; +		KeyPair *current_keypair = &env->keypairs[keypair_id]; +		nm = dnsc_chroot_path(cfg, head->str); +		if(dnsc_read_from_file( +				nm, +				(char *)(current_keypair->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(current_keypair->crypt_publickey, +			current_keypair->crypt_secretkey) != 0) { +			fatal_exit("dnsc_parse_keys: could not generate public key from %s", head->str); +		} +		dnsc_key_to_fingerprint(fingerprint, current_keypair->crypt_publickey); +		verbose(VERB_OPS, "Crypt public key fingerprint for %s: %s", head->str, fingerprint); +		// find the cert matching this key +		for(c = 0; c < env->signed_certs_count; c++) { +			if(memcmp(current_keypair->crypt_publickey, +				env->signed_certs[c].server_publickey, +				crypto_box_PUBLICKEYBYTES) == 0) { +				dnsccert *current_cert = &env->certs[cert_id++]; +				found_cert = 1; +				current_cert->keypair = current_keypair; +				memcpy(current_cert->magic_query, +				       env->signed_certs[c].magic_query, +					sizeof env->signed_certs[c].magic_query); +				memcpy(current_cert->es_version, +				       env->signed_certs[c].version_major, +				       sizeof env->signed_certs[c].version_major +				); +				dnsc_key_to_fingerprint(fingerprint, +							current_cert->keypair->crypt_publickey); +				verbose(VERB_OPS, "Crypt public key fingerprint for %s: %s", +					head->str, fingerprint); +				verbose(VERB_OPS, "Using %s", +					key_get_es_version(current_cert->es_version)); +#ifndef USE_DNSCRYPT_XCHACHA20 +				if (current_cert->es_version[1] == 0x02) { +				    fatal_exit("Certificate for XChacha20 but libsodium does not support it."); +				} +#endif + +            		} +        	} +		if (!found_cert) { +		    fatal_exit("dnsc_parse_keys: could not match certificate for key " +			       "%s. Unable to determine ES version.", +			       head->str); +		} +	} +	return cert_id; +} + +#ifdef SODIUM_MISUSE_HANDLER +static void +sodium_misuse_handler(void) +{ +	fatal_exit( +		"dnscrypt: libsodium could not be initialized, this typically" +		" happens when no good source of entropy is found. If you run" +		" unbound in a chroot, make sure /dev/urandom is available. See" +		" https://www.unbound.net/documentation/unbound.conf.html"); +} +#endif + + +/** + * ######################################################### + * ############# 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->dnsc_cert = dnsc_find_cert(dnscenv, c->buffer)) != NULL) { +        if(dnscrypt_server_uncurve(dnscenv, +                                   repinfo->dnsc_cert, +                                   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->dnsc_cert, +                             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; +#ifdef SODIUM_MISUSE_HANDLER +	sodium_set_misuse_handler(sodium_misuse_handler); +#endif +	if (sodium_init() == -1) { +		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)); +	lock_basic_init(&env->nonces_cache_lock); +	lock_protect(&env->nonces_cache_lock, +                 &env->nonces_cache, +                 sizeof(env->nonces_cache)); +	lock_protect(&env->nonces_cache_lock, +                 &env->num_query_dnscrypt_replay, +                 sizeof(env->num_query_dnscrypt_replay)); + +	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"); +    } +    lock_basic_lock(&env->shared_secrets_cache_lock); +    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 +    ); +    lock_basic_unlock(&env->shared_secrets_cache_lock); +    if(!env->shared_secrets_cache){ +        fatal_exit("dnsc_apply_cfg: could not create shared secrets cache."); +    } +    lock_basic_lock(&env->nonces_cache_lock); +    env->nonces_cache = slabhash_create( +        cfg->dnscrypt_nonce_cache_slabs, +        HASH_DEFAULT_STARTARRAY, +        cfg->dnscrypt_nonce_cache_size, +        dnsc_nonces_sizefunc, +        dnsc_nonces_compfunc, +        dnsc_nonces_delkeyfunc, +        dnsc_nonces_deldatafunc, +        NULL +    ); +    lock_basic_unlock(&env->nonces_cache_lock); +    return 0; +} + +void +dnsc_delete(struct dnsc_env *env) +{ +	if(!env) { +		return; +	} +	verbose(VERB_OPS, "DNSCrypt: Freeing environment."); +	sodium_free(env->signed_certs); +	sodium_free(env->rotated_certs); +	sodium_free(env->certs); +	sodium_free(env->keypairs); +	lock_basic_destroy(&env->shared_secrets_cache_lock); +	lock_basic_destroy(&env->nonces_cache_lock); +	slabhash_delete(env->shared_secrets_cache); +	slabhash_delete(env->nonces_cache); +	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); +} + +/** + * ######################################################### + * ############### Nonces cache functions ################## + * ######################################################### + */ + +size_t +dnsc_nonces_sizefunc(void *k, void* ATTR_UNUSED(d)) +{ +    struct nonce_cache_key* nk = (struct nonce_cache_key*)k; +    size_t key_size = sizeof(struct nonce_cache_key) +        + lock_get_mem(&nk->entry.lock); +    (void)nk; /* otherwise ssk is unused if no threading, or fixed locksize */ +    return key_size; +} + +int +dnsc_nonces_compfunc(void *m1, void *m2) +{ +    struct nonce_cache_key *k1 = m1, *k2 = m2; +    return +        sodium_memcmp( +            k1->nonce, +            k2->nonce, +            crypto_box_HALF_NONCEBYTES) != 0 || +        sodium_memcmp( +            k1->magic_query, +            k2->magic_query, +            DNSCRYPT_MAGIC_HEADER_LEN) != 0 || +        sodium_memcmp( +            k1->client_publickey, k2->client_publickey, +            crypto_box_PUBLICKEYBYTES) != 0; +} + +void +dnsc_nonces_delkeyfunc(void *k, void* ATTR_UNUSED(arg)) +{ +    struct nonce_cache_key* nk = (struct nonce_cache_key*)k; +    lock_rw_destroy(&nk->entry.lock); +    free(nk); +} + +void +dnsc_nonces_deldatafunc(void* ATTR_UNUSED(d), void* ATTR_UNUSED(arg)) +{ +    return; +}  | 
