From: Vsevolod Stakhov Date: Fri, 30 Jun 2017 07:52:05 +0000 (+0100) Subject: [Feature] Implement rdns-curve plugin based on rspamd cryptobox X-Git-Tag: 1.6.2~46 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=be22540eeb544e14b28045c0cffab2076ddd3fea;p=rspamd.git [Feature] Implement rdns-curve plugin based on rspamd cryptobox --- diff --git a/contrib/librdns/CMakeLists.txt b/contrib/librdns/CMakeLists.txt index f2cd9a3e1..a5733e60f 100644 --- a/contrib/librdns/CMakeLists.txt +++ b/contrib/librdns/CMakeLists.txt @@ -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 diff --git a/contrib/librdns/curve.c b/contrib/librdns/curve.c index 4c2a64135..3976b14a0 100644 --- a/contrib/librdns/curve.c +++ b/contrib/librdns/curve.c @@ -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 */ diff --git a/contrib/librdns/dns_private.h b/contrib/librdns/dns_private.h index d2d1c08a3..c51849c8f 100644 --- a/contrib/librdns/dns_private.h +++ b/contrib/librdns/dns_private.h @@ -86,7 +86,7 @@ struct rdns_request { void *async_event; -#ifdef TWEETNACL +#if defined(TWEETNACL) || defined(USE_RSPAMD_CRYPTOBOX) void *curve_plugin_data; #endif