X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/6f47da8d2d526953e8e6403f448d1598c9140df1..a85c067ba6c6940512cf57ec213277a370d87e70:/src/src/pdkim/pdkim.c diff --git a/src/src/pdkim/pdkim.c b/src/src/pdkim/pdkim.c index 7fcfbc76a..eb26b3864 100644 --- a/src/src/pdkim/pdkim.c +++ b/src/src/pdkim/pdkim.c @@ -1,8 +1,10 @@ /* * PDKIM - a RFC4871 (DKIM) implementation * + * Copyright (c) The Exim Maintainers 2021 - 2022 * Copyright (C) 2009 - 2016 Tom Kistner - * Copyright (C) 2016 - 2018 Jeremy Harris + * Copyright (C) 2016 - 2020 Jeremy Harris + * SPDX-License-Identifier: GPL-2.0-or-later * * http://duncanthrax.net/pdkim/ * @@ -107,7 +109,7 @@ pdkim_combined_canon_entry pdkim_combined_canons[] = { }; -static blob lineending = {.data = US"\r\n", .len = 2}; +static const blob lineending = {.data = US"\r\n", .len = 2}; /* -------------------------------------------------------------------------- */ uschar * @@ -181,6 +183,7 @@ switch(ext_status) 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_PUBKEY_KEYSIZE: return "PDKIM_VERIFY_INVALID_PUBKEY_KEYSIZE"; 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"; @@ -246,7 +249,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), FALSE); +pdkim_stringlist * new_entry = store_get(sizeof(pdkim_stringlist), GET_UNTAINTED); memset(new_entry, 0, sizeof(pdkim_stringlist)); new_entry->value = string_copy(str); @@ -336,7 +339,7 @@ pdkim_relax_header_n(const uschar * header, int len, BOOL append_crlf) { BOOL past_field_name = FALSE; BOOL seen_wsp = FALSE; -uschar * relaxed = store_get(len+3, TRUE); /* tainted */ +uschar * relaxed = store_get(len+3, GET_TAINTED); uschar * q = relaxed; for (const uschar * p = header; p - header < len; p++) @@ -416,7 +419,7 @@ pdkim_decode_qp(const uschar * str) int nchar = 0; uschar * q; const uschar * p = str; -uschar * n = store_get(Ustrlen(str)+1, TRUE); +uschar * n = store_get(Ustrlen(str)+1, GET_TAINTED); *n = '\0'; q = n; @@ -473,7 +476,7 @@ BOOL past_hname = FALSE; BOOL in_b_val = FALSE; int where = PDKIM_HDR_LIMBO; -sig = store_get(sizeof(pdkim_signature), FALSE); +sig = store_get(sizeof(pdkim_signature), GET_UNTAINTED); memset(sig, 0, sizeof(pdkim_signature)); sig->bodylength = -1; @@ -482,7 +485,7 @@ sig->version = 0; sig->keytype = -1; sig->hashtype = -1; -q = sig->rawsig_no_b_val = store_get(Ustrlen(raw_hdr)+1, TRUE); /* tainted */ +q = sig->rawsig_no_b_val = store_get(Ustrlen(raw_hdr)+1, GET_TAINTED); for (uschar * p = raw_hdr; ; p++) { @@ -662,7 +665,7 @@ const uschar * ele; int sep = ';'; pdkim_pubkey * pub; -pub = store_get(sizeof(pdkim_pubkey), TRUE); /* tainted */ +pub = store_get(sizeof(pdkim_pubkey), GET_TAINTED); memset(pub, 0, sizeof(pdkim_pubkey)); while ((ele = string_nextinlist(&raw_record, &sep, NULL, 0))) @@ -719,9 +722,11 @@ return NULL; 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_update_ctx_bodyhash(pdkim_bodyhash * b, const blob * orig_data, blob * relaxed_data) { -blob * canon_data = orig_data; +const blob * canon_data = orig_data; +size_t left; + /* Defaults to simple canon (no further treatment necessary) */ if (b->canon_method == PDKIM_CANON_RELAXED) @@ -766,16 +771,17 @@ if (b->canon_method == PDKIM_CANON_RELAXED) } /* Make sure we don't exceed the to-be-signed body length */ +left = canon_data->len; if ( b->bodylength >= 0 - && b->signed_body_bytes + (unsigned long)canon_data->len > b->bodylength + && left > (unsigned long)b->bodylength - b->signed_body_bytes ) - canon_data->len = b->bodylength - b->signed_body_bytes; + left = (unsigned long)b->bodylength - b->signed_body_bytes; -if (canon_data->len > 0) +if (left > 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); + exim_sha_update(&b->body_hash_ctx, CUS canon_data->data, left); + b->signed_body_bytes += left; + DEBUG(D_acl) pdkim_quoteprint(canon_data->data, left); } return relaxed_data; @@ -789,8 +795,9 @@ pdkim_finish_bodyhash(pdkim_ctx * ctx) { 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); + DEBUG(D_acl) debug_printf("DKIM: finish bodyhash %s/%s/%ld len %ld\n", + pdkim_hashes[b->hashtype].dkim_hashname, pdkim_canons[b->canon_method], + b->bodylength, b->signed_body_bytes); exim_sha_finish(&b->body_hash_ctx, &b->bh); } @@ -801,10 +808,10 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next) DEBUG(D_acl) { - 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); + debug_printf("DKIM [%s]%s Body bytes (%s) hashed: %lu\n" + "DKIM [%s]%s Body %s computed: ", + sig->domain, sig->selector, pdkim_canons[b->canon_method], b->signed_body_bytes, + sig->domain, sig->selector, pdkim_hashes[b->hashtype].dkim_hashname); pdkim_hexprint(CUS b->bh.data, b->bh.len); } @@ -821,7 +828,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next) /* VERIFICATION --------------------------------------------------------- */ /* Be careful that the header sig included a bodyash */ - if ( sig->bodyhash.data + if (sig->bodyhash.data && sig->bodyhash.len == b->bh.len && memcmp(b->bh.data, sig->bodyhash.data, b->bh.len) == 0) { DEBUG(D_acl) debug_printf("DKIM [%s] Body hash compared OK\n", sig->domain); @@ -1002,7 +1009,7 @@ else last_sig->next = sig; } - if (--dkim_collect_input == 0) + if (dkim_collect_input && --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'; @@ -1110,14 +1117,14 @@ 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 uschar * (not nul-terminated) + * Returns gstring (not nul-terminated) appending to one supplied * * col: this int holds and receives column number (octets since last '\n') * str: partial string to append to - * pad: padding, split line or space after before or after eg: ";" + * pad: padding, split line or space after before or after eg: ";". + * Only the initial charater is used. * intro: - must join to payload eg "h=", usually the tag name * payload: eg base64 data - long data can be split arbitrarily. * @@ -1126,7 +1133,7 @@ return string_catn(str, US"\r\n\t", 3); * pairs and inside long values. it also always spaces or breaks after the * "pad" * - * no guarantees are made for output given out-of range input. like tag + * No guarantees are made for output given out-of range input. like tag * names longer than 78, or bogus col. Input is assumed to be free of line breaks. */ @@ -1134,92 +1141,64 @@ static gstring * pdkim_headcat(int * col, gstring * str, const uschar * pad, const uschar * intro, const uschar * payload) { -size_t l; - -if (pad) - { - l = Ustrlen(pad); - if (*col + l > 78) - str = pdkim_hdr_cont(str, col); - str = string_catn(str, pad, l); - *col += l; - } - -l = (pad?1:0) + (intro?Ustrlen(intro):0); - -if (*col + l > 78) - { /*can't fit intro - start a new line to make room.*/ - str = pdkim_hdr_cont(str, col); - l = intro?Ustrlen(intro):0; - } - -l += payload ? Ustrlen(payload):0 ; - -while (l>77) - { /* this fragment will not fit on a single line */ - if (pad) - { - str = string_catn(str, US" ", 1); - *col += 1; - pad = NULL; /* only want this once */ - l--; - } - - if (intro) - { - size_t sl = Ustrlen(intro); +int len, chomp, padded = 0; - str = string_catn(str, intro, sl); - *col += sl; - l -= sl; - intro = NULL; /* only want this once */ - } +/* If we can fit at least the pad at the end of current line, do it now. +Otherwise, wrap if there is a pad. */ - if (payload) +if (pad) + if (*col + 1 <= 78) { - size_t sl = Ustrlen(payload); - size_t chomp = *col+sl < 77 ? sl : 78-*col; - - str = string_catn(str, payload, chomp); - *col += chomp; - payload += chomp; - l -= chomp-1; + str = string_catn(str, pad, 1); + (*col)++; + pad = NULL; + padded = 1; } + else + str = pdkim_hdr_cont(str, col); - /* the while precondition tells us it didn't fit. */ - str = pdkim_hdr_cont(str, col); - } +/* Special case: if the whole addition does not fit at the end of the current +line, but could fit on a new line, wrap to give it its full, dedicated line. */ -if (*col + l > 78) +len = (pad ? 2 : padded) + + (intro ? Ustrlen(intro) : 0) + + (payload ? Ustrlen(payload) : 0); +if (len <= 77 && *col+len > 78) { str = pdkim_hdr_cont(str, col); - pad = NULL; + padded = 0; } +/* Either we already dealt with the pad or we know there is room */ + if (pad) { + str = string_catn(str, pad, 1); str = string_catn(str, US" ", 1); - *col += 1; - pad = NULL; + *col += 2; } - -if (intro) +else if (padded && *col < 78) { - size_t sl = Ustrlen(intro); - - str = string_catn(str, intro, sl); - *col += sl; - l -= sl; - intro = NULL; + str = string_catn(str, US" ", 1); + (*col)++; } -if (payload) - { - size_t sl = Ustrlen(payload); +/* Call recursively with intro as payload: it gets the same, special treatment +(that is, not split if < 78). */ - str = string_catn(str, payload, sl); - *col += sl; - } +if (intro) + str = pdkim_headcat(col, str, NULL, NULL, intro); + +if (payload) + for (len = Ustrlen(payload); len; len -= chomp) + { + if (*col >= 78) + str = pdkim_hdr_cont(str, col); + chomp = *col+len > 78 ? 78 - *col : len; + str = string_catn(str, payload, chomp); + *col += chomp; + payload += chomp; + } return str; } @@ -1288,7 +1267,7 @@ if (sig->identity) if (sig->created > 0) { - uschar minibuf[20]; + uschar minibuf[21]; snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->created); hdr = pdkim_headcat(&col, hdr, US";", US"t=", minibuf); @@ -1296,7 +1275,7 @@ if (sig->created > 0) if (sig->expires > 0) { - uschar minibuf[20]; + uschar minibuf[21]; snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->expires); hdr = pdkim_headcat(&col, hdr, US";", US"x=", minibuf); @@ -1304,7 +1283,7 @@ if (sig->expires > 0) if (sig->bodylength >= 0) { - uschar minibuf[20]; + uschar minibuf[21]; snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->bodylength); hdr = pdkim_headcat(&col, hdr, US";", US"l=", minibuf); @@ -1351,7 +1330,8 @@ 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); + DEBUG(D_acl) + debug_printf("DKIM: unexpected pubkey len %lu\n", (unsigned long) p->key.len); p->key.data += excess; p->key.len = 32; } } @@ -1430,7 +1410,7 @@ if (sig->keytype == KEYTYPE_ED25519) if ((*errstr = exim_dkim_verify_init(&p->key, sig->keytype == KEYTYPE_ED25519 ? KEYFMT_ED25519_BARE : KEYFMT_DER, - vctx))) + vctx, &sig->keybits))) { DEBUG(D_acl) debug_printf("verify_init: %s\n", *errstr); sig->verify_status = PDKIM_VERIFY_INVALID; @@ -1634,7 +1614,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next) rh = pdkim_relax_header(rh, TRUE); /* cook header for relaxed canon */ /* Feed header to the hash algorithm */ - exim_sha_update(&hhash_ctx, CUS rh, Ustrlen(rh)); + exim_sha_update_string(&hhash_ctx, CUS rh); /* Remember headers block for signing (when the library cannot do incremental) */ /*XXX we could avoid doing this for all but the GnuTLS/RSA case */ @@ -1695,7 +1675,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next) : string_copy(CUS hdrs->value); /* Feed header to the hash algorithm */ - exim_sha_update(&hhash_ctx, CUS rh, Ustrlen(rh)); + exim_sha_update_string(&hhash_ctx, CUS rh); DEBUG(D_acl) pdkim_quoteprint(rh, Ustrlen(rh)); hdrs->tag = 1; @@ -1736,7 +1716,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next) } /* Finalize header hash */ - exim_sha_update(&hhash_ctx, CUS sig_hdr, Ustrlen(sig_hdr)); + exim_sha_update_string(&hhash_ctx, CUS sig_hdr); exim_sha_finish(&hhash_ctx, &hhash); DEBUG(D_acl) @@ -1886,6 +1866,19 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next) sig->verify_ext_status = PDKIM_VERIFY_FAIL_MESSAGE; goto NEXT_VERIFY; } + if (*dkim_verify_min_keysizes) + { + unsigned minbits; + uschar * ss = expand_getkeyed(US pdkim_keytypes[sig->keytype], + dkim_verify_min_keysizes); + if (ss && (minbits = atoi(CS ss)) > sig->keybits) + { + DEBUG(D_acl) debug_printf("Key too short: Actual: %s %u Minima '%s'\n", + pdkim_keytypes[sig->keytype], sig->keybits, dkim_verify_min_keysizes); + sig->verify_status = PDKIM_VERIFY_FAIL; + sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_KEYSIZE; + } + } /* We have a winner! (if bodyhash was correct earlier) */ @@ -1928,13 +1921,14 @@ pdkim_init_verify(uschar * (*dns_txt_callback)(const uschar *), BOOL dot_stuffin { pdkim_ctx * ctx; -ctx = store_get(sizeof(pdkim_ctx), FALSE); +ctx = store_get(sizeof(pdkim_ctx), GET_UNTAINTED); memset(ctx, 0, sizeof(pdkim_ctx)); 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->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN, GET_TAINTED); ctx->dns_txt_callback = dns_txt_callback; +ctx->cur_header = string_get_tainted(36, GET_TAINTED); return ctx; } @@ -1955,7 +1949,7 @@ if (!domain || !selector || !privkey) /* Allocate & init one signature struct */ -sig = store_get(sizeof(pdkim_signature), FALSE); +sig = store_get(sizeof(pdkim_signature), GET_UNTAINTED); memset(sig, 0, sizeof(pdkim_signature)); sig->bodylength = -1; @@ -2036,14 +2030,14 @@ for (b = ctx->bodyhash; b; b = b->next) && 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); + DEBUG(D_receive) debug_printf("DKIM: using existing bodyhash %s/%s/%ld\n", + pdkim_hashes[hashtype].dkim_hashname, pdkim_canons[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); +DEBUG(D_receive) debug_printf("DKIM: new bodyhash %s/%s/%ld\n", + pdkim_hashes[hashtype].dkim_hashname, pdkim_canons[canon_method], bodylength); +b = store_get(sizeof(pdkim_bodyhash), GET_UNTAINTED); b->next = ctx->bodyhash; b->hashtype = hashtype; b->canon_method = canon_method; @@ -2088,7 +2082,7 @@ pdkim_init_context(pdkim_ctx * ctx, BOOL dot_stuffed, 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); +ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN, GET_TAINTED); DEBUG(D_acl) ctx->dns_txt_callback = dns_txt_callback; }