X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/e220ba1dbf0c31fdc639128384dffe9337a505ac..fc2ba7b9fae5992dd76f721f283714a6d2ea137d:/src/src/pdkim/pdkim.c diff --git a/src/src/pdkim/pdkim.c b/src/src/pdkim/pdkim.c index 381bdbc5d..f10f20627 100644 --- a/src/src/pdkim/pdkim.c +++ b/src/src/pdkim/pdkim.c @@ -26,8 +26,8 @@ #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" @@ -80,7 +80,7 @@ const pdkim_hashtype pdkim_hashes[] = { 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 */ @@ -124,9 +124,8 @@ return string_sprintf("%s-%s", 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; @@ -136,9 +135,8 @@ void 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)) { @@ -192,6 +190,7 @@ switch(status) 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)"; @@ -204,8 +203,7 @@ switch(status) 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) @@ -230,8 +228,7 @@ debug_printf("\n"); 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(""); debug_printf("\n"); } @@ -241,7 +238,7 @@ debug_printf("\n"); static pdkim_stringlist * pdkim_prepend_stringlist(pdkim_stringlist * base, const uschar * str) { -pdkim_stringlist * new_entry = store_get(sizeof(pdkim_stringlist)); +pdkim_stringlist * new_entry = store_get(sizeof(pdkim_stringlist), FALSE); memset(new_entry, 0, sizeof(pdkim_stringlist)); new_entry->value = string_copy(str); @@ -331,11 +328,10 @@ pdkim_relax_header_n(const uschar * header, int len, BOOL append_crlf) { BOOL past_field_name = FALSE; BOOL seen_wsp = FALSE; -const uschar * p; -uschar * relaxed = store_get(len+3); +uschar * relaxed = store_get(len+3, TRUE); /* tainted */ uschar * q = relaxed; -for (p = header; p - header < len; p++) +for (const uschar * p = header; p - header < len; p++) { uschar c = *p; @@ -412,7 +408,7 @@ pdkim_decode_qp(const uschar * str) int nchar = 0; uschar * q; const uschar * p = str; -uschar * n = store_get(Ustrlen(str)+1); +uschar * n = store_get(Ustrlen(str)+1, TRUE); *n = '\0'; q = n; @@ -449,7 +445,7 @@ b->len = dlen; uschar * pdkim_encode_base64(blob * b) { -return b64encode(b->data, b->len); +return b64encode(CUS b->data, b->len); } @@ -462,15 +458,14 @@ static pdkim_signature * 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)); +sig = store_get(sizeof(pdkim_signature), FALSE); memset(sig, 0, sizeof(pdkim_signature)); sig->bodylength = -1; @@ -479,9 +474,9 @@ sig->version = 0; sig->keytype = -1; sig->hashtype = -1; -q = sig->rawsig_no_b_val = store_get(Ustrlen(raw_hdr)+1); +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; @@ -566,11 +561,11 @@ for (p = raw_hdr; ; p++) uschar * elem; if ((elem = string_nextinlist(&list, &sep, NULL, 0))) - for(i = 0; i < nelem(pdkim_keytypes); i++) + 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 (i = 0; i < nelem(pdkim_hashes); i++) + for (int i = 0; i < nelem(pdkim_hashes); i++) if (Ustrcmp(elem, pdkim_hashes[i].dkim_hashname) == 0) { sig->hashtype = i; break; } } @@ -580,7 +575,7 @@ for (p = raw_hdr; ; p++) &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 */ @@ -661,7 +656,7 @@ const uschar * ele; int sep = ';'; pdkim_pubkey * pub; -pub = store_get(sizeof(pdkim_pubkey)); +pub = store_get(sizeof(pdkim_pubkey), TRUE); /* tainted */ memset(pub, 0, sizeof(pdkim_pubkey)); while ((ele = string_nextinlist(&raw_record, &sep, NULL, 0))) @@ -729,7 +724,6 @@ if (b->canon_method == PDKIM_CANON_RELAXED) 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 @@ -740,7 +734,7 @@ if (b->canon_method == PDKIM_CANON_RELAXED) 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') @@ -787,20 +781,17 @@ return relaxed_data; 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) { @@ -848,15 +839,13 @@ for (sig = ctx->sig; sig; sig = sig->next) 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 @@ -877,7 +866,6 @@ static void pdkim_bodyline_complete(pdkim_ctx * ctx) { blob line = {.data = ctx->linebuf, .len = ctx->linebuf_offset}; -pdkim_bodyhash * b; blob * rnl = NULL; blob * rline = NULL; @@ -901,12 +889,13 @@ if (ctx->flags & PDKIM_DOT_TERM) /* 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) { @@ -955,9 +944,6 @@ return; 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; @@ -972,7 +958,7 @@ if (++ctx->num_headers > PDKIM_MAX_HEADERS) goto BAIL; /* 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); @@ -992,6 +978,7 @@ else 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. */ @@ -1008,6 +995,13 @@ else 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 */ @@ -1027,15 +1021,14 @@ return PDKIM_OK; 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; pflags & PDKIM_PAST_HDRS) { @@ -1336,6 +1329,28 @@ 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("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) @@ -1397,8 +1412,7 @@ instead. Assume writing on the sig is ok in that case. */ if (sig->keytype < 0) { - int i; - for(i = 0; i < nelem(pdkim_keytypes); i++) + 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); @@ -1408,6 +1422,9 @@ if (sig->keytype < 0) } 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))) @@ -1429,8 +1446,6 @@ DLLEXPORT int 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 @@ -1444,7 +1459,7 @@ if (ctx->cur_header && ctx->cur_header->ptr > 0) 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); } @@ -1464,7 +1479,7 @@ if (!ctx->sig) 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""; @@ -1528,7 +1543,6 @@ for (sig = ctx->sig; sig; sig = sig->next) if (ctx->flags & PDKIM_MODE_SIGN) { gstring * g = NULL; - pdkim_stringlist *p; const uschar * l; uschar * s; int sep = 0; @@ -1544,8 +1558,8 @@ for (sig = ctx->sig; sig; sig = sig->next) } 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; @@ -1592,12 +1606,11 @@ for (sig = ctx->sig; sig; sig = sig->next) { 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); @@ -1607,7 +1620,7 @@ for (sig = ctx->sig; sig; sig = sig->next) *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)] == ':' @@ -1734,11 +1747,19 @@ for (sig = ctx->sig; sig; sig = sig->next) 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) { @@ -1840,15 +1861,16 @@ return ctx->flags & PDKIM_MODE_SIGN || verify_pass /* -------------------------------------------------------------------------- */ DLLEXPORT pdkim_ctx * -pdkim_init_verify(uschar * (*dns_txt_callback)(uschar *), BOOL dot_stuffing) +pdkim_init_verify(uschar * (*dns_txt_callback)(const uschar *), BOOL dot_stuffing) { pdkim_ctx * ctx; -ctx = store_get(sizeof(pdkim_ctx)); +ctx = store_get(sizeof(pdkim_ctx), FALSE); memset(ctx, 0, sizeof(pdkim_ctx)); if (dot_stuffing) ctx->flags = PDKIM_DOT_TERM; -ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN); +/* 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; @@ -1870,7 +1892,7 @@ if (!domain || !selector || !privkey) /* Allocate & init one signature struct */ -sig = store_get(sizeof(pdkim_signature)); +sig = store_get(sizeof(pdkim_signature), FALSE); memset(sig, 0, sizeof(pdkim_signature)); sig->bodylength = -1; @@ -1956,7 +1978,7 @@ for (b = ctx->bodyhash; b; b = b->next) DEBUG(D_receive) debug_printf("PDKIM: new bodyhash %d/%d/%ld\n", hashtype, canon_method, bodylength); -b = store_get(sizeof(pdkim_bodyhash)); +b = store_get(sizeof(pdkim_bodyhash), FALSE); b->next = ctx->bodyhash; b->hashtype = hashtype; b->canon_method = canon_method; @@ -1996,11 +2018,12 @@ return b; void pdkim_init_context(pdkim_ctx * ctx, BOOL dot_stuffed, - uschar * (*dns_txt_callback)(uschar *)) + 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; -ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN); +/* 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; }