From b9196d48d2dfb2dc8ebac46838cfdaa230ebea4d Mon Sep 17 00:00:00 2001 From: Wouter Wijngaards Date: Tue, 6 Jun 2017 12:52:26 +0000 Subject: [PATCH] - Fix #1276: [dnscrypt] add XChaCha20-Poly1305 cipher. git-svn-id: file:///svn/unbound/trunk@4208 be551aaa-1e26-0410-a405-d3ace91eadb9 --- dnscrypt/cert.h | 2 +- dnscrypt/dnscrypt.c | 187 ++++++++++++++++++++++++++++++++----------- dnscrypt/dnscrypt.h | 11 +++ doc/Changelog | 1 + testcode/do-tests.sh | 2 +- 5 files changed, 156 insertions(+), 47 deletions(-) diff --git a/dnscrypt/cert.h b/dnscrypt/cert.h index 044f49f26..7cad146d9 100644 --- a/dnscrypt/cert.h +++ b/dnscrypt/cert.h @@ -20,12 +20,12 @@ struct SignedCert { uint8_t version_minor[2]; // Signed Content + uint8_t signed_content[64]; uint8_t server_publickey[crypto_box_PUBLICKEYBYTES]; uint8_t magic_query[8]; uint8_t serial[4]; uint8_t ts_begin[4]; uint8_t ts_end[4]; - uint8_t end[64]; }; diff --git a/dnscrypt/dnscrypt.c b/dnscrypt/dnscrypt.c index a6eb6f8d4..c43706421 100644 --- a/dnscrypt/dnscrypt.c +++ b/dnscrypt/dnscrypt.c @@ -15,6 +15,7 @@ #include "dnscrypt/cert.h" #include "dnscrypt/dnscrypt.h" +#include "dnscrypt/dnscrypt_config.h" #include @@ -35,18 +36,18 @@ (DNSCRYPT_MAGIC_HEADER_LEN + crypto_box_HALF_NONCEBYTES + crypto_box_HALF_NONCEBYTES) /** - * Decrypt a query using the keypair that was found using dnsc_find_keypair. + * 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] keypair the keypair that matches this encrypted query. + * \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(const KeyPair *keypair, +dnscrypt_server_uncurve(const dnsccert *cert, uint8_t client_nonce[crypto_box_HALF_NONCEBYTES], uint8_t nmkey[crypto_box_BEFORENMBYTES], struct sldns_buffer* buffer) @@ -62,25 +63,48 @@ dnscrypt_server_uncurve(const KeyPair *keypair, query_header = (struct dnscrypt_query_header *)buf; memcpy(nmkey, query_header->publickey, crypto_box_PUBLICKEYBYTES); - if (crypto_box_beforenm(nmkey, nmkey, keypair->crypt_secretkey) != 0) { + if(cert->es_version[1] == 2) { +#ifdef HAVE_XCHACHA20 + if (crypto_box_curve25519xchacha20poly1305_beforenm( + nmkey, nmkey, cert->keypair->crypt_secretkey) != 0) { + return -1; + } +#else return -1; +#endif + } else { + if (crypto_box_beforenm(nmkey, nmkey, cert->keypair->crypt_secretkey) != 0) { + return -1; + } } memcpy(nonce, query_header->nonce, crypto_box_HALF_NONCEBYTES); memset(nonce + crypto_box_HALF_NONCEBYTES, 0, crypto_box_HALF_NONCEBYTES); - sldns_buffer_set_at(buffer, - DNSCRYPT_QUERY_BOX_OFFSET - crypto_box_BOXZEROBYTES, - 0, crypto_box_BOXZEROBYTES); - - if (crypto_box_open_afternm - (buf + DNSCRYPT_QUERY_BOX_OFFSET - crypto_box_BOXZEROBYTES, - buf + DNSCRYPT_QUERY_BOX_OFFSET - crypto_box_BOXZEROBYTES, - len - DNSCRYPT_QUERY_BOX_OFFSET + crypto_box_BOXZEROBYTES, nonce, - nmkey) != 0) { + if(cert->es_version[1] == 2) { +#ifdef HAVE_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) ; @@ -89,12 +113,9 @@ dnscrypt_server_uncurve(const KeyPair *keypair, } memcpy(client_nonce, nonce, crypto_box_HALF_NONCEBYTES); - memmove(sldns_buffer_begin(buffer), - sldns_buffer_at(buffer, DNSCRYPT_QUERY_HEADER_SIZE), - len - DNSCRYPT_QUERY_HEADER_SIZE); sldns_buffer_set_position(buffer, 0); - sldns_buffer_set_limit(buffer, len - DNSCRYPT_QUERY_HEADER_SIZE); + sldns_buffer_set_limit(buffer, len); return 0; } @@ -182,10 +203,10 @@ add_server_nonce(uint8_t *nonce) } /** - * Encrypt a reply using the keypair that was used with the query. + * 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] keypair the keypair that matches this encrypted query. + * \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. @@ -194,7 +215,7 @@ add_server_nonce(uint8_t *nonce) * \return 0 on success. */ static int -dnscrypt_server_curve(const KeyPair *keypair, +dnscrypt_server_curve(const dnsccert *cert, uint8_t client_nonce[crypto_box_HALF_NONCEBYTES], uint8_t nmkey[crypto_box_BEFORENMBYTES], struct sldns_buffer* buffer, @@ -223,7 +244,7 @@ dnscrypt_server_curve(const KeyPair *keypair, memmove(boxed + crypto_box_MACBYTES, buf, len); len = dnscrypt_pad(boxed + crypto_box_MACBYTES, len, max_len - DNSCRYPT_REPLY_HEADER_SIZE, nonce, - keypair->crypt_secretkey); + cert->keypair->crypt_secretkey); sldns_buffer_set_at(buffer, DNSCRYPT_REPLY_BOX_OFFSET - crypto_box_BOXZEROBYTES, 0, crypto_box_ZEROBYTES); @@ -231,10 +252,20 @@ dnscrypt_server_curve(const KeyPair *keypair, // add server nonce extension add_server_nonce(nonce); - if (crypto_box_afternm - (boxed - crypto_box_BOXZEROBYTES, boxed - crypto_box_BOXZEROBYTES, - len + crypto_box_ZEROBYTES, nonce, nmkey) != 0) { + if(cert->es_version[1] == 2) { +#ifdef HAVE_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); @@ -347,16 +378,17 @@ dnsc_key_to_fingerprint(char fingerprint[80U], const uint8_t * const key) } /** - * Find the keypair matching a DNSCrypt query. - * \param[in] dnscenv The DNSCrypt enviroment, which contains the list of keys + * Find the cert matching a DNSCrypt query. + * \param[in] dnscenv The DNSCrypt enviroment, which contains the list of certs * supported by the server. * \param[in] buffer The encrypted DNS query. - * \return a KeyPair * if we found a key pair matching the query, NULL otherwise. + * \return a dnsccert * if we found a cert matching the magic_number of the + * query, NULL otherwise. */ -static const KeyPair * -dnsc_find_keypair(struct dnsc_env* dnscenv, struct sldns_buffer* buffer) +static const dnsccert * +dnsc_find_cert(struct dnsc_env* dnscenv, struct sldns_buffer* buffer) { - const KeyPair *keypairs = dnscenv->keypairs; + const dnsccert *certs = dnscenv->certs; struct dnscrypt_query_header *dnscrypt_header; size_t i; @@ -364,10 +396,10 @@ dnsc_find_keypair(struct dnsc_env* dnscenv, struct sldns_buffer* buffer) return NULL; } dnscrypt_header = (struct dnscrypt_query_header *)sldns_buffer_begin(buffer); - for (i = 0U; i < dnscenv->keypairs_count; i++) { - if (memcmp(keypairs[i].crypt_publickey, dnscrypt_header->magic_query, + 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 &keypairs[i]; + return &certs[i]; } } return NULL; @@ -425,9 +457,33 @@ dnsc_load_local_data(struct dnsc_env* dnscenv, struct config_file *cfg) 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; + }; + + struct es_version es_versions[] = { + {{0x00, 0x01}, "X25519-XSalsa20Poly1305"}, + {{0x00, 0x02}, "X25519-XChacha20Poly1305"}, + }; + int i; + for(i=0; i < (int)sizeof(es_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 secret/public keys supported by dnscrypt listener. + * 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. */ @@ -435,35 +491,76 @@ static int dnsc_parse_keys(struct dnsc_env *env, struct config_file *cfg) { struct config_strlist *head; - size_t keypair_id; + 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->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 *)(env->keypairs[keypair_id].crypt_secretkey), + (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(env->keypairs[keypair_id].crypt_publickey, - env->keypairs[keypair_id].crypt_secretkey) != 0) { + 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, env->keypairs[keypair_id].crypt_publickey); + 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 HAVE_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 keypair_id; + return cert_id; } @@ -486,8 +583,8 @@ dnsc_handle_curved_request(struct dnsc_env* dnscenv, // 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->keypair = dnsc_find_keypair(dnscenv, c->buffer)) != NULL) { - if(dnscrypt_server_uncurve(repinfo->keypair, + if ((repinfo->dnsc_cert = dnsc_find_cert(dnscenv, c->buffer)) != NULL) { + if(dnscrypt_server_uncurve(repinfo->dnsc_cert, repinfo->client_nonce, repinfo->nmkey, c->buffer) != 0){ @@ -511,7 +608,7 @@ dnsc_handle_uncurved_request(struct comm_reply *repinfo) if(!repinfo->is_dnscrypted) { return 1; } - if(dnscrypt_server_curve(repinfo->keypair, + if(dnscrypt_server_curve(repinfo->dnsc_cert, repinfo->client_nonce, repinfo->nmkey, repinfo->c->dnscrypt_buffer, diff --git a/dnscrypt/dnscrypt.h b/dnscrypt/dnscrypt.h index dac611b05..236a09561 100644 --- a/dnscrypt/dnscrypt.h +++ b/dnscrypt/dnscrypt.h @@ -35,6 +35,10 @@ #define DNSCRYPT_REPLY_HEADER_SIZE \ (DNSCRYPT_MAGIC_HEADER_LEN + crypto_box_HALF_NONCEBYTES * 2 + crypto_box_MACBYTES) +#ifdef crypto_box_curve25519xchacha20poly1305_MACBYTES +# define HAVE_XCHACHA20 1 +#endif + struct sldns_buffer; struct config_file; struct comm_reply; @@ -44,8 +48,15 @@ typedef struct KeyPair_ { uint8_t crypt_secretkey[crypto_box_SECRETKEYBYTES]; } KeyPair; +typedef struct cert_ { + uint8_t magic_query[DNSCRYPT_MAGIC_HEADER_LEN]; + uint8_t es_version[2]; + KeyPair *keypair; +} dnsccert; + struct dnsc_env { struct SignedCert *signed_certs; + dnsccert *certs; size_t signed_certs_count; uint8_t provider_publickey[crypto_sign_ed25519_PUBLICKEYBYTES]; uint8_t provider_secretkey[crypto_sign_ed25519_SECRETKEYBYTES]; diff --git a/doc/Changelog b/doc/Changelog index 020b4e979..54baac1c1 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -2,6 +2,7 @@ - Add an explicit type cast for TCP FASTOPEN fix. - renumbering B-Root's IPv6 address to 2001:500:200::b. - Fix #1275: cached data in cachedb is never used. + - Fix #1276: [dnscrypt] add XChaCha20-Poly1305 cipher. 1 June 2017: Ralph - Fix #1274: automatically trim chroot path from dnscrypt key/cert paths diff --git a/testcode/do-tests.sh b/testcode/do-tests.sh index e356d4fc3..dcf93907e 100755 --- a/testcode/do-tests.sh +++ b/testcode/do-tests.sh @@ -9,7 +9,7 @@ NEED_CURL='06-ianaports.tpkg root_anchor.tpkg' NEED_WHOAMI='07-confroot.tpkg' NEED_IPV6='fwd_ancil.tpkg fwd_tcp_tc6.tpkg stub_udp6.tpkg edns_cache.tpkg' NEED_NOMINGW='tcp_sigpipe.tpkg 07-confroot.tpkg 08-host-lib.tpkg fwd_ancil.tpkg' -NEED_DNSCRYPT_PROXY='dnscrypt_queries.tpkg' +NEED_DNSCRYPT_PROXY='dnscrypt_queries.tpkg dnscrypt_queries_chacha.tpkg' # test if dig and ldns-testns are available. test_tool_avail "dig"