#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"
const uschar * pdkim_keytypes[] = {
[KEYTYPE_RSA] = US"rsa",
#ifdef SIGN_HAVE_ED25519
- [KEYTYPE_ED25519] = US"ed25519", /* Works for 3.6.0 GnuTLS */
+ [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 */
int
pdkim_hashname_to_hashtype(const uschar * s, unsigned len)
{
-int i;
if (!len) len = Ustrlen(s);
-for (i = 0; i < nelem(pdkim_hashes); i++)
+for (int i = 0; i < nelem(pdkim_hashes); i++)
if (Ustrncmp(s, pdkim_hashes[i].dkim_hashname, len) == 0)
return i;
return -1;
pdkim_cstring_to_canons(const uschar * s, unsigned len,
int * canon_head, int * canon_body)
{
-int i;
if (!len) len = Ustrlen(s);
-for (i = 0; pdkim_combined_canons[i].str; i++)
+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))
{
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)";
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)
void
pdkim_hexprint(const uschar *data, int len)
{
-int i;
-if (data) 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");
}
{
BOOL past_field_name = FALSE;
BOOL seen_wsp = FALSE;
-const uschar * p;
uschar * relaxed = store_get(len+3);
uschar * q = relaxed;
-for (p = header; p - header < len; p++)
+for (const uschar * p = header; p - header < len; p++)
{
uschar c = *p;
uschar *
pdkim_encode_base64(blob * b)
{
-return b64encode(b->data, b->len);
+return b64encode(CUS b->data, b->len);
}
pdkim_parse_sig_header(pdkim_ctx * ctx, uschar * raw_hdr)
{
pdkim_signature * sig;
-uschar *p, *q;
+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;
sig = store_get(sizeof(pdkim_signature));
memset(sig, 0, sizeof(pdkim_signature));
q = sig->rawsig_no_b_val = store_get(Ustrlen(raw_hdr)+1);
-for (p = raw_hdr; ; p++)
+for (uschar * p = raw_hdr; ; p++)
{
char c = *p;
break;
case 'a': /* algorithm */
{
- uschar * s = Ustrchr(cur_val->s, '-');
-
- for(i = 0; i < nelem(pdkim_keytypes); i++)
- if (Ustrncmp(cur_val->s, pdkim_keytypes[i], s - cur_val->s) == 0)
- { sig->keytype = i; break; }
- if (sig->keytype < 0)
- log_write(0, LOG_MAIN,
- "DKIM: ignoring signature due to nonhandled keytype in a=%s",
- cur_val->s);
-
- sig->hashtype = pdkim_hashname_to_hashtype(++s, 0);
- break;
+ const uschar * list = cur_val->s;
+ int sep = '-';
+ uschar * elem;
+
+ if ((elem = string_nextinlist(&list, &sep, NULL, 0)))
+ for (int i = 0; i < nelem(pdkim_keytypes); i++)
+ if (Ustrcmp(elem, pdkim_keytypes[i]) == 0)
+ { sig->keytype = i; break; }
+ 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 */
&sig->canon_headers, &sig->canon_body);
break;
case 'q': /* Query method (for pubkey)*/
- for (i = 0; pdkim_querymethods[i]; i++)
+ for (int i = 0; pdkim_querymethods[i]; i++)
if (Ustrcmp(cur_val->s, pdkim_querymethods[i]) == 0)
{
sig->querymethod = i; /* we never actually use this */
if (!relaxed_data)
{
BOOL seen_wsp = FALSE;
- const uschar * p, * r;
int q = 0;
/* We want to be able to free this else we allocate
relaxed_data = store_malloc(sizeof(blob) + orig_data->len+1);
relaxed_data->data = US (relaxed_data+1);
- for (p = orig_data->data, r = p + orig_data->len; p < r; p++)
+ for (const uschar * p = orig_data->data, * r = p + orig_data->len; p < r; p++)
{
char c = *p;
if (c == '\r')
static void
pdkim_finish_bodyhash(pdkim_ctx * ctx)
{
-pdkim_bodyhash * b;
-pdkim_signature * sig;
-
-for (b = ctx->bodyhash; b; b = b->next) /* Finish hashes */
+for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next) /* Finish hashes */
{
- DEBUG(D_acl) debug_printf("PDKIM: finish bodyhash %d/%d/%d len %ld\n",
+ DEBUG(D_acl) debug_printf("PDKIM: 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)
+for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
{
- b = sig->calc_body_hash;
+ pdkim_bodyhash * b = sig->calc_body_hash;
DEBUG(D_acl)
{
static void
pdkim_body_complete(pdkim_ctx * ctx)
{
-pdkim_bodyhash * b;
-
/* 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 (b = ctx->bodyhash; b; b = b->next)
+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
pdkim_bodyline_complete(pdkim_ctx * ctx)
{
blob line = {.data = ctx->linebuf, .len = ctx->linebuf_offset};
-pdkim_bodyhash * b;
blob * rnl = NULL;
blob * rline = NULL;
/* Empty lines need to be buffered until we find a non-empty line */
if (memcmp(line.data, "\r\n", 2) == 0)
{
- for (b = ctx->bodyhash; b; b = b->next) b->num_buffered_blanklines++;
+ for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next)
+ b->num_buffered_blanklines++;
goto all_skip;
}
/* Process line for each bodyhash separately */
-for (b = ctx->bodyhash; b; b = b->next)
+for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next)
{
if (b->canon_method == PDKIM_CANON_RELAXED)
{
static int
pdkim_header_complete(pdkim_ctx * ctx)
{
-pdkim_signature * sig, * last_sig;
-
-/* Special case: The last header can have an extra \r appended */
if ( (ctx->cur_header->ptr > 1) &&
(ctx->cur_header->s[ctx->cur_header->ptr-1] == '\r') )
--ctx->cur_header->ptr;
/* SIGNING -------------------------------------------------------------- */
if (ctx->flags & PDKIM_MODE_SIGN)
- for (sig = ctx->sig; sig; sig = sig->next) /* Traverse all signatures */
+ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next) /* Traverse all signatures */
/* Add header to the signed headers list (in reverse order) */
sig->headers = pdkim_prepend_stringlist(sig->headers, ctx->cur_header->s);
DKIM_SIGNATURE_HEADERNAME,
Ustrlen(DKIM_SIGNATURE_HEADERNAME)) == 0)
{
+ 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. */
while (last_sig->next) last_sig = last_sig->next;
last_sig->next = sig;
}
+
+ 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 */
DLLEXPORT int
pdkim_feed(pdkim_ctx * ctx, uschar * data, int len)
{
-int p, rc;
-
/* Alternate EOD signal, used in non-dotstuffing mode */
if (!data)
pdkim_body_complete(ctx);
-else for (p = 0; p<len; p++)
+else for (int p = 0; p < len; p++)
{
uschar c = data[p];
+ int rc;
if (ctx->flags & PDKIM_PAST_HDRS)
{
/* -------------------------------------------------------------------------- */
+/* 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("PDKIM: 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)
/* Import public key */
+/* 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)
+ {
+ for(int i = 0; i < nelem(pdkim_keytypes); i++)
+ if (Ustrcmp(p->keytype, pdkim_keytypes[i]) == 0)
+ { sig->keytype = i; goto k_ok; }
+ 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;
+ }
+k_ok:
+
+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)))
pdkim_feed_finish(pdkim_ctx * ctx, pdkim_signature ** return_signatures,
const uschar ** err)
{
-pdkim_bodyhash * b;
-pdkim_signature * sig;
BOOL verify_pass = FALSE;
/* Check if we must still flush a (partial) header. If that is the
if ((rc = pdkim_header_complete(ctx)) != PDKIM_OK)
return rc;
- for (b = ctx->bodyhash; b; b = b->next)
+ for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next)
rnl = pdkim_update_ctx_bodyhash(b, &lineending, rnl);
if (rnl) store_free(rnl);
}
return PDKIM_OK;
}
-for (sig = ctx->sig; sig; sig = sig->next)
+for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
{
hctx hhash_ctx;
uschar * sig_hdr = US"";
if (ctx->flags & PDKIM_MODE_SIGN)
{
gstring * g = NULL;
- pdkim_stringlist *p;
const uschar * l;
uschar * s;
int sep = 0;
}
sig->keytype = sctx.keytype;
- for (sig->headernames = NULL, /* Collected signed header names */
- p = sig->headers; p; p = p->next)
+ sig->headernames = NULL; /* Collected signed header names */
+ for (pdkim_stringlist * p = sig->headers; p; p = p->next)
{
uschar * rh = p->value;
{
uschar * p = sig->headernames;
uschar * q;
- pdkim_stringlist * hdrs;
if (p)
{
/* clear tags */
- for (hdrs = ctx->headers; hdrs; hdrs = hdrs->next)
+ for (pdkim_stringlist * hdrs = ctx->headers; hdrs; hdrs = hdrs->next)
hdrs->tag = 0;
p = string_copy(p);
*q = '\0';
/*XXX walk the list of headers in same order as received. */
- for (hdrs = ctx->headers; hdrs; hdrs = hdrs->next)
+ 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)] == ':'
if (ctx->flags & PDKIM_MODE_SIGN)
{
hashmethod hm = sig->keytype == KEYTYPE_ED25519
- ? HASH_SHA2_512 : pdkim_hashes[sig->hashtype].exim_hashmethod;
+#if defined(SIGN_OPENSSL)
+ ? HASH_NULL
+#else
+ ? HASH_SHA2_512
+#endif
+ : pdkim_hashes[sig->hashtype].exim_hashmethod;
#ifdef SIGN_HAVE_ED25519
/* For GCrypt, and for EC, we pass the hash-of-headers to the signing
hhash.len = hdata->ptr;
}
-/*XXX extend for non-RSA algos */
-/*- done for GnuTLS */
if ((*err = exim_dkim_sign(&sctx, hm, &hhash, &sig->sighash)))
{
log_write(0, LOG_MAIN|LOG_PANIC, "signing: %s", *err);
else
{
ev_ctx vctx;
+ hashmethod hm;
/* Make sure we have all required signature tags */
if (!( sig->domain && *sig->domain
sig->verify_ext_status = PDKIM_VERIFY_INVALID_SIGNATURE_ERROR;
DEBUG(D_acl) debug_printf(
- " Error in DKIM-Signature header: tags missing or invalid\n"
- "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ " Error in DKIM-Signature header: tags missing or invalid (%s)\n"
+ "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\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;
}
-
+
/* Make sure sig uses supported DKIM version (only v1) */
if (sig->version != 1)
{
}
}
+ 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 */
-/*XXX extend for non-RSA algos */
-/*- done for GnuTLS */
- if ((*err = exim_dkim_verify(&vctx,
- pdkim_hashes[sig->hashtype].exim_hashmethod,
- &hhash, &sig->sighash)))
+
+ if ((*err = exim_dkim_verify(&vctx, hm, &hhash, &sig->sighash)))
{
DEBUG(D_acl) debug_printf("headers verify: %s\n", *err);
sig->verify_status = PDKIM_VERIFY_FAIL;