]> source.dussan.org Git - rspamd.git/commitdiff
[Feature] Implement rdns-curve plugin based on rspamd cryptobox
authorVsevolod Stakhov <vsevolod@highsecure.ru>
Fri, 30 Jun 2017 07:52:05 +0000 (08:52 +0100)
committerVsevolod Stakhov <vsevolod@highsecure.ru>
Fri, 30 Jun 2017 07:52:05 +0000 (08:52 +0100)
contrib/librdns/CMakeLists.txt
contrib/librdns/curve.c
contrib/librdns/dns_private.h

index f2cd9a3e1306e8d689e15d750cd8dafbf1ecc6ae..a5733e60f13386ef8ac0db185892dc6562cd45a6 100644 (file)
@@ -7,4 +7,5 @@ SET(LIBRDNSSRC                  util.c
                                                packet.c
                                                resolver.c)
 
-ADD_LIBRARY(rdns STATIC ${LIBRDNSSRC})
\ No newline at end of file
+ADD_LIBRARY(rdns STATIC ${LIBRDNSSRC})
+SET_TARGET_PROPERTIES(rdns PROPERTIES COMPILE_FLAGS "-DUSE_RSPAMD_CRYPTOBOX")
\ No newline at end of file
index 4c2a64135a80971ecd09b815c227576ddbfdd24c..3976b14a01414b12c35b3cbd1ea93545aacbe6ae 100644 (file)
@@ -441,7 +441,408 @@ rdns_curve_dtor (struct rdns_resolver *resolver, void *plugin_data)
        }
        REF_RELEASE (ctx->cur_key);
 }
+#elif defined(USE_RSPAMD_CRYPTOBOX)
 
