/* * PDKIM - a RFC4871 (DKIM) implementation * * Copyright (C) 2009 - 2015 Tom Kistner * * http://duncanthrax.net/pdkim/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "../exim.h" #include "pdkim.h" #include "pdkim-rsa.h" #include "polarssl/sha1.h" #include "polarssl/sha2.h" #include "polarssl/rsa.h" #include "polarssl/base64.h" #define PDKIM_SIGNATURE_VERSION "1" #define PDKIM_PUB_RECORD_VERSION "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; }; /* -------------------------------------------------------------------------- */ /* A bunch of list constants */ const char *pdkim_querymethods[] = { "dns/txt", NULL }; const char *pdkim_algos[] = { "rsa-sha256", "rsa-sha1", NULL }; const char *pdkim_canons[] = { "simple", "relaxed", NULL }; const char *pdkim_hashes[] = { "sha256", "sha1", NULL }; const char *pdkim_keytypes[] = { "rsa", NULL }; typedef struct pdkim_combined_canon_entry { const char *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 } }; 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"; } } 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_PARSING: return "PDKIM_VERIFY_INVALID_PUBKEY_PARSING"; default: return "PDKIM_VERIFY_UNKNOWN"; } } /* -------------------------------------------------------------------------- */ /* Print debugging functions */ void pdkim_quoteprint(const char *data, int len, int lf) { int i; const unsigned char *p = (const unsigned char *)data; for (i = 0; i < len; i++) { const int c = p[i]; switch (c) { case ' ' : debug_printf("{SP}"); break; case '\t': debug_printf("{TB}"); break; case '\r': debug_printf("{CR}"); break; case '\n': debug_printf("{LF}"); break; case '{' : debug_printf("{BO}"); break; case '}' : debug_printf("{BC}"); break; default: if ( (c < 32) || (c > 127) ) debug_printf("{%02x}", c); else debug_printf("%c", c); break; } } if (lf) debug_printf("\n"); } void pdkim_hexprint(const char *data, int len, int lf) { int i; const unsigned char *p = (const unsigned char *)data; for (i = 0 ; i < len; i++) debug_printf("%02x", p[i]); if (lf) debug_printf("\n"); } /* -------------------------------------------------------------------------- */ /* Simple string list implementation for convinience */ pdkim_stringlist * pdkim_append_stringlist(pdkim_stringlist *base, char *str) { pdkim_stringlist *new_entry = malloc(sizeof(pdkim_stringlist)); if (!new_entry) return NULL; memset(new_entry, 0, sizeof(pdkim_stringlist)); if (!(new_entry->value = strdup(str))) return NULL; if (base) { pdkim_stringlist *last = base; while (last->next != NULL) { last = last->next; } last->next = new_entry; return base; } else return new_entry; } pdkim_stringlist * pdkim_prepend_stringlist(pdkim_stringlist *base, char *str) { pdkim_stringlist *new_entry = malloc(sizeof(pdkim_stringlist)); if (!new_entry) return NULL; memset(new_entry, 0, sizeof(pdkim_stringlist)); if (!(new_entry->value = strdup(str))) return NULL; if (base) new_entry->next = base; return new_entry; } /* -------------------------------------------------------------------------- */ /* A small "growing string" implementation to escape malloc/realloc hell */ 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; } 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; } char * pdkim_strcat(pdkim_str *str, const char *cstr) { return pdkim_strncat(str, cstr, strlen(cstr)); } char * pdkim_numcat(pdkim_str *str, unsigned long num) { char minibuf[20]; snprintf(minibuf, 20, "%lu", num); return pdkim_strcat(str, minibuf); } char * pdkim_strtrim(pdkim_str *str) { char *p = str->str; char *q = str->str; while ( (*p != '\0') && ((*p == '\t') || (*p == ' ')) ) p++; while (*p != '\0') {*q = *p; q++; p++;} *q = '\0'; while ( (q != str->str) && ( (*q == '\0') || (*q == '\t') || (*q == ' ') ) ) { *q = '\0'; q--; } str->len = strlen(str->str); return str->str; } char * pdkim_strclear(pdkim_str *str) { str->str[0] = '\0'; str->len = 0; return str->str; } void pdkim_strfree(pdkim_str *str) { if (!str) return; if (str->str) free(str->str); free(str); } /* -------------------------------------------------------------------------- */ 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); if (pub->key ) free(pub->key); free(pub); } } /* -------------------------------------------------------------------------- */ void pdkim_free_sig(pdkim_signature *sig) { if (sig) { pdkim_signature *next = (pdkim_signature *)sig->next; pdkim_stringlist *e = sig->headers; while(e) { pdkim_stringlist *c = e; if (e->value) free(e->value); e = e->next; free(c); } if (sig->sigdata ) free(sig->sigdata); if (sig->bodyhash ) free(sig->bodyhash); if (sig->selector ) free(sig->selector); if (sig->domain ) free(sig->domain); if (sig->identity ) free(sig->identity); if (sig->headernames ) free(sig->headernames); 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->sha1_body ) free(sig->sha1_body); if (sig->sha2_body ) free(sig->sha2_body); 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 *e = ctx->headers; while(e) { pdkim_stringlist *c = e; if (e->value) free(e->value); e = e->next; free(c); } 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 "list", starting at entry "start". Returns the position of the header name in the list. */ int header_name_match(const char *header, char *tick, int do_tick) { char *hname; char *lcopy; char *p; char *q; int rc = PDKIM_FAIL; /* Get header name */ char *hcolon = strchr(header, ':'); if (!hcolon) return rc; /* This isn't a 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))) { free(hname); return PDKIM_ERR_OOM; } p = lcopy; q = strchr(p, ':'); while (q) { *q = '\0'; if (strcasecmp(p, hname) == 0) { rc = PDKIM_OK; /* Invalidate header name instance in tick-off list */ if (do_tick) tick[p-lcopy] = '_'; goto BAIL; } p = q+1; q = strchr(p, ':'); } if (strcasecmp(p, hname) == 0) { rc = PDKIM_OK; /* Invalidate header name instance in tick-off list */ if (do_tick) tick[p-lcopy] = '_'; } BAIL: free(hname); free(lcopy); return rc; } /* -------------------------------------------------------------------------- */ /* Performs "relaxed" canonicalization of a header. The returned pointer needs to be free()d. */ char * pdkim_relax_header (char *header, int crlf) { BOOL past_field_name = FALSE; BOOL seen_wsp = FALSE; char *p; char *q; char *relaxed = malloc(strlen(header)+3); if (!relaxed) return NULL; q = relaxed; for (p = header; *p != '\0'; p++) { int c = *p; /* Ignore CR & LF */ if (c == '\r' || c == '\n') continue; if (c == '\t' || c == ' ') { if (seen_wsp) continue; c = ' '; /* Turns WSP into SP */ seen_wsp = TRUE; } 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 */ past_field_name = TRUE; } else seen_wsp = FALSE; /* Lowercase header name */ if (!past_field_name) c = tolower(c); *q++ = c; } if (q > relaxed && q[-1] == ' ') q--; /* Squash eventual trailing SP */ *q = '\0'; if (crlf) strcat(relaxed, "\r\n"); return relaxed; } /* -------------------------------------------------------------------------- */ #define PDKIM_QP_ERROR_DECODE -1 char * pdkim_decode_qp_char(char *qp_p, int *c) { char *initial_pos = qp_p; /* Advance one char */ qp_p++; /* Check for two hex digits and decode them */ if (isxdigit(*qp_p) && isxdigit(qp_p[1])) { /* Do hex conversion */ *c = (isdigit(*qp_p) ? *qp_p - '0' : toupper(*qp_p) - 'A' + 10) << 4; *c != isdigit(qp_p[1]) ? qp_p[1] - '0' : toupper(qp_p[1]) - 'A' + 10; return qp_p + 2; } /* Illegal char here */ *c = PDKIM_QP_ERROR_DECODE; return initial_pos; } /* -------------------------------------------------------------------------- */ char * pdkim_decode_qp(char *str) { int nchar = 0; char *q; char *p = str; char *n = malloc(strlen(p)+1); if (!n) return NULL; *n = '\0'; q = n; while (*p != '\0') { if (*p == '=') { p = pdkim_decode_qp_char(p, &nchar); if (nchar >= 0) { *q++ = nchar; continue; } } else *q++ = *p; p++; } *q = '\0'; return n; } /* -------------------------------------------------------------------------- */ char * pdkim_decode_base64(char *str, int *num_decoded) { int dlen = 0; char *res; base64_decode(NULL, &dlen, (unsigned char *)str, strlen(str)); if (!(res = malloc(dlen+1))) return NULL; if (base64_decode((unsigned char *)res, &dlen, (unsigned char *)str, strlen(str)) != 0) { free(res); return NULL; } if (num_decoded) *num_decoded = dlen; return res; } /* -------------------------------------------------------------------------- */ char * pdkim_encode_base64(char *str, int num) { int dlen = 0; char *res; base64_encode(NULL, &dlen, (unsigned char *)str, num); if (!(res = malloc(dlen+1))) return NULL; if (base64_encode((unsigned char *)res, &dlen, (unsigned char *)str, num) != 0) { free(res); return NULL; } return res; } /* -------------------------------------------------------------------------- */ #define PDKIM_HDR_LIMBO 0 #define PDKIM_HDR_TAG 1 #define PDKIM_HDR_VALUE 2 pdkim_signature * pdkim_parse_sig_header(pdkim_ctx *ctx, char *raw_hdr) { pdkim_signature *sig ; char *p, *q; pdkim_str *cur_tag = NULL; pdkim_str *cur_val = NULL; BOOL past_hname = FALSE; BOOL in_b_val = FALSE; int where = PDKIM_HDR_LIMBO; int i; if (!(sig = malloc(sizeof(pdkim_signature)))) return NULL; memset(sig, 0, sizeof(pdkim_signature)); sig->bodylength = -1; if (!(sig->rawsig_no_b_val = malloc(strlen(raw_hdr)+1))) { free(sig); return NULL; } q = sig->rawsig_no_b_val; for (p = raw_hdr; ; p++) { char c = *p; /* Ignore FWS */ if (c == '\r' || c == '\n') goto NEXT_CHAR; /* Fast-forward through header name */ if (!past_hname) { if (c == ':') past_hname = TRUE; 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 == '=') { if (strcmp(cur_tag->str, "b") == 0) { *q = '='; q++; in_b_val = TRUE; } where = PDKIM_HDR_VALUE; goto NEXT_CHAR; } } 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) { pdkim_strtrim(cur_val); DEBUG(D_acl) debug_printf(" %s=%s\n", cur_tag->str, cur_val->str); switch (cur_tag->str[0]) { case 'b': if (cur_tag->str[1] == 'h') sig->bodyhash = pdkim_decode_base64(cur_val->str, &sig->bodyhash_len); else sig->sigdata = pdkim_decode_base64(cur_val->str, &sig->sigdata_len); break; case 'v': /* 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; } 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; } break; case 'q': for (i = 0; pdkim_querymethods[i]; i++) if (strcmp(cur_val->str, pdkim_querymethods[i]) == 0) { sig->querymethod = i; 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 = strdup(cur_val->str); break; case 'z': sig->copiedheaders = pdkim_decode_qp(cur_val->str); break; default: DEBUG(D_acl) debug_printf(" Unknown tag encountered\n"); break; } } pdkim_strclear(cur_tag); pdkim_strclear(cur_val); in_b_val = FALSE; where = PDKIM_HDR_LIMBO; } else pdkim_strncat(cur_val, p, 1); } NEXT_CHAR: if (c == '\0') break; if (!in_b_val) *q++ = c; } /* 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->bodyhash && sig->sigdata && sig->version)) { pdkim_free_sig(sig); 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 */ DEBUG(D_acl) { debug_printf( "PDKIM >> Raw signature w/o b= tag value >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); pdkim_quoteprint(sig->rawsig_no_b_val, strlen(sig->rawsig_no_b_val), 1); debug_printf( "PDKIM >> Sig size: %4d bits\n", sig->sigdata_len*8); debug_printf( "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); } if ( !(sig->sha1_body = malloc(sizeof(sha1_context))) || !(sig->sha2_body = malloc(sizeof(sha2_context))) ) { pdkim_free_sig(sig); return NULL; } sha1_starts(sig->sha1_body); sha2_starts(sig->sha2_body, 0); return sig; } /* -------------------------------------------------------------------------- */ pdkim_pubkey * pdkim_parse_pubkey_record(pdkim_ctx *ctx, char *raw_record) { pdkim_pubkey *pub; char *p; pdkim_str *cur_tag = NULL; pdkim_str *cur_val = NULL; int where = PDKIM_HDR_LIMBO; if (!(pub = malloc(sizeof(pdkim_pubkey)))) return NULL; 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; 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; } } if (where == PDKIM_HDR_VALUE) { if (!cur_val) cur_val = pdkim_strnew(NULL); if (c == '\r' || c == '\n') goto NEXT_CHAR; if (c == ';' || c == '\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': pub->key = pdkim_decode_base64(cur_val->str, &(pub->key_len)); 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; } 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("*"); /* p= is required */ if (pub->key) return pub; pdkim_free_pubkey(pub); return NULL; } /* -------------------------------------------------------------------------- */ int pdkim_update_bodyhash(pdkim_ctx *ctx, const char *data, int len) { pdkim_signature *sig = ctx->sig; /* Cache relaxed version of data */ char *relaxed_data = NULL; int relaxed_len = 0; /* Traverse all signatures, updating their hashes. */ while (sig) { /* Defaults to simple canon (no further treatment necessary) */ const char *canon_data = data; int canon_len = len; if (sig->canon_body == PDKIM_CANON_RELAXED) { /* Relax the line if not done already */ if (!relaxed_data) { BOOL seen_wsp = FALSE; const char *p; int q = 0; if (!(relaxed_data = malloc(len+1))) return PDKIM_ERR_OOM; 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[q] = '\0'; relaxed_len = q; } canon_data = relaxed_data; canon_len = relaxed_len; } /* 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) { if (sig->algo == PDKIM_ALGO_RSA_SHA1) sha1_update(sig->sha1_body, (unsigned char *)canon_data, canon_len); else sha2_update(sig->sha2_body, (unsigned char *)canon_data, canon_len); sig->signed_body_bytes += canon_len; DEBUG(D_acl) pdkim_quoteprint(canon_data, canon_len, 1); } sig = sig->next; } if (relaxed_data) free(relaxed_data); return PDKIM_OK; } /* -------------------------------------------------------------------------- */ int pdkim_finish_bodyhash(pdkim_ctx *ctx) { pdkim_signature *sig = ctx->sig; /* Traverse all signatures */ while (sig) { /* Finish hashes */ unsigned char bh[32]; /* SHA-256 = 32 Bytes, SHA-1 = 20 Bytes */ if (sig->algo == PDKIM_ALGO_RSA_SHA1) sha1_finish(sig->sha1_body, bh); else sha2_finish(sig->sha2_body, bh); 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((char *)bh, sig->algo == PDKIM_ALGO_RSA_SHA1 ? 20 : 32, 1); } /* SIGNING -------------------------------------------------------------- */ if (ctx->mode == PDKIM_MODE_SIGN) { sig->bodyhash_len = (sig->algo == PDKIM_ALGO_RSA_SHA1)?20:32; if (!(sig->bodyhash = malloc(sig->bodyhash_len))) return PDKIM_ERR_OOM; memcpy(sig->bodyhash, bh, sig->bodyhash_len); /* 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) sig->bodylength = -1; } /* VERIFICATION --------------------------------------------------------- */ else { /* Compare bodyhash */ if (memcmp(bh, sig->bodyhash, (sig->algo == PDKIM_ALGO_RSA_SHA1)?20:32) == 0) { DEBUG(D_acl) debug_printf("PDKIM [%s] Body hash verified OK\n", sig->domain); } else { DEBUG(D_acl) { debug_printf("PDKIM [%s] bh signature: ", sig->domain); pdkim_hexprint(sig->bodyhash, sig->algo == PDKIM_ALGO_RSA_SHA1 ? 20 : 32, 1); debug_printf("PDKIM [%s] Body hash did NOT verify\n", sig->domain); } sig->verify_status = PDKIM_VERIFY_FAIL; sig->verify_ext_status = PDKIM_VERIFY_FAIL_BODY; } } sig = sig->next; } return PDKIM_OK; } /* -------------------------------------------------------------------------- */ /* Callback from pdkim_feed below for processing complete body lines */ static int 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 */ /* Ignore extra data if we've seen the end-of-data marker */ if (ctx->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) { /* 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->seen_eod = TRUE; goto BAIL; } /* Unstuff dots */ if (memcmp(p, "..", 2) == 0) { p++; n--; } /* Empty lines need to be buffered until we find a non-empty line */ if (memcmp(p, "\r\n", 2) == 0) { ctx->num_buffered_crlf++; goto BAIL; } if (sig && sig->canon_body == PDKIM_CANON_RELAXED) { /* Lines with just spaces need to be buffered too */ char *check = p; while (memcmp(check, "\r\n", 2) != 0) { char c = *check; if (c != '\t' && c != ' ') goto PROCESS; check++; } ctx->num_buffered_crlf++; goto BAIL; } 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--; } pdkim_update_bodyhash(ctx, p, n); BAIL: ctx->linebuf_offset = 0; return PDKIM_OK; } /* -------------------------------------------------------------------------- */ /* Callback from pdkim_feed below for processing complete headers */ #define DKIM_SIGNATURE_HEADERNAME "DKIM-Signature:" int 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--; } ctx->num_headers++; 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 */ if (header_name_match(ctx->cur_header->str, sig->sign_headers? sig->sign_headers: PDKIM_DEFAULT_SIGN_HEADERS, 0) == PDKIM_OK) { pdkim_stringlist *list; /* 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; } } /* VERIFICATION ----------------------------------------------------------- */ /* DKIM-Signature: headers are added to the verification list */ if (ctx->mode == PDKIM_MODE_VERIFY) { if (strncasecmp(ctx->cur_header->str, DKIM_SIGNATURE_HEADERNAME, strlen(DKIM_SIGNATURE_HEADERNAME)) == 0) { pdkim_signature *new_sig; /* Create and chain new signature block */ DEBUG(D_acl) debug_printf( "PDKIM >> Found sig, trying to parse >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); if ((new_sig = pdkim_parse_sig_header(ctx, ctx->cur_header->str))) { 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; } } 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; } } BAIL: pdkim_strclear(ctx->cur_header); /* Re-use existing pdkim_str */ return PDKIM_OK; } /* -------------------------------------------------------------------------- */ #define HEADER_BUFFER_FRAG_SIZE 256 DLLEXPORT int pdkim_feed (pdkim_ctx *ctx, char *data, int len) { int p; for (p = 0; ppast_headers) { /* Processing body byte */ ctx->linebuf[ctx->linebuf_offset++] = c; if (c == '\n') { int rc = pdkim_bodyline_complete(ctx); /* End of line */ if (rc != PDKIM_OK) return rc; } if (ctx->linebuf_offset == (PDKIM_MAX_BODY_LINE_LEN-1)) return PDKIM_ERR_LONG_LINE; } else { /* Processing header byte */ if (c != '\r') { if (c == '\n') { if (ctx->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; DEBUG(D_acl) debug_printf( "PDKIM >> Hashed body data, 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->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; } } return PDKIM_OK; } /* * RFC 5322 specifies that header line length SHOULD be no more than 78 * lets make it so! * pdkim_headcat * returns char* * * 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: ";" * intro: - must join to payload eg "h=", usually the tag name * payload: eg base64 data - long data can be split arbitrarily. * * this code doesn't fold the header in some of the places that RFC4871 * allows: As per RFC5322(2.2.3) it only folds before or after tag-value * 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 * names loinger 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) { size_t l; if (pad) { l = strlen(pad); if (*col + l > 78) { pdkim_strcat(str, "\r\n\t"); *col = 1; } pdkim_strncat(str, pad, l); *col += l; } l = (pad?1:0) + (intro?strlen(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; } l += payload ? strlen(payload):0 ; while (l>77) { /* this fragment will not fit on a single line */ if (pad) { pdkim_strcat(str, " "); *col += 1; pad = NULL; /* only want this once */ l--; } if (intro) { size_t sl = strlen(intro); pdkim_strncat(str, intro, sl); *col += sl; l -= sl; intro = NULL; /* only want this once */ } if (payload) { size_t sl = strlen(payload); size_t chomp = *col+sl < 77 ? sl : 78-*col; pdkim_strncat(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; } if (*col + l > 78) { pdkim_strcat(str, "\r\n\t"); *col = 1; pad = NULL; } if (pad) { pdkim_strcat(str, " "); *col += 1; pad = NULL; } if (intro) { size_t sl = strlen(intro); pdkim_strncat(str, intro, sl); *col += sl; l -= sl; intro = NULL; } if (payload) { size_t sl = strlen(payload); pdkim_strncat(str, payload, sl); *col += sl; } return str->str; } /* -------------------------------------------------------------------------- */ char * pdkim_create_header(pdkim_signature *sig, int final) { char *rc = NULL; char *base64_bh = NULL; char *base64_b = NULL; 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; if (!(base64_bh = pdkim_encode_base64(sig->bodyhash, sig->bodyhash_len))) goto BAIL; col = strlen(hdr->str); /* 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) ) { /* list of eader names can be split between items. */ { char *n = strdup(sig->headernames); char *f = n; char *i = "h="; char *s = ";"; if (!n) goto BAIL; while (*n) { char *c = strchr(n, ':'); if (c) *c ='\0'; if (!i) if (!pdkim_headcat(&col, hdr, NULL, NULL, ":")) { free(f); goto BAIL; } if (!pdkim_headcat(&col, hdr, s, i, n)) { free(f); goto BAIL; } if (!c) break; n = c+1; s = NULL; i = NULL; } free(f); } if(!pdkim_headcat(&col, hdr, ";", "bh=", base64_bh)) goto BAIL; /* Optional bits */ if (sig->identity) if(!pdkim_headcat(&col, hdr, ";", "i=", sig->identity)) goto BAIL; if (sig->created > 0) { char minibuf[20]; snprintf(minibuf, 20, "%lu", sig->created); if(!pdkim_headcat(&col, hdr, ";", "t=", minibuf)) goto BAIL; } if (sig->expires > 0) { char minibuf[20]; snprintf(minibuf, 20, "%lu", sig->expires); if(!pdkim_headcat(&col, hdr, ";", "x=", minibuf)) goto BAIL; } if (sig->bodylength >= 0) { char minibuf[20]; snprintf(minibuf, 20, "%lu", sig->bodylength); if(!pdkim_headcat(&col, hdr, ";", "l=", minibuf)) goto BAIL; } /* Preliminary or final version? */ if (final) { if (!(base64_b = pdkim_encode_base64(sig->sigdata, sig->sigdata_len))) goto BAIL; if (!pdkim_headcat(&col, hdr, ";", "b=", base64_b)) goto BAIL; } 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; } rc = strdup(hdr->str); BAIL: pdkim_strfree(hdr); if (canon_all) pdkim_strfree(canon_all); if (base64_bh) free(base64_bh); if (base64_b ) free(base64_b); return rc; } /* -------------------------------------------------------------------------- */ DLLEXPORT int pdkim_feed_finish(pdkim_ctx *ctx, pdkim_signature **return_signatures) { pdkim_signature *sig = ctx->sig; pdkim_str *headernames = NULL; /* Collected signed header names */ /* 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 '' */ if (ctx->cur_header && ctx->cur_header->len) { int rc = pdkim_header_complete(ctx); if (rc != PDKIM_OK) return rc; pdkim_update_bodyhash(ctx, "\r\n", 2); } else DEBUG(D_acl) debug_printf( "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); /* Build (and/or evaluate) body hash */ if (pdkim_finish_bodyhash(ctx) != PDKIM_OK) return PDKIM_ERR_OOM; /* SIGNING -------------------------------------------------------------- */ if (ctx->mode == PDKIM_MODE_SIGN) if (!(headernames = pdkim_strnew(NULL))) return PDKIM_ERR_OOM; /* ---------------------------------------------------------------------- */ while (sig) { sha1_context sha1_headers; sha2_context sha2_headers; char *sig_hdr; char headerhash[32]; if (sig->algo == PDKIM_ALGO_RSA_SHA1) sha1_starts(&sha1_headers); else sha2_starts(&sha2_headers, 0); DEBUG(D_acl) debug_printf( "PDKIM >> Hashed header data, canonicalized, in sequence >>>>>>>>>>>>>>\n"); /* SIGNING ---------------------------------------------------------------- */ /* When signing, walk through our header list and add them to the hash. As we go, construct a list of the header's names to use for the h= parameter. */ if (ctx->mode == PDKIM_MODE_SIGN) { pdkim_stringlist *p; for (p = sig->headers; p; p = p->next) { char *rh = NULL; /* Collect header names (Note: colon presence is guaranteed here) */ char *q = strchr(p->value, ':'); if (!(pdkim_strncat(headernames, p->value, (q-(p->value)) + (p->next ? 1 : 0)))) return PDKIM_ERR_OOM; rh = sig->canon_headers == PDKIM_CANON_RELAXED ? pdkim_relax_header(p->value, 1) /* cook header for relaxed canon */ : strdup(p->value); /* just copy it for simple canon */ if (!rh) return PDKIM_ERR_OOM; /* Feed header to the hash algorithm */ if (sig->algo == PDKIM_ALGO_RSA_SHA1) sha1_update(&(sha1_headers), (unsigned char *)rh, strlen(rh)); else sha2_update(&(sha2_headers), (unsigned char *)rh, strlen(rh)); DEBUG(D_acl) pdkim_quoteprint(rh, strlen(rh), 1); free(rh); } } /* VERIFICATION ----------------------------------------------------------- */ /* When verifying, walk through the header name list in the h= parameter and add the headers to the hash in that order. */ else { char *b = strdup(sig->headernames); char *p = b; char *q = NULL; pdkim_stringlist *hdrs; if (!b) return PDKIM_ERR_OOM; /* clear tags */ for (hdrs = ctx->headers; hdrs; hdrs = hdrs->next) hdrs->tag = 0; while(1) { if ((q = strchr(p, ':'))) *q = '\0'; for (hdrs = ctx->headers; hdrs; hdrs = hdrs->next) if ( hdrs->tag == 0 && strncasecmp(hdrs->value, p, strlen(p)) == 0 && (hdrs->value)[strlen(p)] == ':' ) { char *rh; rh = sig->canon_headers == PDKIM_CANON_RELAXED ? pdkim_relax_header(hdrs->value, 1) /* cook header for relaxed canon */ : strdup(hdrs->value); /* just copy it for simple canon */ if (!rh) return PDKIM_ERR_OOM; /* Feed header to the hash algorithm */ if (sig->algo == PDKIM_ALGO_RSA_SHA1) sha1_update(&sha1_headers, (unsigned char *)rh, strlen(rh)); else sha2_update(&sha2_headers, (unsigned char *)rh, strlen(rh)); DEBUG(D_acl) pdkim_quoteprint(rh, strlen(rh), 1); free(rh); hdrs->tag = 1; break; } if (!q) break; p = q+1; } free(b); } DEBUG(D_acl) debug_printf( "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); /* SIGNING ---------------------------------------------------------------- */ if (ctx->mode == PDKIM_MODE_SIGN) { /* Copy headernames to signature struct */ sig->headernames = strdup(headernames->str); pdkim_strfree(headernames); /* Create signature header with b= omitted */ sig_hdr = pdkim_create_header(ctx->sig, 0); } /* 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; } DEBUG(D_acl) { debug_printf( "PDKIM >> Signed DKIM-Signature header, canonicalized >>>>>>>>>>>>>>>>>\n"); pdkim_quoteprint(sig_hdr, strlen(sig_hdr), 1); debug_printf( "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); } /* Finalize header hash */ if (sig->algo == PDKIM_ALGO_RSA_SHA1) { sha1_update(&sha1_headers, (unsigned char *)sig_hdr, strlen(sig_hdr)); sha1_finish(&sha1_headers, (unsigned char *)headerhash); DEBUG(D_acl) { debug_printf( "PDKIM [%s] hh computed: ", sig->domain); pdkim_hexprint(headerhash, 20, 1); } } else { sha2_update(&sha2_headers, (unsigned char *)sig_hdr, strlen(sig_hdr)); sha2_finish(&sha2_headers, (unsigned char *)headerhash); DEBUG(D_acl) { debug_printf("PDKIM [%s] hh computed: ", sig->domain); pdkim_hexprint(headerhash, 32, 1); } } free(sig_hdr); /* SIGNING ---------------------------------------------------------------- */ if (ctx->mode == PDKIM_MODE_SIGN) { rsa_context rsa; rsa_init(&rsa, RSA_PKCS_V15, 0); /* Perform private key operation */ if (rsa_parse_key(&rsa, (unsigned char *)sig->rsa_privkey, strlen(sig->rsa_privkey), NULL, 0) != 0) return PDKIM_ERR_RSA_PRIVKEY; sig->sigdata_len = mpi_size(&(rsa.N)); if (!(sig->sigdata = malloc(sig->sigdata_len))) return PDKIM_ERR_OOM; if (rsa_pkcs1_sign( &rsa, RSA_PRIVATE, ((sig->algo == PDKIM_ALGO_RSA_SHA1)? SIG_RSA_SHA1:SIG_RSA_SHA256), 0, (unsigned char *)headerhash, (unsigned char *)sig->sigdata ) != 0) return PDKIM_ERR_RSA_SIGNING; rsa_free(&rsa); DEBUG(D_acl) { debug_printf( "PDKIM [%s] b computed: ", sig->domain); pdkim_hexprint(sig->sigdata, sig->sigdata_len, 1); } if (!(sig->signature_header = pdkim_create_header(ctx->sig, 1))) return PDKIM_ERR_OOM; } /* VERIFICATION ----------------------------------------------------------- */ else { rsa_context rsa; char *dns_txt_name, *dns_txt_reply; rsa_init(&rsa, RSA_PKCS_V15, 0); if (!(dns_txt_name = malloc(PDKIM_DNS_TXT_MAX_NAMELEN))) return PDKIM_ERR_OOM; if (!(dns_txt_reply = malloc(PDKIM_DNS_TXT_MAX_RECLEN))) { 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); 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; goto NEXT_VERIFY; } if ( ctx->dns_txt_callback(dns_txt_name, 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; goto NEXT_VERIFY; } DEBUG(D_acl) { debug_printf( "PDKIM >> Parsing public key record >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n" " Raw record: "); pdkim_quoteprint(dns_txt_reply, strlen(dns_txt_reply), 1); } if (!(sig->pubkey = pdkim_parse_pubkey_record(ctx, dns_txt_reply))) { sig->verify_status = PDKIM_VERIFY_INVALID; sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_PARSING; DEBUG(D_acl) debug_printf( " Error while parsing public key record\n" "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); goto NEXT_VERIFY; } DEBUG(D_acl) debug_printf( "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); if (rsa_parse_public_key(&rsa, (unsigned char *)sig->pubkey->key, sig->pubkey->key_len) != 0) { sig->verify_status = PDKIM_VERIFY_INVALID; sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_PARSING; goto NEXT_VERIFY; } /* Check the signature */ if (rsa_pkcs1_verify(&rsa, RSA_PUBLIC, ((sig->algo == PDKIM_ALGO_RSA_SHA1)? SIG_RSA_SHA1:SIG_RSA_SHA256), 0, (unsigned char *)headerhash, (unsigned char *)sig->sigdata) != 0) { 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) */ if (sig->verify_status == PDKIM_VERIFY_NONE) sig->verify_status = PDKIM_VERIFY_PASS; NEXT_VERIFY: DEBUG(D_acl) { debug_printf("PDKIM [%s] signature status: %s", sig->domain, 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"); } rsa_free(&rsa); 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; } /* -------------------------------------------------------------------------- */ DLLEXPORT pdkim_ctx * pdkim_init_verify(int(*dns_txt_callback)(char *, char *)) { pdkim_ctx *ctx = malloc(sizeof(pdkim_ctx)); if (!ctx) return NULL; memset(ctx, 0, sizeof(pdkim_ctx)); if (!(ctx->linebuf = malloc(PDKIM_MAX_BODY_LINE_LEN))) { free(ctx); return NULL; } ctx->mode = PDKIM_MODE_VERIFY; ctx->dns_txt_callback = dns_txt_callback; return ctx; } /* -------------------------------------------------------------------------- */ DLLEXPORT pdkim_ctx * pdkim_init_sign(char *domain, char *selector, char *rsa_privkey) { pdkim_ctx *ctx; pdkim_signature *sig; if (!domain || !selector || !rsa_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; } if (!(sig = malloc(sizeof(pdkim_signature)))) { free(ctx->linebuf); free(ctx); return NULL; } memset(sig, 0, sizeof(pdkim_signature)); sig->bodylength = -1; ctx->mode = PDKIM_MODE_SIGN; ctx->sig = sig; ctx->sig->domain = strdup(domain); ctx->sig->selector = strdup(selector); ctx->sig->rsa_privkey = strdup(rsa_privkey); if (!ctx->sig->domain || !ctx->sig->selector || !ctx->sig->rsa_privkey) goto BAIL; if (!(ctx->sig->sha1_body = malloc(sizeof(sha1_context)))) goto BAIL; sha1_starts(ctx->sig->sha1_body); if (!(ctx->sig->sha2_body = malloc(sizeof(sha2_context)))) goto BAIL; sha2_starts(ctx->sig->sha2_body, 0); return ctx; BAIL: pdkim_free_ctx(ctx); return NULL; } /* -------------------------------------------------------------------------- */ DLLEXPORT int pdkim_set_optional(pdkim_ctx *ctx, char *sign_headers, char *identity, int canon_headers, int canon_body, long bodylength, int algo, unsigned long created, unsigned long expires) { if (identity) if (!(ctx->sig->identity = strdup(identity))) return PDKIM_ERR_OOM; if (sign_headers) if (!(ctx->sig->sign_headers = strdup(sign_headers))) return PDKIM_ERR_OOM; ctx->sig->canon_headers = canon_headers; ctx->sig->canon_body = canon_body; ctx->sig->bodylength = bodylength; ctx->sig->algo = algo; ctx->sig->created = created; ctx->sig->expires = expires; return PDKIM_OK; }