.option dkim_canon smtp string&!! unset
.option dkim_strict smtp string&!! unset
.option dkim_sign_headers smtp string&!! unset
+.option dkim_hash smtp string&!! sha256
DKIM signing options. For details see section &<<SECDKIMSIGN>>&.
signature.
When unspecified, the header names recommended in RFC4871 will be used.
+.new
+.option dkim_hash smtp string&!! sha256
+Can be set alternatively to &"sha1"& to use an alternate hash
+method. Note that sha1 is now condidered insecure, and deprecated.
+.wen
+
.section "Verifying DKIM signatures in incoming mail" "SECID514"
.cindex "DKIM" "verification"
is opened with a TFO cookie. Support varies between platforms
(Linux does both. FreeBSD server only, others unknown).
+13. DKIM support for multiple hashes.
+
Version 4.89
------------
pdkim_signature *dkim_cur_sig = NULL;
static const uschar * dkim_collect_error = NULL;
+
+
+/*XXX the caller only uses the first record if we return multiple.
+Could we hand back an allocated string?
+*/
+
static int
dkim_exim_query_dns_txt(char *name, char *answer)
{
logmsg = string_append(logmsg, &size, &ptr, 7,
" c=", sig->canon_headers == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
"/", sig->canon_body == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
- " a=", sig->algo == PDKIM_ALGO_RSA_SHA256
- ? "rsa-sha256"
- : sig->algo == PDKIM_ALGO_RSA_SHA1 ? "rsa-sha1" : "err",
+ " a=", dkim_sig_to_a_tag(sig),
string_sprintf(" b=%d",
(int)sig->sighash.len > -1 ? sig->sighash.len * 8 : 0));
if ((s= sig->identity)) logmsg = string_append(logmsg, &size, &ptr, 2, " i=", s);
switch (what)
{
case DKIM_ALGO:
- switch (dkim_cur_sig->algo)
- {
- case PDKIM_ALGO_RSA_SHA1: return US"rsa-sha1";
- case PDKIM_ALGO_RSA_SHA256:
- default: return US"rsa-sha256";
- }
+ return dkim_sig_to_a_tag(dkim_cur_sig);
case DKIM_BODYLENGTH:
return dkim_cur_sig->bodylength >= 0
uschar *dkim_canon_expanded;
uschar *dkim_sign_headers_expanded;
uschar *dkim_private_key_expanded;
+uschar *dkim_hash_expanded;
pdkim_ctx *ctx = NULL;
uschar *rc = NULL;
uschar *sigbuf = NULL;
dkim_private_key_expanded = big_buffer;
}
-/*XXX so we currently nail signing to RSA + SHA256. Need to extract algo
-from privkey, and provide means for selecting hash-method.
-Check for disallowed combos.
-Will need new dkim_ transport option for hash. */
+ if (!(dkim_hash_expanded = expand_string(dkim->dkim_hash)))
+ {
+ log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
+ "dkim_hash: %s", expand_string_message);
+ goto bad;
+ }
+
+/*XXX so we currently nail signing to RSA + given hash.
+Need to extract algo from privkey and check for disallowed combos. */
- if (!(ctx = pdkim_init_sign(CS dkim_signing_domain,
- CS dkim_signing_selector,
- CS dkim_private_key_expanded,
- PDKIM_ALGO_RSA_SHA256,
+ if (!(ctx = pdkim_init_sign(dkim_signing_domain,
+ dkim_signing_selector,
+ dkim_private_key_expanded,
+ dkim_hash_expanded,
dkim->dot_stuffed,
&dkim_exim_query_dns_txt,
errstr
blob b;
char st[3];
- if (!exim_sha_init(&h, HASH_SHA256))
+ if (!exim_sha_init(&h, HASH_SHA2_256))
{
expand_string_message = US"unrecognised sha256 variant";
goto EXPAND_FAILED;
/*XXX extend for sha512 */
switch (h->method = m)
{
- case HASH_SHA1: h->hashlen = 20; SHA1_Init (&h->u.sha1); break;
- case HASH_SHA256: h->hashlen = 32; SHA256_Init(&h->u.sha2); break;
- default: h->hashlen = 0; return FALSE;
+ case HASH_SHA1: h->hashlen = 20; SHA1_Init (&h->u.sha1); break;
+ case HASH_SHA2_256: h->hashlen = 32; SHA256_Init(&h->u.sha2_256); break;
+ case HASH_SHA2_384: h->hashlen = 48; SHA384_Init(&h->u.sha2_512); break;
+ case HASH_SHA2_512: h->hashlen = 64; SHA512_Init(&h->u.sha2_512); break;
+ default: h->hashlen = 0; return FALSE;
}
return TRUE;
}
{
switch (h->method)
{
- case HASH_SHA1: SHA1_Update (&h->u.sha1, data, len); break;
- case HASH_SHA256: SHA256_Update(&h->u.sha2, data, len); break;
+ case HASH_SHA1: SHA1_Update (&h->u.sha1, data, len); break;
+ case HASH_SHA2_256: SHA256_Update(&h->u.sha2_256, data, len); break;
+ case HASH_SHA2_384: SHA384_Update(&h->u.sha2_512, data, len); break;
+ case HASH_SHA2_512: SHA512_Update(&h->u.sha2_512, data, len); break;
/* should be blocked by init not handling these, but be explicit to
guard against accidents later (and hush up clang -Wswitch) */
default: assert(0);
b->data = store_get(b->len = h->hashlen);
switch (h->method)
{
- case HASH_SHA1: SHA1_Final (b->data, &h->u.sha1); break;
- case HASH_SHA256: SHA256_Final(b->data, &h->u.sha2); break;
+ case HASH_SHA1: SHA1_Final (b->data, &h->u.sha1); break;
+ case HASH_SHA2_256: SHA256_Final(b->data, &h->u.sha2_256); break;
+ case HASH_SHA2_384: SHA384_Final(b->data, &h->u.sha2_512); break;
+ case HASH_SHA2_512: SHA512_Final(b->data, &h->u.sha2_512); break;
default: assert(0);
}
}
/*XXX extend for sha512 */
switch (h->method = m)
{
- case HASH_SHA1: h->hashlen = 20; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA1); break;
- case HASH_SHA256: h->hashlen = 32; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA256); break;
+ case HASH_SHA1: h->hashlen = 20; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA1); break;
+ case HASH_SHA2_256: h->hashlen = 32; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA256); break;
+ case HASH_SHA2_384: h->hashlen = 48; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA384); break;
+ case HASH_SHA2_512: h->hashlen = 64; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA512); break;
#ifdef EXIM_HAVE_SHA3
case HASH_SHA3_256: h->hashlen = 32; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA3_256); break;
+ case HASH_SHA3_384: h->hashlen = 48; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA3_384); break;
+ case HASH_SHA3_512: h->hashlen = 64; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA3_512); break;
#endif
default: h->hashlen = 0; return FALSE;
}
/*XXX extend for sha512 */
switch (h->method = m)
{
- case HASH_SHA1: h->hashlen = 20; gcry_md_open(&h->sha, GCRY_MD_SHA1, 0); break;
- case HASH_SHA256: h->hashlen = 32; gcry_md_open(&h->sha, GCRY_MD_SHA256, 0); break;
+ case HASH_SHA1: h->hashlen = 20; gcry_md_open(&h->sha, GCRY_MD_SHA1, 0); break;
+ case HASH_SHA2_256: h->hashlen = 32; gcry_md_open(&h->sha, GCRY_MD_SHA256, 0); break;
+ case HASH_SHA2_384: h->hashlen = 48; gcry_md_open(&h->sha, GCRY_MD_SHA384, 0); break;
+ case HASH_SHA2_512: h->hashlen = 64; gcry_md_open(&h->sha, GCRY_MD_SHA512, 0); break;
+ case HASH_SHA3_256: h->hashlen = 32; gcry_md_open(&h->sha, GCRY_MD_SHA3_256, 0); break;
+ case HASH_SHA3_384: h->hashlen = 48; gcry_md_open(&h->sha, GCRY_MD_SHA3_384, 0); break;
+ case HASH_SHA3_512: h->hashlen = 64; gcry_md_open(&h->sha, GCRY_MD_SHA3_512, 0); break;
default: h->hashlen = 0; return FALSE;
}
return TRUE;
switch (h->method = m)
{
case HASH_SHA1: h->hashlen = 20; sha1_starts(&h->u.sha1); break;
- case HASH_SHA256: h->hashlen = 32; sha2_starts(&h->u.sha2, 0); break;
+ case HASH_SHA2_256: h->hashlen = 32; sha2_starts(&h->u.sha2, 0); break;
default: h->hashlen = 0; return FALSE;
}
return TRUE;
switch (h->method)
{
case HASH_SHA1: sha1_update(h->u.sha1, US data, len); break;
- case HASH_SHA256: sha2_update(h->u.sha2, US data, len); break;
+ case HASH_SHA2_256: sha2_update(h->u.sha2, US data, len); break;
}
}
switch (h->method)
{
case HASH_SHA1: sha1_finish(h->u.sha1, b->data); break;
- case HASH_SHA256: sha2_finish(h->u.sha2, b->data); break;
+ case HASH_SHA2_256: sha2_finish(h->u.sha2, b->data); break;
}
}
#endif
-/******************************************************************************/
-
-/* Common to all library versions */
-int
-exim_sha_hashlen(hctx * h)
-{
-return h->method == HASH_SHA1 ? 20
- : h->method == HASH_SHA256 ? 32
- : 0;
-}
/******************************************************************************/
typedef enum hashmethod {
HASH_BADTYPE,
HASH_SHA1,
- HASH_SHA256,
+
+ HASH_SHA2_256,
+ HASH_SHA2_384,
+ HASH_SHA2_512,
+
HASH_SHA3_224,
HASH_SHA3_256,
HASH_SHA3_384,
#ifdef SHA_OPENSSL
union {
SHA_CTX sha1; /* SHA1 block */
- SHA256_CTX sha2; /* SHA256 block */
+ SHA256_CTX sha2_256; /* SHA256 or 224 block */
+ SHA512_CTX sha2_512; /* SHA512 or 384 block */
} u;
#elif defined(SHA_GNUTLS)
extern BOOL exim_sha_init(hctx *, hashmethod);
extern void exim_sha_update(hctx *, const uschar *a, int);
extern void exim_sha_finish(hctx *, blob *);
-extern int exim_sha_hashlen(hctx *);
#endif
/* End of File */
/* Copyright (c) Jeremy Harris 2016 */
/* See the file NOTICE for conditions of use and distribution. */
-/* RSA and SHA routine selection for PDKIM */
+/* Signing and hashing routine selection for PDKIM */
#include "../exim.h"
#include "../sha_ver.h"
# include <gnutls/gnutls.h>
# if GNUTLS_VERSION_NUMBER >= 0x30000
-# define RSA_GNUTLS
+# define SIGN_GNUTLS
# else
-# define RSA_GCRYPT
+# define SIGN_GCRYPT
# endif
#else
-# define RSA_OPENSSL
+# define SIGN_OPENSSL
#endif
#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
US"dns/txt",
NULL
};
-const uschar * pdkim_algos[] = {
- US"rsa-sha256",
- US"rsa-sha1",
- NULL
-};
const uschar * pdkim_canons[] = {
US"simple",
US"relaxed",
NULL
};
-/*XXX currently unused */
-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 }
};
-/*XXX currently unused */
+
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)
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':
-/*XXX this searches a list of combined (algo + hash-method)s */
- 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)
/*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=",
-/*XXX this is a combo of algo and hash-method */
- 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=",
while (sig)
{
-/*XXX bool probably not enough */
- 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;
}
{
es_ctx sctx;
- /* Import private key */
+ /* Import private key, including the keytype */
/*XXX extend for non-RSA algos */
- if ((*err = exim_dkim_signing_init(US sig->rsa_privkey, &sctx)))
+ 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
/*XXX extend for non-RSA algos */
-/*XXX oddly the dkim rfc does _not_ say what variant (sha1 or sha256) of
-RSA signing should be done. We use the same variant as the hash-method. */
-
- if ((*err = exim_dkim_sign(&sctx, is_sha1, &hdata, &sig->sighash)))
+ 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 */
/*XXX needs extension for non-RSA */
- if ((*err = exim_dkim_verify(&vctx, is_sha1, &hhash, &sig->sighash)))
+ 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? Currently the "algo" is actually
-the combo of algo and hash-method */
+/*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 */
-/*XXX extend for sha512 */
-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: hash setup internal error\n");
+ 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 error, possibly nonhandled hashtype\n");
return NULL;
}
/* Some parameter values */
#define PDKIM_QUERYMETHOD_DNS_TXT 0
-#define PDKIM_ALGO_RSA_SHA256 0
-#define PDKIM_ALGO_RSA_SHA1 1
-
#define PDKIM_CANON_SIMPLE 0
#define PDKIM_CANON_RELAXED 1
-#define PDKIM_HASH_SHA256 0
-#define PDKIM_HASH_SHA1 1
+/*XXX change to enums */
+#define PDKIM_HASH_SHA256 1
#define PDKIM_KEYTYPE_RSA 0
/* (v=) The version, as an integer. Currently, always "1" */
int version;
- /* (a=) The signature algorithm. Either PDKIM_ALGO_RSA_SHA256
- or PDKIM_ALGO_RSA_SHA1 */
- int algo;
+ int keytype; /* PDKIM_KEYTYPE_RSA */
+ int hashtype; /* pdkim_hashes index */
/* (c=x/) Header canonicalization method. Either PDKIM_CANON_SIMPLE
or PDKIM_CANON_RELAXED */
unsigned long signed_body_bytes; /* How many body bytes we hashed */
pdkim_stringlist *headers; /* Raw headers included in the sig */
/* Signing specific ------------------------------------------------- */
- uschar * rsa_privkey; /* Private RSA key */
+ uschar * privkey; /* Private key */
uschar * sign_headers; /* To-be-signed header names */
uschar * rawsig_no_b_val; /* Original signature header w/o b= tag value. */
} pdkim_signature;
void pdkim_init (void);
DLLEXPORT
-pdkim_ctx *pdkim_init_sign (char *, char *, char *, int,
+pdkim_ctx *pdkim_init_sign (uschar *, uschar *, uschar *, uschar *,
BOOL, int(*)(char *, char *), const uschar **);
DLLEXPORT
const uschar * pdkim_errstr(int);
+uschar * dkim_sig_to_a_tag(pdkim_signature * sig);
+
#ifdef __cplusplus
}
#endif
#include "../blob.h"
#include "../hash.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
*
* Copyright (C) 2016 Exim maintainers
*
- * RSA signing/verification interface
-XXX rename interfaces to cover all signature methods.
-the method (algo) needs to be extracted from the supplied private-key
-and not only stashed as needed in the sign- or verify- context, but
-indicated to caller for protocol tag construction.
+ * signing/verification interface
*/
#include "../exim.h"
/******************************************************************************/
-#ifdef RSA_GNUTLS
+#ifdef SIGN_GNUTLS
void
exim_dkim_init(void)
k.data = privkey_pem;
k.size = strlen(privkey_pem);
-if ( (rc = gnutls_x509_privkey_init(&sign_ctx->rsa)) != GNUTLS_E_SUCCESS
- || (rc = gnutls_x509_privkey_import(sign_ctx->rsa, &k,
+if ( (rc = gnutls_x509_privkey_init(&sign_ctx->key)) != GNUTLS_E_SUCCESS
+ || (rc = gnutls_x509_privkey_import(sign_ctx->key, &k,
GNUTLS_X509_FMT_PEM)) != GNUTLS_E_SUCCESS
)
return gnutls_strerror(rc);
Return: NULL for success, or an error string */
const uschar *
-exim_dkim_sign(es_ctx * sign_ctx, BOOL is_sha1, blob * data, blob * sig)
+exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, blob * data, blob * sig)
{
+gnutls_digest_algorithm_t dig;
gnutls_datum_t k;
size_t sigsize = 0;
int rc;
const uschar * ret = NULL;
+switch (hash)
+ {
+ case HASH_SHA1: dig = GNUTLS_DIG_SHA1; break;
+ case HASH_SHA2_256: dig = GNUTLS_DIG_SHA256; break;
+ case HASH_SHA2_512: dig = GNUTLS_DIG_SHA512; break;
+ default: return US"nonhandled hash type";
+ }
+
/* Allocate mem for signature */
k.data = data->data;
k.size = data->len;
-(void) gnutls_x509_privkey_sign_data(sign_ctx->rsa,
- is_sha1 ? GNUTLS_DIG_SHA1 : GNUTLS_DIG_SHA256,
+(void) gnutls_x509_privkey_sign_data(sign_ctx->key, dig,
0, &k, NULL, &sigsize);
sig->data = store_get(sigsize);
sig->len = sigsize;
/* Do signing */
-/*XXX will need extension for hash type; looks ok for non-RSA algos
-so long as the privkey_import stage got them. */
-if ((rc = gnutls_x509_privkey_sign_data(sign_ctx->rsa,
- is_sha1 ? GNUTLS_DIG_SHA1 : GNUTLS_DIG_SHA256,
+if ((rc = gnutls_x509_privkey_sign_data(sign_ctx->key, dig,
0, &k, sig->data, &sigsize)) != GNUTLS_E_SUCCESS
)
ret = gnutls_strerror(rc);
-gnutls_x509_privkey_deinit(sign_ctx->rsa);
+gnutls_x509_privkey_deinit(sign_ctx->key);
return ret;
}
int rc;
const uschar * ret = NULL;
-gnutls_pubkey_init(&verify_ctx->rsa);
+gnutls_pubkey_init(&verify_ctx->key);
k.data = pubkey_der->data;
k.size = pubkey_der->len;
-if ((rc = gnutls_pubkey_import(verify_ctx->rsa, &k, GNUTLS_X509_FMT_DER))
+if ((rc = gnutls_pubkey_import(verify_ctx->key, &k, GNUTLS_X509_FMT_DER))
!= GNUTLS_E_SUCCESS)
ret = gnutls_strerror(rc);
return ret;
Return: NULL for success, or an error string */
const uschar *
-exim_dkim_verify(ev_ctx * verify_ctx, BOOL is_sha1, blob * data_hash, blob * sig)
+exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, blob * data_hash, blob * sig)
{
+gnutls_sign_algorithm_t algo;
gnutls_datum_t k, s;
int rc;
const uschar * ret = NULL;
+/*XXX needs extension for non-rsa */
+switch (hash)
+ {
+ case HASH_SHA1: algo = GNUTLS_SIGN_RSA_SHA1; break;
+ case HASH_SHA2_256: algo = GNUTLS_SIGN_RSA_SHA256; break;
+ case HASH_SHA2_512: algo = GNUTLS_SIGN_RSA_SHA512; break;
+ default: return US"nonhandled hash type";
+ }
+
k.data = data_hash->data;
k.size = data_hash->len;
s.data = sig->data;
s.size = sig->len;
-if ((rc = gnutls_pubkey_verify_hash2(verify_ctx->rsa,
-/*XXX needs extension for SHA512 */
- is_sha1 ? GNUTLS_SIGN_RSA_SHA1 : GNUTLS_SIGN_RSA_SHA256,
- 0, &k, &s)) < 0)
+if ((rc = gnutls_pubkey_verify_hash2(verify_ctx->key, algo, 0, &k, &s)) < 0)
ret = gnutls_strerror(rc);
-gnutls_pubkey_deinit(verify_ctx->rsa);
+gnutls_pubkey_deinit(verify_ctx->key);
return ret;
}
-#elif defined(RSA_GCRYPT)
+#elif defined(SIGN_GCRYPT)
/******************************************************************************/
+/* This variant is used under pre-3.0.0 GnuTLS. Only rsa-sha1 and rsa-sha256 */
/* Internal service routine:
Return: NULL for success, or an error string */
const uschar *
-exim_dkim_sign(es_ctx * sign_ctx, BOOL is_sha1, blob * data, blob * sig)
+exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, blob * data, blob * sig)
{
+BOOL is_sha1;
gcry_sexp_t s_hash = NULL, s_key = NULL, s_sig = NULL;
gcry_mpi_t m_sig;
uschar * errstr;
/*XXX will need extension for hash types (though, possibly, should
be re-specced to not rehash but take an already-hashed value? Actually
-current impl looks WRONG - it _is_ given a has so should not be
+current impl looks WRONG - it _is_ given a hash so should not be
re-hashing. Has this been tested?
Will need extension for non-RSA sugning algos. */
+switch (hash)
+ {
+ case HASH_SHA1: is_sha1 = TRUE; break;
+ case HASH_SHA2_256: is_sha1 = FALSE; break;
+ default: return US"nonhandled hash type";
+ }
+
#define SIGSPACE 128
sig->data = store_get(SIGSPACE);
Return: NULL for success, or an error string */
const uschar *
-exim_dkim_verify(ev_ctx * verify_ctx, BOOL is_sha1, blob * data_hash, blob * sig)
+exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, blob * data_hash, blob * sig)
{
/*
cf. libgnutls 2.8.5 _wrap_gcry_pk_verify()
gcry_error_t gerr;
uschar * stage;
+switch (hash)
+ {
+ case HASH_SHA1: is_sha1 = TRUE; break;
+ case HASH_SHA2_256: is_sha1 = FALSE; break;
+ default: return US"nonhandled hash type";
+ }
+
if ( (stage = US"pkey sexp build",
gerr = gcry_sexp_build (&s_pkey, NULL, "(public-key(rsa(n%m)(e%m)))",
verify_ctx->n, verify_ctx->e))
-#elif defined(RSA_OPENSSL)
+#elif defined(SIGN_OPENSSL)
/******************************************************************************/
void
const uschar *
exim_dkim_signing_init(uschar * privkey_pem, es_ctx * sign_ctx)
{
-uschar * p, * q;
-int len;
-
-/*XXX maybe use PEM_read_bio_PrivateKey() ???
-The sign_ctx would need to have an EVP_PKEY* */
-
-/* Convert PEM to DER */
-if ( !(p = Ustrstr(privkey_pem, "-----BEGIN RSA PRIVATE KEY-----"))
- || !(q = Ustrstr(p+=31, "-----END RSA PRIVATE KEY-----"))
- )
- return US"Bad PEM wrapping";
-
-*q = '\0';
-if ((len = b64decode(p, &p)) < 0)
- return US"b64decode failed";
-
-if (!(sign_ctx->rsa = d2i_RSAPrivateKey(NULL, CUSS &p, len)))
- {
- char ssl_errstring[256];
- ERR_load_crypto_strings(); /*XXX move to a startup routine */
- ERR_error_string(ERR_get_error(), ssl_errstring);
- return string_copy(US ssl_errstring);
- }
+BIO * bp = BIO_new_mem_buf(privkey_pem, -1);
+if (!(sign_ctx->key = PEM_read_bio_PrivateKey(bp, NULL, NULL, NULL)))
+ return ERR_error_string(ERR_get_error(), NULL);
return NULL;
}
Return: NULL for success, or an error string */
const uschar *
-exim_dkim_sign(es_ctx * sign_ctx, BOOL is_sha1, blob * data, blob * sig)
+exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, blob * data, blob * sig)
{
-uint len;
-const uschar * ret = NULL;
-
-/*XXX will need extension for non-RSA signing algo. Maybe use
-https://www.openssl.org/docs/man1.0.2/crypto/EVP_PKEY_sign.html ??? */
+const EVP_MD * md;
+EVP_PKEY_CTX * ctx;
+size_t siglen;
-/* Allocate mem for signature */
-len = RSA_size(sign_ctx->rsa);
-sig->data = store_get(len);
-sig->len = len;
+switch (hash)
+ {
+ case HASH_SHA1: md = EVP_sha1(); break;
+ case HASH_SHA2_256: md = EVP_sha256(); break;
+ case HASH_SHA2_512: md = EVP_sha512(); break;
+ default: return US"nonhandled hash type";
+ }
-/* Do signing */
-if (RSA_sign(is_sha1 ? NID_sha1 : NID_sha256,
- CUS data->data, data->len,
- US sig->data, &len, sign_ctx->rsa) != 1)
+if ( (ctx = EVP_PKEY_CTX_new(sign_ctx->key, NULL))
+ && EVP_PKEY_sign_init(ctx) > 0
+ && EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) > 0
+ && EVP_PKEY_CTX_set_signature_md(ctx, md) > 0
+ && EVP_PKEY_sign(ctx, NULL, &siglen, data->data, data->len) > 0
+ )
{
- char ssl_errstring[256];
- ERR_load_crypto_strings(); /*XXX move to a startup routine */
- ERR_error_string(ERR_get_error(), ssl_errstring);
- ret = string_copy(US ssl_errstring);
+ /* Allocate mem for signature */
+ sig->data = store_get(siglen);
+ sig->len = siglen;
+
+ if (EVP_PKEY_sign(ctx, sig->data, &siglen, data->data, data->len) > 0)
+ { EVP_PKEY_CTX_free(ctx); return NULL; }
}
-RSA_free(sign_ctx->rsa);
-return ret;;
+if (ctx) EVP_PKEY_CTX_free(ctx);
+return ERR_error_string(ERR_get_error(), NULL);
}
const uschar *
exim_dkim_verify_init(blob * pubkey_der, ev_ctx * verify_ctx)
{
-const uschar * p = CUS pubkey_der->data;
-const uschar * ret = NULL;
+const uschar * s = pubkey_der->data;
-/*XXX d2i_X509_PUBKEY, X509_get_pubkey(), and an EVP_PKEY* in verify_ctx. */
+/*XXX hmm, we never free this */
-if (!(verify_ctx->rsa = d2i_RSA_PUBKEY(NULL, &p, (long) pubkey_der->len)))
- {
- char ssl_errstring[256];
- ERR_load_crypto_strings(); /*XXX move to a startup routine */
- ERR_error_string(ERR_get_error(), ssl_errstring);
- ret = string_copy(CUS ssl_errstring);
- }
-return ret;
+if ((verify_ctx->key = d2i_PUBKEY(NULL, &s, pubkey_der->len)))
+ return NULL;
+return ERR_error_string(ERR_get_error(), NULL);
}
Return: NULL for success, or an error string */
const uschar *
-exim_dkim_verify(ev_ctx * verify_ctx, BOOL is_sha1, blob * data_hash, blob * sig)
+exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, blob * data_hash, blob * sig)
{
-const uschar * ret = NULL;
+const EVP_MD * md;
+EVP_PKEY_CTX * ctx;
-/*XXX needs extension for SHA512, Possibly EVP_PKEY_verify() is all we need??? */
-/* with EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING)
-- see example code at https://www.openssl.org/docs/man1.0.2/crypto/EVP_PKEY_verify.html
-and maybe also EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256()) though unclear
-if that only sets a pad/type field byte value, or sets up for an actual hash operation...
-Same on the signing side.
-https://www.openssl.org/docs/manmaster/man3/EVP_PKEY_CTX_set_rsa_padding.html says it does what we want. */
-
-if (RSA_verify(is_sha1 ? NID_sha1 : NID_sha256,
- CUS data_hash->data, data_hash->len,
- US sig->data, (uint) sig->len, verify_ctx->rsa) != 1)
+switch (hash)
{
- char ssl_errstring[256];
- ERR_load_crypto_strings(); /*XXX move to a startup routine */
- ERR_error_string(ERR_get_error(), ssl_errstring);
- ret = string_copy(US ssl_errstring);
+ case HASH_SHA1: md = EVP_sha1(); break;
+ case HASH_SHA2_256: md = EVP_sha256(); break;
+ case HASH_SHA2_512: md = EVP_sha512(); break;
+ default: return US"nonhandled hash type";
}
-return ret;
+
+if ( (ctx = EVP_PKEY_CTX_new(verify_ctx->key, NULL))
+ && EVP_PKEY_verify_init(ctx) > 0
+ && EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) > 0
+ && EVP_PKEY_CTX_set_signature_md(ctx, md) > 0
+ && EVP_PKEY_verify(ctx, sig->data, sig->len,
+ data_hash->data, data_hash->len) == 1
+ )
+ { EVP_PKEY_CTX_free(ctx); return NULL; }
+
+if (ctx) EVP_PKEY_CTX_free(ctx);
+return ERR_error_string(ERR_get_error(), NULL);
}
+
#endif
/******************************************************************************/
#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>
# include <gnutls/abstract.h>
-#elif defined(RSA_GCRYPT)
+#elif defined(SIGN_GCRYPT)
# include <gcrypt.h>
# include <libtasn1.h>
#endif
#include "../blob.h"
-#ifdef RSA_OPENSSL
+#ifdef SIGN_OPENSSL
typedef struct {
- RSA * rsa;
+ EVP_PKEY * key;
} es_ctx;
typedef struct {
- RSA * rsa;
+ EVP_PKEY * key;
} ev_ctx;
-#elif defined(RSA_GNUTLS)
+#elif defined(SIGN_GNUTLS)
typedef struct {
- gnutls_x509_privkey_t rsa;
+ gnutls_x509_privkey_t key;
} es_ctx;
typedef struct {
- gnutls_pubkey_t rsa;
+ gnutls_pubkey_t key;
} ev_ctx;
-#elif defined(RSA_GCRYPT)
+#elif defined(SIGN_GCRYPT)
typedef struct {
+ int keytype;
gcry_mpi_t n;
gcry_mpi_t e;
gcry_mpi_t d;
} es_ctx;
typedef struct {
+ int keytype;
gcry_mpi_t n;
gcry_mpi_t e;
} ev_ctx;
uschar *dkim_canon;
uschar *dkim_sign_headers;
uschar *dkim_strict;
+ uschar *dkim_hash;
BOOL dot_stuffed;
};
(void *)offsetof(smtp_transport_options_block, dkim.dkim_canon) },
{ "dkim_domain", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, dkim.dkim_domain) },
+ { "dkim_hash", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, dkim.dkim_hash) },
{ "dkim_private_key", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, dkim.dkim_private_key) },
{ "dkim_selector", opt_stringptr,
.dkim_canon = NULL,
.dkim_sign_headers = NULL,
.dkim_strict = NULL,
+ .dkim_hash = US"sha256",
.dot_stuffed = FALSE},
#endif
};
--- /dev/null
+4500
\ No newline at end of file
.ifndef HEADERS_MAXSIZE
dkim_sign_headers = OPT
.endif
+.ifdef VALUE
+ dkim_hash = VALUE
+.endif
# End
--- /dev/null
+4520
\ No newline at end of file
--- /dev/null
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 10HmaX-0005vi-00 DKIM: d=test.ex s=sel c=simple/simple a=rsa-sha512 b=1024 [verification failed - signature did not verify (headers probably modified in transit)]
+1999-03-02 09:44:33 10HmaX-0005vi-00 signer: test.ex bits: 1024
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=qwerty1234@disco-zombie.net
--- /dev/null
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaX-0005vi-00 => a@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] C="250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 10HmaY-0005vi-00 DKIM: d=test.ex s=sel c=relaxed/relaxed a=rsa-sha512 b=1024 [verification succeeded]
+1999-03-02 09:44:33 10HmaY-0005vi-00 signer: test.ex bits: 1024 h=Date:Sender:Message-Id:From:Reply-To:Subject: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
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtp S=sss id=E10HmaX-0005vi-00@myhost.test.ex
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <a@test.ex> R=server_dump
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
'optional_config' =>
{ 'stdout' => '/^(
- dkim_(canon|domain|private_key|selector|sign_headers|strict)
+ dkim_(canon|domain|private_key|selector|sign_headers|strict|hash)
|gnutls_require_(kx|mac|protocols)
|hosts_(requ(est|ire)|try)_(dane|ocsp)
|hosts_(avoid|nopass|require|verify_avoid)_tls
--- /dev/null
+# DKIM verify, sha512
+#
+exim -DSERVER=server -bd -oX PORT_D
+****
+#
+# This should pass, only Mail::DKIM::Signer does not handle rsa-sha512.
+# - sha512, 1024b
+# Mail original in aux-fixed/4500.msg1.txt
+# Sig generated by: perl aux-fixed/dkim/sign.pl --algorithm=rsa-sha512 \
+# --method=simple/simple < aux-fixed/4500.msg1.txt
+#
+# TODO - until we have that we can only test internal consistency,
+# signing vs. verification.
+#
+client 127.0.0.1 PORT_D
+??? 220
+HELO xxx
+??? 250
+MAIL FROM:<CALLER@bloggs.com>
+??? 250
+RCPT TO:<a@test.ex>
+??? 250
+DATA
+??? 354
+DKIM-Signature: v=1; a=rsa-sha512; c=simple/simple; d=test.ex; h=from:to
+ :date:message-id:subject; s=sel; bh=3UbbJTudPxmejzh7U1Zg33U3QT+1
+ 6kfV2eOTvMeiEis=; b=xQSD/JMqz0C+xKf0A1NTkPTbkDuDdJbpBuyjjT9iYvyP
+ Zez+xl0TkoPobFGVa6EN8+ZeYV18zjifhtWYLSsNmPinUtcpKQLG1zxAKmmS0JEh
+ +qihlWbeGJ5+tK588ugUzXHPj+4JBW0H6kxHvdH0l2SlQE5xs/cdggnx5QX5USY=
+From: mrgus@text.ex
+To: bakawolf@yahoo.com
+Date: Thu, 19 Nov 2015 17:00:07 -0700
+Message-ID: <qwerty1234@disco-zombie.net>
+Subject: simple test
+
+This is a simple test.
+.
+??? 250
+QUIT
+??? 221
+****
+#
+killdaemon
+no_stdout_check
+no_msglog_check
--- /dev/null
+# DKIM signing, sha512
+#
+exim -bd -DSERVER=server -oX PORT_D
+****
+#
+# default header set
+exim -DHEADERS_MAXSIZE=y -DVALUE=sha512 -odf a@test.ex
+From: nobody@example.com
+
+content
+****
+#
+millisleep 500
+killdaemon
+no_msglog_check