]> source.dussan.org Git - rspamd.git/commitdiff
[Feature] Add preliminary ARC support to dkim code
authorVsevolod Stakhov <vsevolod@highsecure.ru>
Thu, 25 May 2017 12:42:52 +0000 (13:42 +0100)
committerVsevolod Stakhov <vsevolod@highsecure.ru>
Thu, 25 May 2017 12:42:52 +0000 (13:42 +0100)
src/libserver/dkim.c
src/libserver/dkim.h
src/plugins/dkim_check.c

index 0339d5d582c27d6740e4fb257e58a9d2109f0843..b7d3f8ea1abb29b9ea1f0a0766b7b03ade3b607c 100644 (file)
@@ -58,6 +58,7 @@
 #define DKIM_SIGN_DEFAULT   (-1)    /* use internal default */
 #define DKIM_SIGN_RSASHA1   0   /* an RSA-signed SHA1 digest */
 #define DKIM_SIGN_RSASHA256 1   /* an RSA-signed SHA256 digest */
+#define RSPAMD_DKIM_MAX_ARC_IDX 10
 
 #define msg_err_dkim(...) rspamd_default_log_function (G_LOG_LEVEL_CRITICAL, \
         "dkim", ctx->pool->tag.uid, \
@@ -134,7 +135,7 @@ struct rspamd_dkim_sign_key_s {
 
 
 struct rspamd_dkim_header {
-       gchar *name;
+       const gchar *name;
        guint count;
 };
 
@@ -417,7 +418,7 @@ rspamd_dkim_parse_hdrlist_common (struct rspamd_dkim_common_ctx *ctx,
                        }
                        else {
                                /* Insert new header to the list */
-                               g_hash_table_insert (htb, new->name, new);
+                               g_hash_table_insert (htb, (gpointer)new->name, new);
                        }
 
                        c = p + 1;
@@ -574,6 +575,38 @@ rspamd_dkim_parse_idx (rspamd_dkim_context_t * ctx,
        return TRUE;
 }
 
+static void
+rspamd_dkim_add_arc_seal_headers (rspamd_mempool_t *pool,
+               struct rspamd_dkim_common_ctx *ctx)
+{
+       struct rspamd_dkim_header *hdr;
+       guint count = ctx->idx + 1, i;
+
+       ctx->hlist = g_ptr_array_sized_new (count * 3 - 1);
+
+       for (i = 0; i < count; i ++) {
+               /* Authentication results */
+               hdr = rspamd_mempool_alloc (pool, sizeof (*hdr));
+               hdr->name = RSPAMD_DKIM_ARC_AUTHHEADER;
+               hdr->count = i + 1;
+               g_ptr_array_add (ctx->hlist, hdr);
+
+               /* Arc signature */
+               hdr = rspamd_mempool_alloc (pool, sizeof (*hdr));
+               hdr->name = RSPAMD_DKIM_ARC_SIGNHEADER;
+               hdr->count = i + 1;
+               g_ptr_array_add (ctx->hlist, hdr);
+
+               /* Arc seal (except last one) */
+               if (i != count - 1) {
+                       hdr = rspamd_mempool_alloc (pool, sizeof (*hdr));
+                       hdr->name = RSPAMD_DKIM_ARC_SEALHEADER;
+                       hdr->count = i + 1;
+                       g_ptr_array_add (ctx->hlist, hdr);
+               }
+       }
+}
+
 /**
  * Create new dkim context from signature
  * @param sig message's signature
@@ -676,7 +709,17 @@ rspamd_create_dkim_context (const gchar *sig,
                                /* Simple tags */
                                switch (*tag) {
                                case 'v':
-                                       param = DKIM_PARAM_VERSION;
+                                       if (type == RSPAMD_DKIM_NORMAL) {
+                                               param = DKIM_PARAM_VERSION;
+                                       }
+                                       else {
+                                               g_set_error (err,
+                                                               DKIM_ERROR,
+                                                               DKIM_SIGERROR_UNKNOWN,
+                                                               "invalid ARC v param");
+                                               state = DKIM_STATE_ERROR;
+                                               break;
+                                       }
                                        break;
                                case 'a':
                                        param = DKIM_PARAM_SIGNALG;
@@ -691,7 +734,17 @@ rspamd_create_dkim_context (const gchar *sig,
                                        param = DKIM_PARAM_DOMAIN;
                                        break;
                                case 'h':
-                                       param = DKIM_PARAM_HDRLIST;
+                                       if (type == RSPAMD_DKIM_ARC_SEAL) {
+                                               g_set_error (err,
+                                                               DKIM_ERROR,
+                                                               DKIM_SIGERROR_UNKNOWN,
+                                                               "ARC seal must NOT have h= tag");
+                                               state = DKIM_STATE_ERROR;
+                                               break;
+                                       }
+                                       else {
+                                               param = DKIM_PARAM_HDRLIST;
+                                       }
                                        break;
                                case 'i':
                                        if (type == RSPAMD_DKIM_NORMAL) {
@@ -731,7 +784,17 @@ rspamd_create_dkim_context (const gchar *sig,
                                break;
                        case 2:
                                if (tag[0] == 'b' && tag[1] == 'h') {
-                                       param = DKIM_PARAM_BODYHASH;
+                                       if (type == RSPAMD_DKIM_ARC_SEAL) {
+                                               g_set_error (err,
+                                                               DKIM_ERROR,
+                                                               DKIM_SIGERROR_UNKNOWN,
+                                                               "ARC seal must NOT have bh= tag");
+                                               state = DKIM_STATE_ERROR;
+                                               break;
+                                       }
+                                       else {
+                                               param = DKIM_PARAM_BODYHASH;
+                                       }
                                }
                                else {
                                        g_set_error (err,
@@ -809,6 +872,10 @@ rspamd_create_dkim_context (const gchar *sig,
                }
        }
 
+       if (type == RSPAMD_DKIM_ARC_SEAL) {
+               rspamd_dkim_add_arc_seal_headers (pool, &ctx->common);
+       }
+
        /* Now check validity of signature */
        if (ctx->b == NULL) {
                g_set_error (err,
@@ -817,7 +884,7 @@ rspamd_create_dkim_context (const gchar *sig,
                        "b parameter missing");
                return NULL;
        }
-       if (ctx->bh == NULL) {
+       if (ctx->common.type != RSPAMD_DKIM_ARC_SEAL && ctx->bh == NULL) {
                g_set_error (err,
                        DKIM_ERROR,
                        DKIM_SIGERROR_EMPTY_BH,
@@ -838,7 +905,7 @@ rspamd_create_dkim_context (const gchar *sig,
                        "selector parameter missing");
                return NULL;
        }
-       if (ctx->ver == 0) {
+       if (ctx->common.type == RSPAMD_DKIM_NORMAL && ctx->ver == 0) {
                g_set_error (err,
                        DKIM_ERROR,
                        DKIM_SIGERROR_EMPTY_V,
@@ -900,6 +967,15 @@ rspamd_create_dkim_context (const gchar *sig,
                return NULL;
        }
 
+       if (ctx->common.type != RSPAMD_DKIM_NORMAL && (ctx->common.idx == 0 ||
+                       ctx->common.idx > RSPAMD_DKIM_MAX_ARC_IDX)) {
+               g_set_error (err,
+                               DKIM_ERROR,
+                               DKIM_SIGERROR_UNKNOWN,
+                               "i parameter missing or invalid for ARC");
+               return NULL;
+       }
+
        /* Now create dns key to request further */
        taglen = strlen (ctx->domain) + strlen (ctx->selector) +
                sizeof (DKIM_DNSKEYNAME) + 2;
@@ -1805,7 +1881,7 @@ rspamd_dkim_canonize_header (struct rspamd_dkim_common_ctx *ctx,
                        /* We need to find our own signature and use it */
                        guint i;
 
-                       ar = g_hash_table_lookup (task->raw_headers, DKIM_SIGNHEADER);
+                       ar = g_hash_table_lookup (task->raw_headers, header_name);
 
                        if (ar) {
                                /* We need to find our own signature */
@@ -1873,10 +1949,14 @@ rspamd_dkim_check (rspamd_dkim_context_t *ctx,
                return DKIM_RECORD_ERROR;
        }
 
-       /* Start canonization of body part */
-       if (!rspamd_dkim_canonize_body (&ctx->common, body_start, body_end, FALSE)) {
-               return DKIM_RECORD_ERROR;
+       if (ctx->common.type != RSPAMD_DKIM_ARC_SEAL) {
+               /* Start canonization of body part */
+               if (!rspamd_dkim_canonize_body (&ctx->common, body_start, body_end,
+                               FALSE)) {
+                       return DKIM_RECORD_ERROR;
+               }
        }
+
        /* Now canonize headers */
        for (i = 0; i < ctx->common.hlist->len; i++) {
                dh = g_ptr_array_index (ctx->common.hlist, i);
@@ -1885,66 +1965,82 @@ rspamd_dkim_check (rspamd_dkim_context_t *ctx,
        }
 
        /* Canonize dkim signature */
-       rspamd_dkim_canonize_header (&ctx->common, task, DKIM_SIGNHEADER, 0,
-                       ctx->dkim_header, ctx->domain);
-
-       dlen = EVP_MD_CTX_size (ctx->common.body_hash);
-       /* Copy md_ctx to deal with broken CRLF at the end */
-       cpy_ctx = EVP_MD_CTX_create ();
-       EVP_MD_CTX_copy (cpy_ctx, ctx->common.body_hash);
-       EVP_DigestFinal_ex (cpy_ctx, raw_digest, NULL);
-
-       /* Check bh field */
-       if (memcmp (ctx->bh, raw_digest, ctx->bhlen) != 0) {
-               msg_debug_dkim ("bh value mismatch: %*xs versus %*xs, try add CRLF",
-                               dlen, ctx->bh,
-                               dlen, raw_digest);
-               /* Try add CRLF */
-#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
-               EVP_MD_CTX_cleanup (cpy_ctx);
-#else
-               EVP_MD_CTX_reset (cpy_ctx);
-#endif
+       switch (ctx->common.type) {
+       case RSPAMD_DKIM_NORMAL:
+               rspamd_dkim_canonize_header (&ctx->common, task, RSPAMD_DKIM_SIGNHEADER, 0,
+                               ctx->dkim_header, ctx->domain);
+               break;
+       case RSPAMD_DKIM_ARC_SIG:
+               rspamd_dkim_canonize_header (&ctx->common, task, RSPAMD_DKIM_ARC_SIGNHEADER, 0,
+                               ctx->dkim_header, ctx->domain);
+               break;
+       case RSPAMD_DKIM_ARC_SEAL:
+               rspamd_dkim_canonize_header (&ctx->common, task, RSPAMD_DKIM_ARC_SEALHEADER, 0,
+                               ctx->dkim_header, ctx->domain);
+               break;
+       }
+
+
+       if (ctx->common.type != RSPAMD_DKIM_ARC_SEAL) {
+               dlen = EVP_MD_CTX_size (ctx->common.body_hash);
+               /* Copy md_ctx to deal with broken CRLF at the end */
+               cpy_ctx = EVP_MD_CTX_create ();
                EVP_MD_CTX_copy (cpy_ctx, ctx->common.body_hash);
-               EVP_DigestUpdate (cpy_ctx, "\r\n", 2);
                EVP_DigestFinal_ex (cpy_ctx, raw_digest, NULL);
 
+               /* Check bh field */
                if (memcmp (ctx->bh, raw_digest, ctx->bhlen) != 0) {
-                       msg_debug_dkim ("bh value mismatch: %*xs versus %*xs, try add LF",
+                       msg_debug_dkim ("bh value mismatch: %*xs versus %*xs, try add CRLF",
                                        dlen, ctx->bh,
                                        dlen, raw_digest);
-
-                       /* Try add LF */
-       #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
+                       /* Try add CRLF */
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
                        EVP_MD_CTX_cleanup (cpy_ctx);
-       #else
+#else
                        EVP_MD_CTX_reset (cpy_ctx);
-       #endif
+#endif
                        EVP_MD_CTX_copy (cpy_ctx, ctx->common.body_hash);
-                       EVP_DigestUpdate (cpy_ctx, "\n", 1);
+                       EVP_DigestUpdate (cpy_ctx, "\r\n", 2);
                        EVP_DigestFinal_ex (cpy_ctx, raw_digest, NULL);
 
                        if (memcmp (ctx->bh, raw_digest, ctx->bhlen) != 0) {
-                               msg_debug_dkim ("bh value mismatch: %*xs versus %*xs",
+                               msg_debug_dkim (
+                                               "bh value mismatch: %*xs versus %*xs, try add LF",
                                                dlen, ctx->bh,
                                                dlen, raw_digest);
+
+                               /* Try add LF */
 #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
                                EVP_MD_CTX_cleanup (cpy_ctx);
 #else
                                EVP_MD_CTX_reset (cpy_ctx);
 #endif
-                               EVP_MD_CTX_destroy (cpy_ctx);
-                               return DKIM_REJECT;
+                               EVP_MD_CTX_copy (cpy_ctx, ctx->common.body_hash);
+                               EVP_DigestUpdate (cpy_ctx, "\n", 1);
+                               EVP_DigestFinal_ex (cpy_ctx, raw_digest, NULL);
+
+                               if (memcmp (ctx->bh, raw_digest, ctx->bhlen) != 0) {
+                                       msg_debug_dkim ("bh value mismatch: %*xs versus %*xs",
+                                                       dlen, ctx->bh,
+                                                       dlen, raw_digest);
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
+                                       EVP_MD_CTX_cleanup (cpy_ctx);
+#else
+                                       EVP_MD_CTX_reset (cpy_ctx);
+#endif
+                                       EVP_MD_CTX_destroy (cpy_ctx);
+                                       return DKIM_REJECT;
+                               }
                        }
                }
-       }
 
 #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
-       EVP_MD_CTX_cleanup (cpy_ctx);
+               EVP_MD_CTX_cleanup (cpy_ctx);
 #else
-       EVP_MD_CTX_reset (cpy_ctx);
+               EVP_MD_CTX_reset (cpy_ctx);
 #endif
-       EVP_MD_CTX_destroy (cpy_ctx);
+               EVP_MD_CTX_destroy (cpy_ctx);
+       }
 
        dlen = EVP_MD_CTX_size (ctx->common.headers_hash);
        EVP_DigestFinal_ex (ctx->common.headers_hash, raw_digest, NULL);
@@ -2212,9 +2308,15 @@ rspamd_create_dkim_sign_context (struct rspamd_task *task,
        nctx->common.body_canon_type = body_canon;
        nctx->common.type = type;
 
-       if (!rspamd_dkim_parse_hdrlist_common (&nctx->common, headers, strlen (headers),
-                       err)) {
-               return NULL;
+       if (type != RSPAMD_DKIM_ARC_SEAL) {
+               if (!rspamd_dkim_parse_hdrlist_common (&nctx->common, headers,
+                               strlen (headers),
+                               err)) {
+                       return NULL;
+               }
+       }
+       else {
+               rspamd_dkim_add_arc_seal_headers (task->task_pool, &nctx->common);
        }
 
        nctx->key = rspamd_dkim_sign_key_ref (priv_key);
@@ -2255,7 +2357,7 @@ rspamd_dkim_sign (struct rspamd_task *task,
 {
        GString *hdr;
        struct rspamd_dkim_header *dh;
-       const gchar *body_end, *body_start;
+       const gchar *body_end, *body_start, *hname;
        guchar raw_digest[EVP_MAX_MD_SIZE];
        gsize dlen;
        guint i, j;
@@ -2278,8 +2380,11 @@ rspamd_dkim_sign (struct rspamd_task *task,
        }
 
        /* Start canonization of body part */
-       if (!rspamd_dkim_canonize_body (&ctx->common, body_start, body_end, TRUE)) {
-               return NULL;
+       if (ctx->common.type != RSPAMD_DKIM_ARC_SEAL) {
+               if (!rspamd_dkim_canonize_body (&ctx->common, body_start, body_end,
+                               TRUE)) {
+                       return NULL;
+               }
        }
 
        hdr = g_string_sized_new (255);
@@ -2303,15 +2408,19 @@ rspamd_dkim_sign (struct rspamd_task *task,
        }
        else {
                /* Shouldn't be called for arc seal */
-               g_assert_not_reached ();
+               rspamd_printf_gstring (hdr, "i=%d; a=rsa-sha256; c=%s/%s; d=%s; s=%s; ",
+                               idx,
+                               domain, selector);
        }
 
        if (expire > 0) {
                rspamd_printf_gstring (hdr, "x=%t; ", expire);
        }
 
-       if (len > 0) {
-               rspamd_printf_gstring (hdr, "l=%z; ", len);
+       if (ctx->common.type != RSPAMD_DKIM_ARC_SEAL) {
+               if (len > 0) {
+                       rspamd_printf_gstring (hdr, "l=%z; ", len);
+               }
        }
 
        rspamd_printf_gstring (hdr, "t=%t; h=", time (NULL));
@@ -2333,17 +2442,34 @@ rspamd_dkim_sign (struct rspamd_task *task,
        /* 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);
+       if (ctx->common.type != RSPAMD_DKIM_ARC_SEAL) {
+               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);
+               b64_data = rspamd_encode_base64 (raw_digest, dlen, 0, NULL);
+               rspamd_printf_gstring (hdr, " bh=%s; b=", b64_data);
+               g_free (b64_data);
+       }
+       else {
+               rspamd_printf_gstring (hdr, " b=");
+       }
+
+       switch (ctx->common.type) {
+       case RSPAMD_DKIM_NORMAL:
+               hname = RSPAMD_DKIM_SIGNHEADER;
+               break;
+       case RSPAMD_DKIM_ARC_SIG:
+               hname = RSPAMD_DKIM_ARC_SIGNHEADER;
+               break;
+       case RSPAMD_DKIM_ARC_SEAL:
+               hname = RSPAMD_DKIM_ARC_SEALHEADER;
+               break;
+       }
 
        if (ctx->common.header_canon_type == DKIM_CANON_RELAXED) {
                if (!rspamd_dkim_canonize_header_relaxed (&ctx->common,
                                hdr->str,
-                               DKIM_SIGNHEADER,
+                               hname,
                                TRUE)) {
 
                        g_string_free (hdr, TRUE);
index 549a743f7f82438adc85c686991ab3193eb8022b..0a7eec21c77fb0be0777af43519fb9e0be3b47ab 100644 (file)
 
 /* Main types and definitions */
 
-#define DKIM_SIGNHEADER     "DKIM-Signature"
+#define RSPAMD_DKIM_SIGNHEADER     "DKIM-Signature"
+#define RSPAMD_DKIM_ARC_SIGNHEADER     "ARC-Message-Signature"
+#define RSPAMD_DKIM_ARC_AUTHHEADER     "ARC-Authentication-Results"
+#define RSPAMD_DKIM_ARC_SEALHEADER     "ARC-Seal"
 /* DKIM signature header */
 
 
index 517e54c83d77f8c36545b9ecd5b10d338f4f380f..e5c7759592d2cddf834737fc48fd81438514e73f 100644 (file)
@@ -924,7 +924,7 @@ dkim_symbol_callback (struct rspamd_task *task, void *unused)
 
        /* Now check if a message has its signature */
        hlist = rspamd_message_get_header_array (task,
-                       DKIM_SIGNHEADER,
+                       RSPAMD_DKIM_SIGNHEADER,
                        FALSE);
        if (hlist != NULL && hlist->len > 0) {
                msg_debug_task ("dkim signature found");