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 (c >= 'a' && c <= 'z')
- cur_tag = string_catn(cur_tag, &ts, &tl, p, 1);
+ uschar c = *p;
- 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 */
we don't know what allocations the SHA routines might
do, not safe to use store_get()/store_reset(). */
- relaxed_data = malloc(len+1);
+ relaxed_data = store_malloc(len+1);
for (p = data; *p; p++)
{
sig = sig->next;
}
-if (relaxed_data) free(relaxed_data);
+if (relaxed_data) store_free(relaxed_data);
return PDKIM_OK;
}
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);
}
{
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);
if (!data)
pdkim_body_complete(ctx);
-for (p = 0; p<len; p++)
+else for (p = 0; p<len; p++)
{
uschar c = data[p];
}
+/* -------------------------------------------------------------------------- */
+
+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
{
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;
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
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;
/* 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);
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(
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);
}
{
ev_ctx vctx;
const uschar * errstr;
-
- uschar *dns_txt_name, *dns_txt_reply;
+ pdkim_pubkey * p;
/* Make sure we have all required signature tags */
if (!( sig->domain && *sig->domain
goto NEXT_VERIFY;
}
- /* 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;
- goto NEXT_VERIFY;
- }
-
- DEBUG(D_acl)
- {
- debug_printf(
- "PDKIM >> Parsing public key record >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"
- " Raw record: ");
- pdkim_quoteprint(CUS dns_txt_reply, Ustrlen(dns_txt_reply));
- }
-
- if (!(sig->pubkey = pdkim_parse_pubkey_record(ctx, CUS dns_txt_reply)))
- {
- sig->verify_status = PDKIM_VERIFY_INVALID;
- sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD;
-
- DEBUG(D_acl) debug_printf(
- " Error while parsing public key record\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)))
}
- /* 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;
/* -------------------------------------------------------------------------- */
DLLEXPORT pdkim_ctx *
-pdkim_init_sign(char *domain, char *selector, char *rsa_privkey, int algo,
- BOOL dot_stuffed)
+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->flags = dot_stuffed ? PDKIM_MODE_SIGN | PDKIM_DOT_TERM : PDKIM_MODE_SIGN;
-ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN);
+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;
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;
}