+#include "cryptobox.h"
+
+#define crypto_box_ZEROBYTES 32
+#define crypto_box_BOXZEROBYTES 16
+
+ssize_t rdns_curve_send (struct rdns_request *req, void *plugin_data);
+ssize_t rdns_curve_recv (struct rdns_io_channel *ioc, void *buf, size_t len,
+               void *plugin_data, struct rdns_request **req_out);
+void rdns_curve_finish_request (struct rdns_request *req, void *plugin_data);
+void rdns_curve_dtor (struct rdns_resolver *resolver, void *plugin_data);
+
+struct rdns_curve_entry {
+       char *name;
+       rspamd_pk_t pk;
+       UT_hash_handle hh;
+};
+
+struct rdns_curve_nm_entry {
+       rspamd_nm_t k;
+       struct rdns_curve_entry *entry;
+       struct rdns_curve_nm_entry *prev, *next;
+};
+
+struct rdns_curve_client_key {
+       rspamd_pk_t pk;
+       rspamd_sk_t sk;
+       struct rdns_curve_nm_entry *nms;
+       uint64_t counter;
+       unsigned int uses;
+       ref_entry_t ref;
+};
+
+struct rdns_curve_request {
+       struct rdns_request *req;
+       struct rdns_curve_client_key *key;
+       struct rdns_curve_entry *entry;
+       struct rdns_curve_nm_entry *nm;
+       rspamd_nonce_t nonce;
+       UT_hash_handle hh;
+};
+
+struct rdns_curve_ctx {
+       struct rdns_curve_entry *entries;
+       struct rdns_curve_client_key *cur_key;
+       struct rdns_curve_request *requests;
+       double key_refresh_interval;
+       void *key_refresh_event;
+       struct rdns_resolver *resolver;
+};
+
+static struct rdns_curve_client_key *
+rdns_curve_client_key_new (struct rdns_curve_ctx *ctx)
+{
+       struct rdns_curve_client_key *new;
+       struct rdns_curve_nm_entry *nm;
+       struct rdns_curve_entry *entry, *tmp;
+
+       new = calloc (1, sizeof (struct rdns_curve_client_key));
+       rspamd_cryptobox_keypair (new->pk, new->sk, RSPAMD_CRYPTOBOX_MODE_25519);
+
+       HASH_ITER (hh, ctx->entries, entry, tmp) {
+               nm = calloc (1, sizeof (struct rdns_curve_nm_entry));
+               nm->entry = entry;
+               rspamd_cryptobox_nm (nm->k, entry->pk, new->sk,
+                               RSPAMD_CRYPTOBOX_MODE_25519);
+
+               DL_APPEND (new->nms, nm);
+       }
+
+       new->counter = ottery_rand_uint64 ();
+
+       return new;
+}
+
+static struct rdns_curve_nm_entry *
+rdns_curve_find_nm (struct rdns_curve_client_key *key, struct rdns_curve_entry *entry)
+{
+       struct rdns_curve_nm_entry *nm;
+
+       DL_FOREACH (key->nms, nm) {
+               if (nm->entry == entry) {
+                       return nm;
+               }
+       }
+
+       return NULL;
+}
+
+static void
+rdns_curve_client_key_free (struct rdns_curve_client_key *key)
+{
+       struct rdns_curve_nm_entry *nm, *tmp;
+
+       DL_FOREACH_SAFE (key->nms, nm, tmp) {
+               rspamd_explicit_memzero (nm->k, sizeof (nm->k));
+               free (nm);
+       }
+
+       rspamd_explicit_memzero (key->sk, sizeof (key->sk));
+       free (key);
+}
+
+struct rdns_curve_ctx*
+rdns_curve_ctx_new (double key_refresh_interval)
+{
+       struct rdns_curve_ctx *new;
+
+       new = calloc (1, sizeof (struct rdns_curve_ctx));
+       new->key_refresh_interval = key_refresh_interval;
+
+       return new;
+}
+
+void
+rdns_curve_ctx_add_key (struct rdns_curve_ctx *ctx,
+               const char *name, const unsigned char *pubkey)
+{
+       struct rdns_curve_entry *entry;
+       bool success = true;
+
+       entry = malloc (sizeof (struct rdns_curve_entry));
+       if (entry != NULL) {
+               entry->name = strdup (name);
+               if (entry->name == NULL) {
+                       success = false;
+               }
+               memcpy (entry->pk, pubkey, sizeof (entry->pk));
+               if (success) {
+                       HASH_ADD_KEYPTR (hh, ctx->entries, entry->name, strlen (entry->name), entry);
+               }
+       }
+}
+
+#define rdns_curve_write_hex(in, out, offset, base) do {                                       \
+    *(out) |= ((in)[(offset)] - (base)) << ((1 - offset) * 4);                         \
+} while (0)
+
+static bool
+rdns_curve_hex_to_byte (const char *in, unsigned char *out)
+{
+       int i;
+
+       for (i = 0; i <= 1; i ++) {
+               if (in[i] >= '0' && in[i] <= '9') {
+                       rdns_curve_write_hex (in, out, i, '0');
+               }
+               else if (in[i] >= 'a' && in[i] <= 'f') {
+                       rdns_curve_write_hex (in, out, i, 'a' - 10);
+               }
+               else if (in[i] >= 'A' && in[i] <= 'F') {
+                       rdns_curve_write_hex (in, out, i, 'A' - 10);
+               }
+               else {
+                       return false;
+               }
+       }
+       return true;
+}
+
+#undef rdns_curve_write_hex
+
+unsigned char *
+rdns_curve_key_from_hex (const char *hex)
+{
+       unsigned int len = strlen (hex), i;
+       unsigned char *res = NULL;
+
+       if (len == rspamd_cryptobox_pk_bytes (RSPAMD_CRYPTOBOX_MODE_25519) * 2) {
+               res = calloc (1, rspamd_cryptobox_pk_bytes (RSPAMD_CRYPTOBOX_MODE_25519));
+               for (i = 0;
+                               i < rspamd_cryptobox_pk_bytes (RSPAMD_CRYPTOBOX_MODE_25519);
+                               i ++) {
+                       if (!rdns_curve_hex_to_byte (&hex[i * 2], &res[i])) {
+                               free (res);
+                               return NULL;
+                       }
+               }
+       }
+
+       return res;
+}
+
+void
+rdns_curve_ctx_destroy (struct rdns_curve_ctx *ctx)
+{
+       struct rdns_curve_entry *entry, *tmp;
+
+       HASH_ITER (hh, ctx->entries, entry, tmp) {
+               free (entry->name);
+               free (entry);
+       }
+
+       free (ctx);
+}
+
+static void
+rdns_curve_refresh_key_callback (void *user_data)
+{
+       struct rdns_curve_ctx *ctx = user_data;
+       struct rdns_resolver *resolver;
+
+       resolver = ctx->resolver;
+       rdns_info ("refresh dnscurve keys");
+       REF_RELEASE (ctx->cur_key);
+       ctx->cur_key = rdns_curve_client_key_new (ctx);
+       REF_INIT_RETAIN (ctx->cur_key, rdns_curve_client_key_free);
+}
+
+void
+rdns_curve_register_plugin (struct rdns_resolver *resolver,
+               struct rdns_curve_ctx *ctx)
+{
+       struct rdns_plugin *plugin;
+
+       if (!resolver->async_binded) {
+               return;
+       }
+
+       plugin = calloc (1, sizeof (struct rdns_plugin));
+       if (plugin != NULL) {
+               plugin->data = ctx;
+               plugin->type = RDNS_PLUGIN_CURVE;
+               plugin->cb.curve_plugin.send_cb = rdns_curve_send;
+               plugin->cb.curve_plugin.recv_cb = rdns_curve_recv;
+               plugin->cb.curve_plugin.finish_cb = rdns_curve_finish_request;
+               plugin->dtor = rdns_curve_dtor;
+               ctx->cur_key = rdns_curve_client_key_new (ctx);
+               REF_INIT_RETAIN (ctx->cur_key, rdns_curve_client_key_free);
+
+               if (ctx->key_refresh_interval > 0) {
+                       ctx->key_refresh_event = resolver->async->add_periodic (
+                                       resolver->async->data, ctx->key_refresh_interval,
+                                       rdns_curve_refresh_key_callback, ctx);
+               }
+               ctx->resolver = resolver;
+               rdns_resolver_register_plugin (resolver, plugin);
+       }
+}
+
+ssize_t
+rdns_curve_send (struct rdns_request *req, void *plugin_data)
+{
+       struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *)plugin_data;
+       struct rdns_curve_entry *entry;
+       struct iovec iov[4];
+       unsigned char *m;
+       static const char qmagic[] = "Q6fnvWj8";
+       struct rdns_curve_request *creq;
+       struct rdns_curve_nm_entry *nm;
+       ssize_t ret, boxed_len;
+
+       /* Check for key */
+       HASH_FIND_STR (ctx->entries, req->io->srv->name, entry);
+       if (entry != NULL) {
+               nm = rdns_curve_find_nm (ctx->cur_key, entry);
+               creq = malloc (sizeof (struct rdns_curve_request));
+               if (creq == NULL) {
+                       return -1;
+               }
+
+               boxed_len = req->pos + crypto_box_ZEROBYTES;
+               m = malloc (boxed_len);
+               if (m == NULL) {
+                       return -1;
+               }
+
+               /* Ottery is faster than sodium native PRG that uses /dev/random only */
+               memcpy (creq->nonce, &ctx->cur_key->counter, sizeof (uint64_t));
+               ottery_rand_bytes (creq->nonce + sizeof (uint64_t), 12 - sizeof (uint64_t));
+               rspamd_explicit_memzero (creq->nonce + 12,
+                               rspamd_cryptobox_nonce_bytes (RSPAMD_CRYPTOBOX_MODE_25519) - 12);
+
+               rspamd_explicit_memzero (m, crypto_box_ZEROBYTES);
+               memcpy (m + crypto_box_ZEROBYTES, req->packet, req->pos);
+
+               rspamd_cryptobox_encrypt_nm_inplace (m + crypto_box_ZEROBYTES,
+                               boxed_len,
+                               creq->nonce,
+                               nm->k,
+                               m,
+                               RSPAMD_CRYPTOBOX_MODE_25519);
+
+               creq->key = ctx->cur_key;
+               REF_RETAIN (ctx->cur_key);
+               creq->entry = entry;
+               creq->req = req;
+               creq->nm = nm;
+               HASH_ADD_KEYPTR (hh, ctx->requests, creq->nonce, 12, creq);
+               req->curve_plugin_data = creq;
+
+               ctx->cur_key->counter ++;
+               ctx->cur_key->uses ++;
+
+               /* Now form a dnscurve packet */
+               iov[0].iov_base = (void *)qmagic;
+               iov[0].iov_len = sizeof (qmagic) - 1;
+               iov[1].iov_base = ctx->cur_key->pk;
+               iov[1].iov_len = sizeof (ctx->cur_key->pk);
+               iov[2].iov_base = creq->nonce;
+               iov[2].iov_len = 12;
+               iov[3].iov_base = m + crypto_box_BOXZEROBYTES;
+               iov[3].iov_len = boxed_len - crypto_box_BOXZEROBYTES;
+
+               ret = writev (req->io->sock, iov, sizeof (iov) / sizeof (iov[0]));
+               rspamd_explicit_memzero (m, boxed_len);
+               free (m);
+       }
+       else {
+               ret = write (req->io->sock, req->packet, req->pos);
+               req->curve_plugin_data = NULL;
+       }
+
+       return ret;
+}
+
+ssize_t
+rdns_curve_recv (struct rdns_io_channel *ioc, void *buf, size_t len, void *plugin_data,
+               struct rdns_request **req_out)
+{
+       struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *)plugin_data;
+       ssize_t ret, boxlen;
+       static const char rmagic[] = "R6fnvWJ8";
+       unsigned char *p, *box;
+       unsigned char enonce[24];
+       struct rdns_curve_request *creq;
+       struct rdns_resolver *resolver;
+
+       resolver = ctx->resolver;
+       ret = read (ioc->sock, buf, len);
+
+       if (ret <= 0 || ret < 64) {
+               /* Definitely not a DNSCurve packet */
+               return ret;
+       }
+
+       if (memcmp (buf, rmagic, sizeof (rmagic) - 1) == 0) {
+               /* Likely DNSCurve packet */
+               p = ((unsigned char *)buf) + 8;
+               HASH_FIND (hh, ctx->requests, p, 12, creq);
+               if (creq == NULL) {
+                       rdns_info ("unable to find nonce in the internal hash");
+                       return ret;
+               }
+
+               memcpy (enonce, p, rspamd_cryptobox_nonce_bytes (RSPAMD_CRYPTOBOX_MODE_25519));
+               p += rspamd_cryptobox_nonce_bytes (RSPAMD_CRYPTOBOX_MODE_25519);
+               boxlen = ret - rspamd_cryptobox_nonce_bytes (RSPAMD_CRYPTOBOX_MODE_25519) +
+                               crypto_box_BOXZEROBYTES -
+                               sizeof (rmagic) + 1;
+               if (boxlen < 0) {
+                       return ret;
+               }
+
+               box = malloc (boxlen);
+               rspamd_explicit_memzero (box, crypto_box_BOXZEROBYTES);
+               memcpy (box + crypto_box_BOXZEROBYTES, p,
+                               boxlen - crypto_box_BOXZEROBYTES);
+
+               if (!rspamd_cryptobox_decrypt_nm_inplace (
+                               box + rspamd_cryptobox_mac_bytes (RSPAMD_CRYPTOBOX_MODE_25519),
+                               boxlen - rspamd_cryptobox_mac_bytes (RSPAMD_CRYPTOBOX_MODE_25519),
+                               enonce, creq->nm->k, box, RSPAMD_CRYPTOBOX_MODE_25519)) {
+                       memcpy (buf, box + crypto_box_ZEROBYTES,
+                                       boxlen - crypto_box_ZEROBYTES);
+                       ret = boxlen - crypto_box_ZEROBYTES;
+                       *req_out = creq->req;
+               }
+               else {
+                       rdns_info ("unable open cryptobox of size %d", (int)boxlen);
+               }
+
+               free (box);
+       }
+
+       return ret;
+}
+
+void
+rdns_curve_finish_request (struct rdns_request *req, void *plugin_data)
+{
+       struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *)plugin_data;
+       struct rdns_curve_request *creq = req->curve_plugin_data;
+
+       if (creq != NULL) {
+               REF_RELEASE (creq->key);
+               HASH_DELETE (hh, ctx->requests, creq);
+       }
+}
+
+void
+rdns_curve_dtor (struct rdns_resolver *resolver, void *plugin_data)
+{
+       struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *)plugin_data;
+
+       if (ctx->key_refresh_event != NULL) {
+               resolver->async->del_periodic (resolver->async->data,
+                               ctx->key_refresh_event);
+       }
+       REF_RELEASE (ctx->cur_key);
+}
 #else
 
 /* Fake functions */
index d2d1c08a32d9ed5631d7b0fd507eeffd579b15a3..c51849c8fb9d608840e594447f3ec53e7dea0f8b 100644 (file)
@@ -86,7 +86,7 @@ struct rdns_request {
 
        void *async_event;
 
-#ifdef TWEETNACL
+#if defined(TWEETNACL) || defined(USE_RSPAMD_CRYPTOBOX)
        void *curve_plugin_data;
 #endif