#include "crypt_ver.h"
-#ifdef RSA_OPENSSL
+#ifdef SIGN_OPENSSL
# include <openssl/rsa.h>
# include <openssl/ssl.h>
# include <openssl/err.h>
-#elif defined(RSA_GNUTLS)
+#elif defined(SIGN_GNUTLS)
# include <gnutls/gnutls.h>
# include <gnutls/x509.h>
#endif
#include "pdkim.h"
-#include "rsa.h"
+#include "signing.h"
#define PDKIM_SIGNATURE_VERSION "1"
#define PDKIM_PUB_RECORD_VERSION US "DKIM1"
US"dns/txt",
NULL
};
-const uschar * pdkim_algos[] = {
- US"rsa-sha256",
- US"rsa-sha1",
- NULL
-};
const uschar * pdkim_canons[] = {
US"simple",
US"relaxed",
NULL
};
-const uschar * pdkim_hashes[] = {
- US"sha256",
- US"sha1",
- NULL
+
+typedef struct {
+ const uschar * dkim_hashname;
+ hashmethod exim_hashmethod;
+} pdkim_hashtype;
+static const pdkim_hashtype pdkim_hashes[] = {
+ { US"sha1", HASH_SHA1 },
+ { US"sha256", HASH_SHA2_256 },
+ { US"sha512", HASH_SHA2_512 }
};
+
const uschar * pdkim_keytypes[] = {
- US"rsa",
- NULL
+ US"rsa"
};
typedef struct pdkim_combined_canon_entry {
/* -------------------------------------------------------------------------- */
+uschar *
+dkim_sig_to_a_tag(pdkim_signature * sig)
+{
+if ( sig->keytype < 0 || sig->keytype > nelem(pdkim_keytypes)
+ || sig->hashtype < 0 || sig->hashtype > nelem(pdkim_hashes))
+ return US"err";
+return string_sprintf("%s-%s",
+ pdkim_keytypes[sig->keytype], pdkim_hashes[sig->hashtype].dkim_hashname);
+}
+
+
const char *
pdkim_verify_status_str(int status)
{
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";
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 "(unknown)";
+ default: return US"(unknown)";
}
}
/* Performs "relaxed" canonicalization of a header. */
static uschar *
-pdkim_relax_header(const uschar * header, int crlf)
+pdkim_relax_header(const uschar * header, BOOL append_crlf)
{
BOOL past_field_name = FALSE;
BOOL seen_wsp = FALSE;
for (p = header; *p; p++)
{
uschar c = *p;
- /* Ignore CR & LF */
- if (c == '\r' || c == '\n')
+
+ if (c == '\r' || c == '\n') /* Ignore CR & LF */
continue;
if (c == '\t' || c == ' ')
{
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 */
+ if (seen_wsp) q--; /* This removes WSP immediately before the colon */
+ seen_wsp = TRUE; /* This removes WSP immediately after the colon */
past_field_name = TRUE;
}
else
if (q > relaxed && q[-1] == ' ') q--; /* Squash eventual trailing SP */
-if (crlf) { *q++ = '\r'; *q++ = '\n'; }
+if (append_crlf) { *q++ = '\r'; *q++ = '\n'; }
*q = '\0';
return relaxed;
}
/* -------------------------------------------------------------------------- */
#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);
sig->bodylength = -1;
/* Set so invalid/missing data error display is accurate */
-sig->algo = -1;
sig->version = 0;
+sig->keytype = -1;
+sig->hashtype = -1;
q = sig->rawsig_no_b_val = store_get(Ustrlen(raw_hdr)+1);
Ustrcmp(cur_val, PDKIM_SIGNATURE_VERSION) == 0 ? 1 : -1;
break;
case 'a':
- for (i = 0; pdkim_algos[i]; i++)
- if (Ustrcmp(cur_val, pdkim_algos[i]) == 0)
- {
- sig->algo = i;
- break;
- }
+ {
+ uschar * s = Ustrchr(cur_val, '-');
+
+ for(i = 0; i < nelem(pdkim_keytypes); i++)
+ if (Ustrncmp(cur_val, pdkim_keytypes[i], s - cur_val) == 0)
+ { sig->keytype = i; break; }
+ for (++s, i = 0; i < nelem(pdkim_hashes); i++)
+ if (Ustrcmp(s, pdkim_hashes[i].dkim_hashname) == 0)
+ { sig->hashtype = i; break; }
break;
+ }
+
case 'c':
for (i = 0; pdkim_combined_canons[i].str; i++)
if (Ustrcmp(cur_val, pdkim_combined_canons[i].str) == 0)
"PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
}
+/*XXX hash method: extend for sha512 */
if (!exim_sha_init(&sig->body_hash_ctx,
- sig->algo == PDKIM_ALGO_RSA_SHA1 ? HASH_SHA1 : HASH_SHA256))
+ pdkim_hashes[sig->hashtype].exim_hashmethod))
{
- DEBUG(D_acl) debug_printf("PDKIM: hash init internal error\n");
+ DEBUG(D_acl)
+ debug_printf("PDKIM: hash init error, possibly nonhandled hashtype\n");
return NULL;
}
return sig;
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 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;
/* Required and static bits */
hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"a=",
- pdkim_algos[sig->algo]);
+ dkim_sig_to_a_tag(sig));
hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"q=",
pdkim_querymethods[sig->querymethod]);
hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"c=",
}
/* Preliminary or final version? */
-base64_b = final ? pdkim_encode_base64(&sig->sighash) : US"";
-hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"b=", base64_b);
+if (final)
+ {
+ base64_b = pdkim_encode_base64(&sig->sighash);
+ hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"b=", base64_b);
-/* add trailing semicolon: I'm not sure if this is actually needed */
-hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, NULL, US";", US"");
+ /* add trailing semicolon: I'm not sure if this is actually needed */
+ hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, NULL, US";", US"");
+ }
+else
+ {
+ /* To satisfy the rule "all surrounding whitespace [...] deleted"
+ ( RFC 6376 section 3.7 ) we ensure there is no whitespace here. Otherwise
+ the headcat routine could insert a linebreak which the relaxer would reduce
+ to a single space preceding the terminating semicolon, resulting in an
+ incorrect header-hash. */
+ hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"b=;", US"");
+ }
hdr[hdr_len] = '\0';
return hdr;
"PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
/* Import public key */
-if ((*errstr = exim_rsa_verify_init(&p->key, vctx)))
+if ((*errstr = exim_dkim_verify_init(&p->key, vctx)))
{
DEBUG(D_acl) debug_printf("verify_init: %s\n", *errstr);
sig->verify_status = PDKIM_VERIFY_INVALID;
while (sig)
{
- BOOL is_sha1 = sig->algo == PDKIM_ALGO_RSA_SHA1;
hctx hhash_ctx;
uschar * sig_hdr = US"";
blob hhash;
hdata.data = NULL;
hdata.len = 0;
- if (!exim_sha_init(&hhash_ctx, is_sha1 ? HASH_SHA1 : HASH_SHA256))
+ if (!exim_sha_init(&hhash_ctx, pdkim_hashes[sig->hashtype].exim_hashmethod))
{
- DEBUG(D_acl) debug_printf("PDKIM: hask setup internal error\n");
+ DEBUG(D_acl)
+ debug_printf("PDKIM: hash setup error, possibly nonhandled hashtype\n");
break;
}
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
p->value, (q - US p->value) + (p->next ? 1 : 0));
rh = sig->canon_headers == PDKIM_CANON_RELAXED
- ? pdkim_relax_header(p->value, 1) /* cook header for relaxed canon */
+ ? pdkim_relax_header(p->value, TRUE) /* cook header for relaxed canon */
: string_copy(CUS p->value); /* just copy it for simple canon */
/* Feed header to the hash algorithm */
exim_sha_update(&hhash_ctx, CUS rh, Ustrlen(rh));
/* Remember headers block for signing (when the library cannot do incremental) */
- (void) exim_rsa_data_append(&hdata, &hdata_alloc, rh);
+ (void) exim_dkim_data_append(&hdata, &hdata_alloc, rh);
DEBUG(D_acl) pdkim_quoteprint(rh, Ustrlen(rh));
}
/* 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)
+ ? pdkim_relax_header(hdrs->value, TRUE)
: string_copy(CUS hdrs->value);
/* Feed header to the hash algorithm */
/* Relax header if necessary */
if (sig->canon_headers == PDKIM_CANON_RELAXED)
- sig_hdr = pdkim_relax_header(sig_hdr, 0);
+ sig_hdr = pdkim_relax_header(sig_hdr, FALSE);
DEBUG(D_acl)
{
}
/* Remember headers block for signing (when the library cannot do incremental) */
+/*XXX is this assuing algo == RSA? */
if (ctx->flags & PDKIM_MODE_SIGN)
- (void) exim_rsa_data_append(&hdata, &hdata_alloc, US sig_hdr);
+ (void) exim_dkim_data_append(&hdata, &hdata_alloc, US sig_hdr);
/* SIGNING ---------------------------------------------------------------- */
if (ctx->flags & PDKIM_MODE_SIGN)
{
es_ctx sctx;
- /* Import private key */
- if ((*err = exim_rsa_signing_init(US sig->rsa_privkey, &sctx)))
+ /* Import private key, including the keytype */
+/*XXX extend for non-RSA algos */
+ if ((*err = exim_dkim_signing_init(US sig->privkey, &sctx)))
{
DEBUG(D_acl) debug_printf("signing_init: %s\n", *err);
return PDKIM_ERR_RSA_PRIVKEY;
calculated, with GnuTLS we have to sign an entire block of headers
(due to available interfaces) and it recalculates the hash internally. */
-#if defined(RSA_OPENSSL) || defined(RSA_GCRYPT)
+#if defined(SIGN_OPENSSL) || defined(SIGN_GCRYPT)
hdata = hhash;
#endif
- if ((*err = exim_rsa_sign(&sctx, is_sha1, &hdata, &sig->sighash)))
+/*XXX extend for non-RSA algos */
+ if ((*err = exim_dkim_sign(&sctx,
+ pdkim_hashes[sig->hashtype].exim_hashmethod,
+ &hdata, &sig->sighash)))
{
DEBUG(D_acl) debug_printf("signing: %s\n", *err);
return PDKIM_ERR_RSA_SIGNING;
&& sig->headernames && *sig->headernames
&& sig->bodyhash.data
&& sig->sighash.data
- && sig->algo > -1
+ && sig->keytype >= 0
+ && sig->hashtype >= 0
&& sig->version
) )
{
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_hashes[sig->hashtype].dkim_hashname) == 0) break;
+ if (!ele)
+ {
+ DEBUG(D_acl) debug_printf("pubkey h=%s vs. sig a=%s_%s\n",
+ sig->pubkey->hashes,
+ pdkim_keytypes[sig->keytype],
+ pdkim_hashes[sig->hashtype].dkim_hashname);
+ sig->verify_status = PDKIM_VERIFY_FAIL;
+ sig->verify_ext_status = PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH;
+ goto NEXT_VERIFY;
+ }
+ }
+
/* Check the signature */
- if ((*err = exim_rsa_verify(&vctx, is_sha1, &hhash, &sig->sighash)))
+/*XXX needs extension for non-RSA */
+ if ((*err = exim_dkim_verify(&vctx,
+ pdkim_hashes[sig->hashtype].exim_hashmethod,
+ &hhash, &sig->sighash)))
{
DEBUG(D_acl) debug_printf("headers verify: %s\n", *err);
sig->verify_status = PDKIM_VERIFY_FAIL;
/* -------------------------------------------------------------------------- */
+/*XXX ? needs extension to cover non-RSA algo? */
+
DLLEXPORT pdkim_ctx *
-pdkim_init_sign(char * domain, char * selector, char * rsa_privkey, int algo,
- BOOL dot_stuffed, int(*dns_txt_callback)(char *, char *),
+pdkim_init_sign(uschar * domain, uschar * selector, uschar * privkey,
+ uschar * hashname, BOOL dot_stuffed, int(*dns_txt_callback)(char *, char *),
const uschar ** errstr)
{
+int hashtype;
pdkim_ctx * ctx;
pdkim_signature * sig;
-if (!domain || !selector || !rsa_privkey)
+if (!domain || !selector || !privkey)
return NULL;
ctx = store_get(sizeof(pdkim_ctx) + PDKIM_MAX_BODY_LINE_LEN + sizeof(pdkim_signature));
sig->domain = string_copy(US domain);
sig->selector = string_copy(US selector);
-sig->rsa_privkey = string_copy(US rsa_privkey);
-sig->algo = algo;
+sig->privkey = string_copy(US privkey);
+/*XXX no keytype yet; comes from privkey */
-if (!exim_sha_init(&sig->body_hash_ctx,
- algo == PDKIM_ALGO_RSA_SHA1 ? HASH_SHA1 : HASH_SHA256))
+for (hashtype = 0; hashtype < nelem(pdkim_hashes); hashtype++)
+ if (Ustrcmp(hashname, pdkim_hashes[hashtype].dkim_hashname) == 0)
+ { sig->hashtype = hashtype; break; }
+if (hashtype >= nelem(pdkim_hashes))
+ {
+ DEBUG(D_acl)
+ debug_printf("PDKIM: unrecognised hashname '%s'\n", hashname);
+ return NULL;
+ }
+
+if (!exim_sha_init(&sig->body_hash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
{
- DEBUG(D_acl) debug_printf("PDKIM: hash setup internal error\n");
+ DEBUG(D_acl)
+ debug_printf("PDKIM: hash setup error, possibly nonhandled hashtype\n");
return NULL;
}
pdkim_signature s = *sig;
ev_ctx vctx;
- debug_printf("PDKIM (checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ 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;
}
void
pdkim_init(void)
{
-exim_rsa_init();
+exim_dkim_init();
}