aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorVsevolod Stakhov <vsevolod@highsecure.ru>2015-01-28 22:52:58 +0000
committerVsevolod Stakhov <vsevolod@highsecure.ru>2015-01-28 22:52:58 +0000
commit1ff1b64500d0423a7a2bf8f4ef66ebec2de1a201 (patch)
tree689172d327250f12af629aecf500a89f179a41cc /src
parentff7aec707dd37abe4f2bd71ddee50cfa5499c98d (diff)
parentd68fea7bc475bdcb88ec8ca85b5567734fc8e7da (diff)
downloadrspamd-1ff1b64500d0423a7a2bf8f4ef66ebec2de1a201.tar.gz
rspamd-1ff1b64500d0423a7a2bf8f4ef66ebec2de1a201.zip
Merge branch 'crypto'
Diffstat (limited to 'src')
-rw-r--r--src/libutil/CMakeLists.txt1
-rw-r--r--src/libutil/http.c239
-rw-r--r--src/libutil/http.h30
-rw-r--r--src/libutil/util.c82
-rw-r--r--src/libutil/util.h8
5 files changed, 346 insertions, 14 deletions
diff --git a/src/libutil/CMakeLists.txt b/src/libutil/CMakeLists.txt
index 655b34487..ddfb5cf30 100644
--- a/src/libutil/CMakeLists.txt
+++ b/src/libutil/CMakeLists.txt
@@ -33,6 +33,7 @@ TARGET_LINK_LIBRARIES(rspamd-util event)
TARGET_LINK_LIBRARIES(rspamd-util xxhash)
TARGET_LINK_LIBRARIES(rspamd-util siphash)
TARGET_LINK_LIBRARIES(rspamd-util blake2)
+TARGET_LINK_LIBRARIES(rspamd-util tweetnacl)
TARGET_LINK_LIBRARIES(rspamd-util rdns)
IF(OPENSSL_FOUND)
TARGET_LINK_LIBRARIES(rspamd-util ${OPENSSL_LIBRARIES})
diff --git a/src/libutil/http.c b/src/libutil/http.c
index 5a597fbd2..74474365b 100644
--- a/src/libutil/http.c
+++ b/src/libutil/http.c
@@ -28,15 +28,28 @@
#include "printf.h"
#include "logger.h"
#include "ref.h"
+#include "tweetnacl.h"
+#include "blake2.h"
+#include "ottery.h"
#include <limits.h>
+struct rspamd_http_keypair {
+ guchar pk[crypto_box_PUBLICKEYBYTES];
+ guchar sk[crypto_box_SECRETKEYBYTES];
+ guchar id[BLAKE2B_OUTBYTES];
+ ref_entry_t ref;
+};
+
struct rspamd_http_connection_private {
struct _rspamd_http_privbuf {
GString *data;
ref_entry_t ref;
} *buf;
gboolean new_header;
+ gboolean encrypted;
+ struct rspamd_http_keypair *local_key;
+ GString *peer_key;
struct rspamd_http_header *header;
struct http_parser parser;
struct http_parser_settings parser_cb;
@@ -71,9 +84,11 @@ static const struct _rspamd_http_magic {
[HTTP_MAGIC_JPG] = { "jpg", "image/jpeg" },
};
-static gchar *http_week[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
-static gchar *http_month[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+static const gchar *http_week[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+static const gchar *http_month[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+static const gchar *key_header = "Key";
+static const gchar *date_header = "Date";
#define HTTP_ERROR http_error_quark ()
@@ -387,13 +402,46 @@ rspamd_http_parse_date (const gchar *header, gsize len)
return (time_t) time;
}
+static void
+rspamd_http_parse_key (GString *data, struct rspamd_http_connection_private *priv)
+{
+ guchar *decoded;
+ gsize decoded_len;
+
+ if (priv->local_key == NULL) {
+ /* In this case we cannot do anything, e.g. we cannot decrypt payload */
+ priv->encrypted = TRUE;
+ }
+ else {
+ /* Check sanity of what we have */
+ decoded = rspamd_decode_base32 (data->str, data->len, &decoded_len);
+ if (decoded != NULL) {
+ if (decoded_len >= sizeof (priv->local_key->id) +
+ sizeof (priv->local_key->pk)) {
+ if (memcmp (priv->local_key->id, decoded,
+ sizeof (priv->local_key->id)) == 0) {
+ priv->peer_key = g_string_sized_new (sizeof (priv->local_key->pk));
+ g_string_append_len (priv->peer_key,
+ decoded + sizeof (priv->local_key->id),
+ sizeof (priv->local_key->pk));
+ }
+ }
+ }
+ priv->encrypted = TRUE;
+ g_free (decoded);
+ }
+}
+
static inline void
-rspamd_http_check_date (struct rspamd_http_connection_private *priv)
+rspamd_http_check_special_header (struct rspamd_http_connection_private *priv)
{
- if (g_ascii_strcasecmp (priv->header->name->str, "date") == 0) {
+ if (g_ascii_strcasecmp (priv->header->name->str, date_header) == 0) {
priv->msg->date = rspamd_http_parse_date (priv->header->value->str,
priv->header->value->len);
}
+ else if (g_ascii_strcasecmp (priv->header->name->str, key_header) == 0) {
+ rspamd_http_parse_key (priv->header->value, priv);
+ }
}
static gint
@@ -447,7 +495,7 @@ rspamd_http_on_header_field (http_parser * parser,
}
else if (priv->new_header) {
DL_APPEND (priv->msg->headers, priv->header);
- rspamd_http_check_date (priv);
+ rspamd_http_check_special_header (priv);
priv->header = g_slice_alloc (sizeof (struct rspamd_http_header));
priv->header->name = g_string_sized_new (32);
priv->header->value = g_string_sized_new (32);
@@ -492,7 +540,7 @@ rspamd_http_on_headers_complete (http_parser * parser)
if (priv->header != NULL) {
DL_APPEND (priv->msg->headers, priv->header);
- rspamd_http_check_date (priv);
+ rspamd_http_check_special_header (priv);
priv->header = NULL;
}
@@ -520,7 +568,8 @@ rspamd_http_on_body (http_parser * parser, const gchar *at, size_t length)
g_string_append_len (priv->msg->body, at, length);
- if (conn->opts & RSPAMD_HTTP_BODY_PARTIAL) {
+ if ((conn->opts & RSPAMD_HTTP_BODY_PARTIAL) && !priv->encrypted) {
+ /* Incremental update is basically impossible for encrypted requests */
return (conn->body_handler (conn, priv->msg, at, length));
}
@@ -534,18 +583,57 @@ rspamd_http_on_message_complete (http_parser * parser)
(struct rspamd_http_connection *)parser->data;
struct rspamd_http_connection_private *priv;
int ret = 0;
+ guchar *nonce, *m;
+ gsize dec_len;
+ GError *err;
priv = conn->priv;
if (conn->body_handler != NULL) {
- rspamd_http_connection_ref (conn);
- if ((conn->opts & RSPAMD_HTTP_BODY_PARTIAL) == 0) {
+
+ if (priv->encrypted) {
+ if (priv->local_key == NULL || priv->peer_key == NULL ||
+ priv->msg->body->len < crypto_box_NONCEBYTES + crypto_box_ZEROBYTES) {
+ err = g_error_new (HTTP_ERROR, 500, "Cannot decrypt message");
+ rspamd_http_connection_ref (conn);
+ conn->error_handler (conn, err);
+ rspamd_http_connection_unref (conn);
+ g_error_free (err);
+ return -1;
+ }
+ /* We have keys, so we can decrypt message */
+ /* TODO: add pubkey<->privkey pairs to LRU cache */
+ nonce = priv->msg->body->str;
+ m = priv->msg->body->str + crypto_box_NONCEBYTES;
+ dec_len = priv->msg->body->len - crypto_box_NONCEBYTES;
+
+ if (crypto_box_open (m + crypto_box_ZEROBYTES, m, dec_len, nonce,
+ priv->peer_key->str, priv->local_key->sk) != 0) {
+ err = g_error_new (HTTP_ERROR, 500, "Cannot verify encrypted message");
+ rspamd_http_connection_ref (conn);
+ conn->error_handler (conn, err);
+ rspamd_http_connection_unref (conn);
+ g_error_free (err);
+ return -1;
+ }
+ m += crypto_box_ZEROBYTES;
+ dec_len -= crypto_box_ZEROBYTES;
+
+ rspamd_http_connection_ref (conn);
+ ret = conn->body_handler (conn,
+ priv->msg,
+ m,
+ dec_len);
+ rspamd_http_connection_unref (conn);
+ }
+ else if ((conn->opts & RSPAMD_HTTP_BODY_PARTIAL) == 0) {
+ rspamd_http_connection_ref (conn);
ret = conn->body_handler (conn,
priv->msg,
priv->msg->body->str,
priv->msg->body->len);
+ rspamd_http_connection_unref (conn);
}
- rspamd_http_connection_unref (conn);
}
if (ret == 0) {
@@ -821,6 +909,11 @@ rspamd_http_connection_reset (struct rspamd_http_connection *conn)
REF_RELEASE (priv->buf);
}
+ if (priv->peer_key != NULL) {
+ g_string_free (priv->peer_key, TRUE);
+ }
+ priv->encrypted = FALSE;
+
rspamd_http_parser_reset (conn);
if (priv->out != NULL) {
@@ -836,6 +929,11 @@ rspamd_http_connection_free (struct rspamd_http_connection *conn)
priv = conn->priv;
rspamd_http_connection_reset (conn);
+
+ if (priv->local_key) {
+ REF_RELEASE (priv->local_key);
+ }
+
g_slice_free1 (sizeof (struct rspamd_http_connection_private), priv);
g_slice_free1 (sizeof (struct rspamd_http_connection), conn);
}
@@ -889,6 +987,10 @@ rspamd_http_connection_write_message (struct rspamd_http_connection *conn,
gint i;
gsize bodylen;
GString *buf;
+ gboolean encrypted = FALSE;
+ gchar *b32_key, *b32_id;
+ guchar nonce[crypto_box_NONCEBYTES], mac[crypto_box_ZEROBYTES], id[BLAKE2B_OUTBYTES];
+ guchar *np, *mp;
conn->fd = fd;
conn->ud = ud;
@@ -931,6 +1033,15 @@ rspamd_http_connection_write_message (struct rspamd_http_connection *conn,
return;
}
+ if (priv->local_key != NULL && priv->peer_key != NULL) {
+ encrypted = TRUE;
+ }
+
+ if (encrypted) {
+ priv->outlen += 2;
+ bodylen += crypto_box_NONCEBYTES + crypto_box_ZEROBYTES;
+ }
+
if (conn->type == RSPAMD_HTTP_SERVER) {
/* Format reply */
if (msg->method < HTTP_SYMBOLS) {
@@ -985,6 +1096,16 @@ rspamd_http_connection_write_message (struct rspamd_http_connection *conn,
host != NULL ? host : msg->host->str,
bodylen);
}
+ if (encrypted) {
+ blake2b (id, priv->local_key->pk, NULL, sizeof (id),
+ sizeof (priv->local_key->pk), 0);
+ b32_key = rspamd_encode_base32 (priv->local_key->pk,
+ sizeof (priv->local_key->pk));
+ b32_id = rspamd_encode_base32 (id, sizeof (id));
+ rspamd_printf_gstring (buf, "Key: %s%s\r\n", b32_id, b32_key);
+ g_free (b32_key);
+ g_free (b32_id);
+ }
}
/* Allocate iov */
priv->wr_total = bodylen + buf->len + 2;
@@ -999,7 +1120,19 @@ rspamd_http_connection_write_message (struct rspamd_http_connection *conn,
/* Now set up all iov */
priv->out[0].iov_base = buf->str;
- priv->out[0].iov_len = buf->len;
+ if (!encrypted) {
+ priv->out[0].iov_len = buf->len;
+ }
+ else {
+ ottery_rand_bytes (nonce, sizeof (nonce));
+ memset (mac, 0, sizeof (mac));
+ np = buf->str + buf->len;
+ g_string_append_len (buf, nonce, sizeof (nonce));
+ mp = buf->str + buf->len;
+ g_string_append_len (buf, mac, sizeof (mac));
+ priv->out[0].iov_len = buf->len - sizeof (nonce) - sizeof (mac);
+ }
+
i = 1;
LL_FOREACH (msg->headers, hdr)
{
@@ -1021,8 +1154,21 @@ rspamd_http_connection_write_message (struct rspamd_http_connection *conn,
priv->wr_total -= 2;
}
if (msg->body != NULL) {
- priv->out[i].iov_base = pbody;
- priv->out[i++].iov_len = bodylen;
+ if (encrypted) {
+ crypto_box_detached (pbody, pbody,
+ bodylen - sizeof (nonce) - sizeof (mac), np,
+ priv->peer_key->str, priv->local_key->sk, mp);
+ priv->out[i].iov_base = np;
+ priv->out[i++].iov_len = sizeof (nonce);
+ priv->out[i].iov_base = mp;
+ priv->out[i++].iov_len = sizeof (mac);
+ priv->out[i].iov_base = pbody;
+ priv->out[i++].iov_len = bodylen;
+ }
+ else {
+ priv->out[i].iov_base = pbody;
+ priv->out[i++].iov_len = bodylen;
+ }
}
event_set (&priv->ev, fd, EV_WRITE, rspamd_http_event_handler, conn);
@@ -1482,3 +1628,70 @@ rspamd_http_router_free (struct rspamd_http_connection_router *router)
g_slice_free1 (sizeof (struct rspamd_http_connection_router), router);
}
}
+
+static void
+rspamd_http_keypair_dtor (struct rspamd_http_keypair *kp)
+{
+ g_slice_free1 (sizeof (*kp), kp);
+}
+
+gpointer
+rspamd_http_connection_make_key (gchar *key, gsize keylen)
+{
+ guchar *decoded;
+ gsize decoded_len;
+ struct rspamd_http_keypair *kp;
+
+ decoded = rspamd_decode_base32 (key, keylen, &decoded_len);
+
+ if (decoded != NULL) {
+ if (decoded_len == crypto_box_PUBLICKEYBYTES + crypto_box_SECRETKEYBYTES) {
+ kp = g_slice_alloc (sizeof (*kp));
+ REF_INIT_RETAIN (kp, rspamd_http_keypair_dtor);
+ memcpy (kp->sk, decoded, crypto_box_SECRETKEYBYTES);
+ memcpy (kp->pk, decoded + crypto_box_SECRETKEYBYTES,
+ crypto_box_PUBLICKEYBYTES);
+ blake2b (kp->id, kp->pk, NULL, sizeof (kp->id), sizeof (kp->pk), 0);
+
+ return (gpointer)kp;
+ }
+ g_free (decoded);
+ }
+
+ return FALSE;
+}
+
+gpointer
+rspamd_http_connection_gen_key (void)
+{
+ struct rspamd_http_keypair *kp;
+
+ kp = g_slice_alloc (sizeof (*kp));
+ REF_INIT_RETAIN (kp, rspamd_http_keypair_dtor);
+
+ crypto_box_keypair (kp->pk, kp->sk);
+ blake2b (kp->id, kp->pk, NULL, sizeof (kp->id), sizeof (kp->pk), 0);
+
+ return (gpointer)kp;
+}
+
+void
+rspamd_http_connection_set_key (struct rspamd_http_connection *conn,
+ gpointer key)
+{
+ struct rspamd_http_connection_private *priv = conn->priv;
+ struct rspamd_http_keypair *kp = (struct rspamd_http_keypair *)key;
+
+ g_assert (key != NULL);
+ REF_RETAIN (kp);
+ priv->local_key = kp;
+}
+
+void
+rspamd_http_connection_key_destroy (gpointer key)
+{
+ struct rspamd_http_keypair *kp = (struct rspamd_http_keypair *)key;
+
+ g_assert (key != NULL);
+ REF_RELEASE (kp);
+}
diff --git a/src/libutil/http.h b/src/libutil/http.h
index c6fcf0b83..d88424626 100644
--- a/src/libutil/http.h
+++ b/src/libutil/http.h
@@ -70,7 +70,8 @@ struct rspamd_http_message {
*/
enum rspamd_http_options {
RSPAMD_HTTP_BODY_PARTIAL = 0x1, /**< Call body handler on all body data portions */
- RSPAMD_HTTP_CLIENT_SIMPLE = 0x2 /**< Read HTTP client reply automatically */
+ RSPAMD_HTTP_CLIENT_SIMPLE = 0x2, /**< Read HTTP client reply automatically */
+ RSPAMD_HTTP_CLIENT_ENCRYPTED = 0x4 /**< Encrypt data for client */
};
struct rspamd_http_connection_private;
@@ -147,6 +148,33 @@ struct rspamd_http_connection * rspamd_http_connection_new (
enum rspamd_http_connection_type type);
/**
+ * Load the encryption keypair
+ * @param key base32 encoded privkey and pubkey (in that order)
+ * @param keylen length of base32 string
+ * @return opaque pointer pr NULL in case of error
+ */
+gpointer rspamd_http_connection_make_key (gchar *key, gsize keylen);
+
+/**
+ * Generate the encryption keypair
+ * @return opaque pointer pr NULL in case of error
+ */
+gpointer rspamd_http_connection_gen_key (void);
+
+/**
+ * Set key pointed by an opaque pointer
+ * @param conn connection structure
+ * @param key opaque key structure
+ */
+void rspamd_http_connection_set_key (struct rspamd_http_connection *conn,
+ gpointer key);
+/**
+ * Release key pointed by an opaque pointer
+ * @param key opaque key structure
+ */
+void rspamd_http_connection_key_destroy (gpointer key);
+
+/**
* Handle a request using socket fd and user data ud
* @param conn connection structure
* @param ud opaque user data
diff --git a/src/libutil/util.c b/src/libutil/util.c
index 1b0e08b9f..ada373eb1 100644
--- a/src/libutil/util.c
+++ b/src/libutil/util.c
@@ -31,6 +31,7 @@
#include "message.h"
#include "xxhash.h"
+#include "ottery.h"
#ifdef HAVE_OPENSSL
#include <openssl/rand.h>
@@ -2115,3 +2116,84 @@ rspamd_encode_base32 (guchar *in, gsize inlen)
return out;
}
+
+static const guchar b32_dec[] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x12, 0xff, 0x19, 0x1a, 0x1b, 0x1e, 0x1d,
+ 0x07, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x18, 0x01, 0x0c, 0x03, 0x08, 0x05, 0x06,
+ 0x1c, 0x15, 0x09, 0x0a, 0xff, 0x0b, 0x02, 0x10,
+ 0x0d, 0x0e, 0x04, 0x16, 0x11, 0x13, 0xff, 0x14,
+ 0x0f, 0x00, 0x17, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x18, 0x01, 0x0c, 0x03, 0x08, 0x05, 0x06,
+ 0x1c, 0x15, 0x09, 0x0a, 0xff, 0x0b, 0x02, 0x10,
+ 0x0d, 0x0e, 0x04, 0x16, 0x11, 0x13, 0xff, 0x14,
+ 0x0f, 0x00, 0x17, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
+
+guchar*
+rspamd_decode_base32 (gchar *in, gsize inlen, gsize *outlen)
+{
+ guchar *res, decoded;
+ guchar c;
+ guint acc = 0U;
+ guint processed_bits = 0;
+ gsize olen = 0, i, allocated_len = inlen * 8 / 5 + 1;
+
+ res = g_malloc (allocated_len);
+
+ for (i = 0; i < inlen; i ++) {
+ c = (guchar)in[i];
+
+ if (processed_bits >= 8) {
+ processed_bits -= 8;
+ res[olen++] = acc & 0xFF;
+ acc >>= 8;
+ }
+
+ decoded = b32_dec[c];
+ if (decoded == 0xff) {
+ g_free (res);
+ return NULL;
+ }
+
+ acc = (decoded << processed_bits) | acc;
+ processed_bits += 5;
+ }
+
+ if (processed_bits > 0) {
+ res[olen++] = (acc & 0xFF);
+ }
+
+ *outlen = olen;
+
+ return res;
+}
+
+/* Required for tweetnacl */
+void
+randombytes (guchar *buf, guint64 len)
+{
+ ottery_rand_bytes (buf, (size_t)len);
+}
diff --git a/src/libutil/util.h b/src/libutil/util.h
index 62a28d474..23964fc07 100644
--- a/src/libutil/util.h
+++ b/src/libutil/util.h
@@ -426,4 +426,12 @@ void rspamd_ucl_emit_gstring (ucl_object_t *obj,
*/
gchar * rspamd_encode_base32 (guchar *in, gsize inlen);
+/**
+ * Decode string using base32 encoding
+ * @param in input
+ * @param inlen input length
+ * @return freshly allocated base32 decoded value or NULL if input is invalid
+ */
+guchar* rspamd_decode_base32 (gchar *in, gsize inlen, gsize *outlen);
+
#endif