{
case PDKIM_VERIFY_FAIL_BODY: return "PDKIM_VERIFY_FAIL_BODY";
case PDKIM_VERIFY_FAIL_MESSAGE: return "PDKIM_VERIFY_FAIL_MESSAGE";
+ case PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH: return "PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH";
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";
}
}
-const char *
+const uschar *
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)";
+ case PDKIM_OK: return US"OK";
+ case PDKIM_FAIL: return US"FAIL";
+ case PDKIM_ERR_RSA_PRIVKEY: return US"RSA_PRIVKEY";
+ case PDKIM_ERR_RSA_SIGNING: return US"RSA SIGNING";
+ case PDKIM_ERR_LONG_LINE: return US"RSA_LONG_LINE";
+ case PDKIM_ERR_BUFFER_TOO_SMALL: return US"BUFFER_TOO_SMALL";
+ case PDKIM_SIGN_PRIVKEY_WRAP: return US"PRIVKEY_WRAP";
+ case PDKIM_SIGN_PRIVKEY_B64D: return US"PRIVKEY_B64D";
+ default: return US"(unknown)";
}
}
/* -------------------------------------------------------------------------- */
#define PDKIM_QP_ERROR_DECODE -1
-static uschar *
-pdkim_decode_qp_char(uschar *qp_p, int *c)
+static const uschar *
+pdkim_decode_qp_char(const uschar *qp_p, int *c)
{
-uschar *initial_pos = qp_p;
+const uschar *initial_pos = qp_p;
/* Advance one char */
qp_p++;
/* -------------------------------------------------------------------------- */
static uschar *
-pdkim_decode_qp(uschar * str)
+pdkim_decode_qp(const uschar * str)
{
int nchar = 0;
uschar * q;
-uschar * p = str;
+const uschar * p = str;
uschar * n = store_get(Ustrlen(str)+1);
*n = '\0';
/* -------------------------------------------------------------------------- */
static void
-pdkim_decode_base64(uschar *str, blob * b)
+pdkim_decode_base64(const uschar * str, blob * b)
{
int dlen;
dlen = b64decode(str, &b->data);
static pdkim_signature *
pdkim_parse_sig_header(pdkim_ctx *ctx, uschar * raw_hdr)
{
-pdkim_signature *sig ;
+pdkim_signature * sig;
uschar *p, *q;
uschar * cur_tag = NULL; int ts = 0, tl = 0;
uschar * cur_val = NULL; int vs = 0, vl = 0;
static pdkim_pubkey *
pdkim_parse_pubkey_record(pdkim_ctx *ctx, const uschar *raw_record)
{
-pdkim_pubkey *pub;
-const uschar *p;
-uschar * cur_tag = NULL; int ts = 0, tl = 0;
-uschar * cur_val = NULL; int vs = 0, vl = 0;
-int where = PDKIM_HDR_LIMBO;
+const uschar * ele;
+int sep = ';';
+pdkim_pubkey * pub;
pub = store_get(sizeof(pdkim_pubkey));
memset(pub, 0, sizeof(pdkim_pubkey));
-for (p = raw_record; ; p++)
+while ((ele = string_nextinlist(&raw_record, &sep, NULL, 0)))
+ {
+ const uschar * val;
+
+ if ((val = Ustrchr(ele, '=')))
{
- uschar c = *p;
+ int taglen = val++ - ele;
- /* Ignore FWS */
- if (c != '\r' && c != '\n') switch (where)
+ DEBUG(D_acl) debug_printf(" %.*s=%s\n", taglen, ele, val);
+ switch (ele[0])
{
- 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*/
-
- case PDKIM_HDR_TAG:
- if (c >= 'a' && c <= 'z')
- cur_tag = string_catn(cur_tag, &ts, &tl, p, 1);
-
- if (c == '=')
- {
- cur_tag[tl] = '\0';
- where = PDKIM_HDR_VALUE;
- }
- 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");
+ case 'v': pub->version = val; break;
+ case 'h': pub->hashes = val; break;
+ case 'k': break;
+ case 'g': pub->granularity = val; break;
+ case 'n': pub->notes = pdkim_decode_qp(val); break;
+ case 'p': pdkim_decode_base64(val, &pub->key); break;
+ case 's': pub->srvtype = val; break;
+ case 't': if (Ustrchr(val, 'y')) pub->testing = 1;
+ if (Ustrchr(val, 's')) pub->no_subdomaining = 1;
break;
- }
- }
- tl = 0;
- vl = 0;
- where = PDKIM_HDR_LIMBO;
- }
- else
- cur_val = string_catn(cur_val, &vs, &vl, p, 1);
- break;
+ default: DEBUG(D_acl) debug_printf(" Unknown tag encountered\n"); 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;
+else if (Ustrcmp(pub->version, PDKIM_PUB_RECORD_VERSION) != 0)
+ {
+ DEBUG(D_acl) debug_printf(" Bad v= field\n");
+ return NULL;
+ }
-if (!pub->granularity) pub->granularity = string_copy(US"*");
+if (!pub->granularity) pub->granularity = US"*";
/*
-if (!pub->keytype ) pub->keytype = string_copy(US"rsa");
+if (!pub->keytype ) pub->keytype = US"rsa";
*/
-if (!pub->srvtype ) pub->srvtype = string_copy(US"*");
+if (!pub->srvtype ) pub->srvtype = US"*";
/* p= is required */
if (pub->key.data)
return pub;
+DEBUG(D_acl) debug_printf(" Missing p= field\n");
return NULL;
}
#define DKIM_SIGNATURE_HEADERNAME "DKIM-Signature:"
static int
-pdkim_header_complete(pdkim_ctx *ctx)
+pdkim_header_complete(pdkim_ctx * ctx)
{
+pdkim_signature * sig, * last_sig;
+
/* Special case: The last header can have an extra \r appended */
if ( (ctx->cur_header_len > 1) &&
(ctx->cur_header[(ctx->cur_header_len)-1] == '\r') )
--ctx->cur_header_len;
ctx->cur_header[ctx->cur_header_len] = '\0';
-ctx->num_headers++;
-if (ctx->num_headers > PDKIM_MAX_HEADERS) goto BAIL;
+if (++ctx->num_headers > PDKIM_MAX_HEADERS) goto BAIL;
/* SIGNING -------------------------------------------------------------- */
if (ctx->flags & PDKIM_MODE_SIGN)
- {
- pdkim_signature *sig;
-
for (sig = ctx->sig; sig; sig = sig->next) /* Traverse all signatures */
/* Add header to the signed headers list (in reverse order) */
sig->headers = pdkim_prepend_stringlist(sig->headers,
ctx->cur_header);
- }
/* VERIFICATION ----------------------------------------------------------- */
/* DKIM-Signature: headers are added to the verification list */
DEBUG(D_acl)
{
debug_printf("PDKIM >> raw hdr: ");
- pdkim_quoteprint(CUS ctx->cur_header, Ustrlen(ctx->cur_header));
+ pdkim_quoteprint(CUS ctx->cur_header, ctx->cur_header_len);
}
#endif
if (strncasecmp(CCS ctx->cur_header,
DKIM_SIGNATURE_HEADERNAME,
Ustrlen(DKIM_SIGNATURE_HEADERNAME)) == 0)
{
- pdkim_signature * new_sig, * last_sig;
-
/* Create and chain new signature block. We could error-check for all
required tags here, but prefer to create the internal sig and expicitly
fail verification of it later. */
DEBUG(D_acl) debug_printf(
"PDKIM >> Found sig, trying to parse >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
- new_sig = pdkim_parse_sig_header(ctx, ctx->cur_header);
+ sig = pdkim_parse_sig_header(ctx, ctx->cur_header);
if (!(last_sig = ctx->sig))
- ctx->sig = new_sig;
+ ctx->sig = sig;
else
{
while (last_sig->next) last_sig = last_sig->next;
- last_sig->next = new_sig;
+ last_sig->next = sig;
}
}
}
BAIL:
-*ctx->cur_header = '\0';
-ctx->cur_header_len = 0; /* leave buffer for reuse */
+ctx->cur_header[ctx->cur_header_len = 0] = '\0'; /* leave buffer for reuse */
return PDKIM_OK;
}
#define HEADER_BUFFER_FRAG_SIZE 256
DLLEXPORT int
-pdkim_feed(pdkim_ctx *ctx, char *data, int len)
+pdkim_feed(pdkim_ctx * ctx, uschar * data, int len)
{
int p, rc;
/* -------------------------------------------------------------------------- */
static pdkim_pubkey *
-pdkim_key_from_dns(pdkim_ctx * ctx, pdkim_signature * sig, ev_ctx * vctx)
+pdkim_key_from_dns(pdkim_ctx * ctx, pdkim_signature * sig, ev_ctx * vctx,
+ const uschar ** errstr)
{
uschar * dns_txt_name, * dns_txt_reply;
pdkim_pubkey * p;
-const uschar * errstr;
/* Fetch public key for signing domain, from DNS */
"PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
/* Import public key */
-if ((errstr = exim_rsa_verify_init(&p->key, vctx)))
+if ((*errstr = exim_rsa_verify_init(&p->key, vctx)))
{
- DEBUG(D_acl) debug_printf("verify_init: %s\n", errstr);
+ 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;
/* -------------------------------------------------------------------------- */
DLLEXPORT int
-pdkim_feed_finish(pdkim_ctx *ctx, pdkim_signature **return_signatures)
+pdkim_feed_finish(pdkim_ctx * ctx, pdkim_signature ** return_signatures,
+ const uschar ** err)
{
pdkim_signature *sig = ctx->sig;
}
DEBUG(D_acl) debug_printf(
- "PDKIM >> Header data for hash, 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)
{
es_ctx sctx;
- const uschar * errstr;
/* Import private key */
- if ((errstr = exim_rsa_signing_init(US sig->rsa_privkey, &sctx)))
+ if ((*err = exim_rsa_signing_init(US sig->rsa_privkey, &sctx)))
{
- DEBUG(D_acl) debug_printf("signing_init: %s\n", errstr);
+ DEBUG(D_acl) debug_printf("signing_init: %s\n", *err);
return PDKIM_ERR_RSA_PRIVKEY;
}
hdata = hhash;
#endif
- if ((errstr = exim_rsa_sign(&sctx, is_sha1, &hdata, &sig->sighash)))
+ if ((*err = exim_rsa_sign(&sctx, is_sha1, &hdata, &sig->sighash)))
{
- DEBUG(D_acl) debug_printf("signing: %s\n", errstr);
+ DEBUG(D_acl) debug_printf("signing: %s\n", *err);
return PDKIM_ERR_RSA_SIGNING;
}
else
{
ev_ctx vctx;
- const uschar * errstr;
- pdkim_pubkey * p;
/* Make sure we have all required signature tags */
if (!( sig->domain && *sig->domain
goto NEXT_VERIFY;
}
- if (!(sig->pubkey = pdkim_key_from_dns(ctx, sig, &vctx)))
+ if (!(sig->pubkey = pdkim_key_from_dns(ctx, sig, &vctx, err)))
goto NEXT_VERIFY;
+ /* If the pubkey limits to a list of specific hashes, ignore sigs that
+ do not have the hash part of the sig algorithm matching */
+
+ if (sig->pubkey->hashes)
+ {
+ const uschar * list = sig->pubkey->hashes, * ele;
+ int sep = ':';
+ while ((ele = string_nextinlist(&list, &sep, NULL, 0)))
+ if (Ustrcmp(ele, pdkim_algos[sig->algo] + 4) == 0) break;
+ if (!ele)
+ {
+ DEBUG(D_acl) debug_printf("pubkey h=%s vs sig a=%s\n",
+ sig->pubkey->hashes, pdkim_algos[sig->algo]);
+ sig->verify_status = PDKIM_VERIFY_FAIL;
+ sig->verify_ext_status = PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH;
+ goto NEXT_VERIFY;
+ }
+ }
+
/* Check the signature */
- if ((errstr = exim_rsa_verify(&vctx, is_sha1, &hhash, &sig->sighash)))
+ if ((*err = exim_rsa_verify(&vctx, is_sha1, &hhash, &sig->sighash)))
{
- DEBUG(D_acl) debug_printf("headers verify: %s\n", errstr);
+ DEBUG(D_acl) debug_printf("headers verify: %s\n", *err);
sig->verify_status = PDKIM_VERIFY_FAIL;
sig->verify_ext_status = PDKIM_VERIFY_FAIL_MESSAGE;
goto NEXT_VERIFY;
DLLEXPORT pdkim_ctx *
pdkim_init_sign(char * domain, char * selector, char * rsa_privkey, int algo,
- BOOL dot_stuffed, int(*dns_txt_callback)(char *, char *))
+ BOOL dot_stuffed, int(*dns_txt_callback)(char *, char *),
+ const uschar ** errstr)
{
pdkim_ctx * ctx;
pdkim_signature * sig;
pdkim_signature s = *sig;
ev_ctx vctx;
- debug_printf("PDKIM (checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
- if (!pdkim_key_from_dns(ctx, &s, &vctx))
+ debug_printf("PDKIM (checking verify key)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
+ if (!pdkim_key_from_dns(ctx, &s, &vctx, errstr))
debug_printf("WARNING: bad dkim key in dns\n");
- debug_printf("PDKIM (finished checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ debug_printf("PDKIM (finished checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
}
return ctx;
}