]> source.dussan.org Git - rspamd.git/commitdiff
[Feature] Support DKIM signing
authorVsevolod Stakhov <vsevolod@highsecure.ru>
Tue, 5 Jul 2016 15:43:29 +0000 (16:43 +0100)
committerVsevolod Stakhov <vsevolod@highsecure.ru>
Tue, 5 Jul 2016 15:43:29 +0000 (16:43 +0100)
src/libserver/dkim.c
src/libserver/dkim.h

index 1f65733ba2a13637a892dee369b1b5f1ecd3f0ee..2031f39fd6a11c9796056af57aaaa077014d3eff 100644 (file)
 #include <openssl/evp.h>
 #include <openssl/rsa.h>
 #include <openssl/engine.h>
+#include <sys/mman.h>
 
 /* special DNS tokens */
 #define DKIM_DNSKEYNAME     "_domainkey"
-/* reserved DNS sub-zone */
-#define DKIM_DNSPOLICYNAME  "_adsp" /* reserved DNS sub-zone */
 
 /* Canonization methods */
 #define DKIM_CANON_UNKNOWN  (-1)    /* unknown method */
         G_STRFUNC, \
         __VA_ARGS__)
 
+
+
+struct rspamd_dkim_common_ctx {
+       rspamd_mempool_t *pool;
+       gsize len;
+       gint header_canon_type;
+       gint body_canon_type;
+       GPtrArray *hlist;
+       EVP_MD_CTX *headers_hash;
+       EVP_MD_CTX *body_hash;
+};
+
+struct rspamd_dkim_context_s {
+       struct rspamd_dkim_common_ctx common;
+       rspamd_mempool_t *pool;
+       gint sig_alg;
+       guint bhlen;
+       guint blen;
+       guint ver;
+       time_t timestamp;
+       time_t expiration;
+       gchar *domain;
+       gchar *selector;
+       gint8 *b;
+       gint8 *bh;
+       gchar *dns_key;
+       const gchar *dkim_header;
+};
+
+struct rspamd_dkim_key_s {
+       guint8 *keydata;
+       guint keylen;
+       gsize decoded_len;
+       guint ttl;
+       RSA *key_rsa;
+       BIO *key_bio;
+       EVP_PKEY *key_evp;
+       ref_entry_t ref;
+};
+
+struct rspamd_dkim_sign_context_s {
+       struct rspamd_dkim_common_ctx common;
+       rspamd_dkim_sign_key_t *key;
+};
+
+struct rspamd_dkim_sign_key_s {
+       guint8 *keydata;
+       guint keylen;
+       RSA *key_rsa;
+       BIO *key_bio;
+       EVP_PKEY *key_evp;
+       ref_entry_t ref;
+};
+
+
+struct rspamd_dkim_header {
+       gchar *name;
+       guint count;
+};
+
 /* Parser of dkim params */
 typedef gboolean (*dkim_parse_param_f) (rspamd_dkim_context_t * ctx,
        const gchar *param, gsize len, GError **err);
@@ -147,47 +206,6 @@ static const dkim_parse_param_f parser_funcs[] = {
        [DKIM_PARAM_BODYLENGTH] = rspamd_dkim_parse_bodylength
 };
 
-struct rspamd_dkim_context_s {
-       rspamd_mempool_t *pool;
-       gint sig_alg;
-       gint header_canon_type;
-       gint body_canon_type;
-       guint bhlen;
-       guint blen;
-       gsize len;
-       guint ver;
-       time_t timestamp;
-       time_t expiration;
-       gchar *domain;
-       gchar *selector;
-       gint8 *b;
-       gint8 *bh;
-
-       GPtrArray *hlist;
-       gchar *dns_key;
-       const gchar *dkim_header;
-
-       EVP_MD_CTX *headers_hash;
-       EVP_MD_CTX *body_hash;
-};
-
-struct rspamd_dkim_key_s {
-       guint8 *keydata;
-       guint keylen;
-       gsize decoded_len;
-       guint ttl;
-       RSA *key_rsa;
-       BIO *key_bio;
-       EVP_PKEY *key_evp;
-       ref_entry_t ref;
-};
-
-
-struct rspamd_dkim_header {
-       gchar *name;
-       guint count;
-};
-
 #define DKIM_ERROR dkim_error_quark ()
 GQuark
 dkim_error_quark (void)
