#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)
/* 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;
}
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;
/* 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;
}
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
) )
{
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 (Ustrcmp(ele, pdkim_hashes[sig->hashtype].dkim_hashname) == 0) break;
if (!ele)
{
- DEBUG(D_acl) debug_printf("pubkey h=%s vs sig a=%s\n",
- sig->pubkey->hashes, pdkim_algos[sig->algo]);
+ 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;
}
void
pdkim_init(void)
{
-exim_rsa_init();
+exim_dkim_init();
}