* PDKIM - a RFC4871 (DKIM) implementation
*
* Copyright (C) 2009 - 2016 Tom Kistner <tom@duncanthrax.net>
- * Copyright (C) 2016 Jeremy Harris <jgh@exim.org>
+ * Copyright (C) 2016 - 2018 Jeremy Harris <jgh@exim.org>
*
* http://duncanthrax.net/pdkim/
*
#ifndef DISABLE_DKIM /* entire file */
-#ifndef SUPPORT_TLS
-# error Need SUPPORT_TLS for DKIM
+#ifdef DISABLE_TLS
+# error Must not DISABLE_TLS, for DKIM
#endif
#include "crypt_ver.h"
-#ifdef RSA_OPENSSL
+#ifdef SIGN_OPENSSL
# include <openssl/rsa.h>
# include <openssl/ssl.h>
# include <openssl/err.h>
-#elif defined(RSA_GNUTLS)
+#elif defined(SIGN_GNUTLS)
# include <gnutls/gnutls.h>
# include <gnutls/x509.h>
#endif
#include "pdkim.h"
-#include "rsa.h"
+#include "signing.h"
#define PDKIM_SIGNATURE_VERSION "1"
-#define PDKIM_PUB_RECORD_VERSION "DKIM1"
+#define PDKIM_PUB_RECORD_VERSION US "DKIM1"
#define PDKIM_MAX_HEADER_LEN 65536
#define PDKIM_MAX_HEADERS 512
#define PDKIM_MAX_BODY_LINE_LEN 16384
#define PDKIM_DNS_TXT_MAX_NAMELEN 1024
-#define PDKIM_DEFAULT_SIGN_HEADERS "From:Sender:Reply-To:Subject:Date:"\
- "Message-ID:To:Cc:MIME-Version:Content-Type:"\
- "Content-Transfer-Encoding:Content-ID:"\
- "Content-Description:Resent-Date:Resent-From:"\
- "Resent-Sender:Resent-To:Resent-Cc:"\
- "Resent-Message-ID:In-Reply-To:References:"\
- "List-Id:List-Help:List-Unsubscribe:"\
- "List-Subscribe:List-Post:List-Owner:List-Archive"
/* -------------------------------------------------------------------------- */
struct pdkim_stringlist {
- char *value;
- int tag;
- void *next;
-};
-
-#define PDKIM_STR_ALLOC_FRAG 256
-struct pdkim_str {
- char *str;
- unsigned int len;
- unsigned int allocated;
+ uschar * value;
+ int tag;
+ void * next;
};
/* -------------------------------------------------------------------------- */
/* A bunch of list constants */
-const char *pdkim_querymethods[] = {
- "dns/txt",
- NULL
-};
-const char *pdkim_algos[] = {
- "rsa-sha256",
- "rsa-sha1",
+const uschar * pdkim_querymethods[] = {
+ US"dns/txt",
NULL
};
-const char *pdkim_canons[] = {
- "simple",
- "relaxed",
+const uschar * pdkim_canons[] = {
+ US"simple",
+ US"relaxed",
NULL
};
-const char *pdkim_hashes[] = {
- "sha256",
- "sha1",
- NULL
+
+const pdkim_hashtype pdkim_hashes[] = {
+ { US"sha1", HASH_SHA1 },
+ { US"sha256", HASH_SHA2_256 },
+ { US"sha512", HASH_SHA2_512 }
};
-const char *pdkim_keytypes[] = {
- "rsa",
- NULL
+
+const uschar * pdkim_keytypes[] = {
+ [KEYTYPE_RSA] = US"rsa",
+#ifdef SIGN_HAVE_ED25519
+ [KEYTYPE_ED25519] = US"ed25519", /* Works for 3.6.0 GnuTLS, OpenSSL 1.1.1 */
+#endif
+
+#ifdef notyet_EC_dkim_extensions /* https://tools.ietf.org/html/draft-srose-dkim-ecc-00 */
+ US"eccp256",
+ US"eccp348",
+ US"ed448",
+#endif
};
typedef struct pdkim_combined_canon_entry {
- const char *str;
- int canon_headers;
- int canon_body;
+ const uschar * str;
+ int canon_headers;
+ int canon_body;
} pdkim_combined_canon_entry;
pdkim_combined_canon_entry pdkim_combined_canons[] = {
- { "simple/simple", PDKIM_CANON_SIMPLE, PDKIM_CANON_SIMPLE },
- { "simple/relaxed", PDKIM_CANON_SIMPLE, PDKIM_CANON_RELAXED },
- { "relaxed/simple", PDKIM_CANON_RELAXED, PDKIM_CANON_SIMPLE },
- { "relaxed/relaxed", PDKIM_CANON_RELAXED, PDKIM_CANON_RELAXED },
- { "simple", PDKIM_CANON_SIMPLE, PDKIM_CANON_SIMPLE },
- { "relaxed", PDKIM_CANON_RELAXED, PDKIM_CANON_SIMPLE },
- { NULL, 0, 0 }
+ { US"simple/simple", PDKIM_CANON_SIMPLE, PDKIM_CANON_SIMPLE },
+ { US"simple/relaxed", PDKIM_CANON_SIMPLE, PDKIM_CANON_RELAXED },
+ { US"relaxed/simple", PDKIM_CANON_RELAXED, PDKIM_CANON_SIMPLE },
+ { US"relaxed/relaxed", PDKIM_CANON_RELAXED, PDKIM_CANON_RELAXED },
+ { US"simple", PDKIM_CANON_SIMPLE, PDKIM_CANON_SIMPLE },
+ { US"relaxed", PDKIM_CANON_RELAXED, PDKIM_CANON_SIMPLE },
+ { NULL, 0, 0 }
};
+static blob lineending = {.data = US"\r\n", .len = 2};
+
/* -------------------------------------------------------------------------- */
+uschar *
+dkim_sig_to_a_tag(const pdkim_signature * sig)
+{
+if ( sig->keytype < 0 || sig->keytype > nelem(pdkim_keytypes)
+ || sig->hashtype < 0 || sig->hashtype > nelem(pdkim_hashes))
+ return US"err";
+return string_sprintf("%s-%s",
+ pdkim_keytypes[sig->keytype], pdkim_hashes[sig->hashtype].dkim_hashname);
+}
+
+
+static int
+pdkim_keyname_to_keytype(const uschar * s)
+{
+for (int i = 0; i < nelem(pdkim_keytypes); i++)
+ if (Ustrcmp(s, pdkim_keytypes[i]) == 0) return i;
+return -1;
+}
+
+int
+pdkim_hashname_to_hashtype(const uschar * s, unsigned len)
+{
+if (!len) len = Ustrlen(s);
+for (int i = 0; i < nelem(pdkim_hashes); i++)
+ if (Ustrncmp(s, pdkim_hashes[i].dkim_hashname, len) == 0)
+ return i;
+return -1;
+}
+
+void
+pdkim_cstring_to_canons(const uschar * s, unsigned len,
+ int * canon_head, int * canon_body)
+{
+if (!len) len = Ustrlen(s);
+for (int i = 0; pdkim_combined_canons[i].str; i++)
+ if ( Ustrncmp(s, pdkim_combined_canons[i].str, len) == 0
+ && len == Ustrlen(pdkim_combined_canons[i].str))
+ {
+ *canon_head = pdkim_combined_canons[i].canon_headers;
+ *canon_body = pdkim_combined_canons[i].canon_body;
+ break;
+ }
+}
+
+
const char *
pdkim_verify_status_str(int status)
{
- switch(status) {
- case PDKIM_VERIFY_NONE: return "PDKIM_VERIFY_NONE";
- case PDKIM_VERIFY_INVALID: return "PDKIM_VERIFY_INVALID";
- case PDKIM_VERIFY_FAIL: return "PDKIM_VERIFY_FAIL";
- case PDKIM_VERIFY_PASS: return "PDKIM_VERIFY_PASS";
- default: return "PDKIM_VERIFY_UNKNOWN";
+switch(status)
+ {
+ case PDKIM_VERIFY_NONE: return "PDKIM_VERIFY_NONE";
+ case PDKIM_VERIFY_INVALID: return "PDKIM_VERIFY_INVALID";
+ case PDKIM_VERIFY_FAIL: return "PDKIM_VERIFY_FAIL";
+ case PDKIM_VERIFY_PASS: return "PDKIM_VERIFY_PASS";
+ default: return "PDKIM_VERIFY_UNKNOWN";
}
}
const char *
pdkim_verify_ext_status_str(int ext_status)
{
- switch(ext_status) {
- case PDKIM_VERIFY_FAIL_BODY: return "PDKIM_VERIFY_FAIL_BODY";
- case PDKIM_VERIFY_FAIL_MESSAGE: return "PDKIM_VERIFY_FAIL_MESSAGE";
- case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE: return "PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE";
- case PDKIM_VERIFY_INVALID_BUFFER_SIZE: return "PDKIM_VERIFY_INVALID_BUFFER_SIZE";
- case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD: return "PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD";
- case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT: return "PDKIM_VERIFY_INVALID_PUBKEY_IMPORT";
- default: return "PDKIM_VERIFY_UNKNOWN";
+switch(ext_status)
+ {
+ case PDKIM_VERIFY_FAIL_BODY: return "PDKIM_VERIFY_FAIL_BODY";
+ case PDKIM_VERIFY_FAIL_MESSAGE: return "PDKIM_VERIFY_FAIL_MESSAGE";
+ case PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH: return "PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH";
+ case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE: return "PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE";
+ case PDKIM_VERIFY_INVALID_BUFFER_SIZE: return "PDKIM_VERIFY_INVALID_BUFFER_SIZE";
+ case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD: return "PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD";
+ case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT: return "PDKIM_VERIFY_INVALID_PUBKEY_IMPORT";
+ case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR: return "PDKIM_VERIFY_INVALID_SIGNATURE_ERROR";
+ case PDKIM_VERIFY_INVALID_DKIM_VERSION: return "PDKIM_VERIFY_INVALID_DKIM_VERSION";
+ default: return "PDKIM_VERIFY_UNKNOWN";
+ }
+}
+
+const uschar *
+pdkim_errstr(int status)
+{
+switch(status)
+ {
+ case PDKIM_OK: return US"OK";
+ case PDKIM_FAIL: return US"FAIL";
+ case PDKIM_ERR_RSA_PRIVKEY: return US"PRIVKEY";
+ case PDKIM_ERR_RSA_SIGNING: return US"SIGNING";
+ case PDKIM_ERR_LONG_LINE: return US"LONG_LINE";
+ case PDKIM_ERR_BUFFER_TOO_SMALL: return US"BUFFER_TOO_SMALL";
+ case PDKIM_ERR_EXCESS_SIGS: return US"EXCESS_SIGS";
+ case PDKIM_SIGN_PRIVKEY_WRAP: return US"PRIVKEY_WRAP";
+ case PDKIM_SIGN_PRIVKEY_B64D: return US"PRIVKEY_B64D";
+ default: return US"(unknown)";
}
}
/* -------------------------------------------------------------------------- */
/* Print debugging functions */
-static void
+void
pdkim_quoteprint(const uschar *data, int len)
{
-int i;
-for (i = 0; i < len; i++)
+for (int i = 0; i < len; i++)
{
const int c = data[i];
switch (c)
debug_printf("\n");
}
-static void
+void
pdkim_hexprint(const uschar *data, int len)
{
-int i;
-for (i = 0 ; i < len; i++) debug_printf("%02x", data[i]);
+if (data) for (int i = 0 ; i < len; i++) debug_printf("%02x", data[i]);
+else debug_printf("<NULL>");
debug_printf("\n");
}
-/* SSS probably want to keep the "stringlist" notion */
-
static pdkim_stringlist *
-pdkim_prepend_stringlist(pdkim_stringlist *base, char *str)
+pdkim_prepend_stringlist(pdkim_stringlist * base, const uschar * str)
{
-pdkim_stringlist *new_entry = malloc(sizeof(pdkim_stringlist));
+pdkim_stringlist * new_entry = store_get(sizeof(pdkim_stringlist), FALSE);
-if (!new_entry) return NULL;
memset(new_entry, 0, sizeof(pdkim_stringlist));
-if (!(new_entry->value = strdup(str))) return NULL;
+new_entry->value = string_copy(str);
if (base) new_entry->next = base;
return new_entry;
}
-/* -------------------------------------------------------------------------- */
-/* A small "growing string" implementation to escape malloc/realloc hell */
-/* String package: should be replaced by Exim standard ones */
-/* SSS Ustrcpy */
-
-static pdkim_str *
-pdkim_strnew (const char *cstr)
-{
-unsigned int len = cstr ? strlen(cstr) : 0;
-pdkim_str *p = malloc(sizeof(pdkim_str));
-
-if (!p) return NULL;
-memset(p, 0, sizeof(pdkim_str));
-if (!(p->str = malloc(len+1)))
- {
- free(p);
- return NULL;
- }
-p->allocated = len+1;
-p->len = len;
-if (cstr)
- strcpy(p->str, cstr);
-else
- p->str[p->len] = '\0';
-return p;
-}
-
-
-/*SSS Ustrncat */
-
-static char *
-pdkim_strncat(pdkim_str *str, const char *data, int len)
-{
-if ((str->allocated - str->len) < (len+1))
- {
- /* Extend the buffer */
- int num_frags = ((len+1)/PDKIM_STR_ALLOC_FRAG)+1;
- char *n = realloc(str->str,
- (str->allocated+(num_frags*PDKIM_STR_ALLOC_FRAG)));
- if (n == NULL) return NULL;
- str->str = n;
- str->allocated += (num_frags*PDKIM_STR_ALLOC_FRAG);
- }
-strncpy(&(str->str[str->len]), data, len);
-str->len += len;
-str->str[str->len] = '\0';
-return str->str;
-}
-
-
-/* SSS Ustrcat */
-
-static char *
-pdkim_strcat(pdkim_str *str, const char *cstr)
-{
-return pdkim_strncat(str, cstr, strlen(cstr));
-}
-
-
/* Trim whitespace fore & aft */
-static char *
-pdkim_strtrim(pdkim_str *str)
-{
-char *p = str->str;
-char *q = str->str;
-while (*p == '\t' || *p == ' ') p++; /* skip whitespace */
-while (*p) {*q = *p; q++; p++;} /* dump the leading whitespace */
-*q = '\0';
-while (q != str->str && ( (*q == '\0') || (*q == '\t') || (*q == ' ') ) )
- { /* dump trailing whitespace */
- *q = '\0';
- q--;
- }
-str->len = strlen(str->str);
-return str->str;
-}
-
-
-
-static char *
-pdkim_strclear(pdkim_str *str)
-{
-str->str[0] = '\0';
-str->len = 0;
-return str->str;
-}
-
-
-
static void
-pdkim_strfree(pdkim_str *str)
+pdkim_strtrim(gstring * str)
{
-if (!str) return;
-if (str->str) free(str->str);
-free(str);
-}
-
-
-static void
-pdkim_stringlist_free(pdkim_stringlist * e)
-{
-while(e)
- {
- pdkim_stringlist * c = e;
- if (e->value) free(e->value);
- e = e->next;
- free(c);
- }
-}
+uschar * p = str->s;
+uschar * q;
+while (*p == '\t' || *p == ' ') /* dump the leading whitespace */
+ { str->size--; str->ptr--; str->s++; }
+while ( str->ptr > 0
+ && ((q = str->s + str->ptr - 1), (*q == '\t' || *q == ' '))
+ )
+ str->ptr--; /* dump trailing whitespace */
-/* -------------------------------------------------------------------------- */
-
-static void
-pdkim_free_pubkey(pdkim_pubkey *pub)
-{
-if (pub)
- {
- if (pub->version ) free(pub->version);
- if (pub->granularity) free(pub->granularity);
- if (pub->hashes ) free(pub->hashes);
- if (pub->keytype ) free(pub->keytype);
- if (pub->srvtype ) free(pub->srvtype);
- if (pub->notes ) free(pub->notes);
- free(pub);
- }
+(void) string_from_gstring(str);
}
-/* -------------------------------------------------------------------------- */
-
-static void
-pdkim_free_sig(pdkim_signature *sig)
-{
-if (sig)
- {
- pdkim_signature *next = (pdkim_signature *)sig->next;
-
- pdkim_stringlist_free(sig->headers);
- if (sig->selector ) free(sig->selector);
- if (sig->domain ) free(sig->domain);
- if (sig->identity ) free(sig->identity);
- if (sig->copiedheaders ) free(sig->copiedheaders);
- if (sig->rsa_privkey ) free(sig->rsa_privkey);
- if (sig->sign_headers ) free(sig->sign_headers);
- if (sig->signature_header) free(sig->signature_header);
-
- if (sig->pubkey) pdkim_free_pubkey(sig->pubkey);
-
- free(sig);
- if (next) pdkim_free_sig(next);
- }
-}
-
/* -------------------------------------------------------------------------- */
DLLEXPORT void
pdkim_free_ctx(pdkim_ctx *ctx)
{
-if (ctx)
- {
- pdkim_stringlist_free(ctx->headers);
- pdkim_free_sig(ctx->sig);
- pdkim_strfree(ctx->cur_header);
- free(ctx);
- }
}
/* -------------------------------------------------------------------------- */
/* Matches the name of the passed raw "header" against
the passed colon-separated "tick", and invalidates
- the entry in tick. Returns OK or fail-code */
-/*XXX might be safer done using a pdkim_stringlist for "tick" */
+ the entry in tick. Entries can be prefixed for multi- or over-signing,
+ in which case do not invalidate.
+
+ Returns OK for a match, or fail-code
+*/
static int
-header_name_match(const char * header, char * tick)
+header_name_match(const uschar * header, uschar * tick)
{
-char *hname;
-char *lcopy;
-char *p;
-char *q;
-int rc = PDKIM_FAIL;
+const uschar * ticklist = tick;
+int sep = ':';
+BOOL multisign;
+uschar * hname, * p, * ele;
+uschar * hcolon = Ustrchr(header, ':'); /* Get header name */
-/* Get header name */
-char *hcolon = strchr(header, ':');
+if (!hcolon)
+ return PDKIM_FAIL; /* This isn't a header */
-if (!hcolon) return rc; /* This isn't a header */
+/* if we had strncmpic() we wouldn't need this copy */
+hname = string_copyn(header, hcolon-header);
-if (!(hname = malloc((hcolon-header)+1)))
- return PDKIM_ERR_OOM;
-memset(hname, 0, (hcolon-header)+1);
-strncpy(hname, header, (hcolon-header));
-
-/* Copy tick-off list locally, so we can punch zeroes into it */
-if (!(lcopy = strdup(tick)))
+while (p = US ticklist, ele = string_nextinlist(&ticklist, &sep, NULL, 0))
{
- free(hname);
- return PDKIM_ERR_OOM;
- }
-p = lcopy;
-q = strchr(p, ':');
-while (q)
+ switch (*ele)
{
- *q = '\0';
+ case '=': case '+': multisign = TRUE; ele++; break;
+ default: multisign = FALSE; break;
+ }
- if (strcasecmp(p, hname) == 0)
+ if (strcmpic(ele, hname) == 0)
{
- rc = PDKIM_OK;
- /* Invalidate header name instance in tick-off list */
- tick[p-lcopy] = '_';
- goto BAIL;
+ if (!multisign)
+ *p = '_'; /* Invalidate this header name instance in tick-off list */
+ return PDKIM_OK;
}
-
- p = q+1;
- q = strchr(p, ':');
- }
-
-if (strcasecmp(p, hname) == 0)
- {
- rc = PDKIM_OK;
- /* Invalidate header name instance in tick-off list */
- tick[p-lcopy] = '_';
}
-
-BAIL:
-free(hname);
-free(lcopy);
-return rc;
+return PDKIM_FAIL;
}
/* -------------------------------------------------------------------------- */
-/* Performs "relaxed" canonicalization of a header. The returned pointer needs
- to be free()d. */
+/* Performs "relaxed" canonicalization of a header. */
-static char *
-pdkim_relax_header (char *header, int crlf)
+uschar *
+pdkim_relax_header_n(const uschar * header, int len, BOOL append_crlf)
{
BOOL past_field_name = FALSE;
BOOL seen_wsp = FALSE;
-char *p;
-char *q;
-char *relaxed = malloc(strlen(header)+3);
+uschar * relaxed = store_get(len+3, TRUE); /* tainted */
+uschar * q = relaxed;
-if (!relaxed) return NULL;
-
-q = relaxed;
-for (p = header; *p != '\0'; p++)
+for (const uschar * p = header; p - header < len; p++)
{
- int c = *p;
- /* Ignore CR & LF */
- if (c == '\r' || c == '\n')
+ uschar c = *p;
+
+ if (c == '\r' || c == '\n') /* Ignore CR & LF */
continue;
if (c == '\t' || c == ' ')
{
else
if (!past_field_name && c == ':')
{
- if (seen_wsp) q--; /* This removes WSP before the colon */
- seen_wsp = TRUE; /* This removes WSP after the colon */
+ if (seen_wsp) q--; /* This removes WSP immediately before the colon */
+ seen_wsp = TRUE; /* This removes WSP immediately after the colon */
past_field_name = TRUE;
}
else
}
if (q > relaxed && q[-1] == ' ') q--; /* Squash eventual trailing SP */
-*q = '\0';
-if (crlf) strcat(relaxed, "\r\n");
+if (append_crlf) { *q++ = '\r'; *q++ = '\n'; }
+*q = '\0';
return relaxed;
}
+uschar *
+pdkim_relax_header(const uschar * header, BOOL append_crlf)
+{
+return pdkim_relax_header_n(header, Ustrlen(header), append_crlf);
+}
+
+
/* -------------------------------------------------------------------------- */
#define PDKIM_QP_ERROR_DECODE -1
-static char *
-pdkim_decode_qp_char(char *qp_p, int *c)
+static const uschar *
+pdkim_decode_qp_char(const uschar *qp_p, int *c)
{
-char *initial_pos = qp_p;
+const uschar *initial_pos = qp_p;
/* Advance one char */
qp_p++;
/* -------------------------------------------------------------------------- */
-static char *
-pdkim_decode_qp(char *str)
+static uschar *
+pdkim_decode_qp(const uschar * str)
{
int nchar = 0;
-char *q;
-char *p = str;
-char *n = malloc(strlen(p)+1);
-
-if (!n) return NULL;
+uschar * q;
+const uschar * p = str;
+uschar * n = store_get(Ustrlen(str)+1, TRUE);
*n = '\0';
q = n;
-while (*p != '\0')
+while (*p)
{
if (*p == '=')
{
/* -------------------------------------------------------------------------- */
-static void
-pdkim_decode_base64(uschar *str, blob * b)
+void
+pdkim_decode_base64(const uschar * str, blob * b)
{
-int dlen;
-dlen = b64decode(str, &b->data);
+int dlen = b64decode(str, &b->data);
if (dlen < 0) b->data = NULL;
b->len = dlen;
}
-/* -------------------------------------------------------------------------- */
-
-static char *
+uschar *
pdkim_encode_base64(blob * b)
{
-char * ret;
-int old_pool = store_pool;
-
-store_pool = POOL_PERM;
-ret = CS b64encode(b->data, b->len);
-store_pool = old_pool;
-return ret;
+return b64encode(CUS b->data, b->len);
}
#define PDKIM_HDR_VALUE 2
static pdkim_signature *
-pdkim_parse_sig_header(pdkim_ctx *ctx, char *raw_hdr)
+pdkim_parse_sig_header(pdkim_ctx * ctx, uschar * raw_hdr)
{
-pdkim_signature *sig ;
-char *p, *q;
-pdkim_str *cur_tag = NULL;
-pdkim_str *cur_val = NULL;
+pdkim_signature * sig;
+uschar *q;
+gstring * cur_tag = NULL;
+gstring * cur_val = NULL;
BOOL past_hname = FALSE;
BOOL in_b_val = FALSE;
int where = PDKIM_HDR_LIMBO;
-int i;
-int old_pool = store_pool;
-
-/* There is a store-reset between header & body reception
-so cannot use the main pool. Any allocs done by Exim
-memory-handling must use the perm pool. */
-store_pool = POOL_PERM;
-
-if (!(sig = malloc(sizeof(pdkim_signature)))) return NULL;
+sig = store_get(sizeof(pdkim_signature), FALSE);
memset(sig, 0, sizeof(pdkim_signature));
sig->bodylength = -1;
-if (!(sig->rawsig_no_b_val = malloc(strlen(raw_hdr)+1)))
- {
- free(sig);
- return NULL;
- }
+/* Set so invalid/missing data error display is accurate */
+sig->version = 0;
+sig->keytype = -1;
+sig->hashtype = -1;
-q = sig->rawsig_no_b_val;
+q = sig->rawsig_no_b_val = store_get(Ustrlen(raw_hdr)+1, TRUE); /* tainted */
-for (p = raw_hdr; ; p++)
+for (uschar * p = raw_hdr; ; p++)
{
char c = *p;
if (where == PDKIM_HDR_TAG)
{
- if (!cur_tag)
- cur_tag = pdkim_strnew(NULL);
-
if (c >= 'a' && c <= 'z')
- pdkim_strncat(cur_tag, p, 1);
+ cur_tag = string_catn(cur_tag, p, 1);
if (c == '=')
{
- if (strcmp(cur_tag->str, "b") == 0)
+ if (Ustrcmp(string_from_gstring(cur_tag), "b") == 0)
{
- *q = '='; q++;
+ *q++ = '=';
in_b_val = TRUE;
}
where = PDKIM_HDR_VALUE;
if (where == PDKIM_HDR_VALUE)
{
- if (!cur_val)
- cur_val = pdkim_strnew(NULL);
-
if (c == '\r' || c == '\n' || c == ' ' || c == '\t')
goto NEXT_CHAR;
if (c == ';' || c == '\0')
{
- if (cur_tag->len > 0)
+ /* We must have both tag and value, and tags must be one char except
+ for the possibility of "bh". */
+
+ if ( cur_tag && cur_val
+ && (cur_tag->ptr == 1 || *cur_tag->s == 'b')
+ )
{
+ (void) string_from_gstring(cur_val);
pdkim_strtrim(cur_val);
- DEBUG(D_acl) debug_printf(" %s=%s\n", cur_tag->str, cur_val->str);
+ DEBUG(D_acl) debug_printf(" %s=%s\n", cur_tag->s, cur_val->s);
- switch (cur_tag->str[0])
+ switch (*cur_tag->s)
{
- case 'b':
- if (cur_tag->str[1] == 'h')
- pdkim_decode_base64(US cur_val->str, &sig->bodyhash);
- else
- pdkim_decode_base64(US cur_val->str, &sig->sigdata);
+ case 'b': /* sig-data or body-hash */
+ switch (cur_tag->s[1])
+ {
+ case '\0': pdkim_decode_base64(cur_val->s, &sig->sighash); break;
+ case 'h': if (cur_tag->ptr == 2)
+ pdkim_decode_base64(cur_val->s, &sig->bodyhash);
+ break;
+ default: break;
+ }
break;
- case 'v':
+ case 'v': /* version */
/* We only support version 1, and that is currently the
only version there is. */
- if (strcmp(cur_val->str, PDKIM_SIGNATURE_VERSION) == 0)
- sig->version = 1;
- break;
- case 'a':
- for (i = 0; pdkim_algos[i]; i++)
- if (strcmp(cur_val->str, pdkim_algos[i]) == 0)
- {
- sig->algo = i;
- break;
- }
+ sig->version =
+ Ustrcmp(cur_val->s, PDKIM_SIGNATURE_VERSION) == 0 ? 1 : -1;
break;
- case 'c':
- for (i = 0; pdkim_combined_canons[i].str; i++)
- if (strcmp(cur_val->str, pdkim_combined_canons[i].str) == 0)
- {
- sig->canon_headers = pdkim_combined_canons[i].canon_headers;
- sig->canon_body = pdkim_combined_canons[i].canon_body;
- break;
- }
+ case 'a': /* algorithm */
+ {
+ const uschar * list = cur_val->s;
+ int sep = '-';
+ uschar * elem;
+
+ if ((elem = string_nextinlist(&list, &sep, NULL, 0)))
+ sig->keytype = pdkim_keyname_to_keytype(elem);
+ if ((elem = string_nextinlist(&list, &sep, NULL, 0)))
+ for (int i = 0; i < nelem(pdkim_hashes); i++)
+ if (Ustrcmp(elem, pdkim_hashes[i].dkim_hashname) == 0)
+ { sig->hashtype = i; break; }
+ }
+
+ case 'c': /* canonicalization */
+ pdkim_cstring_to_canons(cur_val->s, 0,
+ &sig->canon_headers, &sig->canon_body);
break;
- case 'q':
- for (i = 0; pdkim_querymethods[i]; i++)
- if (strcmp(cur_val->str, pdkim_querymethods[i]) == 0)
+ case 'q': /* Query method (for pubkey)*/
+ for (int i = 0; pdkim_querymethods[i]; i++)
+ if (Ustrcmp(cur_val->s, pdkim_querymethods[i]) == 0)
{
- sig->querymethod = i;
+ sig->querymethod = i; /* we never actually use this */
break;
}
break;
- case 's':
- sig->selector = strdup(cur_val->str); break;
- case 'd':
- sig->domain = strdup(cur_val->str); break;
- case 'i':
- sig->identity = pdkim_decode_qp(cur_val->str); break;
- case 't':
- sig->created = strtoul(cur_val->str, NULL, 10); break;
- case 'x':
- sig->expires = strtoul(cur_val->str, NULL, 10); break;
- case 'l':
- sig->bodylength = strtol(cur_val->str, NULL, 10); break;
- case 'h':
- sig->headernames = string_copy(US cur_val->str); break;
- case 'z':
- sig->copiedheaders = pdkim_decode_qp(cur_val->str); break;
+ case 's': /* Selector */
+ sig->selector = string_copyn(cur_val->s, cur_val->ptr); break;
+ case 'd': /* SDID */
+ sig->domain = string_copyn(cur_val->s, cur_val->ptr); break;
+ case 'i': /* AUID */
+ sig->identity = pdkim_decode_qp(cur_val->s); break;
+ case 't': /* Timestamp */
+ sig->created = strtoul(CS cur_val->s, NULL, 10); break;
+ case 'x': /* Expiration */
+ sig->expires = strtoul(CS cur_val->s, NULL, 10); break;
+ case 'l': /* Body length count */
+ sig->bodylength = strtol(CS cur_val->s, NULL, 10); break;
+ case 'h': /* signed header fields */
+ sig->headernames = string_copyn(cur_val->s, cur_val->ptr); break;
+ case 'z': /* Copied headfields */
+ sig->copiedheaders = pdkim_decode_qp(cur_val->s); break;
+/*XXX draft-ietf-dcrup-dkim-crypto-05 would need 'p' tag support
+for rsafp signatures. But later discussion is dropping those. */
default:
DEBUG(D_acl) debug_printf(" Unknown tag encountered\n");
break;
}
}
- pdkim_strclear(cur_tag);
- pdkim_strclear(cur_val);
+ cur_tag = cur_val = NULL;
in_b_val = FALSE;
where = PDKIM_HDR_LIMBO;
}
else
- pdkim_strncat(cur_val, p, 1);
+ cur_val = string_catn(cur_val, p, 1);
}
NEXT_CHAR:
*q++ = c;
}
-store_pool = old_pool;
-
-/* Make sure the most important bits are there. */
-if (!(sig->domain && (*(sig->domain) != '\0') &&
- sig->selector && (*(sig->selector) != '\0') &&
- sig->headernames && (*(sig->headernames) != '\0') &&
- sig->version))
- {
- pdkim_free_sig(sig);
+if (sig->keytype < 0 || sig->hashtype < 0) /* Cannot verify this signature */
return NULL;
- }
*q = '\0';
/* Chomp raw header. The final newline must not be added to the signature. */
-q--;
-while (q > sig->rawsig_no_b_val && (*q == '\r' || *q == '\n'))
- *q = '\0'; q--; /*XXX questionable code layout; possible bug */
+while (--q > sig->rawsig_no_b_val && (*q == '\r' || *q == '\n'))
+ *q = '\0';
DEBUG(D_acl)
{
debug_printf(
- "PDKIM >> Raw signature w/o b= tag value >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
- pdkim_quoteprint(US sig->rawsig_no_b_val, strlen(sig->rawsig_no_b_val));
+ "DKIM >> Raw signature w/o b= tag value >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
+ pdkim_quoteprint(US sig->rawsig_no_b_val, Ustrlen(sig->rawsig_no_b_val));
debug_printf(
- "PDKIM >> Sig size: %4u bits\n", (unsigned) sig->sigdata.len*8);
+ "DKIM >> Sig size: %4u bits\n", (unsigned) sig->sighash.len*8);
debug_printf(
- "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
}
-exim_sha_init(&sig->body_hash, sig->algo == PDKIM_ALGO_RSA_SHA1);
+if (!pdkim_set_sig_bodyhash(ctx, sig))
+ return NULL;
+
return sig;
}
/* -------------------------------------------------------------------------- */
-static pdkim_pubkey *
-pdkim_parse_pubkey_record(pdkim_ctx *ctx, char *raw_record)
+pdkim_pubkey *
+pdkim_parse_pubkey_record(const uschar *raw_record)
{
-pdkim_pubkey *pub;
-char *p;
-pdkim_str *cur_tag = NULL;
-pdkim_str *cur_val = NULL;
-int where = PDKIM_HDR_LIMBO;
+const uschar * ele;
+int sep = ';';
+pdkim_pubkey * pub;
-if (!(pub = malloc(sizeof(pdkim_pubkey)))) return NULL;
+pub = store_get(sizeof(pdkim_pubkey), TRUE); /* tainted */
memset(pub, 0, sizeof(pdkim_pubkey));
-for (p = raw_record; ; p++)
+while ((ele = string_nextinlist(&raw_record, &sep, NULL, 0)))
{
- char c = *p;
-
- /* Ignore FWS */
- if (c == '\r' || c == '\n')
- goto NEXT_CHAR;
-
- if (where == PDKIM_HDR_LIMBO)
- {
- /* In limbo, just wait for a tag-char to appear */
- if (!(c >= 'a' && c <= 'z'))
- goto NEXT_CHAR;
-
- where = PDKIM_HDR_TAG;
- }
-
- if (where == PDKIM_HDR_TAG)
- {
- if (!cur_tag)
- cur_tag = pdkim_strnew(NULL);
-
- if (c >= 'a' && c <= 'z')
- pdkim_strncat(cur_tag, p, 1);
-
- if (c == '=')
- {
- where = PDKIM_HDR_VALUE;
- goto NEXT_CHAR;
- }
- }
+ const uschar * val;
- if (where == PDKIM_HDR_VALUE)
+ if ((val = Ustrchr(ele, '=')))
{
- if (!cur_val)
- cur_val = pdkim_strnew(NULL);
+ int taglen = val++ - ele;
- if (c == ';' || c == '\0')
+ DEBUG(D_acl) debug_printf(" %.*s=%s\n", taglen, ele, val);
+ switch (ele[0])
{
- if (cur_tag->len > 0)
- {
- pdkim_strtrim(cur_val);
- DEBUG(D_acl) debug_printf(" %s=%s\n", cur_tag->str, cur_val->str);
-
- switch (cur_tag->str[0])
- {
- case 'v':
- /* This tag isn't evaluated because:
- - We only support version DKIM1.
- - Which is the default for this value (set below)
- - Other versions are currently not specified. */
- break;
- case 'h':
- pub->hashes = strdup(cur_val->str); break;
- case 'g':
- pub->granularity = strdup(cur_val->str); break;
- case 'n':
- pub->notes = pdkim_decode_qp(cur_val->str); break;
- case 'p':
- pdkim_decode_base64(US cur_val->str, &pub->key);
- break;
- case 'k':
- pub->hashes = strdup(cur_val->str); break;
- case 's':
- pub->srvtype = strdup(cur_val->str); break;
- case 't':
- if (strchr(cur_val->str, 'y') != NULL) pub->testing = 1;
- if (strchr(cur_val->str, 's') != NULL) pub->no_subdomaining = 1;
- break;
- default:
- DEBUG(D_acl) debug_printf(" Unknown tag encountered\n");
- break;
- }
- }
- pdkim_strclear(cur_tag);
- pdkim_strclear(cur_val);
- where = PDKIM_HDR_LIMBO;
+ case 'v': pub->version = val; break;
+ case 'h': pub->hashes = val; break;
+ case 'k': pub->keytype = val; break;
+ case 'g': pub->granularity = val; break;
+ case 'n': pub->notes = pdkim_decode_qp(val); break;
+ case 'p': pdkim_decode_base64(val, &pub->key); break;
+ case 's': pub->srvtype = val; break;
+ case 't': if (Ustrchr(val, 'y')) pub->testing = 1;
+ if (Ustrchr(val, 's')) pub->no_subdomaining = 1;
+ break;
+ default: DEBUG(D_acl) debug_printf(" Unknown tag encountered\n"); break;
}
- else
- pdkim_strncat(cur_val, p, 1);
}
-
-NEXT_CHAR:
- if (c == '\0') break;
}
/* Set fallback defaults */
-if (!pub->version ) pub->version = strdup(PDKIM_PUB_RECORD_VERSION);
-if (!pub->granularity) pub->granularity = strdup("*");
-if (!pub->keytype ) pub->keytype = strdup("rsa");
-if (!pub->srvtype ) pub->srvtype = strdup("*");
+if (!pub->version)
+ pub->version = string_copy(PDKIM_PUB_RECORD_VERSION);
+else if (Ustrcmp(pub->version, PDKIM_PUB_RECORD_VERSION) != 0)
+ {
+ DEBUG(D_acl) debug_printf(" Bad v= field\n");
+ return NULL;
+ }
+
+if (!pub->granularity) pub->granularity = US"*";
+if (!pub->keytype ) pub->keytype = US"rsa";
+if (!pub->srvtype ) pub->srvtype = US"*";
/* p= is required */
if (pub->key.data)
return pub;
-pdkim_free_pubkey(pub);
+DEBUG(D_acl) debug_printf(" Missing p= field\n");
return NULL;
}
/* -------------------------------------------------------------------------- */
-static int
-pdkim_update_bodyhash(pdkim_ctx *ctx, const char *data, int len)
+/* Update one bodyhash with some additional data.
+If we have to relax the data for this sig, return our copy of it. */
+
+static blob *
+pdkim_update_ctx_bodyhash(pdkim_bodyhash * b, blob * orig_data, blob * relaxed_data)
{
-pdkim_signature *sig = ctx->sig;
-/* Cache relaxed version of data */
-uschar *relaxed_data = NULL;
-int relaxed_len = 0;
+blob * canon_data = orig_data;
+/* Defaults to simple canon (no further treatment necessary) */
-/* Traverse all signatures, updating their hashes. */
-while (sig)
+if (b->canon_method == PDKIM_CANON_RELAXED)
{
- /* Defaults to simple canon (no further treatment necessary) */
- const uschar *canon_data = CUS data;
- int canon_len = len;
-
- if (sig->canon_body == PDKIM_CANON_RELAXED)
+ /* Relax the line if not done already */
+ if (!relaxed_data)
{
- /* Relax the line if not done already */
- if (!relaxed_data)
- {
- BOOL seen_wsp = FALSE;
- const char *p;
- int q = 0;
+ BOOL seen_wsp = FALSE;
+ int q = 0;
- if (!(relaxed_data = malloc(len+1)))
- return PDKIM_ERR_OOM;
+ /* We want to be able to free this else we allocate
+ for the entire message which could be many MB. Since
+ we don't know what allocations the SHA routines might
+ do, not safe to use store_get()/store_reset(). */
- for (p = data; *p; p++)
- {
- char c = *p;
- if (c == '\r')
- {
- if (q > 0 && relaxed_data[q-1] == ' ')
- q--;
- }
- else if (c == '\t' || c == ' ')
- {
- c = ' '; /* Turns WSP into SP */
- if (seen_wsp)
- continue;
- seen_wsp = TRUE;
- }
- else
- seen_wsp = FALSE;
- relaxed_data[q++] = c;
+ relaxed_data = store_malloc(sizeof(blob) + orig_data->len+1);
+ relaxed_data->data = US (relaxed_data+1);
+
+ for (const uschar * p = orig_data->data, * r = p + orig_data->len; p < r; p++)
+ {
+ char c = *p;
+ if (c == '\r')
+ {
+ if (q > 0 && relaxed_data->data[q-1] == ' ')
+ q--;
}
- relaxed_data[q] = '\0';
- relaxed_len = q;
+ else if (c == '\t' || c == ' ')
+ {
+ c = ' '; /* Turns WSP into SP */
+ if (seen_wsp)
+ continue;
+ seen_wsp = TRUE;
+ }
+ else
+ seen_wsp = FALSE;
+ relaxed_data->data[q++] = c;
}
- canon_data = relaxed_data;
- canon_len = relaxed_len;
+ relaxed_data->data[q] = '\0';
+ relaxed_data->len = q;
}
+ canon_data = relaxed_data;
+ }
- /* Make sure we don't exceed the to-be-signed body length */
- if ( sig->bodylength >= 0
- && sig->signed_body_bytes + (unsigned long)canon_len > sig->bodylength
- )
- canon_len = sig->bodylength - sig->signed_body_bytes;
-
- if (canon_len > 0)
- {
- exim_sha_update(&sig->body_hash, CCS canon_data, canon_len);
- sig->signed_body_bytes += canon_len;
- DEBUG(D_acl) pdkim_quoteprint(canon_data, canon_len);
- }
+/* Make sure we don't exceed the to-be-signed body length */
+if ( b->bodylength >= 0
+ && b->signed_body_bytes + (unsigned long)canon_data->len > b->bodylength
+ )
+ canon_data->len = b->bodylength - b->signed_body_bytes;
- sig = sig->next;
+if (canon_data->len > 0)
+ {
+ exim_sha_update(&b->body_hash_ctx, CUS canon_data->data, canon_data->len);
+ b->signed_body_bytes += canon_data->len;
+ DEBUG(D_acl) pdkim_quoteprint(canon_data->data, canon_data->len);
}
-if (relaxed_data) free(relaxed_data);
-return PDKIM_OK;
+return relaxed_data;
}
/* -------------------------------------------------------------------------- */
-static int
-pdkim_finish_bodyhash(pdkim_ctx *ctx)
+static void
+pdkim_finish_bodyhash(pdkim_ctx * ctx)
{
-pdkim_signature *sig;
+for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next) /* Finish hashes */
+ {
+ DEBUG(D_acl) debug_printf("DKIM: finish bodyhash %d/%d/%ld len %ld\n",
+ b->hashtype, b->canon_method, b->bodylength, b->signed_body_bytes);
+ exim_sha_finish(&b->body_hash_ctx, &b->bh);
+ }
/* Traverse all signatures */
-for (sig = ctx->sig; sig; sig = sig->next)
- { /* Finish hashes */
- blob bh;
-
- exim_sha_finish(&sig->body_hash, &bh);
+for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
+ {
+ pdkim_bodyhash * b = sig->calc_body_hash;
DEBUG(D_acl)
{
- debug_printf("PDKIM [%s] Body bytes hashed: %lu\n"
- "PDKIM [%s] bh computed: ",
- sig->domain, sig->signed_body_bytes, sig->domain);
- pdkim_hexprint(CUS bh.data, bh.len);
+ debug_printf("DKIM [%s] Body bytes (%s) hashed: %lu\n"
+ "DKIM [%s] Body %s computed: ",
+ sig->domain, pdkim_canons[b->canon_method], b->signed_body_bytes,
+ sig->domain, pdkim_hashes[b->hashtype].dkim_hashname);
+ pdkim_hexprint(CUS b->bh.data, b->bh.len);
}
/* SIGNING -------------------------------------------------------------- */
- if (ctx->mode == PDKIM_MODE_SIGN)
+ if (ctx->flags & PDKIM_MODE_SIGN)
{
- sig->bodyhash = bh;
-
/* If bodylength limit is set, and we have received less bytes
than the requested amount, effectively remove the limit tag. */
- if (sig->signed_body_bytes < sig->bodylength)
+ if (b->signed_body_bytes < sig->bodylength)
sig->bodylength = -1;
}
- /* VERIFICATION --------------------------------------------------------- */
else
- {
- /* Compare bodyhash */
- if (memcmp(bh.data, sig->bodyhash.data, bh.len) == 0)
+ /* VERIFICATION --------------------------------------------------------- */
+ /* Be careful that the header sig included a bodyash */
+
+ if ( sig->bodyhash.data
+ && memcmp(b->bh.data, sig->bodyhash.data, b->bh.len) == 0)
{
- DEBUG(D_acl) debug_printf("PDKIM [%s] Body hash verified OK\n", sig->domain);
+ DEBUG(D_acl) debug_printf("DKIM [%s] Body hash compared OK\n", sig->domain);
}
else
{
DEBUG(D_acl)
{
- debug_printf("PDKIM [%s] bh signature: ", sig->domain);
- pdkim_hexprint(sig->bodyhash.data,
- exim_sha_hashlen(&sig->body_hash));
- debug_printf("PDKIM [%s] Body hash did NOT verify\n", sig->domain);
+ debug_printf("DKIM [%s] Body hash signature from headers: ", sig->domain);
+ pdkim_hexprint(sig->bodyhash.data, sig->bodyhash.len);
+ debug_printf("DKIM [%s] Body hash did NOT verify\n", sig->domain);
}
sig->verify_status = PDKIM_VERIFY_FAIL;
sig->verify_ext_status = PDKIM_VERIFY_FAIL_BODY;
}
- }
}
+}
-return PDKIM_OK;
+
+
+static void
+pdkim_body_complete(pdkim_ctx * ctx)
+{
+/* In simple body mode, if any empty lines were buffered,
+replace with one. rfc 4871 3.4.3 */
+/*XXX checking the signed-body-bytes is a gross hack; I think
+it indicates that all linebreaks should be buffered, including
+the one terminating a text line */
+
+for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next)
+ if ( b->canon_method == PDKIM_CANON_SIMPLE
+ && b->signed_body_bytes == 0
+ && b->num_buffered_blanklines > 0
+ )
+ (void) pdkim_update_ctx_bodyhash(b, &lineending, NULL);
+
+ctx->flags |= PDKIM_SEEN_EOD;
+ctx->linebuf_offset = 0;
}
/* -------------------------------------------------------------------------- */
-/* Callback from pdkim_feed below for processing complete body lines */
+/* Call from pdkim_feed below for processing complete body lines */
+/* NOTE: the line is not NUL-terminated; but we have a count */
-static int
-pdkim_bodyline_complete(pdkim_ctx *ctx)
+static void
+pdkim_bodyline_complete(pdkim_ctx * ctx)
{
-char *p = ctx->linebuf;
-int n = ctx->linebuf_offset;
-pdkim_signature *sig = ctx->sig; /*XXX assumes only one sig */
+blob line = {.data = ctx->linebuf, .len = ctx->linebuf_offset};
+blob * rnl = NULL;
+blob * rline = NULL;
/* Ignore extra data if we've seen the end-of-data marker */
-if (ctx->seen_eod) goto BAIL;
+if (ctx->flags & PDKIM_SEEN_EOD) goto all_skip;
/* We've always got one extra byte to stuff a zero ... */
-ctx->linebuf[ctx->linebuf_offset] = '\0';
+ctx->linebuf[line.len] = '\0';
/* Terminate on EOD marker */
-if (memcmp(p, ".\r\n", 3) == 0)
+if (ctx->flags & PDKIM_DOT_TERM)
{
- /* In simple body mode, if any empty lines were buffered,
- replace with one. rfc 4871 3.4.3 */
- /*XXX checking the signed-body-bytes is a gross hack; I think
- it indicates that all linebreaks should be buffered, including
- the one terminating a text line */
- if ( sig && sig->canon_body == PDKIM_CANON_SIMPLE
- && sig->signed_body_bytes == 0
- && ctx->num_buffered_crlf > 0
- )
- pdkim_update_bodyhash(ctx, "\r\n", 2);
+ if (memcmp(line.data, ".\r\n", 3) == 0)
+ { pdkim_body_complete(ctx); return; }
- ctx->seen_eod = TRUE;
- goto BAIL;
- }
-/* Unstuff dots */
-if (memcmp(p, "..", 2) == 0)
- {
- p++;
- n--;
+ /* Unstuff dots */
+ if (memcmp(line.data, "..", 2) == 0)
+ { line.data++; line.len--; }
}
/* Empty lines need to be buffered until we find a non-empty line */
-if (memcmp(p, "\r\n", 2) == 0)
+if (memcmp(line.data, "\r\n", 2) == 0)
{
- ctx->num_buffered_crlf++;
- goto BAIL;
+ for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next)
+ b->num_buffered_blanklines++;
+ goto all_skip;
}
-if (sig && sig->canon_body == PDKIM_CANON_RELAXED)
+/* Process line for each bodyhash separately */
+for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next)
{
- /* Lines with just spaces need to be buffered too */
- char *check = p;
- while (memcmp(check, "\r\n", 2) != 0)
+ if (b->canon_method == PDKIM_CANON_RELAXED)
{
- char c = *check;
+ /* Lines with just spaces need to be buffered too */
+ uschar * cp = line.data;
+ char c;
- if (c != '\t' && c != ' ')
- goto PROCESS;
- check++;
- }
+ while ((c = *cp))
+ {
+ if (c == '\r' && cp[1] == '\n') break;
+ if (c != ' ' && c != '\t') goto hash_process;
+ cp++;
+ }
- ctx->num_buffered_crlf++;
- goto BAIL;
-}
+ b->num_buffered_blanklines++;
+ goto hash_skip;
+ }
-PROCESS:
-/* At this point, we have a non-empty line, so release the buffered ones. */
-while (ctx->num_buffered_crlf)
- {
- pdkim_update_bodyhash(ctx, "\r\n", 2);
- ctx->num_buffered_crlf--;
+hash_process:
+ /* At this point, we have a non-empty line, so release the buffered ones. */
+
+ while (b->num_buffered_blanklines)
+ {
+ rnl = pdkim_update_ctx_bodyhash(b, &lineending, rnl);
+ b->num_buffered_blanklines--;
+ }
+
+ rline = pdkim_update_ctx_bodyhash(b, &line, rline);
+hash_skip: ;
}
-pdkim_update_bodyhash(ctx, p, n);
+if (rnl) store_free(rnl);
+if (rline) store_free(rline);
+
+all_skip:
-BAIL:
ctx->linebuf_offset = 0;
-return PDKIM_OK;
+return;
}
#define DKIM_SIGNATURE_HEADERNAME "DKIM-Signature:"
static int
-pdkim_header_complete(pdkim_ctx *ctx)
+pdkim_header_complete(pdkim_ctx * ctx)
{
-/* Special case: The last header can have an extra \r appended */
-if ( (ctx->cur_header->len > 1) &&
- (ctx->cur_header->str[(ctx->cur_header->len)-1] == '\r') )
- {
- ctx->cur_header->str[(ctx->cur_header->len)-1] = '\0';
- ctx->cur_header->len--;
- }
+if ( (ctx->cur_header->ptr > 1) &&
+ (ctx->cur_header->s[ctx->cur_header->ptr-1] == '\r') )
+ --ctx->cur_header->ptr;
+(void) string_from_gstring(ctx->cur_header);
+
+#ifdef EXPERIMENTAL_ARC
+/* Feed the header line to ARC processing */
+(void) arc_header_feed(ctx->cur_header, !(ctx->flags & PDKIM_MODE_SIGN));
+#endif
-ctx->num_headers++;
-if (ctx->num_headers > PDKIM_MAX_HEADERS) goto BAIL;
+if (++ctx->num_headers > PDKIM_MAX_HEADERS) goto BAIL;
/* SIGNING -------------------------------------------------------------- */
-if (ctx->mode == PDKIM_MODE_SIGN)
- {
- pdkim_signature *sig;
-
- for (sig = ctx->sig; sig; sig = sig->next) /* Traverse all signatures */
- {
- pdkim_stringlist *list;
+if (ctx->flags & PDKIM_MODE_SIGN)
+ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next) /* Traverse all signatures */
/* Add header to the signed headers list (in reverse order) */
- if (!(list = pdkim_prepend_stringlist(sig->headers,
- ctx->cur_header->str)))
- return PDKIM_ERR_OOM;
- sig->headers = list;
- }
- }
+ sig->headers = pdkim_prepend_stringlist(sig->headers, ctx->cur_header->s);
/* VERIFICATION ----------------------------------------------------------- */
/* DKIM-Signature: headers are added to the verification list */
-if (ctx->mode == PDKIM_MODE_VERIFY)
+else
{
- if (strncasecmp(ctx->cur_header->str,
+#ifdef notdef
+ DEBUG(D_acl)
+ {
+ debug_printf("DKIM >> raw hdr: ");
+ pdkim_quoteprint(CUS ctx->cur_header->s, ctx->cur_header->ptr);
+ }
+#endif
+ if (strncasecmp(CCS ctx->cur_header->s,
DKIM_SIGNATURE_HEADERNAME,
- strlen(DKIM_SIGNATURE_HEADERNAME)) == 0)
+ Ustrlen(DKIM_SIGNATURE_HEADERNAME)) == 0)
{
- pdkim_signature *new_sig;
+ pdkim_signature * sig, * last_sig;
+ /* Create and chain new signature block. We could error-check for all
+ required tags here, but prefer to create the internal sig and expicitly
+ fail verification of it later. */
- /* Create and chain new signature block */
DEBUG(D_acl) debug_printf(
- "PDKIM >> Found sig, trying to parse >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
+ "DKIM >> Found sig, trying to parse >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
- if ((new_sig = pdkim_parse_sig_header(ctx, ctx->cur_header->str)))
+ sig = pdkim_parse_sig_header(ctx, ctx->cur_header->s);
+
+ if (!(last_sig = ctx->sig))
+ ctx->sig = sig;
+ else
{
- pdkim_signature *last_sig = ctx->sig;
- if (!last_sig)
- ctx->sig = new_sig;
- else
- {
- while (last_sig->next) last_sig = last_sig->next;
- last_sig->next = new_sig;
- }
+ while (last_sig->next) last_sig = last_sig->next;
+ last_sig->next = sig;
}
- else
- DEBUG(D_acl) debug_printf(
- "Error while parsing signature header\n"
- "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
- }
-
- /* every other header is stored for signature verification */
- else
- {
- pdkim_stringlist *list;
- if (!(list = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header->str)))
- return PDKIM_ERR_OOM;
- ctx->headers = list;
+ if (--dkim_collect_input == 0)
+ {
+ ctx->headers = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header->s);
+ ctx->cur_header->s[ctx->cur_header->ptr = 0] = '\0';
+ return PDKIM_ERR_EXCESS_SIGS;
+ }
}
+
+ /* all headers are stored for signature verification */
+ ctx->headers = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header->s);
}
BAIL:
-pdkim_strclear(ctx->cur_header); /* Re-use existing pdkim_str */
+ctx->cur_header->s[ctx->cur_header->ptr = 0] = '\0'; /* leave buffer for reuse */
return PDKIM_OK;
}
#define HEADER_BUFFER_FRAG_SIZE 256
DLLEXPORT int
-pdkim_feed (pdkim_ctx *ctx, char *data, int len)
+pdkim_feed(pdkim_ctx * ctx, uschar * data, int len)
{
-int p;
+/* Alternate EOD signal, used in non-dotstuffing mode */
+if (!data)
+ pdkim_body_complete(ctx);
-for (p = 0; p<len; p++)
+else for (int p = 0; p < len; p++)
{
- char c = data[p];
+ uschar c = data[p];
+ int rc;
- if (ctx->past_headers)
+ if (ctx->flags & PDKIM_PAST_HDRS)
{
+ if (c == '\n' && !(ctx->flags & PDKIM_SEEN_CR)) /* emulate the CR */
+ {
+ ctx->linebuf[ctx->linebuf_offset++] = '\r';
+ if (ctx->linebuf_offset == PDKIM_MAX_BODY_LINE_LEN-1)
+ return PDKIM_ERR_LONG_LINE;
+ }
+
/* Processing body byte */
ctx->linebuf[ctx->linebuf_offset++] = c;
- if (c == '\n')
+ if (c == '\r')
+ ctx->flags |= PDKIM_SEEN_CR;
+ else if (c == '\n')
{
- int rc = pdkim_bodyline_complete(ctx); /* End of line */
- if (rc != PDKIM_OK) return rc;
+ ctx->flags &= ~PDKIM_SEEN_CR;
+ pdkim_bodyline_complete(ctx);
}
- if (ctx->linebuf_offset == (PDKIM_MAX_BODY_LINE_LEN-1))
+
+ if (ctx->linebuf_offset == PDKIM_MAX_BODY_LINE_LEN-1)
return PDKIM_ERR_LONG_LINE;
}
else
{
/* Processing header byte */
- if (c != '\r')
+ if (c == '\r')
+ ctx->flags |= PDKIM_SEEN_CR;
+ else if (c == '\n')
{
- if (c == '\n')
- {
- if (ctx->seen_lf)
- {
- int rc = pdkim_header_complete(ctx); /* Seen last header line */
- if (rc != PDKIM_OK) return rc;
+ if (!(ctx->flags & PDKIM_SEEN_CR)) /* emulate the CR */
+ ctx->cur_header = string_catn(ctx->cur_header, CUS "\r", 1);
- ctx->past_headers = TRUE;
- ctx->seen_lf = 0;
- DEBUG(D_acl) debug_printf(
- "PDKIM >> Body data for hash, canonicalized >>>>>>>>>>>>>>>>>>>>>>\n");
- continue;
- }
- else
- ctx->seen_lf = TRUE;
- }
- else if (ctx->seen_lf)
- {
- if (!(c == '\t' || c == ' '))
- {
- int rc = pdkim_header_complete(ctx); /* End of header */
- if (rc != PDKIM_OK) return rc;
- }
- ctx->seen_lf = FALSE;
+ if (ctx->flags & PDKIM_SEEN_LF) /* Seen last header line */
+ {
+ if ((rc = pdkim_header_complete(ctx)) != PDKIM_OK)
+ return rc;
+
+ ctx->flags = (ctx->flags & ~(PDKIM_SEEN_LF|PDKIM_SEEN_CR)) | PDKIM_PAST_HDRS;
+ DEBUG(D_acl) debug_printf(
+ "DKIM >> Body data for hash, canonicalized >>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
+ continue;
}
+ else
+ ctx->flags = (ctx->flags & ~PDKIM_SEEN_CR) | PDKIM_SEEN_LF;
+ }
+ else if (ctx->flags & PDKIM_SEEN_LF)
+ {
+ if (!(c == '\t' || c == ' ')) /* End of header */
+ if ((rc = pdkim_header_complete(ctx)) != PDKIM_OK)
+ return rc;
+ ctx->flags &= ~PDKIM_SEEN_LF;
}
- if (!ctx->cur_header)
- if (!(ctx->cur_header = pdkim_strnew(NULL)))
- return PDKIM_ERR_OOM;
-
- if (ctx->cur_header->len < PDKIM_MAX_HEADER_LEN)
- if (!pdkim_strncat(ctx->cur_header, &data[p], 1))
- return PDKIM_ERR_OOM;
+ if (!ctx->cur_header || ctx->cur_header->ptr < PDKIM_MAX_HEADER_LEN)
+ ctx->cur_header = string_catn(ctx->cur_header, CUS &data[p], 1);
}
}
return PDKIM_OK;
}
+
+
+/* Extend a growing header with a continuation-linebreak */
+static gstring *
+pdkim_hdr_cont(gstring * str, int * col)
+{
+*col = 1;
+return string_catn(str, US"\r\n\t", 3);
+}
+
+
+
/*
* RFC 5322 specifies that header line length SHOULD be no more than 78
* lets make it so!
* pdkim_headcat
- * returns char*
+ *
+ * returns uschar * (not nul-terminated)
*
* col: this int holds and receives column number (octets since last '\n')
* str: partial string to append to
* names longer than 78, or bogus col. Input is assumed to be free of line breaks.
*/
-static char *
-pdkim_headcat(int *col, pdkim_str *str, const char * pad,
- const char *intro, const char *payload)
+static gstring *
+pdkim_headcat(int * col, gstring * str,
+ const uschar * pad, const uschar * intro, const uschar * payload)
{
size_t l;
if (pad)
{
- l = strlen(pad);
+ l = Ustrlen(pad);
if (*col + l > 78)
- {
- pdkim_strcat(str, "\r\n\t");
- *col = 1;
- }
- pdkim_strncat(str, pad, l);
+ str = pdkim_hdr_cont(str, col);
+ str = string_catn(str, pad, l);
*col += l;
}
-l = (pad?1:0) + (intro?strlen(intro):0);
+l = (pad?1:0) + (intro?Ustrlen(intro):0);
if (*col + l > 78)
{ /*can't fit intro - start a new line to make room.*/
- pdkim_strcat(str, "\r\n\t");
- *col = 1;
- l = intro?strlen(intro):0;
+ str = pdkim_hdr_cont(str, col);
+ l = intro?Ustrlen(intro):0;
}
-l += payload ? strlen(payload):0 ;
+l += payload ? Ustrlen(payload):0 ;
while (l>77)
{ /* this fragment will not fit on a single line */
if (pad)
{
- pdkim_strcat(str, " ");
+ str = string_catn(str, US" ", 1);
*col += 1;
pad = NULL; /* only want this once */
l--;
if (intro)
{
- size_t sl = strlen(intro);
+ size_t sl = Ustrlen(intro);
- pdkim_strncat(str, intro, sl);
+ str = string_catn(str, intro, sl);
*col += sl;
l -= sl;
intro = NULL; /* only want this once */
if (payload)
{
- size_t sl = strlen(payload);
+ size_t sl = Ustrlen(payload);
size_t chomp = *col+sl < 77 ? sl : 78-*col;
- pdkim_strncat(str, payload, chomp);
+ str = string_catn(str, payload, chomp);
*col += chomp;
payload += chomp;
l -= chomp-1;
}
/* the while precondition tells us it didn't fit. */
- pdkim_strcat(str, "\r\n\t");
- *col = 1;
+ str = pdkim_hdr_cont(str, col);
}
if (*col + l > 78)
{
- pdkim_strcat(str, "\r\n\t");
- *col = 1;
+ str = pdkim_hdr_cont(str, col);
pad = NULL;
}
if (pad)
{
- pdkim_strcat(str, " ");
+ str = string_catn(str, US" ", 1);
*col += 1;
pad = NULL;
}
if (intro)
{
- size_t sl = strlen(intro);
+ size_t sl = Ustrlen(intro);
- pdkim_strncat(str, intro, sl);
+ str = string_catn(str, intro, sl);
*col += sl;
l -= sl;
intro = NULL;
if (payload)
{
- size_t sl = strlen(payload);
+ size_t sl = Ustrlen(payload);
- pdkim_strncat(str, payload, sl);
+ str = string_catn(str, payload, sl);
*col += sl;
}
-return str->str;
+return str;
}
/* -------------------------------------------------------------------------- */
-static char *
-pdkim_create_header(pdkim_signature *sig, BOOL final)
+/* Signing: create signature header
+*/
+static uschar *
+pdkim_create_header(pdkim_signature * sig, BOOL final)
{
-char *rc = NULL;
-char *base64_bh = NULL;
-char *base64_b = NULL;
+uschar * base64_bh;
+uschar * base64_b;
int col = 0;
-pdkim_str *hdr;
-pdkim_str *canon_all;
-
-if (!(hdr = pdkim_strnew("DKIM-Signature: v="PDKIM_SIGNATURE_VERSION)))
- return NULL;
-
-if (!(canon_all = pdkim_strnew(pdkim_canons[sig->canon_headers])))
- goto BAIL;
+gstring * hdr;
+gstring * canon_all;
-if (!(base64_bh = pdkim_encode_base64(&sig->bodyhash)))
- goto BAIL;
+canon_all = string_cat (NULL, pdkim_canons[sig->canon_headers]);
+canon_all = string_catn(canon_all, US"/", 1);
+canon_all = string_cat (canon_all, pdkim_canons[sig->canon_body]);
+(void) string_from_gstring(canon_all);
-col = strlen(hdr->str);
+hdr = string_cat(NULL, US"DKIM-Signature: v="PDKIM_SIGNATURE_VERSION);
+col = hdr->ptr;
/* Required and static bits */
-if ( pdkim_headcat(&col, hdr, ";", "a=", pdkim_algos[sig->algo])
- && pdkim_headcat(&col, hdr, ";", "q=", pdkim_querymethods[sig->querymethod])
- && pdkim_strcat(canon_all, "/")
- && pdkim_strcat(canon_all, pdkim_canons[sig->canon_body])
- && pdkim_headcat(&col, hdr, ";", "c=", canon_all->str)
- && pdkim_headcat(&col, hdr, ";", "d=", sig->domain)
- && pdkim_headcat(&col, hdr, ";", "s=", sig->selector)
- )
+hdr = pdkim_headcat(&col, hdr, US";", US"a=", dkim_sig_to_a_tag(sig));
+hdr = pdkim_headcat(&col, hdr, US";", US"q=", pdkim_querymethods[sig->querymethod]);
+hdr = pdkim_headcat(&col, hdr, US";", US"c=", canon_all->s);
+hdr = pdkim_headcat(&col, hdr, US";", US"d=", sig->domain);
+hdr = pdkim_headcat(&col, hdr, US";", US"s=", sig->selector);
+
+/* list of header names can be split between items. */
{
- /* list of header names can be split between items. */
- {
- char *n = CS string_copy(sig->headernames);
- char *i = "h=";
- char *s = ";";
+ uschar * n = string_copy(sig->headernames);
+ uschar * i = US"h=";
+ uschar * s = US";";
- if (!n) goto BAIL;
- while (*n)
- {
- char *c = strchr(n, ':');
+ while (*n)
+ {
+ uschar * c = Ustrchr(n, ':');
- if (c) *c ='\0';
+ if (c) *c ='\0';
- if (!i)
- if (!pdkim_headcat(&col, hdr, NULL, NULL, ":"))
- {
- goto BAIL;
- }
+ if (!i)
+ hdr = pdkim_headcat(&col, hdr, NULL, NULL, US":");
- if (!pdkim_headcat(&col, hdr, s, i, n))
- {
- goto BAIL;
- }
+ hdr = pdkim_headcat(&col, hdr, s, i, n);
- if (!c)
- break;
+ if (!c)
+ break;
- n = c+1;
- s = NULL;
- i = NULL;
- }
+ n = c+1;
+ s = NULL;
+ i = NULL;
}
+ }
- if(!pdkim_headcat(&col, hdr, ";", "bh=", base64_bh))
- goto BAIL;
+base64_bh = pdkim_encode_base64(&sig->calc_body_hash->bh);
+hdr = pdkim_headcat(&col, hdr, US";", US"bh=", base64_bh);
- /* Optional bits */
- if (sig->identity)
- if(!pdkim_headcat(&col, hdr, ";", "i=", sig->identity))
- goto BAIL;
+/* Optional bits */
+if (sig->identity)
+ hdr = pdkim_headcat(&col, hdr, US";", US"i=", sig->identity);
- if (sig->created > 0)
- {
- char minibuf[20];
+if (sig->created > 0)
+ {
+ uschar minibuf[20];
+
+ snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->created);
+ hdr = pdkim_headcat(&col, hdr, US";", US"t=", minibuf);
+}
+
+if (sig->expires > 0)
+ {
+ uschar minibuf[20];
- snprintf(minibuf, 20, "%lu", sig->created);
- if(!pdkim_headcat(&col, hdr, ";", "t=", minibuf))
- goto BAIL;
+ snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->expires);
+ hdr = pdkim_headcat(&col, hdr, US";", US"x=", minibuf);
}
- if (sig->expires > 0)
- {
- char minibuf[20];
+if (sig->bodylength >= 0)
+ {
+ uschar minibuf[20];
- snprintf(minibuf, 20, "%lu", sig->expires);
- if(!pdkim_headcat(&col, hdr, ";", "x=", minibuf))
- goto BAIL;
- }
+ snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->bodylength);
+ hdr = pdkim_headcat(&col, hdr, US";", US"l=", minibuf);
+ }
- if (sig->bodylength >= 0)
- {
- char minibuf[20];
+/* Preliminary or final version? */
+if (final)
+ {
+ base64_b = pdkim_encode_base64(&sig->sighash);
+ hdr = pdkim_headcat(&col, hdr, US";", US"b=", base64_b);
+
+ /* add trailing semicolon: I'm not sure if this is actually needed */
+ hdr = pdkim_headcat(&col, hdr, NULL, US";", US"");
+ }
+else
+ {
+ /* To satisfy the rule "all surrounding whitespace [...] deleted"
+ ( RFC 6376 section 3.7 ) we ensure there is no whitespace here. Otherwise
+ the headcat routine could insert a linebreak which the relaxer would reduce
+ to a single space preceding the terminating semicolon, resulting in an
+ incorrect header-hash. */
+ hdr = pdkim_headcat(&col, hdr, US";", US"b=;", US"");
+ }
- snprintf(minibuf, 20, "%lu", sig->bodylength);
- if(!pdkim_headcat(&col, hdr, ";", "l=", minibuf))
- goto BAIL;
+return string_from_gstring(hdr);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/* According to draft-ietf-dcrup-dkim-crypto-07 "keys are 256 bits" (referring
+to DNS, hence the pubkey). Check for more than 32 bytes; if so assume the
+alternate possible representation (still) being discussed: a
+SubjectPublickeyInfo wrapped key - and drop all but the trailing 32-bytes (it
+should be a DER, with exactly 12 leading bytes - but we could accept a BER also,
+which could be any size). We still rely on the crypto library for checking for
+undersize.
+
+When the RFC is published this should be re-addressed. */
+
+static void
+check_bare_ed25519_pubkey(pdkim_pubkey * p)
+{
+int excess = p->key.len - 32;
+if (excess > 0)
+ {
+ DEBUG(D_acl) debug_printf("DKIM: unexpected pubkey len %lu\n", p->key.len);
+ p->key.data += excess; p->key.len = 32;
+ }
+}
+
+
+static pdkim_pubkey *
+pdkim_key_from_dns(pdkim_ctx * ctx, pdkim_signature * sig, ev_ctx * vctx,
+ const uschar ** errstr)
+{
+uschar * dns_txt_name, * dns_txt_reply;
+pdkim_pubkey * p;
+
+/* Fetch public key for signing domain, from DNS */
+
+dns_txt_name = string_sprintf("%s._domainkey.%s.", sig->selector, sig->domain);
+
+if ( !(dns_txt_reply = ctx->dns_txt_callback(dns_txt_name))
+ || dns_txt_reply[0] == '\0'
+ )
+ {
+ sig->verify_status = PDKIM_VERIFY_INVALID;
+ sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE;
+ return NULL;
+ }
+
+DEBUG(D_acl)
+ {
+ debug_printf(
+ "DKIM >> Parsing public key record >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"
+ " %s\n"
+ " Raw record: ",
+ dns_txt_name);
+ pdkim_quoteprint(CUS dns_txt_reply, Ustrlen(dns_txt_reply));
+ }
+
+if ( !(p = pdkim_parse_pubkey_record(CUS dns_txt_reply))
+ || (Ustrcmp(p->srvtype, "*") != 0 && Ustrcmp(p->srvtype, "email") != 0)
+ )
+ {
+ sig->verify_status = PDKIM_VERIFY_INVALID;
+ sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD;
+
+ DEBUG(D_acl)
+ {
+ if (p)
+ debug_printf(" Invalid public key service type '%s'\n", p->srvtype);
+ else
+ debug_printf(" Error while parsing public key record\n");
+ debug_printf(
+ "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
}
+ return NULL;
+ }
+
+DEBUG(D_acl) debug_printf(
+ "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+
+/* Import public key */
- /* Preliminary or final version? */
- if (final)
+/* Normally we use the signature a= tag to tell us the pubkey format.
+When signing under debug we do a test-import of the pubkey, and at that
+time we do not have a signature so we must interpret the pubkey k= tag
+instead. Assume writing on the sig is ok in that case. */
+
+if (sig->keytype < 0)
+ if ((sig->keytype = pdkim_keyname_to_keytype(p->keytype)) < 0)
{
- if (!(base64_b = pdkim_encode_base64(&sig->sigdata)))
- goto BAIL;
- if (!pdkim_headcat(&col, hdr, ";", "b=", base64_b))
- goto BAIL;
+ DEBUG(D_acl) debug_printf("verify_init: unhandled keytype %s\n", p->keytype);
+ sig->verify_status = PDKIM_VERIFY_INVALID;
+ sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_IMPORT;
+ return NULL;
+ }
+
+if (sig->keytype == KEYTYPE_ED25519)
+ check_bare_ed25519_pubkey(p);
+
+if ((*errstr = exim_dkim_verify_init(&p->key,
+ sig->keytype == KEYTYPE_ED25519 ? KEYFMT_ED25519_BARE : KEYFMT_DER,
+ vctx)))
+ {
+ DEBUG(D_acl) debug_printf("verify_init: %s\n", *errstr);
+ sig->verify_status = PDKIM_VERIFY_INVALID;
+ sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_IMPORT;
+ return NULL;
}
- else
- if(!pdkim_headcat(&col, hdr, ";", "b=", ""))
- goto BAIL;
- /* add trailing semicolon: I'm not sure if this is actually needed */
- if (!pdkim_headcat(&col, hdr, NULL, ";", ""))
- goto BAIL;
+vctx->keytype = sig->keytype;
+return p;
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* Sort and filter the sigs developed from the message */
+
+static pdkim_signature *
+sort_sig_methods(pdkim_signature * siglist)
+{
+pdkim_signature * yield, ** ss;
+const uschar * prefs;
+uschar * ele;
+int sep;
+
+if (!siglist) return NULL;
+
+/* first select in order of hashtypes */
+DEBUG(D_acl) debug_printf("DKIM: dkim_verify_hashes '%s'\n", dkim_verify_hashes);
+for (prefs = dkim_verify_hashes, sep = 0, yield = NULL, ss = &yield;
+ ele = string_nextinlist(&prefs, &sep, NULL, 0); )
+ {
+ int i = pdkim_hashname_to_hashtype(CUS ele, 0);
+ for (pdkim_signature * s = siglist, * next, ** prev = &siglist; s;
+ s = next)
+ {
+ next = s->next;
+ if (s->hashtype == i)
+ { *prev = next; s->next = NULL; *ss = s; ss = &s->next; }
+ else
+ prev = &s->next;
+ }
}
-rc = strdup(hdr->str);
+/* then in order of keytypes */
+siglist = yield;
+DEBUG(D_acl) debug_printf("DKIM: dkim_verify_keytypes '%s'\n", dkim_verify_keytypes);
+for (prefs = dkim_verify_keytypes, sep = 0, yield = NULL, ss = &yield;
+ ele = string_nextinlist(&prefs, &sep, NULL, 0); )
+ {
+ int i = pdkim_keyname_to_keytype(CUS ele);
+ for (pdkim_signature * s = siglist, * next, ** prev = &siglist; s;
+ s = next)
+ {
+ next = s->next;
+ if (s->keytype == i)
+ { *prev = next; s->next = NULL; *ss = s; ss = &s->next; }
+ else
+ prev = &s->next;
+ }
+ }
-BAIL:
-pdkim_strfree(hdr);
-if (canon_all) pdkim_strfree(canon_all);
-return rc;
+DEBUG(D_acl) for (pdkim_signature * s = yield; s; s = s->next)
+ debug_printf(" retain d=%s s=%s a=%s\n",
+ s->domain, s->selector, dkim_sig_to_a_tag(s));
+return yield;
}
/* -------------------------------------------------------------------------- */
DLLEXPORT int
-pdkim_feed_finish(pdkim_ctx *ctx, pdkim_signature **return_signatures)
+pdkim_feed_finish(pdkim_ctx * ctx, pdkim_signature ** return_signatures,
+ const uschar ** err)
{
-pdkim_signature *sig = ctx->sig;
-pdkim_str *headernames = NULL; /* Collected signed header names */
+BOOL verify_pass = FALSE;
/* Check if we must still flush a (partial) header. If that is the
case, the message has no body, and we must compute a body hash
out of '<CR><LF>' */
-if (ctx->cur_header && ctx->cur_header->len)
+if (ctx->cur_header && ctx->cur_header->ptr > 0)
{
- int rc = pdkim_header_complete(ctx);
- if (rc != PDKIM_OK) return rc;
- pdkim_update_bodyhash(ctx, "\r\n", 2);
+ blob * rnl = NULL;
+ int rc;
+
+ if ((rc = pdkim_header_complete(ctx)) != PDKIM_OK)
+ return rc;
+
+ for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next)
+ rnl = pdkim_update_ctx_bodyhash(b, &lineending, rnl);
+ if (rnl) store_free(rnl);
}
else
DEBUG(D_acl) debug_printf(
- "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
-/* Build (and/or evaluate) body hash */
-if (pdkim_finish_bodyhash(ctx) != PDKIM_OK)
- return PDKIM_ERR_OOM;
+/* Build (and/or evaluate) body hash. Do this even if no DKIM sigs, in case we
+have a hash to do for ARC. */
-/* SIGNING -------------------------------------------------------------- */
-if (ctx->mode == PDKIM_MODE_SIGN)
- if (!(headernames = pdkim_strnew(NULL)))
- return PDKIM_ERR_OOM;
-/* ---------------------------------------------------------------------- */
+pdkim_finish_bodyhash(ctx);
+
+/* Sort and filter the recived signatures */
+
+if (!(ctx->flags & PDKIM_MODE_SIGN))
+ ctx->sig = sort_sig_methods(ctx->sig);
+
+if (!ctx->sig)
+ {
+ DEBUG(D_acl) debug_printf("DKIM: no signatures\n");
+ *return_signatures = NULL;
+ return PDKIM_OK;
+ }
-while (sig)
+for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
{
- BOOL is_sha1 = sig->algo == PDKIM_ALGO_RSA_SHA1;
hctx hhash_ctx;
- char * sig_hdr;
+ uschar * sig_hdr = US"";
blob hhash;
- blob hdata;
- int hdata_alloc = 0;
+ gstring * hdata = NULL;
+ es_ctx sctx;
+
+ if ( !(ctx->flags & PDKIM_MODE_SIGN)
+ && sig->verify_status == PDKIM_VERIFY_FAIL)
+ {
+ DEBUG(D_acl)
+ debug_printf("DKIM: [%s] abandoning this signature\n", sig->domain);
+ continue;
+ }
- hdata.data = NULL;
- hdata.len = 0;
+ /*XXX The hash of the headers is needed for GCrypt (for which we can do RSA
+ signing only, as it happens) and for either GnuTLS and OpenSSL when we are
+ signing with EC (specifically, Ed25519). The former is because the GCrypt
+ signing operation is pure (does not do its own hash) so we must hash. The
+ latter is because we (stupidly, but this is what the IETF draft is saying)
+ must hash with the declared hash method, then pass the result to the library
+ hash-and-sign routine (because that's all the libraries are providing. And
+ we're stuck with whatever that hidden hash method is, too). We may as well
+ do this hash incrementally.
+ We don't need the hash we're calculating here for the GnuTLS and OpenSSL
+ cases of RSA signing, since those library routines can do hash-and-sign.
+
+ Some time in the future we could easily avoid doing the hash here for those
+ cases (which will be common for a long while. We could also change from
+ the current copy-all-the-headers-into-one-block, then call the hash-and-sign
+ implementation - to a proper incremental one. Unfortunately, GnuTLS just
+ cannot do incremental - either signing or verification. Unsure about GCrypt.
+ */
+
+ /*XXX The header hash is also used (so far) by the verify operation */
+
+ if (!exim_sha_init(&hhash_ctx, pdkim_hashes[sig->hashtype].exim_hashmethod))
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "DKIM: hash setup error, possibly nonhandled hashtype");
+ break;
+ }
- exim_sha_init(&hhash_ctx, is_sha1);
+ if (ctx->flags & PDKIM_MODE_SIGN)
+ DEBUG(D_acl) debug_printf(
+ "DKIM >> Headers to be signed: >>>>>>>>>>>>\n"
+ " %s\n",
+ sig->sign_headers);
DEBUG(D_acl) debug_printf(
- "PDKIM >> Hashed header data, canonicalized, in sequence >>>>>>>>>>>>>>\n");
+ "DKIM >> Header data for hash, canonicalized (%-7s), in sequence >>\n",
+ pdkim_canons[sig->canon_headers]);
+
/* SIGNING ---------------------------------------------------------------- */
/* When signing, walk through our header list and add them to the hash. As we
Then append to that list any remaining header names for which there was no
header to sign. */
- if (ctx->mode == PDKIM_MODE_SIGN)
+ if (ctx->flags & PDKIM_MODE_SIGN)
{
- pdkim_stringlist *p;
+ gstring * g = NULL;
const uschar * l;
uschar * s;
int sep = 0;
- for (p = sig->headers; p; p = p->next)
- if (header_name_match(p->value, sig->sign_headers) == PDKIM_OK)
+ /* Import private key, including the keytype which we need for building
+ the signature header */
+
+ if ((*err = exim_dkim_signing_init(CUS sig->privkey, &sctx)))
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "signing_init: %s", *err);
+ return PDKIM_ERR_RSA_PRIVKEY;
+ }
+ sig->keytype = sctx.keytype;
+
+ sig->headernames = NULL; /* Collected signed header names */
+ for (pdkim_stringlist * p = sig->headers; p; p = p->next)
+ {
+ uschar * rh = p->value;
+
+ if (header_name_match(rh, sig->sign_headers) == PDKIM_OK)
{
- uschar * rh;
/* Collect header names (Note: colon presence is guaranteed here) */
- uschar * q = Ustrchr(p->value, ':');
+ g = string_append_listele_n(g, ':', rh, Ustrchr(rh, ':') - rh);
- if (!(pdkim_strncat(headernames, p->value,
- (q - US p->value) + (p->next ? 1 : 0))))
- return PDKIM_ERR_OOM;
-
- rh = sig->canon_headers == PDKIM_CANON_RELAXED
- ? US pdkim_relax_header(p->value, 1) /* cook header for relaxed canon */
- : string_copy(CUS p->value); /* just copy it for simple canon */
- if (!rh)
- return PDKIM_ERR_OOM;
+ if (sig->canon_headers == PDKIM_CANON_RELAXED)
+ rh = pdkim_relax_header(rh, TRUE); /* cook header for relaxed canon */
/* Feed header to the hash algorithm */
- exim_sha_update(&hhash_ctx, CCS rh, Ustrlen(rh));
+ exim_sha_update(&hhash_ctx, CUS rh, Ustrlen(rh));
/* Remember headers block for signing (when the library cannot do incremental) */
- (void) exim_rsa_data_append(&hdata, &hdata_alloc, rh);
+ /*XXX we could avoid doing this for all but the GnuTLS/RSA case */
+ hdata = exim_dkim_data_append(hdata, rh);
DEBUG(D_acl) pdkim_quoteprint(rh, Ustrlen(rh));
}
+ }
- l = US sig->sign_headers;
+ /* Any headers we wanted to sign but were not present must also be listed.
+ Ignore elements that have been ticked-off or are marked as never-oversign. */
+
+ l = sig->sign_headers;
while((s = string_nextinlist(&l, &sep, NULL, 0)))
- if (*s != '_')
- { /*SSS string_append_listele() */
- if (headernames->len > 0 && headernames->str[headernames->len-1] != ':')
- if (!(pdkim_strncat(headernames, ":", 1)))
- return PDKIM_ERR_OOM;
- if (!(pdkim_strncat(headernames, CS s, Ustrlen(s))))
- return PDKIM_ERR_OOM;
- }
+ {
+ if (*s == '+') /* skip oversigning marker */
+ s++;
+ if (*s != '_' && *s != '=')
+ g = string_append_listele(g, ':', s);
+ }
+ sig->headernames = string_from_gstring(g);
+
+ /* Create signature header with b= omitted */
+ sig_hdr = pdkim_create_header(sig, FALSE);
}
/* VERIFICATION ----------------------------------------------------------- */
add the headers to the hash in that order. */
else
{
- uschar * b = string_copy(sig->headernames);
- uschar * p = b;
+ uschar * p = sig->headernames;
uschar * q;
- pdkim_stringlist * hdrs;
- if (!b) return PDKIM_ERR_OOM;
+ if (p)
+ {
+ /* clear tags */
+ for (pdkim_stringlist * hdrs = ctx->headers; hdrs; hdrs = hdrs->next)
+ hdrs->tag = 0;
- /* clear tags */
- for (hdrs = ctx->headers; hdrs; hdrs = hdrs->next)
- hdrs->tag = 0;
+ p = string_copy(p);
+ while(1)
+ {
+ if ((q = Ustrchr(p, ':')))
+ *q = '\0';
+
+ /*XXX walk the list of headers in same order as received. */
+ for (pdkim_stringlist * hdrs = ctx->headers; hdrs; hdrs = hdrs->next)
+ if ( hdrs->tag == 0
+ && strncasecmp(CCS hdrs->value, CCS p, Ustrlen(p)) == 0
+ && (hdrs->value)[Ustrlen(p)] == ':'
+ )
+ {
+ /* cook header for relaxed canon, or just copy it for simple */
+
+ uschar * rh = sig->canon_headers == PDKIM_CANON_RELAXED
+ ? pdkim_relax_header(hdrs->value, TRUE)
+ : string_copy(CUS hdrs->value);
+
+ /* Feed header to the hash algorithm */
+ exim_sha_update(&hhash_ctx, CUS rh, Ustrlen(rh));
+
+ DEBUG(D_acl) pdkim_quoteprint(rh, Ustrlen(rh));
+ hdrs->tag = 1;
+ break;
+ }
- while(1)
- {
- if ((q = Ustrchr(p, ':')))
- *q = '\0';
-
-/*XXX walk the list of headers in same order as received. */
- for (hdrs = ctx->headers; hdrs; hdrs = hdrs->next)
- if ( hdrs->tag == 0
- && strncasecmp(hdrs->value, CS p, Ustrlen(p)) == 0
- && (hdrs->value)[Ustrlen(p)] == ':'
- )
- {
- uschar * rh = sig->canon_headers == PDKIM_CANON_RELAXED
- ? US pdkim_relax_header(hdrs->value, 1) /* cook header for relaxed canon */
- : string_copy(CUS hdrs->value); /* just copy it for simple canon */
- if (!rh)
- return PDKIM_ERR_OOM;
-
- /* Feed header to the hash algorithm */
- exim_sha_update(&hhash_ctx, CCS rh, Ustrlen(rh));
-
- DEBUG(D_acl) pdkim_quoteprint(rh, Ustrlen(rh));
- hdrs->tag = 1;
- break;
- }
+ if (!q) break;
+ p = q+1;
+ }
- if (!q) break;
- p = q+1;
+ sig_hdr = string_copy(sig->rawsig_no_b_val);
}
}
DEBUG(D_acl) debug_printf(
- "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
- /* SIGNING ---------------------------------------------------------------- */
- if (ctx->mode == PDKIM_MODE_SIGN)
+ DEBUG(D_acl)
{
- /* Copy headernames to signature struct */
- sig->headernames = string_copy(US headernames->str);
- pdkim_strfree(headernames);
-
- /* Create signature header with b= omitted */
- sig_hdr = pdkim_create_header(sig, FALSE);
+ debug_printf(
+ "DKIM >> Signed DKIM-Signature header, pre-canonicalized >>>>>>>>>>>>>\n");
+ pdkim_quoteprint(CUS sig_hdr, Ustrlen(sig_hdr));
+ debug_printf(
+ "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
}
- /* VERIFICATION ----------------------------------------------------------- */
- else
- sig_hdr = strdup(sig->rawsig_no_b_val);
- /* ------------------------------------------------------------------------ */
-
- if (!sig_hdr)
- return PDKIM_ERR_OOM;
-
/* Relax header if necessary */
if (sig->canon_headers == PDKIM_CANON_RELAXED)
- {
- char *relaxed_hdr = pdkim_relax_header(sig_hdr, 0);
-
- free(sig_hdr);
- if (!relaxed_hdr)
- return PDKIM_ERR_OOM;
- sig_hdr = relaxed_hdr;
- }
+ sig_hdr = pdkim_relax_header(sig_hdr, FALSE);
DEBUG(D_acl)
{
+ debug_printf("DKIM >> Signed DKIM-Signature header, canonicalized (%-7s) >>>>>>>\n",
+ pdkim_canons[sig->canon_headers]);
+ pdkim_quoteprint(CUS sig_hdr, Ustrlen(sig_hdr));
debug_printf(
- "PDKIM >> Signed DKIM-Signature header, canonicalized >>>>>>>>>>>>>>>>>\n");
- pdkim_quoteprint(CUS sig_hdr, strlen(sig_hdr));
- debug_printf(
- "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
}
/* Finalize header hash */
- exim_sha_update(&hhash_ctx, sig_hdr, strlen(sig_hdr));
+ exim_sha_update(&hhash_ctx, CUS sig_hdr, Ustrlen(sig_hdr));
exim_sha_finish(&hhash_ctx, &hhash);
DEBUG(D_acl)
{
- debug_printf("PDKIM [%s] hh computed: ", sig->domain);
+ debug_printf("DKIM [%s] Header %s computed: ",
+ sig->domain, pdkim_hashes[sig->hashtype].dkim_hashname);
pdkim_hexprint(hhash.data, hhash.len);
}
- /* Remember headers block for signing (when the library cannot do incremental) */
- if (ctx->mode == PDKIM_MODE_SIGN)
- (void) exim_rsa_data_append(&hdata, &hdata_alloc, US sig_hdr);
-
- free(sig_hdr);
+ /* Remember headers block for signing (when the signing library cannot do
+ incremental) */
+ if (ctx->flags & PDKIM_MODE_SIGN)
+ hdata = exim_dkim_data_append(hdata, US sig_hdr);
/* SIGNING ---------------------------------------------------------------- */
- if (ctx->mode == PDKIM_MODE_SIGN)
+ if (ctx->flags & PDKIM_MODE_SIGN)
{
- es_ctx sctx;
- const uschar * errstr;
-
- /* Import private key */
- if ((errstr = exim_rsa_signing_init(US sig->rsa_privkey, &sctx)))
- {
- DEBUG(D_acl) debug_printf("signing_init: %s\n", errstr);
- return PDKIM_ERR_RSA_PRIVKEY;
- }
+ hashmethod hm = sig->keytype == KEYTYPE_ED25519
+#if defined(SIGN_OPENSSL)
+ ? HASH_NULL
+#else
+ ? HASH_SHA2_512
+#endif
+ : pdkim_hashes[sig->hashtype].exim_hashmethod;
- /* Do signing. With OpenSSL we are signing the hash of headers just
- calculated, with GnuTLS we have to sign an entire block of headers
- (due to available interfaces) and it recalculates the hash internally. */
+#ifdef SIGN_HAVE_ED25519
+ /* For GCrypt, and for EC, we pass the hash-of-headers to the signing
+ routine. For anything else we just pass the headers. */
-#if defined(RSA_OPENSSL) || defined(RSA_GCRYPT)
- hdata = hhash;
+ if (sig->keytype != KEYTYPE_ED25519)
#endif
+ {
+ hhash.data = hdata->s;
+ hhash.len = hdata->ptr;
+ }
- if ((errstr = exim_rsa_sign(&sctx, is_sha1, &hdata, &sig->sigdata)))
+ if ((*err = exim_dkim_sign(&sctx, hm, &hhash, &sig->sighash)))
{
- DEBUG(D_acl) debug_printf("signing: %s\n", errstr);
+ log_write(0, LOG_MAIN|LOG_PANIC, "signing: %s", *err);
return PDKIM_ERR_RSA_SIGNING;
}
DEBUG(D_acl)
{
- debug_printf( "PDKIM [%s] b computed: ", sig->domain);
- pdkim_hexprint(sig->sigdata.data, sig->sigdata.len);
+ debug_printf( "DKIM [%s] b computed: ", sig->domain);
+ pdkim_hexprint(sig->sighash.data, sig->sighash.len);
}
- if (!(sig->signature_header = pdkim_create_header(sig, TRUE)))
- return PDKIM_ERR_OOM;
-
- /* We only ever sign with one sig, and we free'd "headernames"
- above. So to keep static-analysers happy, exit the loop explicitly.
- Perhaps the code would be more clear if signing and verification
- loops were separated? */
-
- break;
+ sig->signature_header = pdkim_create_header(sig, TRUE);
}
/* VERIFICATION ----------------------------------------------------------- */
else
{
ev_ctx vctx;
- const uschar * errstr;
-
- char *dns_txt_name, *dns_txt_reply;
-
- /* Fetch public key for signing domain, from DNS */
-
- if (!(dns_txt_name = malloc(PDKIM_DNS_TXT_MAX_NAMELEN)))
- return PDKIM_ERR_OOM;
-
- if (!(dns_txt_reply = malloc(PDKIM_DNS_TXT_MAX_RECLEN)))
+ hashmethod hm;
+
+ /* Make sure we have all required signature tags */
+ if (!( sig->domain && *sig->domain
+ && sig->selector && *sig->selector
+ && sig->headernames && *sig->headernames
+ && sig->bodyhash.data
+ && sig->sighash.data
+ && sig->keytype >= 0
+ && sig->hashtype >= 0
+ && sig->version
+ ) )
{
- free(dns_txt_name);
- return PDKIM_ERR_OOM;
- }
-
- memset(dns_txt_reply, 0, PDKIM_DNS_TXT_MAX_RECLEN);
- memset(dns_txt_name , 0, PDKIM_DNS_TXT_MAX_NAMELEN);
+ sig->verify_status = PDKIM_VERIFY_INVALID;
+ sig->verify_ext_status = PDKIM_VERIFY_INVALID_SIGNATURE_ERROR;
- if (snprintf(dns_txt_name, PDKIM_DNS_TXT_MAX_NAMELEN,
- "%s._domainkey.%s.",
- sig->selector, sig->domain) >= PDKIM_DNS_TXT_MAX_NAMELEN)
- {
- sig->verify_status = PDKIM_VERIFY_INVALID;
- sig->verify_ext_status = PDKIM_VERIFY_INVALID_BUFFER_SIZE;
+ DEBUG(D_acl) debug_printf(
+ " Error in DKIM-Signature header: tags missing or invalid (%s)\n"
+ "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n",
+ !(sig->domain && *sig->domain) ? "d="
+ : !(sig->selector && *sig->selector) ? "s="
+ : !(sig->headernames && *sig->headernames) ? "h="
+ : !sig->bodyhash.data ? "bh="
+ : !sig->sighash.data ? "b="
+ : sig->keytype < 0 || sig->hashtype < 0 ? "a="
+ : "v="
+ );
goto NEXT_VERIFY;
}
- if ( ctx->dns_txt_callback(dns_txt_name, dns_txt_reply) != PDKIM_OK
- || dns_txt_reply[0] == '\0')
+ /* Make sure sig uses supported DKIM version (only v1) */
+ if (sig->version != 1)
{
- sig->verify_status = PDKIM_VERIFY_INVALID;
- sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE;
+ sig->verify_status = PDKIM_VERIFY_INVALID;
+ sig->verify_ext_status = PDKIM_VERIFY_INVALID_DKIM_VERSION;
+
+ DEBUG(D_acl) debug_printf(
+ " Error in DKIM-Signature header: unsupported DKIM version\n"
+ "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
goto NEXT_VERIFY;
}
DEBUG(D_acl)
{
- debug_printf(
- "PDKIM >> Parsing public key record >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"
- " Raw record: ");
- pdkim_quoteprint(CUS dns_txt_reply, strlen(dns_txt_reply));
+ debug_printf( "DKIM [%s] b from mail: ", sig->domain);
+ pdkim_hexprint(sig->sighash.data, sig->sighash.len);
}
- if (!(sig->pubkey = pdkim_parse_pubkey_record(ctx, dns_txt_reply)))
+ if (!(sig->pubkey = pdkim_key_from_dns(ctx, sig, &vctx, err)))
{
- sig->verify_status = PDKIM_VERIFY_INVALID;
- sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD;
-
- DEBUG(D_acl) debug_printf(
- " Error while parsing public key record\n"
- "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ log_write(0, LOG_MAIN, "DKIM: %s%s %s%s [failed key import]",
+ sig->domain ? "d=" : "", sig->domain ? sig->domain : US"",
+ sig->selector ? "s=" : "", sig->selector ? sig->selector : US"");
goto NEXT_VERIFY;
}
- DEBUG(D_acl) debug_printf(
- "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ /* If the pubkey limits to a list of specific hashes, ignore sigs that
+ do not have the hash part of the sig algorithm matching */
- /* Import public key */
- if ((errstr = exim_rsa_verify_init(&sig->pubkey->key, &vctx)))
+ if (sig->pubkey->hashes)
{
- DEBUG(D_acl) debug_printf("verify_init: %s\n", errstr);
- sig->verify_status = PDKIM_VERIFY_INVALID;
- sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_IMPORT;
- goto NEXT_VERIFY;
+ const uschar * list = sig->pubkey->hashes, * ele;
+ int sep = ':';
+ while ((ele = string_nextinlist(&list, &sep, NULL, 0)))
+ if (Ustrcmp(ele, pdkim_hashes[sig->hashtype].dkim_hashname) == 0) break;
+ if (!ele)
+ {
+ DEBUG(D_acl) debug_printf("pubkey h=%s vs. sig a=%s_%s\n",
+ sig->pubkey->hashes,
+ pdkim_keytypes[sig->keytype],
+ pdkim_hashes[sig->hashtype].dkim_hashname);
+ sig->verify_status = PDKIM_VERIFY_FAIL;
+ sig->verify_ext_status = PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH;
+ goto NEXT_VERIFY;
+ }
}
+ hm = sig->keytype == KEYTYPE_ED25519
+#if defined(SIGN_OPENSSL)
+ ? HASH_NULL
+#else
+ ? HASH_SHA2_512
+#endif
+ : pdkim_hashes[sig->hashtype].exim_hashmethod;
+
/* Check the signature */
- if ((errstr = exim_rsa_verify(&vctx, is_sha1, &hhash, &sig->sigdata)))
+
+ if ((*err = exim_dkim_verify(&vctx, hm, &hhash, &sig->sighash)))
{
- DEBUG(D_acl) debug_printf("headers verify: %s\n", errstr);
+ DEBUG(D_acl) debug_printf("headers verify: %s\n", *err);
sig->verify_status = PDKIM_VERIFY_FAIL;
sig->verify_ext_status = PDKIM_VERIFY_FAIL_MESSAGE;
goto NEXT_VERIFY;
}
- /* We have a winner! (if bodydhash was correct earlier) */
+ /* We have a winner! (if bodyhash was correct earlier) */
if (sig->verify_status == PDKIM_VERIFY_NONE)
+ {
sig->verify_status = PDKIM_VERIFY_PASS;
+ verify_pass = TRUE;
+ if (dkim_verify_minimal) break;
+ }
NEXT_VERIFY:
DEBUG(D_acl)
{
- debug_printf("PDKIM [%s] signature status: %s",
- sig->domain, pdkim_verify_status_str(sig->verify_status));
+ debug_printf("DKIM [%s] %s signature status: %s",
+ sig->domain, dkim_sig_to_a_tag(sig),
+ pdkim_verify_status_str(sig->verify_status));
if (sig->verify_ext_status > 0)
debug_printf(" (%s)\n",
pdkim_verify_ext_status_str(sig->verify_ext_status));
else
debug_printf("\n");
}
-
- free(dns_txt_name);
- free(dns_txt_reply);
}
-
- sig = sig->next;
}
/* If requested, set return pointer to signature(s) */
if (return_signatures)
*return_signatures = ctx->sig;
-return PDKIM_OK;
+return ctx->flags & PDKIM_MODE_SIGN || verify_pass
+ ? PDKIM_OK : PDKIM_FAIL;
}
/* -------------------------------------------------------------------------- */
DLLEXPORT pdkim_ctx *
-pdkim_init_verify(int(*dns_txt_callback)(char *, char *))
+pdkim_init_verify(uschar * (*dns_txt_callback)(const uschar *), BOOL dot_stuffing)
{
-pdkim_ctx *ctx = malloc(sizeof(pdkim_ctx));
+pdkim_ctx * ctx;
-if (!ctx)
- return NULL;
+ctx = store_get(sizeof(pdkim_ctx), FALSE);
memset(ctx, 0, sizeof(pdkim_ctx));
-if (!(ctx->linebuf = malloc(PDKIM_MAX_BODY_LINE_LEN)))
- {
- free(ctx);
- return NULL;
- }
-
-ctx->mode = PDKIM_MODE_VERIFY;
+if (dot_stuffing) ctx->flags = PDKIM_DOT_TERM;
+/* The line-buffer is for message data, hence tainted */
+ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN, TRUE);
ctx->dns_txt_callback = dns_txt_callback;
return ctx;
/* -------------------------------------------------------------------------- */
-DLLEXPORT pdkim_ctx *
-pdkim_init_sign(char *domain, char *selector, char *rsa_privkey, int algo)
+DLLEXPORT pdkim_signature *
+pdkim_init_sign(pdkim_ctx * ctx,
+ uschar * domain, uschar * selector, uschar * privkey,
+ uschar * hashname, const uschar ** errstr)
{
-pdkim_ctx *ctx;
-pdkim_signature *sig;
+int hashtype;
+pdkim_signature * sig;
-if (!domain || !selector || !rsa_privkey)
+if (!domain || !selector || !privkey)
return NULL;
-if (!(ctx = malloc(sizeof(pdkim_ctx))))
- return NULL;
-memset(ctx, 0, sizeof(pdkim_ctx));
-
-if (!(ctx->linebuf = malloc(PDKIM_MAX_BODY_LINE_LEN)))
- {
- free(ctx);
- return NULL;
- }
+/* Allocate & init one signature struct */
-if (!(sig = malloc(sizeof(pdkim_signature))))
- {
- free(ctx->linebuf);
- free(ctx);
- return NULL;
- }
+sig = store_get(sizeof(pdkim_signature), FALSE);
memset(sig, 0, sizeof(pdkim_signature));
sig->bodylength = -1;
-ctx->mode = PDKIM_MODE_SIGN;
-ctx->sig = sig;
+sig->domain = string_copy(US domain);
+sig->selector = string_copy(US selector);
+sig->privkey = string_copy(US privkey);
+sig->keytype = -1;
-sig->domain = strdup(domain);
-sig->selector = strdup(selector);
-sig->rsa_privkey = strdup(rsa_privkey);
-sig->algo = algo;
-
-if (!sig->domain || !sig->selector || !sig->rsa_privkey)
- goto BAIL;
+for (hashtype = 0; hashtype < nelem(pdkim_hashes); hashtype++)
+ if (Ustrcmp(hashname, pdkim_hashes[hashtype].dkim_hashname) == 0)
+ { sig->hashtype = hashtype; break; }
+if (hashtype >= nelem(pdkim_hashes))
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "DKIM: unrecognised hashname '%s'", hashname);
+ return NULL;
+ }
-exim_sha_init(&sig->body_hash, algo == PDKIM_ALGO_RSA_SHA1);
-return ctx;
+DEBUG(D_acl)
+ {
+ pdkim_signature s = *sig;
+ ev_ctx vctx;
-BAIL:
- pdkim_free_ctx(ctx);
- return NULL;
+ debug_printf("DKIM (checking verify key)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
+ if (!pdkim_key_from_dns(ctx, &s, &vctx, errstr))
+ debug_printf("WARNING: bad dkim key in dns\n");
+ debug_printf("DKIM (finished checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ }
+return sig;
}
/* -------------------------------------------------------------------------- */
-DLLEXPORT int
-pdkim_set_optional(pdkim_ctx *ctx,
- char *sign_headers,
- char *identity,
+DLLEXPORT void
+pdkim_set_optional(pdkim_signature * sig,
+ char * sign_headers,
+ char * identity,
int canon_headers,
int canon_body,
long bodylength,
unsigned long created,
unsigned long expires)
{
-pdkim_signature * sig = ctx->sig;
-
if (identity)
- if (!(sig->identity = strdup(identity)))
- return PDKIM_ERR_OOM;
+ sig->identity = string_copy(US identity);
-if (!(sig->sign_headers = strdup(sign_headers
- ? sign_headers : PDKIM_DEFAULT_SIGN_HEADERS)))
- return PDKIM_ERR_OOM;
+sig->sign_headers = string_copy(sign_headers
+ ? US sign_headers : US PDKIM_DEFAULT_SIGN_HEADERS);
sig->canon_headers = canon_headers;
sig->canon_body = canon_body;
sig->created = created;
sig->expires = expires;
-return PDKIM_OK;
+return;
+}
+
+
+
+/* Set up a blob for calculating the bodyhash according to the
+given needs. Use an existing one if possible, or create a new one.
+
+Return: hashblob pointer, or NULL on error
+*/
+pdkim_bodyhash *
+pdkim_set_bodyhash(pdkim_ctx * ctx, int hashtype, int canon_method,
+ long bodylength)
+{
+pdkim_bodyhash * b;
+
+if (hashtype == -1 || canon_method == -1) return NULL;
+
+for (b = ctx->bodyhash; b; b = b->next)
+ if ( hashtype == b->hashtype
+ && canon_method == b->canon_method
+ && bodylength == b->bodylength)
+ {
+ DEBUG(D_receive) debug_printf("DKIM: using existing bodyhash %d/%d/%ld\n",
+ hashtype, canon_method, bodylength);
+ return b;
+ }
+
+DEBUG(D_receive) debug_printf("DKIM: new bodyhash %d/%d/%ld\n",
+ hashtype, canon_method, bodylength);
+b = store_get(sizeof(pdkim_bodyhash), FALSE);
+b->next = ctx->bodyhash;
+b->hashtype = hashtype;
+b->canon_method = canon_method;
+b->bodylength = bodylength;
+if (!exim_sha_init(&b->body_hash_ctx, /*XXX hash method: extend for sha512 */
+ pdkim_hashes[hashtype].exim_hashmethod))
+ {
+ DEBUG(D_acl)
+ debug_printf("DKIM: hash init error, possibly nonhandled hashtype\n");
+ return NULL;
+ }
+b->signed_body_bytes = 0;
+b->num_buffered_blanklines = 0;
+ctx->bodyhash = b;
+return b;
+}
+
+
+/* Set up a blob for calculating the bodyhash according to the
+needs of this signature. Use an existing one if possible, or
+create a new one.
+
+Return: hashblob pointer, or NULL on error (only used as a boolean).
+*/
+pdkim_bodyhash *
+pdkim_set_sig_bodyhash(pdkim_ctx * ctx, pdkim_signature * sig)
+{
+pdkim_bodyhash * b = pdkim_set_bodyhash(ctx,
+ sig->hashtype, sig->canon_body, sig->bodylength);
+sig->calc_body_hash = b;
+return b;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void
+pdkim_init_context(pdkim_ctx * ctx, BOOL dot_stuffed,
+ uschar * (*dns_txt_callback)(const uschar *))
+{
+memset(ctx, 0, sizeof(pdkim_ctx));
+ctx->flags = dot_stuffed ? PDKIM_MODE_SIGN | PDKIM_DOT_TERM : PDKIM_MODE_SIGN;
+/* The line buffer is for message data, hence tainted */
+ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN, TRUE);
+DEBUG(D_acl) ctx->dns_txt_callback = dns_txt_callback;
}
void
pdkim_init(void)
{
-exim_rsa_init();
+exim_dkim_init();
}