@@ -276,21 +294,21 @@ rspamd_dkim_parse_canonalg (rspamd_dkim_context_t * ctx,
        if (slash == NULL) {
                /* Only check header */
                if (len == 6 && memcmp (param, "simple", len) == 0) {
-                       ctx->header_canon_type = DKIM_CANON_SIMPLE;
+                       ctx->common.header_canon_type = DKIM_CANON_SIMPLE;
                        return TRUE;
                }
                else if (len == 7 && memcmp (param, "relaxed", len) == 0) {
-                       ctx->header_canon_type = DKIM_CANON_RELAXED;
+                       ctx->common.header_canon_type = DKIM_CANON_RELAXED;
                        return TRUE;
                }
        }
        else {
                /* First check header */
                if (sl == 6 && memcmp (param, "simple", sl) == 0) {
-                       ctx->header_canon_type = DKIM_CANON_SIMPLE;
+                       ctx->common.header_canon_type = DKIM_CANON_SIMPLE;
                }
                else if (sl == 7 && memcmp (param, "relaxed", sl) == 0) {
-                       ctx->header_canon_type = DKIM_CANON_RELAXED;
+                       ctx->common.header_canon_type = DKIM_CANON_RELAXED;
                }
                else {
                        goto err;
@@ -299,11 +317,11 @@ rspamd_dkim_parse_canonalg (rspamd_dkim_context_t * ctx,
                len -= sl + 1;
                slash++;
                if (len == 6 && memcmp (slash, "simple", len) == 0) {
-                       ctx->body_canon_type = DKIM_CANON_SIMPLE;
+                       ctx->common.body_canon_type = DKIM_CANON_SIMPLE;
                        return TRUE;
                }
                else if (len == 7 && memcmp (slash, "relaxed", len) == 0) {
-                       ctx->body_canon_type = DKIM_CANON_RELAXED;
+                       ctx->common.body_canon_type = DKIM_CANON_RELAXED;
                        return TRUE;
                }
        }
@@ -337,22 +355,6 @@ rspamd_dkim_parse_selector (rspamd_dkim_context_t * ctx,
        return TRUE;
 }
 
-static struct rspamd_dkim_header *
-rspamd_dkim_find_header (GPtrArray *arr, const gchar *name, gsize len)
-{
-       guint i;
-       struct rspamd_dkim_header *h;
-
-       for (i = 0; i < arr->len; i++) {
-               h = g_ptr_array_index (arr, i);
-               if (g_ascii_strncasecmp (h->name, name, len) == 0) {
-                       return h;
-               }
-       }
-
-       return NULL;
-}
-
 static void
 rspamd_dkim_hlist_free (void *ud)
 {
@@ -362,7 +364,7 @@ rspamd_dkim_hlist_free (void *ud)
 }
 
 static gboolean
-rspamd_dkim_parse_hdrlist (rspamd_dkim_context_t * ctx,
+rspamd_dkim_parse_hdrlist_common (struct rspamd_dkim_common_ctx *ctx,
        const gchar *param,
        gsize len,
        GError **err)
@@ -372,6 +374,7 @@ rspamd_dkim_parse_hdrlist (rspamd_dkim_context_t * ctx,
        gboolean from_found = FALSE;
        guint count = 0;
        struct rspamd_dkim_header *new;
+       GHashTable *htb;
 
        p = param;
        while (p <= end) {
@@ -390,10 +393,15 @@ rspamd_dkim_parse_hdrlist (rspamd_dkim_context_t * ctx,
 
        c = param;
        p = param;
+       htb = g_hash_table_new (rspamd_strcase_hash, rspamd_strcase_equal);
+
        while (p <= end) {
                if ((p == end || *p == ':') && p - c > 0) {
-                       if ((new =
-                               rspamd_dkim_find_header (ctx->hlist, c, p - c)) != NULL) {
+                       h = rspamd_mempool_alloc (ctx->pool, p - c + 1);
+                       rspamd_strlcpy (h, c, p - c + 1);
+                       g_strstrip (h);
+
+                       if ((new = g_hash_table_lookup (htb, h)) != NULL) {
                                new->count++;
                        }
                        else {
@@ -401,15 +409,15 @@ rspamd_dkim_parse_hdrlist (rspamd_dkim_context_t * ctx,
                                new =
                                        rspamd_mempool_alloc (ctx->pool,
                                                sizeof (struct rspamd_dkim_header));
-                               h = rspamd_mempool_alloc (ctx->pool, p - c + 1);
-                               rspamd_strlcpy (h, c, p - c + 1);
-                               g_strstrip (h);
                                new->name = h;
                                new->count = 1;
+                               g_hash_table_insert (htb, new->name, new);
+
                                /* Check mandatory from */
                                if (!from_found && g_ascii_strcasecmp (h, "from") == 0) {
                                        from_found = TRUE;
                                }
+
                                g_ptr_array_add (ctx->hlist, new);
                        }
                        c = p + 1;
@@ -420,6 +428,8 @@ rspamd_dkim_parse_hdrlist (rspamd_dkim_context_t * ctx,
                }
        }
 
+       g_hash_table_unref (htb);
+
        if (!ctx->hlist) {
                g_set_error (err,
                        DKIM_ERROR,
@@ -445,6 +455,15 @@ rspamd_dkim_parse_hdrlist (rspamd_dkim_context_t * ctx,
        return TRUE;
 }
 
+static gboolean
+rspamd_dkim_parse_hdrlist (rspamd_dkim_context_t *ctx,
+       const gchar *param,
+       gsize len,
+       GError **err)
+{
+       return rspamd_dkim_parse_hdrlist_common (&ctx->common, param, len, err);
+}
+
 static gboolean
 rspamd_dkim_parse_version (rspamd_dkim_context_t * ctx,
        const gchar *param,
@@ -539,7 +558,7 @@ rspamd_dkim_parse_bodylength (rspamd_dkim_context_t * ctx,
                        "invalid dkim body length");
                return FALSE;
        }
-       ctx->len = val;
+       ctx->common.len = val;
 
        return TRUE;
 }
@@ -582,9 +601,10 @@ rspamd_create_dkim_context (const gchar *sig,
 
        ctx = rspamd_mempool_alloc0 (pool, sizeof (rspamd_dkim_context_t));
        ctx->pool = pool;
-       ctx->header_canon_type = DKIM_CANON_DEFAULT;
-       ctx->body_canon_type = DKIM_CANON_DEFAULT;
+       ctx->common.header_canon_type = DKIM_CANON_DEFAULT;
+       ctx->common.body_canon_type = DKIM_CANON_DEFAULT;
        ctx->sig_alg = DKIM_SIGN_UNKNOWN;
+       ctx->common.pool = pool;
        /* A simple state machine of parsing tags */
        state = DKIM_STATE_SKIP_SPACES;
        next_state = DKIM_STATE_TAG;
@@ -592,6 +612,7 @@ rspamd_create_dkim_context (const gchar *sig,
        p = sig;
        c = sig;
        end = p + strlen (p);
+
        while (p <= end) {
                switch (state) {
                case DKIM_STATE_TAG:
@@ -805,7 +826,7 @@ rspamd_create_dkim_context (const gchar *sig,
                        "v parameter missing");
                return NULL;
        }
-       if (ctx->hlist == NULL) {
+       if (ctx->common.hlist == NULL) {
                g_set_error (err,
                        DKIM_ERROR,
                        DKIM_SIGERROR_EMPTY_H,
@@ -887,23 +908,23 @@ rspamd_create_dkim_context (const gchar *sig,
                return NULL;
        }
 #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
-       ctx->body_hash = EVP_MD_CTX_create ();
-       EVP_DigestInit_ex (ctx->body_hash, md_alg, NULL);
-       ctx->headers_hash = EVP_MD_CTX_create ();
-       EVP_DigestInit_ex (ctx->headers_hash, md_alg, NULL);
+       ctx->common.body_hash = EVP_MD_CTX_create ();
+       EVP_DigestInit_ex (ctx->common.body_hash, md_alg, NULL);
+       ctx->common.headers_hash = EVP_MD_CTX_create ();
+       EVP_DigestInit_ex (ctx->common.headers_hash, md_alg, NULL);
        rspamd_mempool_add_destructor (pool,
-                       (rspamd_mempool_destruct_t)EVP_MD_CTX_destroy, ctx->body_hash);
+                       (rspamd_mempool_destruct_t)EVP_MD_CTX_destroy, ctx->common.body_hash);
        rspamd_mempool_add_destructor (pool,
-                       (rspamd_mempool_destruct_t)EVP_MD_CTX_destroy, ctx->headers_hash);
+                       (rspamd_mempool_destruct_t)EVP_MD_CTX_destroy, ctx->common.headers_hash);
 #else
-       ctx->body_hash = EVP_MD_CTX_new ();
-       EVP_DigestInit_ex (ctx->body_hash, md_alg, NULL);
-       ctx->headers_hash = EVP_MD_CTX_new ();
-       EVP_DigestInit_ex (ctx->headers_hash, md_alg, NULL);
+       ctx->common.body_hash = EVP_MD_CTX_new ();
+       EVP_DigestInit_ex (ctx->common.body_hash, md_alg, NULL);
+       ctx->common.headers_hash = EVP_MD_CTX_new ();
+       EVP_DigestInit_ex (ctx->common.headers_hash, md_alg, NULL);
        rspamd_mempool_add_destructor (pool,
-                       (rspamd_mempool_destruct_t)EVP_MD_CTX_free, ctx->body_hash);
+                       (rspamd_mempool_destruct_t)EVP_MD_CTX_free, ctx->common.body_hash);
        rspamd_mempool_add_destructor (pool,
-                       (rspamd_mempool_destruct_t)EVP_MD_CTX_free, ctx->headers_hash);
+                       (rspamd_mempool_destruct_t)EVP_MD_CTX_free, ctx->common.headers_hash);
 #endif
        ctx->dkim_header = sig;
 
@@ -1001,6 +1022,26 @@ rspamd_dkim_key_free (rspamd_dkim_key_t *key)
        g_slice_free1 (sizeof (rspamd_dkim_key_t), key);
 }
 
+void
+rspamd_dkim_sign_key_free (rspamd_dkim_sign_key_t *key)
+{
+       if (key->key_evp) {
+               EVP_PKEY_free (key->key_evp);
+       }
+       if (key->key_rsa) {
+               RSA_free (key->key_rsa);
+       }
+       if (key->key_bio) {
+               BIO_free (key->key_bio);
+       }
+
+       if (key->keydata && key->keylen > 0) {
+               munmap (key->keydata, key->keylen);
+       }
+
+       g_slice_free1 (sizeof (rspamd_dkim_sign_key_t), key);
+}
+
 static rspamd_dkim_key_t *
 rspamd_dkim_parse_key (rspamd_dkim_context_t *ctx, const gchar *txt,
                gsize *keylen, GError **err)
@@ -1133,7 +1174,7 @@ rspamd_get_dkim_key (rspamd_dkim_context_t *ctx,
 }
 
 static gboolean
-rspamd_dkim_relaxed_body_step (rspamd_dkim_context_t *ctx, EVP_MD_CTX *ck,
+rspamd_dkim_relaxed_body_step (struct rspamd_dkim_common_ctx *ctx, EVP_MD_CTX *ck,
                const gchar **start, guint size,
                guint *remain)
 {
@@ -1212,7 +1253,7 @@ rspamd_dkim_relaxed_body_step (rspamd_dkim_context_t *ctx, EVP_MD_CTX *ck,
 }
 
 static gboolean
-rspamd_dkim_simple_body_step (rspamd_dkim_context_t *ctx,
+rspamd_dkim_simple_body_step (struct rspamd_dkim_common_ctx *ctx,
                EVP_MD_CTX *ck, const gchar **start, guint size,
                guint *remain)
 {
@@ -1402,7 +1443,7 @@ end:
 }
 
 static gboolean
-rspamd_dkim_canonize_body (rspamd_dkim_context_t *ctx,
+rspamd_dkim_canonize_body (struct rspamd_dkim_common_ctx *ctx,
        const gchar *start,
        const gchar *end)
 {
@@ -1495,7 +1536,7 @@ rspamd_dkim_hash_update (EVP_MD_CTX *ck, const gchar *begin, gsize len)
 
 /* Update hash by signature value (ignoring b= tag) */
 static void
-rspamd_dkim_signature_update (rspamd_dkim_context_t *ctx,
+rspamd_dkim_signature_update (struct rspamd_dkim_common_ctx *ctx,
        const gchar *begin,
        guint len)
 {
@@ -1543,7 +1584,7 @@ rspamd_dkim_signature_update (rspamd_dkim_context_t *ctx,
 }
 
 static gboolean
-rspamd_dkim_canonize_header_relaxed (rspamd_dkim_context_t *ctx,
+rspamd_dkim_canonize_header_relaxed (struct rspamd_dkim_common_ctx *ctx,
        const gchar *header,
        const gchar *header_name,
        gboolean is_sign)
@@ -1626,12 +1667,13 @@ struct rspamd_dkim_sign_chunk {
 };
 
 static gboolean
-rspamd_dkim_canonize_header_simple (rspamd_dkim_context_t *ctx,
+rspamd_dkim_canonize_header_simple (struct rspamd_dkim_common_ctx *ctx,
        const gchar *headers,
        gsize headers_length,
        const gchar *header_name,
        guint count,
-       gboolean is_sign)
+       gboolean is_sign,
+       const gchar *dkim_domain)
 {
        const gchar *p, *c, *end;
        gint state = 0, hlen;
@@ -1738,7 +1780,7 @@ rspamd_dkim_canonize_header_simple (rspamd_dkim_context_t *ctx,
                                                struct rspamd_dkim_sign_chunk,
                                                i);
                                if (rspamd_substring_search (elt->begin, elt->len,
-                                                       ctx->domain, strlen (ctx->domain)) != -1) {
+                                                       dkim_domain, strlen (dkim_domain)) != -1) {
                                        break;
                                }
                        }
@@ -1758,11 +1800,12 @@ rspamd_dkim_canonize_header_simple (rspamd_dkim_context_t *ctx,
 }
 
 static gboolean
-rspamd_dkim_canonize_header (rspamd_dkim_context_t *ctx,
+rspamd_dkim_canonize_header (struct rspamd_dkim_common_ctx *ctx,
        struct rspamd_task *task,
        const gchar *header_name,
        guint count,
-       gboolean is_sig)
+       const gchar *dkim_header,
+       const gchar *dkim_domain)
 {
        struct raw_header *rh, *rh_iter;
        guint rh_num = 0;
@@ -1774,12 +1817,13 @@ rspamd_dkim_canonize_header (rspamd_dkim_context_t *ctx,
                                   task->raw_headers_content.len,
                                   header_name,
                                   count,
-                                  is_sig);
+                                  dkim_header != NULL,
+                                  dkim_domain);
        }
        else {
                rh = g_hash_table_lookup (task->raw_headers, header_name);
                if (rh) {
-                       if (!is_sig) {
+                       if (dkim_header == NULL) {
                                rh_iter = rh;
                                while (rh_iter) {
                                        rh_num++;
@@ -1807,7 +1851,8 @@ rspamd_dkim_canonize_header (rspamd_dkim_context_t *ctx,
                                while (cur) {
                                        rh = cur->data;
                                        if (!rspamd_dkim_canonize_header_relaxed (ctx, rh->value,
-                                               header_name, is_sig)) {
+                                               header_name, FALSE)) {
+
                                                g_list_free (nh);
                                                return FALSE;
                                        }
@@ -1820,10 +1865,11 @@ rspamd_dkim_canonize_header (rspamd_dkim_context_t *ctx,
                        else {
                                /* For signature check just use the saved dkim header */
                                rspamd_dkim_canonize_header_relaxed (ctx,
-                                       ctx->dkim_header,
+                                       dkim_header,
                                        header_name,
-                                       is_sig);
+                                       TRUE);
                        }
+
                        return TRUE;
                }
        }
@@ -1844,8 +1890,7 @@ rspamd_dkim_check (rspamd_dkim_context_t *ctx,
        rspamd_dkim_key_t *key,
        struct rspamd_task *task)
 {
-       const gchar *p, *headers_end = NULL, *end, *body_end;
-       gboolean got_cr = FALSE, got_crlf = FALSE, got_lf = FALSE;
+       const gchar *p, *headers_end = NULL, *body_end;
        guchar raw_digest[EVP_MAX_MD_SIZE];
        gsize dlen;
        gint res = DKIM_CONTINUE;
@@ -1859,87 +1904,26 @@ rspamd_dkim_check (rspamd_dkim_context_t *ctx,
 
        /* First of all find place of body */
        p = task->msg.begin;
-
-       end = task->msg.begin + task->msg.len;
-
-       while (p <= end) {
-               /* Search for \r\n\r\n at the end of headers */
-               if (*p == '\n') {
-                       if (got_cr && *(p - 1) == '\r') {
-                               if (got_crlf) {
-                                       /* \r\n\r\n */
-                                       headers_end = p + 1;
-                                       break;
-                               }
-                               else if (got_lf) {
-                                       /* \n\r\n */
-                                       headers_end = p + 1;
-                                       break;
-                               }
-                               else {
-                                       /* Set got crlf flag */
-                                       got_crlf = TRUE;
-                                       got_cr = FALSE;
-                                       got_lf = FALSE;
-                               }
-                       }
-                       else if (got_cr && *(p - 1) != '\r') {
-                               /* We got CR somewhere but not right before */
-                               got_cr = FALSE;
-                               if (*(p - 1) == '\n') {
-                                       /* \r\n\n case */
-                                       headers_end = p + 1;
-                                       break;
-                               }
-                               got_lf = TRUE;
-                       }
-                       else if (got_lf && *(p - 1) == '\n') {
-                               /* \n\n case */
-                               headers_end = p + 1;
-                               break;
-                       }
-                       else {
-                               got_lf = TRUE;
-                       }
-               }
-               else if (*p == '\r') {
-                       if (got_cr && *(p - 1) == '\r') {
-                               /* \r\r case */
-                               headers_end = p + 1;
-                               break;
-                       }
-                       else if (got_lf && *(p - 1) != '\n') {
-                               /* Sequence is broken */
-                               got_lf = FALSE;
-                               got_cr = TRUE;
-                       }
-                       else {
-                               got_cr = TRUE;
-                       }
-               }
-               else {
-                       got_cr = FALSE;
-                       got_crlf = FALSE;
-               }
-               p++;
-       }
+       body_end = task->msg.begin + task->msg.len;
+       headers_end = task->msg.begin + task->raw_headers_content.len;
 
        /* Start canonization of body part */
-       body_end = end;
-       if (!rspamd_dkim_canonize_body (ctx, headers_end, body_end)) {
+       if (!rspamd_dkim_canonize_body (&ctx->common, headers_end, body_end)) {
                return DKIM_RECORD_ERROR;
        }
        /* Now canonize headers */
-       for (i = 0; i < ctx->hlist->len; i++) {
-               dh = g_ptr_array_index (ctx->hlist, i);
-               rspamd_dkim_canonize_header (ctx, task, dh->name, dh->count, FALSE);
+       for (i = 0; i < ctx->common.hlist->len; i++) {
+               dh = g_ptr_array_index (ctx->common.hlist, i);
+               rspamd_dkim_canonize_header (&ctx->common, task, dh->name, dh->count,
+                               NULL, NULL);
        }
 
        /* Canonize dkim signature */
-       rspamd_dkim_canonize_header (ctx, task, DKIM_SIGNHEADER, 1, TRUE);
+       rspamd_dkim_canonize_header (&ctx->common, task, DKIM_SIGNHEADER, 1,
+                       ctx->dkim_header, ctx->domain);
 
-       dlen = EVP_MD_CTX_size (ctx->body_hash);
-       EVP_DigestFinal_ex (ctx->body_hash, raw_digest, NULL);
+       dlen = EVP_MD_CTX_size (ctx->common.body_hash);
+       EVP_DigestFinal_ex (ctx->common.body_hash, raw_digest, NULL);
 
        /* Check bh field */
        if (memcmp (ctx->bh, raw_digest, ctx->bhlen) != 0) {
@@ -1948,8 +1932,8 @@ rspamd_dkim_check (rspamd_dkim_context_t *ctx,
                return DKIM_REJECT;
        }
 
-       dlen = EVP_MD_CTX_size (ctx->headers_hash);
-       EVP_DigestFinal_ex (ctx->headers_hash, raw_digest, NULL);
+       dlen = EVP_MD_CTX_size (ctx->common.headers_hash);
+       EVP_DigestFinal_ex (ctx->common.headers_hash, raw_digest, NULL);
        /* Check headers signature */
 
        if (ctx->sig_alg == DKIM_SIGN_RSASHA1) {
@@ -1985,6 +1969,20 @@ rspamd_dkim_key_unref (rspamd_dkim_key_t *k)
        REF_RELEASE (k);
 }
 
+rspamd_dkim_sign_key_t *
+rspamd_dkim_sign_key_ref (rspamd_dkim_sign_key_t *k)
+{
+       REF_RETAIN (k);
+
+       return k;
+}
+
+void
+rspamd_dkim_sign_key_unref (rspamd_dkim_sign_key_t *k)
+{
+       REF_RELEASE (k);
+}
+
 const gchar*
 rspamd_dkim_get_domain (rspamd_dkim_context_t *ctx)
 {
@@ -2014,3 +2012,215 @@ rspamd_dkim_get_dns_key (rspamd_dkim_context_t *ctx)
 
        return NULL;
 }
+
+rspamd_dkim_sign_key_t*
+rspamd_dkim_sign_key_load (const gchar *path, GError **err)
+{
+       gpointer map;
+       gsize len = 0;
+       rspamd_dkim_sign_key_t *nkey;
+
+       map = rspamd_file_xmap (path, PROT_READ, &len);
+
+       if (map == NULL) {
+               g_set_error (err, dkim_error_quark (), DKIM_SIGERROR_KEYFAIL,
+                               "cannot map private key %s: %s",
+                               path, strerror (errno));
+
+               return NULL;
+       }
+
+       nkey = g_slice_alloc0 (sizeof (*nkey));
+       nkey->keydata = map;
+       nkey->keylen = len;
+
+       nkey->key_bio = BIO_new_mem_buf (map, len);
+
+       if (!PEM_read_bio_PrivateKey (nkey->key_bio, &nkey->key_evp, NULL, NULL)) {
+               g_set_error (err, dkim_error_quark (), DKIM_SIGERROR_KEYFAIL,
+                               "cannot read private key from %s: %s",
+                               path, ERR_error_string (ERR_get_error (), NULL));
+               rspamd_dkim_sign_key_free (nkey);
+
+               return NULL;
+       }
+
+       nkey->key_rsa = EVP_PKEY_get1_RSA (nkey->key_evp);
+       if (nkey->key_rsa == NULL) {
+               g_set_error (err,
+                               DKIM_ERROR,
+                               DKIM_SIGERROR_KEYFAIL,
+                               "cannot extract rsa key from evp key");
+               rspamd_dkim_sign_key_free (nkey);
+
+               return NULL;
+       }
+
+       REF_INIT_RETAIN (nkey, rspamd_dkim_sign_key_free);
+
+       return nkey;
+}
+
+rspamd_dkim_sign_context_t *
+rspamd_create_dkim_sign_context (struct rspamd_task *task,
+               rspamd_dkim_sign_key_t *priv_key,
+               gint headers_canon,
+               gint body_canon,
+               const gchar *headers,
+               GError **err)
+{
+       rspamd_dkim_sign_context_t *nctx;
+
+       if (headers_canon != DKIM_CANON_SIMPLE && headers_canon != DKIM_CANON_RELAXED) {
+               g_set_error (err,
+                               DKIM_ERROR,
+                               DKIM_SIGERROR_INVALID_HC,
+                               "bad headers canonicalisation");
+
+               return NULL;
+       }
+       if (body_canon != DKIM_CANON_SIMPLE && body_canon != DKIM_CANON_RELAXED) {
+               g_set_error (err,
+                               DKIM_ERROR,
+                               DKIM_SIGERROR_INVALID_BC,
+                               "bad body canonicalisation");
+
+               return NULL;
+       }
+
+       if (!priv_key || !priv_key->key_rsa) {
+               g_set_error (err,
+                               DKIM_ERROR,
+                               DKIM_SIGERROR_KEYFAIL,
+                               "bad key to sign");
+
+               return NULL;
+       }
+
+       nctx = rspamd_mempool_alloc0 (task->task_pool, sizeof (*nctx));
+       nctx->common.pool = task->task_pool;
+       nctx->common.header_canon_type = headers_canon;
+       nctx->common.body_canon_type = body_canon;
+
+       if (!rspamd_dkim_parse_hdrlist_common (&nctx->common, headers, strlen (headers),
+                       err)) {
+               return NULL;
+       }
+
+       nctx->key = rspamd_dkim_sign_key_ref (priv_key);
+
+       rspamd_mempool_add_destructor (task->task_pool,
+                       (rspamd_mempool_destruct_t)rspamd_dkim_sign_key_unref, priv_key);
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
+       nctx->common.body_hash = EVP_MD_CTX_create ();
+       EVP_DigestInit_ex (nctx->common.body_hash, EVP_sha256 (), NULL);
+       nctx->common.headers_hash = EVP_MD_CTX_create ();
+       EVP_DigestInit_ex (nctx->common.headers_hash, EVP_sha256 (), NULL);
+       rspamd_mempool_add_destructor (task->task_pool,
+                       (rspamd_mempool_destruct_t)EVP_MD_CTX_destroy, nctx->common.body_hash);
+       rspamd_mempool_add_destructor (task->task_pool,
+                       (rspamd_mempool_destruct_t)EVP_MD_CTX_destroy, nctx->common.headers_hash);
+#else
+       nctx->common.body_hash = EVP_MD_CTX_new ();
+       EVP_DigestInit_ex (nctx->common.body_hash, EVP_sha256 (), NULL);
+       nctx->common.headers_hash = EVP_MD_CTX_new ();
+       EVP_DigestInit_ex (nctx->common.headers_hash, EVP_sha256 (), NULL);
+       rspamd_mempool_add_destructor (task->task_pool,
+                       (rspamd_mempool_destruct_t)EVP_MD_CTX_free, nctx->common.body_hash);
+       rspamd_mempool_add_destructor (task->task_pool,
+                       (rspamd_mempool_destruct_t)EVP_MD_CTX_free, nctx->common.headers_hash);
+#endif
+
+       return nctx;
+}
+
+
+GString*
+rspamd_dkim_sign (struct rspamd_task *task,
+               const gchar *selector, const gchar *domain,
+               time_t expire, gsize len,
+               rspamd_dkim_sign_context_t *ctx)
+{
+       GString *hdr;
+       struct rspamd_dkim_header *dh;
+       const gchar *p, *headers_end = NULL, *body_end;
+       guchar raw_digest[EVP_MAX_MD_SIZE];
+       gsize dlen;
+       guint i, j;
+       gchar *b64_data;
+       guchar *rsa_buf;
+       guint rsa_len;
+
+       g_assert (ctx != NULL);
+
+       /* First of all find place of body */
+       p = task->msg.begin;
+       body_end = task->msg.begin + task->msg.len;
+       headers_end = task->msg.begin + task->raw_headers_content.len;
+
+       if (len > 0) {
+               ctx->common.len = len;
+       }
+
+       /* Start canonization of body part */
+       if (!rspamd_dkim_canonize_body (&ctx->common, headers_end, body_end)) {
+               return NULL;
+       }
+
+       hdr = g_string_sized_new (255);
+       rspamd_printf_gstring (hdr, "v=1; a=rsa-sha256; c=%s/%s; d=%s; s=%s;",
+                       ctx->common.header_canon_type == DKIM_CANON_RELAXED ? "relaxed" : "simple",
+                       ctx->common.body_canon_type == DKIM_CANON_RELAXED ? "relaxed" : "simple",
+                       domain, selector);
+
+       if (expire > 0) {
+               rspamd_printf_gstring (hdr, "x=%t;", expire);
+       }
+       if (len > 0) {
+               rspamd_printf_gstring (hdr, "l=%z;", len);
+       }
+
+       rspamd_printf_gstring (hdr, "t=%t;h=", time (NULL));
+
+       /* Now canonize headers */
+       for (i = 0; i < ctx->common.hlist->len; i++) {
+               dh = g_ptr_array_index (ctx->common.hlist, i);
+               rspamd_dkim_canonize_header (&ctx->common, task, dh->name, dh->count,
+                               NULL, NULL);
+
+               for (j = 0; j < dh->count; j++) {
+                       rspamd_printf_gstring (hdr, "%s:", dh->name);
+               }
+       }
+
+       /* Replace the last ':' with ';' */
+       hdr->str[hdr->len - 1] = ';';
+
+       dlen = EVP_MD_CTX_size (ctx->common.body_hash);
+       EVP_DigestFinal_ex (ctx->common.body_hash, raw_digest, NULL);
+
+       b64_data = rspamd_encode_base64 (raw_digest, dlen, 0, NULL);
+       rspamd_printf_gstring (hdr, "bh=%s;b=", b64_data);
+       g_free (b64_data);
+
+       dlen = EVP_MD_CTX_size (ctx->common.headers_hash);
+       EVP_DigestFinal_ex (ctx->common.headers_hash, raw_digest, NULL);
+       rsa_len = RSA_size (ctx->key->key_rsa);
+       rsa_buf = g_alloca (rsa_len);
+
+       if (RSA_sign (NID_sha256, raw_digest, dlen, rsa_buf, &rsa_len,
+                       ctx->key->key_rsa) != 1) {
+               g_string_free (hdr, TRUE);
+               msg_err_task ("rsa sign error: %s",
+                               ERR_error_string (ERR_get_error (), NULL));
+
+               return NULL;
+       }
+
+       b64_data = rspamd_encode_base64 (rsa_buf, rsa_len, 0, NULL);
+       rspamd_printf_gstring (hdr, "%s", b64_data);
+       g_free (b64_data);
+
+       return hdr;
+}
index 8decab2d66214e2ddce41e9d8931527b06a8237c..35f2988075274387b2bcf13fbda2e1d3d702de89 100644 (file)
 #define DKIM_NOTFOUND   3   /* requested record not found */
 #define DKIM_RECORD_ERROR   4   /* error requesting record */
 
+#define DKIM_CANON_SIMPLE   0   /* as specified in DKIM spec */
+#define DKIM_CANON_RELAXED  1   /* as specified in DKIM spec */
+
 struct rspamd_dkim_context_s;
 typedef struct rspamd_dkim_context_s rspamd_dkim_context_t;
 
+struct rspamd_dkim_sign_context_s;
+typedef struct rspamd_dkim_sign_context_s rspamd_dkim_sign_context_t;
+
 struct rspamd_dkim_key_s;
 typedef struct rspamd_dkim_key_s rspamd_dkim_key_t;
 
+struct rspamd_dkim_sign_key_s;
+typedef struct rspamd_dkim_sign_key_s rspamd_dkim_sign_key_t;
+
 struct rspamd_task;
 
 /* Err MUST be freed if it is not NULL, key is allocated by slice allocator */
@@ -109,6 +118,28 @@ rspamd_dkim_context_t * rspamd_create_dkim_context (const gchar *sig,
        guint time_jitter,
        GError **err);
 
+/**
+ * Create new dkim context for making a signature
+ * @param task
+ * @param priv_key
+ * @param err
+ * @return
+ */
+rspamd_dkim_sign_context_t * rspamd_create_dkim_sign_context (struct rspamd_task *task,
+               rspamd_dkim_sign_key_t *priv_key,
+               gint headers_canon,
+               gint body_canon,
+               const gchar *dkim_headers,
+               GError **err);
+
+/**
+ * Load dkim key from a file
+ * @param path
+ * @param err
+ * @return
+ */
+rspamd_dkim_sign_key_t* rspamd_dkim_sign_key_load (const gchar *path, GError **err);
+
 /**
  * Make DNS request for specified context and obtain and parse key
  * @param ctx dkim context from signature
@@ -132,8 +163,15 @@ gint rspamd_dkim_check (rspamd_dkim_context_t *ctx,
        rspamd_dkim_key_t *key,
        struct rspamd_task *task);
 
+GString* rspamd_dkim_sign (struct rspamd_task *task,
+               const gchar *selector, const gchar *domain,
+               time_t expire, gsize len,
+               rspamd_dkim_sign_context_t *ctx);
+
 rspamd_dkim_key_t * rspamd_dkim_key_ref (rspamd_dkim_key_t *k);
 void rspamd_dkim_key_unref (rspamd_dkim_key_t *k);
+rspamd_dkim_sign_key_t * rspamd_dkim_sign_key_ref (rspamd_dkim_sign_key_t *k);
+void rspamd_dkim_sign_key_unref (rspamd_dkim_sign_key_t *k);
 const gchar* rspamd_dkim_get_domain (rspamd_dkim_context_t *ctx);
 const gchar* rspamd_dkim_get_dns_key (rspamd_dkim_context_t *ctx);
 guint rspamd_dkim_key_get_ttl (rspamd_dkim_key_t *k);