X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/1ed59855863174523aabfba933434950e051e00a..4c04137d73637107669e02b21f890387aaa2ef34:/src/src/pdkim/pdkim.c diff --git a/src/src/pdkim/pdkim.c b/src/src/pdkim/pdkim.c index 29277baeb..58838c2ef 100644 --- a/src/src/pdkim/pdkim.c +++ b/src/src/pdkim/pdkim.c @@ -115,26 +115,47 @@ pdkim_combined_canon_entry pdkim_combined_canons[] = { 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_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 char * +pdkim_errstr(int status) +{ +switch(status) + { + case PDKIM_OK: return "OK"; + case PDKIM_FAIL: return "FAIL"; + case PDKIM_ERR_RSA_PRIVKEY: return "RSA_PRIVKEY"; + case PDKIM_ERR_RSA_SIGNING: return "RSA SIGNING"; + case PDKIM_ERR_LONG_LINE: return "RSA_LONG_LINE"; + case PDKIM_ERR_BUFFER_TOO_SMALL: return "BUFFER_TOO_SMALL"; + case PDKIM_SIGN_PRIVKEY_WRAP: return "PRIVKEY_WRAP"; + case PDKIM_SIGN_PRIVKEY_B64D: return "PRIVKEY_B64D"; + default: return "(unknown)"; } } @@ -407,6 +428,10 @@ sig = store_get(sizeof(pdkim_signature)); memset(sig, 0, sizeof(pdkim_signature)); sig->bodylength = -1; +/* Set so invalid/missing data error display is accurate */ +sig->algo = -1; +sig->version = 0; + q = sig->rawsig_no_b_val = store_get(Ustrlen(raw_hdr)+1); for (p = raw_hdr; ; p++) @@ -476,8 +501,8 @@ for (p = raw_hdr; ; p++) case 'v': /* We only support version 1, and that is currently the only version there is. */ - if (Ustrcmp(cur_val, PDKIM_SIGNATURE_VERSION) == 0) - sig->version = 1; + sig->version = + Ustrcmp(cur_val, PDKIM_SIGNATURE_VERSION) == 0 ? 1 : -1; break; case 'a': for (i = 0; pdkim_algos[i]; i++) @@ -542,10 +567,6 @@ NEXT_CHAR: *q++ = c; } -/* Make sure the most important bits are there. */ -if (!sig->version) - return NULL; - *q = '\0'; /* Chomp raw header. The final newline must not be added to the signature. */ while (--q > sig->rawsig_no_b_val && (*q == '\r' || *q == '\n')) @@ -582,90 +603,87 @@ pub = store_get(sizeof(pdkim_pubkey)); memset(pub, 0, sizeof(pdkim_pubkey)); for (p = raw_record; ; p++) - { - 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; + uschar c = *p; - where = PDKIM_HDR_TAG; - } - - if (where == PDKIM_HDR_TAG) - { - if (c >= 'a' && c <= 'z') - cur_tag = string_catn(cur_tag, &ts, &tl, p, 1); - - if (c == '=') + /* Ignore FWS */ + if (c != '\r' && c != '\n') switch (where) { - cur_tag[tl] = '\0'; - where = PDKIM_HDR_VALUE; - goto NEXT_CHAR; - } - } + case PDKIM_HDR_LIMBO: /* In limbo, just wait for a tag-char to appear */ + if (!(c >= 'a' && c <= 'z')) + break; + where = PDKIM_HDR_TAG; + /*FALLTHROUGH*/ - if (where == PDKIM_HDR_VALUE) - { - if (c == ';' || c == '\0') - { - if (tl && vl) - { - cur_val[vl] = '\0'; - pdkim_strtrim(cur_val); - DEBUG(D_acl) debug_printf(" %s=%s\n", cur_tag, cur_val); + case PDKIM_HDR_TAG: + if (c >= 'a' && c <= 'z') + cur_tag = string_catn(cur_tag, &ts, &tl, p, 1); - switch (cur_tag[0]) + if (c == '=') { - 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': - case 'k': - pub->hashes = string_copy(cur_val); break; - case 'g': - pub->granularity = string_copy(cur_val); break; - case 'n': - pub->notes = pdkim_decode_qp(cur_val); break; - case 'p': - pdkim_decode_base64(US cur_val, &pub->key); - break; - case 's': - pub->srvtype = string_copy(cur_val); break; - case 't': - if (Ustrchr(cur_val, 'y') != NULL) pub->testing = 1; - if (Ustrchr(cur_val, 's') != NULL) pub->no_subdomaining = 1; - break; - default: - DEBUG(D_acl) debug_printf(" Unknown tag encountered\n"); - break; + cur_tag[tl] = '\0'; + where = PDKIM_HDR_VALUE; } - } - tl = 0; - vl = 0; - where = PDKIM_HDR_LIMBO; + break; + + case PDKIM_HDR_VALUE: + if (c == ';' || c == '\0') + { + if (tl && vl) + { + cur_val[vl] = '\0'; + pdkim_strtrim(cur_val); + DEBUG(D_acl) debug_printf(" %s=%s\n", cur_tag, cur_val); + + switch (cur_tag[0]) + { + case 'v': + pub->version = string_copy(cur_val); break; + case 'h': + case 'k': +/* This field appears to never be used. Also, unclear why +a 'k' (key-type_ would go in this field name. There is a field +"keytype", also never used. + pub->hashes = string_copy(cur_val); +*/ + break; + case 'g': + pub->granularity = string_copy(cur_val); break; + case 'n': + pub->notes = pdkim_decode_qp(cur_val); break; + case 'p': + pdkim_decode_base64(US cur_val, &pub->key); break; + case 's': + pub->srvtype = string_copy(cur_val); break; + case 't': + if (Ustrchr(cur_val, 'y') != NULL) pub->testing = 1; + if (Ustrchr(cur_val, 's') != NULL) pub->no_subdomaining = 1; + break; + default: + DEBUG(D_acl) debug_printf(" Unknown tag encountered\n"); + break; + } + } + tl = 0; + vl = 0; + where = PDKIM_HDR_LIMBO; + } + else + cur_val = string_catn(cur_val, &vs, &vl, p, 1); + break; } - else - cur_val = string_catn(cur_val, &vs, &vl, p, 1); - } -NEXT_CHAR: - if (c == '\0') break; - } + if (c == '\0') break; + } /* Set fallback defaults */ if (!pub->version ) pub->version = string_copy(PDKIM_PUB_RECORD_VERSION); +else if (Ustrcmp(pub->version, PDKIM_PUB_RECORD_VERSION) != 0) return NULL; + if (!pub->granularity) pub->granularity = string_copy(US"*"); +/* if (!pub->keytype ) pub->keytype = string_copy(US"rsa"); +*/ if (!pub->srvtype ) pub->srvtype = string_copy(US"*"); /* p= is required */ @@ -702,7 +720,12 @@ while (sig) const char *p; int q = 0; - relaxed_data = store_get(len+1); + /* 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(). */ + + relaxed_data = store_malloc(len+1); for (p = data; *p; p++) { @@ -746,6 +769,7 @@ while (sig) sig = sig->next; } +if (relaxed_data) store_free(relaxed_data); return PDKIM_OK; } @@ -767,13 +791,13 @@ for (sig = ctx->sig; sig; sig = sig->next) DEBUG(D_acl) { debug_printf("PDKIM [%s] Body bytes hashed: %lu\n" - "PDKIM [%s] bh computed: ", + "PDKIM [%s] Body hash computed: ", sig->domain, sig->signed_body_bytes, sig->domain); pdkim_hexprint(CUS bh.data, bh.len); } /* SIGNING -------------------------------------------------------------- */ - if (ctx->mode == PDKIM_MODE_SIGN) + if (ctx->flags & PDKIM_MODE_SIGN) { sig->bodyhash = bh; @@ -795,7 +819,7 @@ for (sig = ctx->sig; sig; sig = sig->next) { DEBUG(D_acl) { - debug_printf("PDKIM [%s] bh signature: ", sig->domain); + debug_printf("PDKIM [%s] Body hash signature from headers: ", 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); @@ -809,8 +833,32 @@ for (sig = ctx->sig; sig; sig = sig->next) +static int +pdkim_body_complete(pdkim_ctx * ctx) +{ +pdkim_signature * sig = ctx->sig; /*XXX assumes only one sig */ + +/* 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); + +ctx->flags |= PDKIM_SEEN_EOD; +ctx->linebuf_offset = 0; +return PDKIM_OK; +} + + + /* -------------------------------------------------------------------------- */ -/* Callback from pdkim_feed below for processing complete body lines */ +/* Call from pdkim_feed below for processing complete body lines */ static int pdkim_bodyline_complete(pdkim_ctx *ctx) @@ -820,33 +868,23 @@ int n = ctx->linebuf_offset; pdkim_signature *sig = ctx->sig; /*XXX assumes only one sig */ /* 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 BAIL; /* We've always got one extra byte to stuff a zero ... */ ctx->linebuf[ctx->linebuf_offset] = '\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(p, ".\r\n", 3) == 0) + return pdkim_body_complete(ctx); - ctx->seen_eod = TRUE; - goto BAIL; - } -/* Unstuff dots */ -if (memcmp(p, "..", 2) == 0) - { - p++; - n--; + /* Unstuff dots */ + if (memcmp(p, "..", 2) == 0) + { + p++; + n--; + } } /* Empty lines need to be buffered until we find a non-empty line */ @@ -906,7 +944,7 @@ ctx->num_headers++; if (ctx->num_headers > PDKIM_MAX_HEADERS) goto BAIL; /* SIGNING -------------------------------------------------------------- */ -if (ctx->mode == PDKIM_MODE_SIGN) +if (ctx->flags & PDKIM_MODE_SIGN) { pdkim_signature *sig; @@ -919,7 +957,7 @@ if (ctx->mode == PDKIM_MODE_SIGN) /* VERIFICATION ----------------------------------------------------------- */ /* DKIM-Signature: headers are added to the verification list */ -if (ctx->mode == PDKIM_MODE_VERIFY) +else { if (strncasecmp(CCS ctx->cur_header, DKIM_SIGNATURE_HEADERNAME, @@ -969,11 +1007,15 @@ pdkim_feed(pdkim_ctx *ctx, char *data, int len) { int p; -for (p = 0; ppast_headers) + if (ctx->flags & PDKIM_PAST_HDRS) { /* Processing body byte */ ctx->linebuf[ctx->linebuf_offset++] = c; @@ -992,28 +1034,27 @@ for (p = 0; pseen_lf) + if (ctx->flags & PDKIM_SEEN_LF) { int rc = pdkim_header_complete(ctx); /* Seen last header line */ if (rc != PDKIM_OK) return rc; - ctx->past_headers = TRUE; - ctx->seen_lf = 0; + ctx->flags = ctx->flags & ~PDKIM_SEEN_LF | PDKIM_PAST_HDRS; DEBUG(D_acl) debug_printf( "PDKIM >> Body data for hash, canonicalized >>>>>>>>>>>>>>>>>>>>>>\n"); continue; } else - ctx->seen_lf = TRUE; + ctx->flags |= PDKIM_SEEN_LF; } - else if (ctx->seen_lf) + else if (ctx->flags & PDKIM_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; + ctx->flags &= ~PDKIM_SEEN_LF; } } @@ -1259,14 +1300,80 @@ return hdr; } +/* -------------------------------------------------------------------------- */ + +static pdkim_pubkey * +pdkim_key_from_dns(pdkim_ctx * ctx, pdkim_signature * sig, ev_ctx * vctx) +{ +uschar * dns_txt_name, * dns_txt_reply; +pdkim_pubkey * p; +const uschar * errstr; + +/* Fetch public key for signing domain, from DNS */ + +dns_txt_name = string_sprintf("%s._domainkey.%s.", sig->selector, sig->domain); + +dns_txt_reply = store_get(PDKIM_DNS_TXT_MAX_RECLEN); +memset(dns_txt_reply, 0, PDKIM_DNS_TXT_MAX_RECLEN); + +if ( ctx->dns_txt_callback(CS dns_txt_name, CS dns_txt_reply) != PDKIM_OK + || 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( + "PDKIM >> Parsing public key record >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n" + " Raw record: "); + pdkim_quoteprint(CUS dns_txt_reply, Ustrlen(dns_txt_reply)); + } + +if ( !(p = pdkim_parse_pubkey_record(ctx, 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( + "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); + } + return NULL; + } + +DEBUG(D_acl) debug_printf( + "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); + +/* Import public key */ +if ((errstr = exim_rsa_verify_init(&p->key, 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; + } + +return p; +} + + /* -------------------------------------------------------------------------- */ DLLEXPORT int pdkim_feed_finish(pdkim_ctx *ctx, pdkim_signature **return_signatures) { pdkim_signature *sig = ctx->sig; -uschar * headernames = NULL; /* Collected signed header names */ -int hs = 0, hl = 0; /* 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 @@ -1288,7 +1395,7 @@ while (sig) { BOOL is_sha1 = sig->algo == PDKIM_ALGO_RSA_SHA1; hctx hhash_ctx; - uschar * sig_hdr; + uschar * sig_hdr = US""; blob hhash; blob hdata; int hdata_alloc = 0; @@ -1299,7 +1406,7 @@ while (sig) exim_sha_init(&hhash_ctx, is_sha1 ? HASH_SHA1 : HASH_SHA256); DEBUG(D_acl) debug_printf( - "PDKIM >> Hashed header data, canonicalized, in sequence >>>>>>>>>>>>>>\n"); + "PDKIM >> Header data for hash, canonicalized, in sequence >>>>>>>>>>>>>>\n"); /* SIGNING ---------------------------------------------------------------- */ /* When signing, walk through our header list and add them to the hash. As we @@ -1307,8 +1414,10 @@ while (sig) 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) { + uschar * headernames = NULL; /* Collected signed header names */ + int hs = 0, hl = 0; pdkim_stringlist *p; const uschar * l; uschar * s; @@ -1350,7 +1459,6 @@ while (sig) /* Copy headernames to signature struct */ sig->headernames = headernames; - headernames = NULL, hs = hl = 0; /* Create signature header with b= omitted */ sig_hdr = pdkim_create_header(sig, FALSE); @@ -1361,44 +1469,49 @@ while (sig) 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; - /* clear tags */ - for (hdrs = ctx->headers; hdrs; hdrs = hdrs->next) - hdrs->tag = 0; - - while(1) + if (p) { - if ((q = Ustrchr(p, ':'))) - *q = '\0'; - -/*XXX walk the list of headers in same order as received. */ + /* clear tags */ 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 - ? pdkim_relax_header(hdrs->value, 1) /* cook header for relaxed canon */ - : string_copy(CUS hdrs->value); /* just copy it for simple canon */ + hdrs->tag = 0; - /* Feed header to the hash algorithm */ - exim_sha_update(&hhash_ctx, CUS rh, Ustrlen(rh)); + p = string_copy(p); + 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(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, 1) + : 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; + } - 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); } - - sig_hdr = string_copy(sig->rawsig_no_b_val); } DEBUG(D_acl) debug_printf( @@ -1423,16 +1536,16 @@ while (sig) DEBUG(D_acl) { - debug_printf("PDKIM [%s] hh computed: ", sig->domain); + debug_printf("PDKIM [%s] Header hash computed: ", sig->domain); pdkim_hexprint(hhash.data, hhash.len); } /* Remember headers block for signing (when the library cannot do incremental) */ - if (ctx->mode == PDKIM_MODE_SIGN) + if (ctx->flags & PDKIM_MODE_SIGN) (void) exim_rsa_data_append(&hdata, &hdata_alloc, US sig_hdr); /* SIGNING ---------------------------------------------------------------- */ - if (ctx->mode == PDKIM_MODE_SIGN) + if (ctx->flags & PDKIM_MODE_SIGN) { es_ctx sctx; const uschar * errstr; @@ -1472,55 +1585,41 @@ while (sig) { ev_ctx vctx; const uschar * errstr; - - uschar *dns_txt_name, *dns_txt_reply; - - /* Fetch public key for signing domain, from DNS */ - - dns_txt_name = string_sprintf("%s._domainkey.%s.", - sig->selector, sig->domain); - - dns_txt_reply = store_get(PDKIM_DNS_TXT_MAX_RECLEN); - memset(dns_txt_reply, 0, PDKIM_DNS_TXT_MAX_RECLEN); - - if ( ctx->dns_txt_callback(CS dns_txt_name, CS dns_txt_reply) != PDKIM_OK - || dns_txt_reply[0] == '\0') + pdkim_pubkey * p; + + /* 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->sigdata.data + && sig->algo > -1 + && sig->version + ) ) { - sig->verify_status = PDKIM_VERIFY_INVALID; - sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE; - goto NEXT_VERIFY; - } + sig->verify_status = PDKIM_VERIFY_INVALID; + sig->verify_ext_status = PDKIM_VERIFY_INVALID_SIGNATURE_ERROR; - DEBUG(D_acl) - { - debug_printf( - "PDKIM >> Parsing public key record >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n" - " Raw record: "); - pdkim_quoteprint(CUS dns_txt_reply, Ustrlen(dns_txt_reply)); + DEBUG(D_acl) debug_printf( + " Error in DKIM-Signature header: tags missing or invalid\n" + "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); + goto NEXT_VERIFY; } - if (!(sig->pubkey = pdkim_parse_pubkey_record(ctx, CUS dns_txt_reply))) + /* 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_DNSRECORD; + sig->verify_status = PDKIM_VERIFY_INVALID; + sig->verify_ext_status = PDKIM_VERIFY_INVALID_DKIM_VERSION; DEBUG(D_acl) debug_printf( - " Error while parsing public key record\n" - "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); + " Error in DKIM-Signature header: unsupported DKIM version\n" + "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); goto NEXT_VERIFY; } - DEBUG(D_acl) debug_printf( - "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); - - /* Import public key */ - if ((errstr = exim_rsa_verify_init(&sig->pubkey->key, &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; + if (!(sig->pubkey = pdkim_key_from_dns(ctx, sig, &vctx))) goto NEXT_VERIFY; - } /* Check the signature */ if ((errstr = exim_rsa_verify(&vctx, is_sha1, &hhash, &sig->sigdata))) @@ -1532,7 +1631,7 @@ while (sig) } - /* 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; @@ -1564,15 +1663,15 @@ return PDKIM_OK; /* -------------------------------------------------------------------------- */ DLLEXPORT pdkim_ctx * -pdkim_init_verify(int(*dns_txt_callback)(char *, char *)) +pdkim_init_verify(int(*dns_txt_callback)(char *, char *), BOOL dot_stuffing) { pdkim_ctx * ctx; ctx = store_get(sizeof(pdkim_ctx)); memset(ctx, 0, sizeof(pdkim_ctx)); +if (dot_stuffing) ctx->flags = PDKIM_DOT_TERM; ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN); -ctx->mode = PDKIM_MODE_VERIFY; ctx->dns_txt_callback = dns_txt_callback; return ctx; @@ -1582,25 +1681,27 @@ return ctx; /* -------------------------------------------------------------------------- */ DLLEXPORT pdkim_ctx * -pdkim_init_sign(char *domain, char *selector, char *rsa_privkey, int algo) +pdkim_init_sign(char * domain, char * selector, char * rsa_privkey, int algo, + BOOL dot_stuffed, int(*dns_txt_callback)(char *, char *)) { -pdkim_ctx *ctx; -pdkim_signature *sig; +pdkim_ctx * ctx; +pdkim_signature * sig; if (!domain || !selector || !rsa_privkey) return NULL; -ctx = store_get(sizeof(pdkim_ctx)); +ctx = store_get(sizeof(pdkim_ctx) + PDKIM_MAX_BODY_LINE_LEN + sizeof(pdkim_signature)); memset(ctx, 0, sizeof(pdkim_ctx)); -ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN); +ctx->flags = dot_stuffed ? PDKIM_MODE_SIGN | PDKIM_DOT_TERM : PDKIM_MODE_SIGN; +ctx->linebuf = CS (ctx+1); -sig = store_get(sizeof(pdkim_signature)); +DEBUG(D_acl) ctx->dns_txt_callback = dns_txt_callback; + +sig = (pdkim_signature *)(ctx->linebuf + PDKIM_MAX_BODY_LINE_LEN); memset(sig, 0, sizeof(pdkim_signature)); sig->bodylength = -1; - -ctx->mode = PDKIM_MODE_SIGN; ctx->sig = sig; sig->domain = string_copy(US domain); @@ -1609,6 +1710,18 @@ sig->rsa_privkey = string_copy(US rsa_privkey); sig->algo = algo; exim_sha_init(&sig->body_hash, algo == PDKIM_ALGO_RSA_SHA1 ? HASH_SHA1 : HASH_SHA256); + +DEBUG(D_acl) + { + pdkim_signature s = *sig; + ev_ctx vctx; + + debug_printf("PDKIM (checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); + if (!pdkim_key_from_dns(ctx, &s, &vctx)) + debug_printf("WARNING: bad dkim key in dns\n"); + debug_printf("PDKIM (finished checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); + } + return ctx; }