]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- Fix #1276: [dnscrypt] add XChaCha20-Poly1305 cipher.
authorWouter Wijngaards <wouter@nlnetlabs.nl>
Tue, 6 Jun 2017 12:52:26 +0000 (12:52 +0000)
committerWouter Wijngaards <wouter@nlnetlabs.nl>
Tue, 6 Jun 2017 12:52:26 +0000 (12:52 +0000)
git-svn-id: file:///svn/unbound/trunk@4208 be551aaa-1e26-0410-a405-d3ace91eadb9

dnscrypt/cert.h
dnscrypt/dnscrypt.c
dnscrypt/dnscrypt.h
doc/Changelog
testcode/do-tests.sh

index 044f49f2642c7c92cf8fe75c6cf9330bd807c0bc..7cad146d9498ab8e4c01ad851aca13061fa984e1 100644 (file)
@@ -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];
 };
 
 
index a6eb6f8d43c3bc0442bd33ec7432555d88d8b60d..c437064212bc7d8967d072ba562ca5aaa7f6b14b 100644 (file)
@@ -15,6 +15,7 @@
 
 #include "dnscrypt/cert.h"
 #include "dnscrypt/dnscrypt.h"
+#include "dnscrypt/dnscrypt_config.h"
 
 #include <ctype.h>
 
     (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);
+               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,
index dac611b056f816e8c841be90b7cab1770f60f750..236a09561c42af4a8c439f2a8205cd0b3dc1eadc 100644 (file)
 #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];
index 020b4e9794f8ecc34bde3026608b8141b609db40..54baac1c15af2542f97abe36b0d22e7360550b7f 100644 (file)
@@ -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
index e356d4fc312ca31e1eb48a79b9d2bb396cb25b52..dcf93907e38827e9c9c348183c501920b0bdacaf 100755 (executable)
@@ -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"