summaryrefslogtreecommitdiffstats
path: root/src/pdkim
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/pdkim/Makefile19
-rw-r--r--src/pdkim/README9
-rw-r--r--src/pdkim/config.h4
-rw-r--r--src/pdkim/crypt_ver.h33
-rw-r--r--src/pdkim/pdkim.c2097
-rw-r--r--src/pdkim/pdkim.h369
-rw-r--r--src/pdkim/pdkim_hash.h38
-rw-r--r--src/pdkim/signing.c903
-rw-r--r--src/pdkim/signing.h97
9 files changed, 3569 insertions, 0 deletions
diff --git a/src/pdkim/Makefile b/src/pdkim/Makefile
new file mode 100644
index 0000000..47f92ee
--- /dev/null
+++ b/src/pdkim/Makefile
@@ -0,0 +1,19 @@
+# Make file for building the pdkim library.
+# Copyright (c) The Exim Maintainers 1995 - 2018
+
+OBJ = pdkim.o signing.o
+
+pdkim.a: $(OBJ)
+ @$(RM_COMMAND) -f pdkim.a
+ @echo "$(AR) pdkim.a"
+ $(FE)$(AR) pdkim.a $(OBJ)
+ $(RANLIB) $@
+
+.SUFFIXES: .o .c
+.c.o:; @echo "$(CC) $*.c"
+ $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) -I. $*.c
+
+pdkim.o: $(HDRS) crypt_ver.h pdkim.h pdkim.c
+signing.o: $(HDRS) crypt_ver.h signing.h signing.c
+
+# End
diff --git a/src/pdkim/README b/src/pdkim/README
new file mode 100644
index 0000000..953e86e
--- /dev/null
+++ b/src/pdkim/README
@@ -0,0 +1,9 @@
+PDKIM - a RFC4871 (DKIM) implementation
+http://duncanthrax.net/pdkim/
+Copyright (C) 2009 Tom Kistner <tom@duncanthrax.net>
+
+No longer includes code from the PolarSSL project.
+Copyright (C) 2016 Jeremy Harris <jgh@exim.org>
+
+This copy of PDKIM is included with Exim. For a standalone distribution,
+visit http://duncanthrax.net/pdkim/.
diff --git a/src/pdkim/config.h b/src/pdkim/config.h
new file mode 100644
index 0000000..fdd4cfe
--- /dev/null
+++ b/src/pdkim/config.h
@@ -0,0 +1,4 @@
+#define POLARSSL_BASE64_C
+
+
+
diff --git a/src/pdkim/crypt_ver.h b/src/pdkim/crypt_ver.h
new file mode 100644
index 0000000..a6d7e36
--- /dev/null
+++ b/src/pdkim/crypt_ver.h
@@ -0,0 +1,33 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* Signing and hashing routine selection for PDKIM */
+
+#include "../exim.h"
+#include "../sha_ver.h"
+
+
+#ifdef USE_GNUTLS
+# include <gnutls/gnutls.h>
+
+# if GNUTLS_VERSION_NUMBER >= 0x030000
+# define SIGN_GNUTLS
+# if GNUTLS_VERSION_NUMBER >= 0x030600
+# define SIGN_HAVE_ED25519
+# endif
+# else
+# define SIGN_GCRYPT
+# endif
+#endif
+
+#ifdef USE_OPENSSL
+# define SIGN_OPENSSL
+# if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10101000L
+# define SIGN_HAVE_ED25519
+# endif
+#endif
+
diff --git a/src/pdkim/pdkim.c b/src/pdkim/pdkim.c
new file mode 100644
index 0000000..e47bfc5
--- /dev/null
+++ b/src/pdkim/pdkim.c
@@ -0,0 +1,2097 @@
+/*
+ * PDKIM - a RFC4871 (DKIM) implementation
+ *
+ * Copyright (c) The Exim Maintainers 2021 - 2022
+ * Copyright (C) 2009 - 2016 Tom Kistner <tom@duncanthrax.net>
+ * Copyright (C) 2016 - 2020 Jeremy Harris <jgh@exim.org>
+ *
+ * http://duncanthrax.net/pdkim/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "../exim.h"
+
+
+#ifndef DISABLE_DKIM /* entire file */
+
+#ifdef DISABLE_TLS
+# error Must not DISABLE_TLS, for DKIM
+#endif
+
+#include "crypt_ver.h"
+
+#ifdef SIGN_OPENSSL
+# include <openssl/rsa.h>
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+#elif defined(SIGN_GNUTLS)
+# include <gnutls/gnutls.h>
+# include <gnutls/x509.h>
+#endif
+
+#include "pdkim.h"
+#include "signing.h"
+
+#define PDKIM_SIGNATURE_VERSION "1"
+#define PDKIM_PUB_RECORD_VERSION US "DKIM1"
+
+#define PDKIM_MAX_HEADER_LEN 65536
+#define PDKIM_MAX_HEADERS 512
+#define PDKIM_MAX_BODY_LINE_LEN 16384
+#define PDKIM_DNS_TXT_MAX_NAMELEN 1024
+
+/* -------------------------------------------------------------------------- */
+struct pdkim_stringlist {
+ uschar * value;
+ int tag;
+ void * next;
+};
+
+/* -------------------------------------------------------------------------- */
+/* A bunch of list constants */
+const uschar * pdkim_querymethods[] = {
+ US"dns/txt",
+ NULL
+};
+const uschar * pdkim_canons[] = {
+ US"simple",
+ US"relaxed",
+ NULL
+};
+
+const pdkim_hashtype pdkim_hashes[] = {
+ { US"sha1", HASH_SHA1 },
+ { US"sha256", HASH_SHA2_256 },
+ { US"sha512", HASH_SHA2_512 }
+};
+
+const uschar * pdkim_keytypes[] = {
+ [KEYTYPE_RSA] = US"rsa",
+#ifdef SIGN_HAVE_ED25519
+ [KEYTYPE_ED25519] = US"ed25519", /* Works for 3.6.0 GnuTLS, OpenSSL 1.1.1 */
+#endif
+
+#ifdef notyet_EC_dkim_extensions /* https://tools.ietf.org/html/draft-srose-dkim-ecc-00 */
+ US"eccp256",
+ US"eccp348",
+ US"ed448",
+#endif
+};
+
+typedef struct pdkim_combined_canon_entry {
+ const uschar * str;
+ int canon_headers;
+ int canon_body;
+} pdkim_combined_canon_entry;
+
+pdkim_combined_canon_entry pdkim_combined_canons[] = {
+ { US"simple/simple", PDKIM_CANON_SIMPLE, PDKIM_CANON_SIMPLE },
+ { US"simple/relaxed", PDKIM_CANON_SIMPLE, PDKIM_CANON_RELAXED },
+ { US"relaxed/simple", PDKIM_CANON_RELAXED, PDKIM_CANON_SIMPLE },
+ { US"relaxed/relaxed", PDKIM_CANON_RELAXED, PDKIM_CANON_RELAXED },
+ { US"simple", PDKIM_CANON_SIMPLE, PDKIM_CANON_SIMPLE },
+ { US"relaxed", PDKIM_CANON_RELAXED, PDKIM_CANON_SIMPLE },
+ { NULL, 0, 0 }
+};
+
+
+static const blob lineending = {.data = US"\r\n", .len = 2};
+
+/* -------------------------------------------------------------------------- */
+uschar *
+dkim_sig_to_a_tag(const 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);
+}
+
+
+static int
+pdkim_keyname_to_keytype(const uschar * s)
+{
+for (int i = 0; i < nelem(pdkim_keytypes); i++)
+ if (Ustrcmp(s, pdkim_keytypes[i]) == 0) return i;
+return -1;
+}
+
+int
+pdkim_hashname_to_hashtype(const uschar * s, unsigned len)
+{
+if (!len) len = Ustrlen(s);
+for (int i = 0; i < nelem(pdkim_hashes); i++)
+ if (Ustrncmp(s, pdkim_hashes[i].dkim_hashname, len) == 0)
+ return i;
+return -1;
+}
+
+void
+pdkim_cstring_to_canons(const uschar * s, unsigned len,
+ int * canon_head, int * canon_body)
+{
+if (!len) len = Ustrlen(s);
+for (int i = 0; pdkim_combined_canons[i].str; i++)
+ if ( Ustrncmp(s, pdkim_combined_canons[i].str, len) == 0
+ && len == Ustrlen(pdkim_combined_canons[i].str))
+ {
+ *canon_head = pdkim_combined_canons[i].canon_headers;
+ *canon_body = pdkim_combined_canons[i].canon_body;
+ break;
+ }
+}
+
+
+
+const char *
+pdkim_verify_status_str(int status)
+{
+switch(status)
+ {
+ case PDKIM_VERIFY_NONE: return "PDKIM_VERIFY_NONE";
+ case PDKIM_VERIFY_INVALID: return "PDKIM_VERIFY_INVALID";
+ case PDKIM_VERIFY_FAIL: return "PDKIM_VERIFY_FAIL";
+ case PDKIM_VERIFY_PASS: return "PDKIM_VERIFY_PASS";
+ default: return "PDKIM_VERIFY_UNKNOWN";
+ }
+}
+
+const char *
+pdkim_verify_ext_status_str(int ext_status)
+{
+switch(ext_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_VERIFY_INVALID_PUBKEY_IMPORT: return "PDKIM_VERIFY_INVALID_PUBKEY_IMPORT";
+ case PDKIM_VERIFY_INVALID_PUBKEY_KEYSIZE: return "PDKIM_VERIFY_INVALID_PUBKEY_KEYSIZE";
+ case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR: return "PDKIM_VERIFY_INVALID_SIGNATURE_ERROR";
+ case PDKIM_VERIFY_INVALID_DKIM_VERSION: return "PDKIM_VERIFY_INVALID_DKIM_VERSION";
+ default: return "PDKIM_VERIFY_UNKNOWN";
+ }
+}
+
+const uschar *
+pdkim_errstr(int status)
+{
+switch(status)
+ {
+ case PDKIM_OK: return US"OK";
+ case PDKIM_FAIL: return US"FAIL";
+ case PDKIM_ERR_RSA_PRIVKEY: return US"PRIVKEY";
+ case PDKIM_ERR_RSA_SIGNING: return US"SIGNING";
+ case PDKIM_ERR_LONG_LINE: return US"LONG_LINE";
+ case PDKIM_ERR_BUFFER_TOO_SMALL: return US"BUFFER_TOO_SMALL";
+ case PDKIM_ERR_EXCESS_SIGS: return US"EXCESS_SIGS";
+ case PDKIM_SIGN_PRIVKEY_WRAP: return US"PRIVKEY_WRAP";
+ case PDKIM_SIGN_PRIVKEY_B64D: return US"PRIVKEY_B64D";
+ default: return US"(unknown)";
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* Print debugging functions */
+void
+pdkim_quoteprint(const uschar *data, int len)
+{
+for (int i = 0; i < len; i++)
+ {
+ const int c = data[i];
+ switch (c)
+ {
+ case ' ' : debug_printf("{SP}"); break;
+ case '\t': debug_printf("{TB}"); break;
+ case '\r': debug_printf("{CR}"); break;
+ case '\n': debug_printf("{LF}"); break;
+ case '{' : debug_printf("{BO}"); break;
+ case '}' : debug_printf("{BC}"); break;
+ default:
+ if ( (c < 32) || (c > 127) )
+ debug_printf("{%02x}", c);
+ else
+ debug_printf("%c", c);
+ break;
+ }
+ }
+debug_printf("\n");
+}
+
+void
+pdkim_hexprint(const uschar *data, int len)
+{
+if (data) for (int i = 0 ; i < len; i++) debug_printf("%02x", data[i]);
+else debug_printf("<NULL>");
+debug_printf("\n");
+}
+
+
+
+static pdkim_stringlist *
+pdkim_prepend_stringlist(pdkim_stringlist * base, const uschar * str)
+{
+pdkim_stringlist * new_entry = store_get(sizeof(pdkim_stringlist), GET_UNTAINTED);
+
+memset(new_entry, 0, sizeof(pdkim_stringlist));
+new_entry->value = string_copy(str);
+if (base) new_entry->next = base;
+return new_entry;
+}
+
+
+
+/* Trim whitespace fore & aft */
+
+static void
+pdkim_strtrim(gstring * str)
+{
+uschar * p = str->s;
+uschar * q;
+
+while (*p == '\t' || *p == ' ') /* dump the leading whitespace */
+ { str->size--; str->ptr--; str->s++; }
+
+while ( str->ptr > 0
+ && ((q = str->s + str->ptr - 1), (*q == '\t' || *q == ' '))
+ )
+ str->ptr--; /* dump trailing whitespace */
+
+(void) string_from_gstring(str);
+}
+
+
+
+/* -------------------------------------------------------------------------- */
+
+DLLEXPORT void
+pdkim_free_ctx(pdkim_ctx *ctx)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* Matches the name of the passed raw "header" against
+ the passed colon-separated "tick", and invalidates
+ the entry in tick. Entries can be prefixed for multi- or over-signing,
+ in which case do not invalidate.
+
+ Returns OK for a match, or fail-code
+*/
+
+static int
+header_name_match(const uschar * header, uschar * tick)
+{
+const uschar * ticklist = tick;
+int sep = ':';
+BOOL multisign;
+uschar * hname, * p, * ele;
+uschar * hcolon = Ustrchr(header, ':'); /* Get header name */
+
+if (!hcolon)
+ return PDKIM_FAIL; /* This isn't a header */
+
+/* if we had strncmpic() we wouldn't need this copy */
+hname = string_copyn(header, hcolon-header);
+
+while (p = US ticklist, ele = string_nextinlist(&ticklist, &sep, NULL, 0))
+ {
+ switch (*ele)
+ {
+ case '=': case '+': multisign = TRUE; ele++; break;
+ default: multisign = FALSE; break;
+ }
+
+ if (strcmpic(ele, hname) == 0)
+ {
+ if (!multisign)
+ *p = '_'; /* Invalidate this header name instance in tick-off list */
+ return PDKIM_OK;
+ }
+ }
+return PDKIM_FAIL;
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* Performs "relaxed" canonicalization of a header. */
+
+uschar *
+pdkim_relax_header_n(const uschar * header, int len, BOOL append_crlf)
+{
+BOOL past_field_name = FALSE;
+BOOL seen_wsp = FALSE;
+uschar * relaxed = store_get(len+3, GET_TAINTED);
+uschar * q = relaxed;
+
+for (const uschar * p = header; p - header < len; p++)
+ {
+ uschar c = *p;
+
+ if (c == '\r' || c == '\n') /* Ignore CR & LF */
+ continue;
+ if (c == '\t' || c == ' ')
+ {
+ if (seen_wsp)
+ continue;
+ c = ' '; /* Turns WSP into SP */
+ seen_wsp = TRUE;
+ }
+ else
+ if (!past_field_name && c == ':')
+ {
+ 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
+ seen_wsp = FALSE;
+
+ /* Lowercase header name */
+ if (!past_field_name) c = tolower(c);
+ *q++ = c;
+ }
+
+if (q > relaxed && q[-1] == ' ') q--; /* Squash eventual trailing SP */
+
+if (append_crlf) { *q++ = '\r'; *q++ = '\n'; }
+*q = '\0';
+return relaxed;
+}
+
+
+uschar *
+pdkim_relax_header(const uschar * header, BOOL append_crlf)
+{
+return pdkim_relax_header_n(header, Ustrlen(header), append_crlf);
+}
+
+
+/* -------------------------------------------------------------------------- */
+#define PDKIM_QP_ERROR_DECODE -1
+
+static const uschar *
+pdkim_decode_qp_char(const uschar *qp_p, int *c)
+{
+const uschar *initial_pos = qp_p;
+
+/* Advance one char */
+qp_p++;
+
+/* Check for two hex digits and decode them */
+if (isxdigit(*qp_p) && isxdigit(qp_p[1]))
+ {
+ /* Do hex conversion */
+ *c = (isdigit(*qp_p) ? *qp_p - '0' : toupper(*qp_p) - 'A' + 10) << 4;
+ *c |= isdigit(qp_p[1]) ? qp_p[1] - '0' : toupper(qp_p[1]) - 'A' + 10;
+ return qp_p + 2;
+ }
+
+/* Illegal char here */
+*c = PDKIM_QP_ERROR_DECODE;
+return initial_pos;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+static uschar *
+pdkim_decode_qp(const uschar * str)
+{
+int nchar = 0;
+uschar * q;
+const uschar * p = str;
+uschar * n = store_get(Ustrlen(str)+1, GET_TAINTED);
+
+*n = '\0';
+q = n;
+while (*p)
+ {
+ if (*p == '=')
+ {
+ p = pdkim_decode_qp_char(p, &nchar);
+ if (nchar >= 0)
+ {
+ *q++ = nchar;
+ continue;
+ }
+ }
+ else
+ *q++ = *p;
+ p++;
+ }
+*q = '\0';
+return n;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+void
+pdkim_decode_base64(const uschar * str, blob * b)
+{
+int dlen = b64decode(str, &b->data);
+if (dlen < 0) b->data = NULL;
+b->len = dlen;
+}
+
+uschar *
+pdkim_encode_base64(blob * b)
+{
+return b64encode(CUS b->data, b->len);
+}
+
+
+/* -------------------------------------------------------------------------- */
+#define PDKIM_HDR_LIMBO 0
+#define PDKIM_HDR_TAG 1
+#define PDKIM_HDR_VALUE 2
+
+static pdkim_signature *
+pdkim_parse_sig_header(pdkim_ctx * ctx, uschar * raw_hdr)
+{
+pdkim_signature * sig;
+uschar *q;
+gstring * cur_tag = NULL;
+gstring * cur_val = NULL;
+BOOL past_hname = FALSE;
+BOOL in_b_val = FALSE;
+int where = PDKIM_HDR_LIMBO;
+
+sig = store_get(sizeof(pdkim_signature), GET_UNTAINTED);
+memset(sig, 0, sizeof(pdkim_signature));
+sig->bodylength = -1;
+
+/* Set so invalid/missing data error display is accurate */
+sig->version = 0;
+sig->keytype = -1;
+sig->hashtype = -1;
+
+q = sig->rawsig_no_b_val = store_get(Ustrlen(raw_hdr)+1, GET_TAINTED);
+
+for (uschar * p = raw_hdr; ; p++)
+ {
+ char c = *p;
+
+ /* Ignore FWS */
+ if (c == '\r' || c == '\n')
+ goto NEXT_CHAR;
+
+ /* Fast-forward through header name */
+ if (!past_hname)
+ {
+ if (c == ':') past_hname = TRUE;
+ 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, p, 1);
+
+ if (c == '=')
+ {
+ if (Ustrcmp(string_from_gstring(cur_tag), "b") == 0)
+ {
+ *q++ = '=';
+ in_b_val = TRUE;
+ }
+ where = PDKIM_HDR_VALUE;
+ goto NEXT_CHAR;
+ }
+ }
+
+ if (where == PDKIM_HDR_VALUE)
+ {
+ if (c == '\r' || c == '\n' || c == ' ' || c == '\t')
+ goto NEXT_CHAR;
+
+ if (c == ';' || c == '\0')
+ {
+ /* We must have both tag and value, and tags must be one char except
+ for the possibility of "bh". */
+
+ if ( cur_tag && cur_val
+ && (cur_tag->ptr == 1 || *cur_tag->s == 'b')
+ )
+ {
+ (void) string_from_gstring(cur_val);
+ pdkim_strtrim(cur_val);
+
+ DEBUG(D_acl) debug_printf(" %s=%s\n", cur_tag->s, cur_val->s);
+
+ switch (*cur_tag->s)
+ {
+ case 'b': /* sig-data or body-hash */
+ switch (cur_tag->s[1])
+ {
+ case '\0': pdkim_decode_base64(cur_val->s, &sig->sighash); break;
+ case 'h': if (cur_tag->ptr == 2)
+ pdkim_decode_base64(cur_val->s, &sig->bodyhash);
+ break;
+ default: break;
+ }
+ break;
+ case 'v': /* version */
+ /* We only support version 1, and that is currently the
+ only version there is. */
+ sig->version =
+ Ustrcmp(cur_val->s, PDKIM_SIGNATURE_VERSION) == 0 ? 1 : -1;
+ break;
+ case 'a': /* algorithm */
+ {
+ const uschar * list = cur_val->s;
+ int sep = '-';
+ uschar * elem;
+
+ if ((elem = string_nextinlist(&list, &sep, NULL, 0)))
+ sig->keytype = pdkim_keyname_to_keytype(elem);
+ if ((elem = string_nextinlist(&list, &sep, NULL, 0)))
+ for (int i = 0; i < nelem(pdkim_hashes); i++)
+ if (Ustrcmp(elem, pdkim_hashes[i].dkim_hashname) == 0)
+ { sig->hashtype = i; break; }
+ }
+
+ case 'c': /* canonicalization */
+ pdkim_cstring_to_canons(cur_val->s, 0,
+ &sig->canon_headers, &sig->canon_body);
+ break;
+ case 'q': /* Query method (for pubkey)*/
+ for (int i = 0; pdkim_querymethods[i]; i++)
+ if (Ustrcmp(cur_val->s, pdkim_querymethods[i]) == 0)
+ {
+ sig->querymethod = i; /* we never actually use this */
+ break;
+ }
+ break;
+ case 's': /* Selector */
+ sig->selector = string_copyn(cur_val->s, cur_val->ptr); break;
+ case 'd': /* SDID */
+ sig->domain = string_copyn(cur_val->s, cur_val->ptr); break;
+ case 'i': /* AUID */
+ sig->identity = pdkim_decode_qp(cur_val->s); break;
+ case 't': /* Timestamp */
+ sig->created = strtoul(CS cur_val->s, NULL, 10); break;
+ case 'x': /* Expiration */
+ sig->expires = strtoul(CS cur_val->s, NULL, 10); break;
+ case 'l': /* Body length count */
+ sig->bodylength = strtol(CS cur_val->s, NULL, 10); break;
+ case 'h': /* signed header fields */
+ sig->headernames = string_copyn(cur_val->s, cur_val->ptr); break;
+ case 'z': /* Copied headfields */
+ sig->copiedheaders = pdkim_decode_qp(cur_val->s); break;
+/*XXX draft-ietf-dcrup-dkim-crypto-05 would need 'p' tag support
+for rsafp signatures. But later discussion is dropping those. */
+ default:
+ DEBUG(D_acl) debug_printf(" Unknown tag encountered\n");
+ break;
+ }
+ }
+ cur_tag = cur_val = NULL;
+ in_b_val = FALSE;
+ where = PDKIM_HDR_LIMBO;
+ }
+ else
+ cur_val = string_catn(cur_val, p, 1);
+ }
+
+NEXT_CHAR:
+ if (c == '\0')
+ break;
+
+ if (!in_b_val)
+ *q++ = c;
+ }
+
+if (sig->keytype < 0 || sig->hashtype < 0) /* Cannot verify this signature */
+ return NULL;
+
+*q = '\0';
+/* Chomp raw header. The final newline must not be added to the signature. */
+while (--q > sig->rawsig_no_b_val && (*q == '\r' || *q == '\n'))
+ *q = '\0';
+
+DEBUG(D_acl)
+ {
+ debug_printf(
+ "DKIM >> Raw signature w/o b= tag value >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
+ pdkim_quoteprint(US sig->rawsig_no_b_val, Ustrlen(sig->rawsig_no_b_val));
+ debug_printf(
+ "DKIM >> Sig size: %4u bits\n", (unsigned) sig->sighash.len*8);
+ debug_printf(
+ "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ }
+
+if (!pdkim_set_sig_bodyhash(ctx, sig))
+ return NULL;
+
+return sig;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+pdkim_pubkey *
+pdkim_parse_pubkey_record(const uschar *raw_record)
+{
+const uschar * ele;
+int sep = ';';
+pdkim_pubkey * pub;
+
+pub = store_get(sizeof(pdkim_pubkey), GET_TAINTED);
+memset(pub, 0, sizeof(pdkim_pubkey));
+
+while ((ele = string_nextinlist(&raw_record, &sep, NULL, 0)))
+ {
+ const uschar * val;
+
+ if ((val = Ustrchr(ele, '=')))
+ {
+ int taglen = val++ - ele;
+
+ DEBUG(D_acl) debug_printf(" %.*s=%s\n", taglen, ele, val);
+ switch (ele[0])
+ {
+ case 'v': pub->version = val; break;
+ case 'h': pub->hashes = val; break;
+ case 'k': pub->keytype = val; 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;
+ default: DEBUG(D_acl) debug_printf(" Unknown tag encountered\n"); 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)
+ {
+ DEBUG(D_acl) debug_printf(" Bad v= field\n");
+ return NULL;
+ }
+
+if (!pub->granularity) pub->granularity = US"*";
+if (!pub->keytype ) pub->keytype = US"rsa";
+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;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/* Update one bodyhash with some additional data.
+If we have to relax the data for this sig, return our copy of it. */
+
+static blob *
+pdkim_update_ctx_bodyhash(pdkim_bodyhash * b, const blob * orig_data, blob * relaxed_data)
+{
+const blob * canon_data = orig_data;
+size_t left;
+
+/* Defaults to simple canon (no further treatment necessary) */
+
+if (b->canon_method == PDKIM_CANON_RELAXED)
+ {
+ /* Relax the line if not done already */
+ if (!relaxed_data)
+ {
+ BOOL seen_wsp = FALSE;
+ int q = 0;
+
+ /* We want to be able to free this else we allocate
+ for the entire message which could be many MB. Since
+ we don't know what allocations the SHA routines might
+ do, not safe to use store_get()/store_reset(). */
+
+ relaxed_data = store_malloc(sizeof(blob) + orig_data->len+1);
+ relaxed_data->data = US (relaxed_data+1);
+
+ for (const uschar * p = orig_data->data, * r = p + orig_data->len; p < r; p++)
+ {
+ char c = *p;
+ if (c == '\r')
+ {
+ if (q > 0 && relaxed_data->data[q-1] == ' ')
+ q--;
+ }
+ else if (c == '\t' || c == ' ')
+ {
+ c = ' '; /* Turns WSP into SP */
+ if (seen_wsp)
+ continue;
+ seen_wsp = TRUE;
+ }
+ else
+ seen_wsp = FALSE;
+ relaxed_data->data[q++] = c;
+ }
+ relaxed_data->data[q] = '\0';
+ relaxed_data->len = q;
+ }
+ canon_data = relaxed_data;
+ }
+
+/* Make sure we don't exceed the to-be-signed body length */
+left = canon_data->len;
+if ( b->bodylength >= 0
+ && left > (unsigned long)b->bodylength - b->signed_body_bytes
+ )
+ left = (unsigned long)b->bodylength - b->signed_body_bytes;
+
+if (left > 0)
+ {
+ exim_sha_update(&b->body_hash_ctx, CUS canon_data->data, left);
+ b->signed_body_bytes += left;
+ DEBUG(D_acl) pdkim_quoteprint(canon_data->data, left);
+ }
+
+return relaxed_data;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+static void
+pdkim_finish_bodyhash(pdkim_ctx * ctx)
+{
+for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next) /* Finish hashes */
+ {
+ DEBUG(D_acl) debug_printf("DKIM: finish bodyhash %s/%s/%ld len %ld\n",
+ pdkim_hashes[b->hashtype].dkim_hashname, pdkim_canons[b->canon_method],
+ b->bodylength, b->signed_body_bytes);
+ exim_sha_finish(&b->body_hash_ctx, &b->bh);
+ }
+
+/* Traverse all signatures */
+for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
+ {
+ pdkim_bodyhash * b = sig->calc_body_hash;
+
+ DEBUG(D_acl)
+ {
+ debug_printf("DKIM [%s]%s Body bytes (%s) hashed: %lu\n"
+ "DKIM [%s]%s Body %s computed: ",
+ sig->domain, sig->selector, pdkim_canons[b->canon_method], b->signed_body_bytes,
+ sig->domain, sig->selector, pdkim_hashes[b->hashtype].dkim_hashname);
+ pdkim_hexprint(CUS b->bh.data, b->bh.len);
+ }
+
+ /* SIGNING -------------------------------------------------------------- */
+ if (ctx->flags & PDKIM_MODE_SIGN)
+ {
+ /* If bodylength limit is set, and we have received less bytes
+ than the requested amount, effectively remove the limit tag. */
+ if (b->signed_body_bytes < sig->bodylength)
+ sig->bodylength = -1;
+ }
+
+ else
+ /* VERIFICATION --------------------------------------------------------- */
+ /* Be careful that the header sig included a bodyash */
+
+ if (sig->bodyhash.data && sig->bodyhash.len == b->bh.len
+ && memcmp(b->bh.data, sig->bodyhash.data, b->bh.len) == 0)
+ {
+ DEBUG(D_acl) debug_printf("DKIM [%s] Body hash compared OK\n", sig->domain);
+ }
+ else
+ {
+ DEBUG(D_acl)
+ {
+ debug_printf("DKIM [%s] Body hash signature from headers: ", sig->domain);
+ pdkim_hexprint(sig->bodyhash.data, sig->bodyhash.len);
+ debug_printf("DKIM [%s] Body hash did NOT verify\n", sig->domain);
+ }
+ sig->verify_status = PDKIM_VERIFY_FAIL;
+ sig->verify_ext_status = PDKIM_VERIFY_FAIL_BODY;
+ }
+ }
+}
+
+
+
+static void
+pdkim_body_complete(pdkim_ctx * ctx)
+{
+/* In simple body mode, if any empty lines were buffered,
+replace with one. rfc 4871 3.4.3 */
+/*XXX checking the signed-body-bytes is a gross hack; I think
+it indicates that all linebreaks should be buffered, including
+the one terminating a text line */
+
+for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next)
+ if ( b->canon_method == PDKIM_CANON_SIMPLE
+ && b->signed_body_bytes == 0
+ && b->num_buffered_blanklines > 0
+ )
+ (void) pdkim_update_ctx_bodyhash(b, &lineending, NULL);
+
+ctx->flags |= PDKIM_SEEN_EOD;
+ctx->linebuf_offset = 0;
+}
+
+
+
+/* -------------------------------------------------------------------------- */
+/* Call from pdkim_feed below for processing complete body lines */
+/* NOTE: the line is not NUL-terminated; but we have a count */
+
+static void
+pdkim_bodyline_complete(pdkim_ctx * ctx)
+{
+blob line = {.data = ctx->linebuf, .len = ctx->linebuf_offset};
+blob * rnl = NULL;
+blob * rline = NULL;
+
+/* Ignore extra data if we've seen the end-of-data marker */
+if (ctx->flags & PDKIM_SEEN_EOD) goto all_skip;
+
+/* We've always got one extra byte to stuff a zero ... */
+ctx->linebuf[line.len] = '\0';
+
+/* Terminate on EOD marker */
+if (ctx->flags & PDKIM_DOT_TERM)
+ {
+ if (memcmp(line.data, ".\r\n", 3) == 0)
+ { pdkim_body_complete(ctx); return; }
+
+ /* Unstuff dots */
+ if (memcmp(line.data, "..", 2) == 0)
+ { line.data++; line.len--; }
+ }
+
+/* Empty lines need to be buffered until we find a non-empty line */
+if (memcmp(line.data, "\r\n", 2) == 0)
+ {
+ for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next)
+ b->num_buffered_blanklines++;
+ goto all_skip;
+ }
+
+/* Process line for each bodyhash separately */
+for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next)
+ {
+ if (b->canon_method == PDKIM_CANON_RELAXED)
+ {
+ /* Lines with just spaces need to be buffered too */
+ uschar * cp = line.data;
+ char c;
+
+ while ((c = *cp))
+ {
+ if (c == '\r' && cp[1] == '\n') break;
+ if (c != ' ' && c != '\t') goto hash_process;
+ cp++;
+ }
+
+ b->num_buffered_blanklines++;
+ goto hash_skip;
+ }
+
+hash_process:
+ /* At this point, we have a non-empty line, so release the buffered ones. */
+
+ while (b->num_buffered_blanklines)
+ {
+ rnl = pdkim_update_ctx_bodyhash(b, &lineending, rnl);
+ b->num_buffered_blanklines--;
+ }
+
+ rline = pdkim_update_ctx_bodyhash(b, &line, rline);
+hash_skip: ;
+ }
+
+if (rnl) store_free(rnl);
+if (rline) store_free(rline);
+
+all_skip:
+
+ctx->linebuf_offset = 0;
+return;
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* Callback from pdkim_feed below for processing complete headers */
+#define DKIM_SIGNATURE_HEADERNAME "DKIM-Signature:"
+
+static int
+pdkim_header_complete(pdkim_ctx * ctx)
+{
+if ( (ctx->cur_header->ptr > 1) &&
+ (ctx->cur_header->s[ctx->cur_header->ptr-1] == '\r') )
+ --ctx->cur_header->ptr;
+(void) string_from_gstring(ctx->cur_header);
+
+#ifdef EXPERIMENTAL_ARC
+/* Feed the header line to ARC processing */
+(void) arc_header_feed(ctx->cur_header, !(ctx->flags & PDKIM_MODE_SIGN));
+#endif
+
+if (++ctx->num_headers > PDKIM_MAX_HEADERS) goto BAIL;
+
+/* SIGNING -------------------------------------------------------------- */
+if (ctx->flags & PDKIM_MODE_SIGN)
+ for (pdkim_signature * 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->s);
+
+/* VERIFICATION ----------------------------------------------------------- */
+/* DKIM-Signature: headers are added to the verification list */
+else
+ {
+#ifdef notdef
+ DEBUG(D_acl)
+ {
+ debug_printf("DKIM >> raw hdr: ");
+ pdkim_quoteprint(CUS ctx->cur_header->s, ctx->cur_header->ptr);
+ }
+#endif
+ if (strncasecmp(CCS ctx->cur_header->s,
+ DKIM_SIGNATURE_HEADERNAME,
+ Ustrlen(DKIM_SIGNATURE_HEADERNAME)) == 0)
+ {
+ pdkim_signature * 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(
+ "DKIM >> Found sig, trying to parse >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
+
+ sig = pdkim_parse_sig_header(ctx, ctx->cur_header->s);
+
+ if (!(last_sig = ctx->sig))
+ ctx->sig = sig;
+ else
+ {
+ while (last_sig->next) last_sig = last_sig->next;
+ last_sig->next = sig;
+ }
+
+ if (dkim_collect_input && --dkim_collect_input == 0)
+ {
+ ctx->headers = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header->s);
+ ctx->cur_header->s[ctx->cur_header->ptr = 0] = '\0';
+ return PDKIM_ERR_EXCESS_SIGS;
+ }
+ }
+
+ /* all headers are stored for signature verification */
+ ctx->headers = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header->s);
+ }
+
+BAIL:
+ctx->cur_header->s[ctx->cur_header->ptr = 0] = '\0'; /* leave buffer for reuse */
+return PDKIM_OK;
+}
+
+
+
+/* -------------------------------------------------------------------------- */
+#define HEADER_BUFFER_FRAG_SIZE 256
+
+DLLEXPORT int
+pdkim_feed(pdkim_ctx * ctx, uschar * data, int len)
+{
+/* Alternate EOD signal, used in non-dotstuffing mode */
+if (!data)
+ pdkim_body_complete(ctx);
+
+else for (int p = 0; p < len; p++)
+ {
+ uschar c = data[p];
+ int rc;
+
+ if (ctx->flags & PDKIM_PAST_HDRS)
+ {
+ if (c == '\n' && !(ctx->flags & PDKIM_SEEN_CR)) /* emulate the CR */
+ {
+ ctx->linebuf[ctx->linebuf_offset++] = '\r';
+ if (ctx->linebuf_offset == PDKIM_MAX_BODY_LINE_LEN-1)
+ return PDKIM_ERR_LONG_LINE;
+ }
+
+ /* Processing body byte */
+ ctx->linebuf[ctx->linebuf_offset++] = c;
+ if (c == '\r')
+ ctx->flags |= PDKIM_SEEN_CR;
+ else if (c == '\n')
+ {
+ ctx->flags &= ~PDKIM_SEEN_CR;
+ pdkim_bodyline_complete(ctx);
+ }
+
+ if (ctx->linebuf_offset == PDKIM_MAX_BODY_LINE_LEN-1)
+ return PDKIM_ERR_LONG_LINE;
+ }
+ else
+ {
+ /* Processing header byte */
+ if (c == '\r')
+ ctx->flags |= PDKIM_SEEN_CR;
+ else if (c == '\n')
+ {
+ if (!(ctx->flags & PDKIM_SEEN_CR)) /* emulate the CR */
+ ctx->cur_header = string_catn(ctx->cur_header, CUS "\r", 1);
+
+ if (ctx->flags & PDKIM_SEEN_LF) /* Seen last header line */
+ {
+ if ((rc = pdkim_header_complete(ctx)) != PDKIM_OK)
+ return rc;
+
+ ctx->flags = (ctx->flags & ~(PDKIM_SEEN_LF|PDKIM_SEEN_CR)) | PDKIM_PAST_HDRS;
+ DEBUG(D_acl) debug_printf(
+ "DKIM >> Body data for hash, canonicalized >>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
+ continue;
+ }
+ else
+ ctx->flags = (ctx->flags & ~PDKIM_SEEN_CR) | PDKIM_SEEN_LF;
+ }
+ else if (ctx->flags & PDKIM_SEEN_LF)
+ {
+ if (!(c == '\t' || c == ' ')) /* End of header */
+ if ((rc = pdkim_header_complete(ctx)) != PDKIM_OK)
+ return rc;
+ ctx->flags &= ~PDKIM_SEEN_LF;
+ }
+
+ if (!ctx->cur_header || ctx->cur_header->ptr < PDKIM_MAX_HEADER_LEN)
+ ctx->cur_header = string_catn(ctx->cur_header, CUS &data[p], 1);
+ }
+ }
+return PDKIM_OK;
+}
+
+
+
+/* Extend a growing header with a continuation-linebreak */
+static gstring *
+pdkim_hdr_cont(gstring * str, int * col)
+{
+*col = 1;
+return string_catn(str, US"\r\n\t", 3);
+}
+
+
+
+/*
+ * RFC 5322 specifies that header line length SHOULD be no more than 78
+ * pdkim_headcat
+ *
+ * Returns gstring (not nul-terminated) appending to one supplied
+ *
+ * col: this int holds and receives column number (octets since last '\n')
+ * str: partial string to append to
+ * pad: padding, split line or space after before or after eg: ";".
+ * Only the initial charater is used.
+ * intro: - must join to payload eg "h=", usually the tag name
+ * payload: eg base64 data - long data can be split arbitrarily.
+ *
+ * this code doesn't fold the header in some of the places that RFC4871
+ * allows: As per RFC5322(2.2.3) it only folds before or after tag-value
+ * pairs and inside long values. it also always spaces or breaks after the
+ * "pad"
+ *
+ * No guarantees are made for output given out-of range input. like tag
+ * names longer than 78, or bogus col. Input is assumed to be free of line breaks.
+ */
+
+static gstring *
+pdkim_headcat(int * col, gstring * str,
+ const uschar * pad, const uschar * intro, const uschar * payload)
+{
+int len, chomp, padded = 0;
+
+/* If we can fit at least the pad at the end of current line, do it now.
+Otherwise, wrap if there is a pad. */
+
+if (pad)
+ if (*col + 1 <= 78)
+ {
+ str = string_catn(str, pad, 1);
+ (*col)++;
+ pad = NULL;
+ padded = 1;
+ }
+ else
+ str = pdkim_hdr_cont(str, col);
+
+/* Special case: if the whole addition does not fit at the end of the current
+line, but could fit on a new line, wrap to give it its full, dedicated line. */
+
+len = (pad ? 2 : padded)
+ + (intro ? Ustrlen(intro) : 0)
+ + (payload ? Ustrlen(payload) : 0);
+if (len <= 77 && *col+len > 78)
+ {
+ str = pdkim_hdr_cont(str, col);
+ padded = 0;
+ }
+
+/* Either we already dealt with the pad or we know there is room */
+
+if (pad)
+ {
+ str = string_catn(str, pad, 1);
+ str = string_catn(str, US" ", 1);
+ *col += 2;
+ }
+else if (padded && *col < 78)
+ {
+ str = string_catn(str, US" ", 1);
+ (*col)++;
+ }
+
+/* Call recursively with intro as payload: it gets the same, special treatment
+(that is, not split if < 78). */
+
+if (intro)
+ str = pdkim_headcat(col, str, NULL, NULL, intro);
+
+if (payload)
+ for (len = Ustrlen(payload); len; len -= chomp)
+ {
+ if (*col >= 78)
+ str = pdkim_hdr_cont(str, col);
+ chomp = *col+len > 78 ? 78 - *col : len;
+ str = string_catn(str, payload, chomp);
+ *col += chomp;
+ payload += chomp;
+ }
+
+return str;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/* Signing: create signature header
+*/
+static uschar *
+pdkim_create_header(pdkim_signature * sig, BOOL final)
+{
+uschar * base64_bh;
+uschar * base64_b;
+int col = 0;
+gstring * hdr;
+gstring * canon_all;
+
+canon_all = string_cat (NULL, pdkim_canons[sig->canon_headers]);
+canon_all = string_catn(canon_all, US"/", 1);
+canon_all = string_cat (canon_all, pdkim_canons[sig->canon_body]);
+(void) string_from_gstring(canon_all);
+
+hdr = string_cat(NULL, US"DKIM-Signature: v="PDKIM_SIGNATURE_VERSION);
+col = hdr->ptr;
+
+/* Required and static bits */
+hdr = pdkim_headcat(&col, hdr, US";", US"a=", dkim_sig_to_a_tag(sig));
+hdr = pdkim_headcat(&col, hdr, US";", US"q=", pdkim_querymethods[sig->querymethod]);
+hdr = pdkim_headcat(&col, hdr, US";", US"c=", canon_all->s);
+hdr = pdkim_headcat(&col, hdr, US";", US"d=", sig->domain);
+hdr = pdkim_headcat(&col, hdr, US";", US"s=", sig->selector);
+
+/* list of header names can be split between items. */
+ {
+ uschar * n = string_copy(sig->headernames);
+ uschar * i = US"h=";
+ uschar * s = US";";
+
+ while (*n)
+ {
+ uschar * c = Ustrchr(n, ':');
+
+ if (c) *c ='\0';
+
+ if (!i)
+ hdr = pdkim_headcat(&col, hdr, NULL, NULL, US":");
+
+ hdr = pdkim_headcat(&col, hdr, s, i, n);
+
+ if (!c)
+ break;
+
+ n = c+1;
+ s = NULL;
+ i = NULL;
+ }
+ }
+
+base64_bh = pdkim_encode_base64(&sig->calc_body_hash->bh);
+hdr = pdkim_headcat(&col, hdr, US";", US"bh=", base64_bh);
+
+/* Optional bits */
+if (sig->identity)
+ hdr = pdkim_headcat(&col, hdr, US";", US"i=", sig->identity);
+
+if (sig->created > 0)
+ {
+ uschar minibuf[21];
+
+ snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->created);
+ hdr = pdkim_headcat(&col, hdr, US";", US"t=", minibuf);
+}
+
+if (sig->expires > 0)
+ {
+ uschar minibuf[21];
+
+ snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->expires);
+ hdr = pdkim_headcat(&col, hdr, US";", US"x=", minibuf);
+ }
+
+if (sig->bodylength >= 0)
+ {
+ uschar minibuf[21];
+
+ snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->bodylength);
+ hdr = pdkim_headcat(&col, hdr, US";", US"l=", minibuf);
+ }
+
+/* Preliminary or final version? */
+if (final)
+ {
+ base64_b = pdkim_encode_base64(&sig->sighash);
+ hdr = pdkim_headcat(&col, hdr, US";", US"b=", base64_b);
+
+ /* add trailing semicolon: I'm not sure if this is actually needed */
+ hdr = pdkim_headcat(&col, hdr, 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, US";", US"b=;", US"");
+ }
+
+return string_from_gstring(hdr);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/* According to draft-ietf-dcrup-dkim-crypto-07 "keys are 256 bits" (referring
+to DNS, hence the pubkey). Check for more than 32 bytes; if so assume the
+alternate possible representation (still) being discussed: a
+SubjectPublickeyInfo wrapped key - and drop all but the trailing 32-bytes (it
+should be a DER, with exactly 12 leading bytes - but we could accept a BER also,
+which could be any size). We still rely on the crypto library for checking for
+undersize.
+
+When the RFC is published this should be re-addressed. */
+
+static void
+check_bare_ed25519_pubkey(pdkim_pubkey * p)
+{
+int excess = p->key.len - 32;
+if (excess > 0)
+ {
+ DEBUG(D_acl)
+ debug_printf("DKIM: unexpected pubkey len %lu\n", (unsigned long) p->key.len);
+ p->key.data += excess; p->key.len = 32;
+ }
+}
+
+
+static pdkim_pubkey *
+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;
+
+/* Fetch public key for signing domain, from DNS */
+
+dns_txt_name = string_sprintf("%s._domainkey.%s.", sig->selector, sig->domain);
+
+if ( !(dns_txt_reply = ctx->dns_txt_callback(dns_txt_name))
+ || 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(
+ "DKIM >> Parsing public key record >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"
+ " %s\n"
+ " Raw record: ",
+ dns_txt_name);
+ pdkim_quoteprint(CUS dns_txt_reply, Ustrlen(dns_txt_reply));
+ }
+
+if ( !(p = pdkim_parse_pubkey_record(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(
+ "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ }
+ return NULL;
+ }
+
+DEBUG(D_acl) debug_printf(
+ "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+
+/* Import public key */
+
+/* Normally we use the signature a= tag to tell us the pubkey format.
+When signing under debug we do a test-import of the pubkey, and at that
+time we do not have a signature so we must interpret the pubkey k= tag
+instead. Assume writing on the sig is ok in that case. */
+
+if (sig->keytype < 0)
+ if ((sig->keytype = pdkim_keyname_to_keytype(p->keytype)) < 0)
+ {
+ DEBUG(D_acl) debug_printf("verify_init: unhandled keytype %s\n", p->keytype);
+ sig->verify_status = PDKIM_VERIFY_INVALID;
+ sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_IMPORT;
+ return NULL;
+ }
+
+if (sig->keytype == KEYTYPE_ED25519)
+ check_bare_ed25519_pubkey(p);
+
+if ((*errstr = exim_dkim_verify_init(&p->key,
+ sig->keytype == KEYTYPE_ED25519 ? KEYFMT_ED25519_BARE : KEYFMT_DER,
+ vctx, &sig->keybits)))
+ {
+ 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;
+ }
+
+vctx->keytype = sig->keytype;
+return p;
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* Sort and filter the sigs developed from the message */
+
+static pdkim_signature *
+sort_sig_methods(pdkim_signature * siglist)
+{
+pdkim_signature * yield, ** ss;
+const uschar * prefs;
+uschar * ele;
+int sep;
+
+if (!siglist) return NULL;
+
+/* first select in order of hashtypes */
+DEBUG(D_acl) debug_printf("DKIM: dkim_verify_hashes '%s'\n", dkim_verify_hashes);
+for (prefs = dkim_verify_hashes, sep = 0, yield = NULL, ss = &yield;
+ ele = string_nextinlist(&prefs, &sep, NULL, 0); )
+ {
+ int i = pdkim_hashname_to_hashtype(CUS ele, 0);
+ for (pdkim_signature * s = siglist, * next, ** prev = &siglist; s;
+ s = next)
+ {
+ next = s->next;
+ if (s->hashtype == i)
+ { *prev = next; s->next = NULL; *ss = s; ss = &s->next; }
+ else
+ prev = &s->next;
+ }
+ }
+
+/* then in order of keytypes */
+siglist = yield;
+DEBUG(D_acl) debug_printf("DKIM: dkim_verify_keytypes '%s'\n", dkim_verify_keytypes);
+for (prefs = dkim_verify_keytypes, sep = 0, yield = NULL, ss = &yield;
+ ele = string_nextinlist(&prefs, &sep, NULL, 0); )
+ {
+ int i = pdkim_keyname_to_keytype(CUS ele);
+ for (pdkim_signature * s = siglist, * next, ** prev = &siglist; s;
+ s = next)
+ {
+ next = s->next;
+ if (s->keytype == i)
+ { *prev = next; s->next = NULL; *ss = s; ss = &s->next; }
+ else
+ prev = &s->next;
+ }
+ }
+
+DEBUG(D_acl) for (pdkim_signature * s = yield; s; s = s->next)
+ debug_printf(" retain d=%s s=%s a=%s\n",
+ s->domain, s->selector, dkim_sig_to_a_tag(s));
+return yield;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+DLLEXPORT int
+pdkim_feed_finish(pdkim_ctx * ctx, pdkim_signature ** return_signatures,
+ const uschar ** err)
+{
+BOOL verify_pass = FALSE;
+
+/* 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
+ out of '<CR><LF>' */
+if (ctx->cur_header && ctx->cur_header->ptr > 0)
+ {
+ blob * rnl = NULL;
+ int rc;
+
+ if ((rc = pdkim_header_complete(ctx)) != PDKIM_OK)
+ return rc;
+
+ for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next)
+ rnl = pdkim_update_ctx_bodyhash(b, &lineending, rnl);
+ if (rnl) store_free(rnl);
+ }
+else
+ DEBUG(D_acl) debug_printf(
+ "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+
+/* Build (and/or evaluate) body hash. Do this even if no DKIM sigs, in case we
+have a hash to do for ARC. */
+
+pdkim_finish_bodyhash(ctx);
+
+/* Sort and filter the recived signatures */
+
+if (!(ctx->flags & PDKIM_MODE_SIGN))
+ ctx->sig = sort_sig_methods(ctx->sig);
+
+if (!ctx->sig)
+ {
+ DEBUG(D_acl) debug_printf("DKIM: no signatures\n");
+ *return_signatures = NULL;
+ return PDKIM_OK;
+ }
+
+for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
+ {
+ hctx hhash_ctx;
+ uschar * sig_hdr = US"";
+ blob hhash;
+ gstring * hdata = NULL;
+ es_ctx sctx;
+
+ if ( !(ctx->flags & PDKIM_MODE_SIGN)
+ && sig->verify_status == PDKIM_VERIFY_FAIL)
+ {
+ DEBUG(D_acl)
+ debug_printf("DKIM: [%s] abandoning this signature\n", sig->domain);
+ continue;
+ }
+
+ /*XXX The hash of the headers is needed for GCrypt (for which we can do RSA
+ signing only, as it happens) and for either GnuTLS and OpenSSL when we are
+ signing with EC (specifically, Ed25519). The former is because the GCrypt
+ signing operation is pure (does not do its own hash) so we must hash. The
+ latter is because we (stupidly, but this is what the IETF draft is saying)
+ must hash with the declared hash method, then pass the result to the library
+ hash-and-sign routine (because that's all the libraries are providing. And
+ we're stuck with whatever that hidden hash method is, too). We may as well
+ do this hash incrementally.
+ We don't need the hash we're calculating here for the GnuTLS and OpenSSL
+ cases of RSA signing, since those library routines can do hash-and-sign.
+
+ Some time in the future we could easily avoid doing the hash here for those
+ cases (which will be common for a long while. We could also change from
+ the current copy-all-the-headers-into-one-block, then call the hash-and-sign
+ implementation - to a proper incremental one. Unfortunately, GnuTLS just
+ cannot do incremental - either signing or verification. Unsure about GCrypt.
+ */
+
+ /*XXX The header hash is also used (so far) by the verify operation */
+
+ if (!exim_sha_init(&hhash_ctx, pdkim_hashes[sig->hashtype].exim_hashmethod))
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "DKIM: hash setup error, possibly nonhandled hashtype");
+ break;
+ }
+
+ if (ctx->flags & PDKIM_MODE_SIGN)
+ DEBUG(D_acl) debug_printf(
+ "DKIM >> Headers to be signed: >>>>>>>>>>>>\n"
+ " %s\n",
+ sig->sign_headers);
+
+ DEBUG(D_acl) debug_printf(
+ "DKIM >> Header data for hash, canonicalized (%-7s), in sequence >>\n",
+ pdkim_canons[sig->canon_headers]);
+
+
+ /* SIGNING ---------------------------------------------------------------- */
+ /* When signing, walk through our header list and add them to the hash. As we
+ go, construct a list of the header's names to use for the h= parameter.
+ Then append to that list any remaining header names for which there was no
+ header to sign. */
+
+ if (ctx->flags & PDKIM_MODE_SIGN)
+ {
+ gstring * g = NULL;
+ const uschar * l;
+ uschar * s;
+ int sep = 0;
+
+ /* Import private key, including the keytype which we need for building
+ the signature header */
+
+ if ((*err = exim_dkim_signing_init(CUS sig->privkey, &sctx)))
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "signing_init: %s", *err);
+ return PDKIM_ERR_RSA_PRIVKEY;
+ }
+ sig->keytype = sctx.keytype;
+
+ sig->headernames = NULL; /* Collected signed header names */
+ for (pdkim_stringlist * p = sig->headers; p; p = p->next)
+ {
+ uschar * rh = p->value;
+
+ if (header_name_match(rh, sig->sign_headers) == PDKIM_OK)
+ {
+ /* Collect header names (Note: colon presence is guaranteed here) */
+ g = string_append_listele_n(g, ':', rh, Ustrchr(rh, ':') - rh);
+
+ if (sig->canon_headers == PDKIM_CANON_RELAXED)
+ rh = pdkim_relax_header(rh, TRUE); /* cook header for relaxed canon */
+
+ /* Feed header to the hash algorithm */
+ exim_sha_update_string(&hhash_ctx, CUS rh);
+
+ /* Remember headers block for signing (when the library cannot do incremental) */
+ /*XXX we could avoid doing this for all but the GnuTLS/RSA case */
+ hdata = exim_dkim_data_append(hdata, rh);
+
+ DEBUG(D_acl) pdkim_quoteprint(rh, Ustrlen(rh));
+ }
+ }
+
+ /* Any headers we wanted to sign but were not present must also be listed.
+ Ignore elements that have been ticked-off or are marked as never-oversign. */
+
+ l = sig->sign_headers;
+ while((s = string_nextinlist(&l, &sep, NULL, 0)))
+ {
+ if (*s == '+') /* skip oversigning marker */
+ s++;
+ if (*s != '_' && *s != '=')
+ g = string_append_listele(g, ':', s);
+ }
+ sig->headernames = string_from_gstring(g);
+
+ /* Create signature header with b= omitted */
+ sig_hdr = pdkim_create_header(sig, FALSE);
+ }
+
+ /* VERIFICATION ----------------------------------------------------------- */
+ /* When verifying, walk through the header name list in the h= parameter and
+ add the headers to the hash in that order. */
+ else
+ {
+ uschar * p = sig->headernames;
+ uschar * q;
+
+ if (p)
+ {
+ /* clear tags */
+ for (pdkim_stringlist * hdrs = ctx->headers; hdrs; hdrs = hdrs->next)
+ hdrs->tag = 0;
+
+ p = string_copy(p);
+ while(1)
+ {
+ if ((q = Ustrchr(p, ':')))
+ *q = '\0';
+
+ /*XXX walk the list of headers in same order as received. */
+ for (pdkim_stringlist * 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, TRUE)
+ : string_copy(CUS hdrs->value);
+
+ /* Feed header to the hash algorithm */
+ exim_sha_update_string(&hhash_ctx, CUS rh);
+
+ DEBUG(D_acl) pdkim_quoteprint(rh, Ustrlen(rh));
+ hdrs->tag = 1;
+ break;
+ }
+
+ if (!q) break;
+ p = q+1;
+ }
+
+ sig_hdr = string_copy(sig->rawsig_no_b_val);
+ }
+ }
+
+ DEBUG(D_acl) debug_printf(
+ "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+
+ DEBUG(D_acl)
+ {
+ debug_printf(
+ "DKIM >> Signed DKIM-Signature header, pre-canonicalized >>>>>>>>>>>>>\n");
+ pdkim_quoteprint(CUS sig_hdr, Ustrlen(sig_hdr));
+ debug_printf(
+ "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ }
+
+ /* Relax header if necessary */
+ if (sig->canon_headers == PDKIM_CANON_RELAXED)
+ sig_hdr = pdkim_relax_header(sig_hdr, FALSE);
+
+ DEBUG(D_acl)
+ {
+ debug_printf("DKIM >> Signed DKIM-Signature header, canonicalized (%-7s) >>>>>>>\n",
+ pdkim_canons[sig->canon_headers]);
+ pdkim_quoteprint(CUS sig_hdr, Ustrlen(sig_hdr));
+ debug_printf(
+ "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ }
+
+ /* Finalize header hash */
+ exim_sha_update_string(&hhash_ctx, CUS sig_hdr);
+ exim_sha_finish(&hhash_ctx, &hhash);
+
+ DEBUG(D_acl)
+ {
+ debug_printf("DKIM [%s] Header %s computed: ",
+ sig->domain, pdkim_hashes[sig->hashtype].dkim_hashname);
+ pdkim_hexprint(hhash.data, hhash.len);
+ }
+
+ /* Remember headers block for signing (when the signing library cannot do
+ incremental) */
+ if (ctx->flags & PDKIM_MODE_SIGN)
+ hdata = exim_dkim_data_append(hdata, US sig_hdr);
+
+ /* SIGNING ---------------------------------------------------------------- */
+ if (ctx->flags & PDKIM_MODE_SIGN)
+ {
+ hashmethod hm = sig->keytype == KEYTYPE_ED25519
+#if defined(SIGN_OPENSSL)
+ ? HASH_NULL
+#else
+ ? HASH_SHA2_512
+#endif
+ : pdkim_hashes[sig->hashtype].exim_hashmethod;
+
+#ifdef SIGN_HAVE_ED25519
+ /* For GCrypt, and for EC, we pass the hash-of-headers to the signing
+ routine. For anything else we just pass the headers. */
+
+ if (sig->keytype != KEYTYPE_ED25519)
+#endif
+ {
+ hhash.data = hdata->s;
+ hhash.len = hdata->ptr;
+ }
+
+ if ((*err = exim_dkim_sign(&sctx, hm, &hhash, &sig->sighash)))
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "signing: %s", *err);
+ return PDKIM_ERR_RSA_SIGNING;
+ }
+
+ DEBUG(D_acl)
+ {
+ debug_printf( "DKIM [%s] b computed: ", sig->domain);
+ pdkim_hexprint(sig->sighash.data, sig->sighash.len);
+ }
+
+ sig->signature_header = pdkim_create_header(sig, TRUE);
+ }
+
+ /* VERIFICATION ----------------------------------------------------------- */
+ else
+ {
+ ev_ctx vctx;
+ hashmethod hm;
+
+ /* Make sure we have all required signature tags */
+ if (!( sig->domain && *sig->domain
+ && sig->selector && *sig->selector
+ && sig->headernames && *sig->headernames
+ && sig->bodyhash.data
+ && sig->sighash.data
+ && sig->keytype >= 0
+ && sig->hashtype >= 0
+ && sig->version
+ ) )
+ {
+ sig->verify_status = PDKIM_VERIFY_INVALID;
+ sig->verify_ext_status = PDKIM_VERIFY_INVALID_SIGNATURE_ERROR;
+
+ DEBUG(D_acl) debug_printf(
+ " Error in DKIM-Signature header: tags missing or invalid (%s)\n"
+ "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n",
+ !(sig->domain && *sig->domain) ? "d="
+ : !(sig->selector && *sig->selector) ? "s="
+ : !(sig->headernames && *sig->headernames) ? "h="
+ : !sig->bodyhash.data ? "bh="
+ : !sig->sighash.data ? "b="
+ : sig->keytype < 0 || sig->hashtype < 0 ? "a="
+ : "v="
+ );
+ goto NEXT_VERIFY;
+ }
+
+ /* Make sure sig uses supported DKIM version (only v1) */
+ if (sig->version != 1)
+ {
+ sig->verify_status = PDKIM_VERIFY_INVALID;
+ sig->verify_ext_status = PDKIM_VERIFY_INVALID_DKIM_VERSION;
+
+ DEBUG(D_acl) debug_printf(
+ " Error in DKIM-Signature header: unsupported DKIM version\n"
+ "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ goto NEXT_VERIFY;
+ }
+
+ DEBUG(D_acl)
+ {
+ debug_printf( "DKIM [%s] b from mail: ", sig->domain);
+ pdkim_hexprint(sig->sighash.data, sig->sighash.len);
+ }
+
+ if (!(sig->pubkey = pdkim_key_from_dns(ctx, sig, &vctx, err)))
+ {
+ log_write(0, LOG_MAIN, "DKIM: %s%s %s%s [failed key import]",
+ sig->domain ? "d=" : "", sig->domain ? sig->domain : US"",
+ sig->selector ? "s=" : "", sig->selector ? sig->selector : US"");
+ 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;
+ }
+ }
+
+ hm = sig->keytype == KEYTYPE_ED25519
+#if defined(SIGN_OPENSSL)
+ ? HASH_NULL
+#else
+ ? HASH_SHA2_512
+#endif
+ : pdkim_hashes[sig->hashtype].exim_hashmethod;
+
+ /* Check the signature */
+
+ if ((*err = exim_dkim_verify(&vctx, hm, &hhash, &sig->sighash)))
+ {
+ 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;
+ }
+ if (*dkim_verify_min_keysizes)
+ {
+ unsigned minbits;
+ uschar * ss = expand_getkeyed(US pdkim_keytypes[sig->keytype],
+ dkim_verify_min_keysizes);
+ if (ss && (minbits = atoi(CS ss)) > sig->keybits)
+ {
+ DEBUG(D_acl) debug_printf("Key too short: Actual: %s %u Minima '%s'\n",
+ pdkim_keytypes[sig->keytype], sig->keybits, dkim_verify_min_keysizes);
+ sig->verify_status = PDKIM_VERIFY_FAIL;
+ sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_KEYSIZE;
+ }
+ }
+
+
+ /* We have a winner! (if bodyhash was correct earlier) */
+ if (sig->verify_status == PDKIM_VERIFY_NONE)
+ {
+ sig->verify_status = PDKIM_VERIFY_PASS;
+ verify_pass = TRUE;
+ if (dkim_verify_minimal) break;
+ }
+
+NEXT_VERIFY:
+
+ DEBUG(D_acl)
+ {
+ debug_printf("DKIM [%s] %s signature status: %s",
+ sig->domain, dkim_sig_to_a_tag(sig),
+ pdkim_verify_status_str(sig->verify_status));
+ if (sig->verify_ext_status > 0)
+ debug_printf(" (%s)\n",
+ pdkim_verify_ext_status_str(sig->verify_ext_status));
+ else
+ debug_printf("\n");
+ }
+ }
+ }
+
+/* If requested, set return pointer to signature(s) */
+if (return_signatures)
+ *return_signatures = ctx->sig;
+
+return ctx->flags & PDKIM_MODE_SIGN || verify_pass
+ ? PDKIM_OK : PDKIM_FAIL;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+DLLEXPORT pdkim_ctx *
+pdkim_init_verify(uschar * (*dns_txt_callback)(const uschar *), BOOL dot_stuffing)
+{
+pdkim_ctx * ctx;
+
+ctx = store_get(sizeof(pdkim_ctx), GET_UNTAINTED);
+memset(ctx, 0, sizeof(pdkim_ctx));
+
+if (dot_stuffing) ctx->flags = PDKIM_DOT_TERM;
+/* The line-buffer is for message data, hence tainted */
+ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN, GET_TAINTED);
+ctx->dns_txt_callback = dns_txt_callback;
+ctx->cur_header = string_get_tainted(36, GET_TAINTED);
+
+return ctx;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+DLLEXPORT pdkim_signature *
+pdkim_init_sign(pdkim_ctx * ctx,
+ uschar * domain, uschar * selector, uschar * privkey,
+ uschar * hashname, const uschar ** errstr)
+{
+int hashtype;
+pdkim_signature * sig;
+
+if (!domain || !selector || !privkey)
+ return NULL;
+
+/* Allocate & init one signature struct */
+
+sig = store_get(sizeof(pdkim_signature), GET_UNTAINTED);
+memset(sig, 0, sizeof(pdkim_signature));
+
+sig->bodylength = -1;
+
+sig->domain = string_copy(US domain);
+sig->selector = string_copy(US selector);
+sig->privkey = string_copy(US privkey);
+sig->keytype = -1;
+
+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))
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "DKIM: unrecognised hashname '%s'", hashname);
+ return NULL;
+ }
+
+DEBUG(D_acl)
+ {
+ pdkim_signature s = *sig;
+ ev_ctx vctx;
+
+ debug_printf("DKIM (checking verify key)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
+ if (!pdkim_key_from_dns(ctx, &s, &vctx, errstr))
+ debug_printf("WARNING: bad dkim key in dns\n");
+ debug_printf("DKIM (finished checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ }
+return sig;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+DLLEXPORT void
+pdkim_set_optional(pdkim_signature * sig,
+ char * sign_headers,
+ char * identity,
+ int canon_headers,
+ int canon_body,
+ long bodylength,
+ unsigned long created,
+ unsigned long expires)
+{
+if (identity)
+ sig->identity = string_copy(US identity);
+
+sig->sign_headers = string_copy(sign_headers
+ ? US sign_headers : US PDKIM_DEFAULT_SIGN_HEADERS);
+
+sig->canon_headers = canon_headers;
+sig->canon_body = canon_body;
+sig->bodylength = bodylength;
+sig->created = created;
+sig->expires = expires;
+
+return;
+}
+
+
+
+/* Set up a blob for calculating the bodyhash according to the
+given needs. Use an existing one if possible, or create a new one.
+
+Return: hashblob pointer, or NULL on error
+*/
+pdkim_bodyhash *
+pdkim_set_bodyhash(pdkim_ctx * ctx, int hashtype, int canon_method,
+ long bodylength)
+{
+pdkim_bodyhash * b;
+
+if (hashtype == -1 || canon_method == -1) return NULL;
+
+for (b = ctx->bodyhash; b; b = b->next)
+ if ( hashtype == b->hashtype
+ && canon_method == b->canon_method
+ && bodylength == b->bodylength)
+ {
+ DEBUG(D_receive) debug_printf("DKIM: using existing bodyhash %s/%s/%ld\n",
+ pdkim_hashes[hashtype].dkim_hashname, pdkim_canons[canon_method], bodylength);
+ return b;
+ }
+
+DEBUG(D_receive) debug_printf("DKIM: new bodyhash %s/%s/%ld\n",
+ pdkim_hashes[hashtype].dkim_hashname, pdkim_canons[canon_method], bodylength);
+b = store_get(sizeof(pdkim_bodyhash), GET_UNTAINTED);
+b->next = ctx->bodyhash;
+b->hashtype = hashtype;
+b->canon_method = canon_method;
+b->bodylength = bodylength;
+if (!exim_sha_init(&b->body_hash_ctx, /*XXX hash method: extend for sha512 */
+ pdkim_hashes[hashtype].exim_hashmethod))
+ {
+ DEBUG(D_acl)
+ debug_printf("DKIM: hash init error, possibly nonhandled hashtype\n");
+ return NULL;
+ }
+b->signed_body_bytes = 0;
+b->num_buffered_blanklines = 0;
+ctx->bodyhash = b;
+return b;
+}
+
+
+/* Set up a blob for calculating the bodyhash according to the
+needs of this signature. Use an existing one if possible, or
+create a new one.
+
+Return: hashblob pointer, or NULL on error (only used as a boolean).
+*/
+pdkim_bodyhash *
+pdkim_set_sig_bodyhash(pdkim_ctx * ctx, pdkim_signature * sig)
+{
+pdkim_bodyhash * b = pdkim_set_bodyhash(ctx,
+ sig->hashtype, sig->canon_body, sig->bodylength);
+sig->calc_body_hash = b;
+return b;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void
+pdkim_init_context(pdkim_ctx * ctx, BOOL dot_stuffed,
+ uschar * (*dns_txt_callback)(const uschar *))
+{
+memset(ctx, 0, sizeof(pdkim_ctx));
+ctx->flags = dot_stuffed ? PDKIM_MODE_SIGN | PDKIM_DOT_TERM : PDKIM_MODE_SIGN;
+/* The line buffer is for message data, hence tainted */
+ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN, GET_TAINTED);
+DEBUG(D_acl) ctx->dns_txt_callback = dns_txt_callback;
+}
+
+
+void
+pdkim_init(void)
+{
+exim_dkim_init();
+}
+
+
+
+#endif /*DISABLE_DKIM*/
diff --git a/src/pdkim/pdkim.h b/src/pdkim/pdkim.h
new file mode 100644
index 0000000..f6ff782
--- /dev/null
+++ b/src/pdkim/pdkim.h
@@ -0,0 +1,369 @@
+/*
+ * PDKIM - a RFC4871 (DKIM) implementation
+ *
+ * Copyright (C) 2009 - 2012 Tom Kistner <tom@duncanthrax.net>
+ * Copyright (c) 2016 - 2020 Jeremy Harris
+ *
+ * http://duncanthrax.net/pdkim/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifndef PDKIM_H
+#define PDKIM_H
+
+#include "../blob.h"
+#include "../hash.h"
+
+#define PDKIM_DEFAULT_SIGN_HEADERS "From:Sender:Reply-To:Subject:Date:"\
+ "Message-ID: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"
+
+#define PDKIM_OVERSIGN_HEADERS "+From:+Sender:+Reply-To:+Subject:+Date:"\
+ "+Message-ID:+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"
+
+/* -------------------------------------------------------------------------- */
+/* Length of the preallocated buffer for the "answer" from the dns/txt
+ callback function. This should match the maximum RDLENGTH from DNS. */
+#define PDKIM_DNS_TXT_MAX_RECLEN (1 << 16)
+
+/* -------------------------------------------------------------------------- */
+/* Function success / error codes */
+#define PDKIM_OK 0
+#define PDKIM_FAIL -1
+#define PDKIM_ERR_RSA_PRIVKEY -101
+#define PDKIM_ERR_RSA_SIGNING -102
+#define PDKIM_ERR_LONG_LINE -103
+#define PDKIM_ERR_BUFFER_TOO_SMALL -104
+#define PDKIM_ERR_EXCESS_SIGS -105
+#define PDKIM_SIGN_PRIVKEY_WRAP -106
+#define PDKIM_SIGN_PRIVKEY_B64D -107
+
+/* -------------------------------------------------------------------------- */
+/* Main/Extended verification status */
+#define PDKIM_VERIFY_NONE 0
+#define PDKIM_VERIFY_INVALID 1
+#define PDKIM_VERIFY_FAIL 2
+#define PDKIM_VERIFY_PASS 3
+#define PDKIM_VERIFY_POLICY BIT(31)
+
+#define PDKIM_VERIFY_FAIL_BODY 1
+#define PDKIM_VERIFY_FAIL_MESSAGE 2
+#define PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH 3
+#define PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE 4
+#define PDKIM_VERIFY_INVALID_BUFFER_SIZE 5
+#define PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD 6
+#define PDKIM_VERIFY_INVALID_PUBKEY_IMPORT 7
+#define PDKIM_VERIFY_INVALID_PUBKEY_KEYSIZE 8
+#define PDKIM_VERIFY_INVALID_SIGNATURE_ERROR 9
+#define PDKIM_VERIFY_INVALID_DKIM_VERSION 10
+
+/* -------------------------------------------------------------------------- */
+/* Some parameter values */
+#define PDKIM_QUERYMETHOD_DNS_TXT 0
+
+#define PDKIM_CANON_SIMPLE 0
+#define PDKIM_CANON_RELAXED 1
+
+/* -------------------------------------------------------------------------- */
+/* Some required forward declarations, please ignore */
+typedef struct pdkim_stringlist pdkim_stringlist;
+typedef struct pdkim_str pdkim_str;
+typedef struct sha1_context sha1_context;
+typedef struct sha2_context sha2_context;
+#define HAVE_SHA1_CONTEXT
+#define HAVE_SHA2_CONTEXT
+
+/* -------------------------------------------------------------------------- */
+/* Some concessions towards Redmond */
+#ifdef WINDOWS
+#define snprintf _snprintf
+#define strcasecmp _stricmp
+#define strncasecmp _strnicmp
+#define DLLEXPORT __declspec(dllexport)
+#else
+#define DLLEXPORT
+#endif
+
+
+/* -------------------------------------------------------------------------- */
+/* Public key as (usually) fetched from DNS */
+typedef struct pdkim_pubkey {
+ const uschar * version; /* v= */
+ const uschar *granularity; /* g= */
+
+ const uschar * hashes; /* h= */
+ const uschar * keytype; /* k= */
+ const uschar * srvtype; /* s= */
+ uschar *notes; /* n= */
+
+ blob key; /* p= */
+ int testing; /* t=y */
+ int no_subdomaining; /* t=s */
+} pdkim_pubkey;
+
+/* -------------------------------------------------------------------------- */
+/* Body-hash to be calculated */
+typedef struct pdkim_bodyhash {
+ struct pdkim_bodyhash * next;
+ int hashtype;
+ int canon_method;
+ long bodylength;
+
+ hctx body_hash_ctx;
+ unsigned long signed_body_bytes; /* done so far */
+ int num_buffered_blanklines;
+
+ blob bh; /* completed hash */
+} pdkim_bodyhash;
+
+/* -------------------------------------------------------------------------- */
+/* Signature as it appears in a DKIM-Signature header */
+typedef struct pdkim_signature {
+ struct pdkim_signature * next;
+
+ /* Bits stored in a DKIM signature header --------------------------- */
+
+ /* (v=) The version, as an integer. Currently, always "1" */
+ int version;
+
+ /* (a=) The signature algorithm. */
+ int keytype; /* pdkim_keytypes index */
+ unsigned keybits; /* size of the key */
+ int hashtype; /* pdkim_hashes index */
+
+ /* (c=x/) Header canonicalization method. Either PDKIM_CANON_SIMPLE
+ or PDKIM_CANON_RELAXED */
+ int canon_headers;
+
+ /* (c=/x) Body canonicalization method. Either PDKIM_CANON_SIMPLE
+ or PDKIM_CANON_RELAXED */
+ int canon_body;
+
+ /* (q=) Query Method. Currently, only PDKIM_QUERYMETHOD_DNS_TXT
+ is specified */
+ int querymethod;
+
+ /* (s=) The selector string as given in the signature */
+ uschar *selector;
+
+ /* (d=) The domain as given in the signature */
+ uschar *domain;
+
+ /* (i=) The identity as given in the signature */
+ uschar *identity;
+
+ /* (t=) Timestamp of signature creation */
+ unsigned long created;
+
+ /* (x=) Timestamp of expiry of signature */
+ unsigned long expires;
+
+ /* (l=) Amount of hashed body bytes (after canonicalization). Default
+ is -1. Note: a value of 0 means that the body is unsigned! */
+ long bodylength;
+
+ /* (h=) Colon-separated list of header names that are included in the
+ signature */
+ uschar *headernames;
+
+ /* (z=) */
+ uschar *copiedheaders;
+
+ /* (b=) Raw signature data, along with its length in bytes */
+ blob sighash;
+
+ /* (bh=) Raw body hash data, along with its length in bytes */
+ blob bodyhash;
+
+ /* Folded DKIM-Signature: header. Signing only, NULL for verifying.
+ Ready for insertion into the message. Note: Folded using CRLFTB,
+ but final line terminator is NOT included. Note2: This buffer is
+ free()d when you call pdkim_free_ctx(). */
+ uschar *signature_header;
+
+ /* The main verification status. Verification only. One of:
+
+ PDKIM_VERIFY_NONE Verification was not attempted. This status
+ should not appear.
+
+ PDKIM_VERIFY_INVALID There was an error while trying to verify
+ the signature. A more precise description
+ is available in verify_ext_status.
+
+ PDKIM_VERIFY_FAIL Verification failed because either the body
+ hash did not match, or the signature verification
+ failed. This means the message was modified.
+ Check verify_ext_status for the exact reason.
+
+ PDKIM_VERIFY_PASS Verification succeeded.
+ */
+ int verify_status;
+
+ /* Extended verification status. Verification only. Depending on the value
+ of verify_status, it can contain:
+
+ For verify_status == PDKIM_VERIFY_INVALID:
+
+ PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE
+ Unable to retrieve a public key container.
+
+ PDKIM_VERIFY_INVALID_BUFFER_SIZE
+ Either the DNS name constructed to retrieve the public key record
+ does not fit into PDKIM_DNS_TXT_MAX_NAMELEN bytes, or the retrieved
+ record is longer than PDKIM_DNS_TXT_MAX_RECLEN bytes.
+
+ PDKIM_VERIFY_INVALID_PUBKEY_PARSING
+ (Syntax) error while parsing the retrieved public key record.
+
+
+ For verify_status == PDKIM_VERIFY_FAIL:
+
+ PDKIM_VERIFY_FAIL_BODY
+ The calculated body hash does not match the advertised body hash
+ from the bh= tag of the signature.
+
+ PDKIM_VERIFY_FAIL_MESSAGE
+ RSA verification of the signature (b= tag) failed.
+ */
+ int verify_ext_status;
+
+ /* Pointer to a public key record that was used to verify the signature.
+ See pdkim_pubkey declaration above for more information.
+ Caution: is NULL if signing or if no record was retrieved. */
+ pdkim_pubkey *pubkey;
+
+ /* Properties below this point are used internally only ------------- */
+
+ /* Per-signature helper variables ----------------------------------- */
+ pdkim_bodyhash *calc_body_hash; /* hash to be / being calculated */
+
+ pdkim_stringlist *headers; /* Raw headers included in the sig */
+
+ /* Signing specific ------------------------------------------------- */
+ 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;
+
+
+/* -------------------------------------------------------------------------- */
+/* Context to keep state between all operations. */
+typedef struct pdkim_ctx {
+
+#define PDKIM_MODE_SIGN BIT(0) /* if unset, mode==verify */
+#define PDKIM_DOT_TERM BIT(1) /* dot termination and unstuffing */
+#define PDKIM_SEEN_CR BIT(2)
+#define PDKIM_SEEN_LF BIT(3)
+#define PDKIM_PAST_HDRS BIT(4)
+#define PDKIM_SEEN_EOD BIT(5)
+ unsigned flags;
+
+ /* One (signing) or several chained (verification) signatures */
+ pdkim_signature *sig;
+
+ /* One (signing) or several chained (verification) bodyhashes */
+ pdkim_bodyhash *bodyhash;
+
+ /* Callback for dns/txt query method (verification only) */
+ uschar * (*dns_txt_callback)(const uschar *);
+
+ /* Coder's little helpers */
+ gstring *cur_header;
+ uschar *linebuf;
+ int linebuf_offset;
+ int num_headers;
+ pdkim_stringlist *headers; /* Raw headers for verification */
+} pdkim_ctx;
+
+
+/******************************************************************************/
+
+typedef struct {
+ const uschar * dkim_hashname;
+ hashmethod exim_hashmethod;
+} pdkim_hashtype;
+extern const pdkim_hashtype pdkim_hashes[];
+
+/******************************************************************************/
+
+
+/* -------------------------------------------------------------------------- */
+/* API functions. Please see the sample code in sample/test_sign.c and
+ sample/test_verify.c for documentation.
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void pdkim_init (void);
+
+void pdkim_init_context (pdkim_ctx *, BOOL, uschar * (*)(const uschar *));
+
+DLLEXPORT
+pdkim_signature *pdkim_init_sign (pdkim_ctx *,
+ uschar *, uschar *, uschar *, uschar *,
+ const uschar **);
+
+DLLEXPORT
+pdkim_ctx *pdkim_init_verify (uschar * (*)(const uschar *), BOOL);
+
+DLLEXPORT
+void pdkim_set_optional (pdkim_signature *, char *, char *,int, int,
+ long,
+ unsigned long,
+ unsigned long);
+
+int pdkim_hashname_to_hashtype(const uschar *, unsigned);
+void pdkim_cstring_to_canons(const uschar *, unsigned, int *, int *);
+pdkim_bodyhash *pdkim_set_bodyhash(pdkim_ctx *, int, int, long);
+pdkim_bodyhash *pdkim_set_sig_bodyhash(pdkim_ctx *, pdkim_signature *);
+
+DLLEXPORT
+int pdkim_feed (pdkim_ctx *, uschar *, int);
+DLLEXPORT
+int pdkim_feed_finish (pdkim_ctx *, pdkim_signature **, const uschar **);
+
+DLLEXPORT
+void pdkim_free_ctx (pdkim_ctx *);
+
+
+const uschar * pdkim_errstr(int);
+
+extern uschar * pdkim_encode_base64(blob *);
+extern void pdkim_decode_base64(const uschar *, blob *);
+extern void pdkim_hexprint(const uschar *, int);
+extern void pdkim_quoteprint(const uschar *, int);
+extern pdkim_pubkey * pdkim_parse_pubkey_record(const uschar *);
+extern uschar * pdkim_relax_header_n(const uschar *, int, BOOL);
+extern uschar * pdkim_relax_header(const uschar *, BOOL);
+extern uschar * dkim_sig_to_a_tag(const pdkim_signature *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/pdkim/pdkim_hash.h b/src/pdkim/pdkim_hash.h
new file mode 100644
index 0000000..8f9a126
--- /dev/null
+++ b/src/pdkim/pdkim_hash.h
@@ -0,0 +1,38 @@
+/*
+ * PDKIM - a RFC4871 (DKIM) implementation
+ *
+ * Copyright (C) 1995 - 2018 Exim maintainers
+ *
+ * Hash interface functions
+ */
+
+#include "../exim.h"
+
+#if !defined(HASH_H) /* entire file */
+#define HASH_H
+
+#ifdef DISABLE_TLS
+# error Must not DISABLE_TLS, for DKIM
+#endif
+
+#include "crypt_ver.h"
+#include "../blob.h"
+#include "../hash.h"
+
+#ifdef SIGN_OPENSSL
+# include <openssl/rsa.h>
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+#elif defined(SIGN_GNUTLS)
+# include <gnutls/gnutls.h>
+# include <gnutls/x509.h>
+#endif
+
+#if defined(SHA_OPENSSL)
+# include "pdkim.h"
+#elif defined(SHA_GCRYPT)
+# include "pdkim.h"
+#endif
+
+#endif
+/* End of File */
diff --git a/src/pdkim/signing.c b/src/pdkim/signing.c
new file mode 100644
index 0000000..d78f31a
--- /dev/null
+++ b/src/pdkim/signing.c
@@ -0,0 +1,903 @@
+/*
+ * PDKIM - a RFC4871 (DKIM) implementation
+ * Copyright (c) The Exim Maintainers 1995 - 2022
+ *
+ * signing/verification interface
+ */
+
+#include "../exim.h"
+#include "crypt_ver.h"
+#include "signing.h"
+
+
+#ifdef MACRO_PREDEF
+# include "../macro_predef.h"
+
+void
+features_crypto(void)
+{
+# ifdef SIGN_HAVE_ED25519
+ builtin_macro_create(US"_CRYPTO_SIGN_ED25519");
+# endif
+# ifdef EXIM_HAVE_SHA3
+ builtin_macro_create(US"_CRYPTO_HASH_SHA3");
+# endif
+}
+#else
+
+#ifndef DISABLE_DKIM /* rest of file */
+
+#ifdef DISABLE_TLS
+# error Must no DISABLE_TLS, for DKIM
+#endif
+
+
+/******************************************************************************/
+#ifdef SIGN_GNUTLS
+# define EXIM_GNUTLS_LIBRARY_LOG_LEVEL 3
+
+# ifndef GNUTLS_VERIFY_ALLOW_BROKEN
+# define GNUTLS_VERIFY_ALLOW_BROKEN 0
+# endif
+
+
+/* Logging function which can be registered with
+ * gnutls_global_set_log_function()
+ * gnutls_global_set_log_level() 0..9
+ */
+#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
+static void
+exim_gnutls_logger_cb(int level, const char *message)
+{
+size_t len = strlen(message);
+if (len < 1)
+ {
+ DEBUG(D_tls) debug_printf("GnuTLS<%d> empty debug message\n", level);
+ return;
+ }
+DEBUG(D_tls) debug_printf("GnuTLS<%d>: %s%s", level, message,
+ message[len-1] == '\n' ? "" : "\n");
+}
+#endif
+
+
+
+void
+exim_dkim_init(void)
+{
+#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
+DEBUG(D_tls)
+ {
+ gnutls_global_set_log_function(exim_gnutls_logger_cb);
+ /* arbitrarily chosen level; bump upto 9 for more */
+ gnutls_global_set_log_level(EXIM_GNUTLS_LIBRARY_LOG_LEVEL);
+ }
+#endif
+}
+
+
+/* accumulate data (gnutls-only). String to be appended must be nul-terminated. */
+gstring *
+exim_dkim_data_append(gstring * g, uschar * s)
+{
+return string_cat(g, s);
+}
+
+
+
+/* import private key from PEM string in memory.
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_signing_init(const uschar * privkey_pem, es_ctx * sign_ctx)
+{
+gnutls_datum_t k = { .data = (void *)privkey_pem, .size = Ustrlen(privkey_pem) };
+gnutls_x509_privkey_t x509_key;
+const uschar * where;
+int rc;
+
+if ( (where = US"internal init", rc = gnutls_x509_privkey_init(&x509_key))
+ || (rc = gnutls_privkey_init(&sign_ctx->key))
+ || (where = US"privkey PEM-block import",
+ rc = gnutls_x509_privkey_import(x509_key, &k, GNUTLS_X509_FMT_PEM))
+ || (where = US"internal privkey transfer",
+ rc = gnutls_privkey_import_x509(sign_ctx->key, x509_key, 0))
+ )
+ return string_sprintf("%s: %s", where, gnutls_strerror(rc));
+
+switch (rc = gnutls_privkey_get_pk_algorithm(sign_ctx->key, NULL))
+ {
+ case GNUTLS_PK_RSA: sign_ctx->keytype = KEYTYPE_RSA; break;
+#ifdef SIGN_HAVE_ED25519
+ case GNUTLS_PK_EDDSA_ED25519: sign_ctx->keytype = KEYTYPE_ED25519; break;
+#endif
+ default: return rc < 0
+ ? CUS gnutls_strerror(rc)
+ : string_sprintf("Unhandled key type: %d '%s'", rc, gnutls_pk_get_name(rc));
+ }
+
+return NULL;
+}
+
+
+
+/* allocate mem for signature (when signing) */
+/* hash & sign data. No way to do incremental.
+
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, blob * data, blob * sig)
+{
+gnutls_datum_t k_data = { .data = data->data, .size = data->len };
+gnutls_digest_algorithm_t dig;
+gnutls_datum_t k_sig;
+int rc;
+
+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";
+ }
+
+if ((rc = gnutls_privkey_sign_data(sign_ctx->key, dig, 0, &k_data, &k_sig)))
+ return CUS gnutls_strerror(rc);
+
+/* Don't care about deinit for the key; shortlived process */
+
+sig->data = k_sig.data;
+sig->len = k_sig.size;
+return NULL;
+}
+
+
+
+/* import public key (from blob in memory)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_verify_init(blob * pubkey, keyformat fmt, ev_ctx * verify_ctx,
+ unsigned * bits)
+{
+gnutls_datum_t k;
+int rc;
+const uschar * ret = NULL;
+
+gnutls_pubkey_init(&verify_ctx->key);
+k.data = pubkey->data;
+k.size = pubkey->len;
+
+switch(fmt)
+ {
+ case KEYFMT_DER:
+ if ((rc = gnutls_pubkey_import(verify_ctx->key, &k, GNUTLS_X509_FMT_DER)))
+ ret = US gnutls_strerror(rc);
+ break;
+#ifdef SIGN_HAVE_ED25519
+ case KEYFMT_ED25519_BARE:
+ if ((rc = gnutls_pubkey_import_ecc_raw(verify_ctx->key,
+ GNUTLS_ECC_CURVE_ED25519, &k, NULL)))
+ ret = US gnutls_strerror(rc);
+ break;
+#endif
+ default:
+ ret = US"pubkey format not handled";
+ break;
+ }
+if (!ret && bits) gnutls_pubkey_get_pk_algorithm(verify_ctx->key, bits);
+return ret;
+}
+
+
+/* verify signature (of hash if RSA sig, of data if EC sig. No way to do incremental)
+(given pubkey & alleged sig)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, blob * data_hash, blob * sig)
+{
+gnutls_datum_t k = { .data = data_hash->data, .size = data_hash->len };
+gnutls_datum_t s = { .data = sig->data, .size = sig->len };
+int rc;
+const uschar * ret = NULL;
+
+#ifdef SIGN_HAVE_ED25519
+if (verify_ctx->keytype == KEYTYPE_ED25519)
+ {
+ if ((rc = gnutls_pubkey_verify_data2(verify_ctx->key,
+ GNUTLS_SIGN_EDDSA_ED25519, 0, &k, &s)) < 0)
+ ret = US gnutls_strerror(rc);
+ }
+else
+#endif
+ {
+ gnutls_sign_algorithm_t algo;
+ 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";
+ }
+
+ if ((rc = gnutls_pubkey_verify_hash2(verify_ctx->key, algo,
+ GNUTLS_VERIFY_ALLOW_BROKEN, &k, &s)) < 0)
+ ret = US gnutls_strerror(rc);
+ }
+
+gnutls_pubkey_deinit(verify_ctx->key);
+return ret;
+}
+
+
+
+
+#elif defined(SIGN_GCRYPT)
+/******************************************************************************/
+/* This variant is used under pre-3.0.0 GnuTLS. Only rsa-sha1 and rsa-sha256 */
+
+
+/* Internal service routine:
+Read and move past an asn.1 header, checking class & tag,
+optionally returning the data-length */
+
+static int
+as_tag(blob * der, uschar req_cls, long req_tag, long * alen)
+{
+int rc;
+uschar tag_class;
+int taglen;
+long tag, len;
+
+debug_printf_indent("as_tag: %02x %02x %02x %02x\n",
+ der->data[0], der->data[1], der->data[2], der->data[3]);
+
+if ((rc = asn1_get_tag_der(der->data++, der->len--, &tag_class, &taglen, &tag))
+ != ASN1_SUCCESS)
+ return rc;
+
+if (tag_class != req_cls || tag != req_tag) return ASN1_ELEMENT_NOT_FOUND;
+
+if ((len = asn1_get_length_der(der->data, der->len, &taglen)) < 0)
+ return ASN1_DER_ERROR;
+if (alen) *alen = len;
+
+/* debug_printf_indent("as_tag: tlen %d dlen %d\n", taglen, (int)len); */
+
+der->data += taglen;
+der->len -= taglen;
+return rc;
+}
+
+/* Internal service routine:
+Read and move over an asn.1 integer, setting an MPI to the value
+*/
+
+static uschar *
+as_mpi(blob * der, gcry_mpi_t * mpi)
+{
+long alen;
+int rc;
+gcry_error_t gerr;
+
+debug_printf_indent("%s\n", __FUNCTION__);
+
+/* integer; move past the header */
+if ((rc = as_tag(der, 0, ASN1_TAG_INTEGER, &alen)) != ASN1_SUCCESS)
+ return US asn1_strerror(rc);
+
+/* read to an MPI */
+if ((gerr = gcry_mpi_scan(mpi, GCRYMPI_FMT_STD, der->data, alen, NULL)))
+ return US gcry_strerror(gerr);
+
+/* move over the data */
+der->data += alen; der->len -= alen;
+return NULL;
+}
+
+
+
+void
+exim_dkim_init(void)
+{
+/* Version check should be the very first call because it
+makes sure that important subsystems are initialized. */
+if (!gcry_check_version (GCRYPT_VERSION))
+ {
+ fputs ("libgcrypt version mismatch\n", stderr);
+ exit (2);
+ }
+
+/* We don't want to see any warnings, e.g. because we have not yet
+parsed program options which might be used to suppress such
+warnings. */
+gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
+
+/* ... If required, other initialization goes here. Note that the
+process might still be running with increased privileges and that
+the secure memory has not been initialized. */
+
+/* Allocate a pool of 16k secure memory. This make the secure memory
+available and also drops privileges where needed. */
+gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
+
+/* It is now okay to let Libgcrypt complain when there was/is
+a problem with the secure memory. */
+gcry_control (GCRYCTL_RESUME_SECMEM_WARN);
+
+/* ... If required, other initialization goes here. */
+
+/* Tell Libgcrypt that initialization has completed. */
+gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
+
+return;
+}
+
+
+
+
+/* Accumulate data (gnutls-only).
+String to be appended must be nul-terminated. */
+
+gstring *
+exim_dkim_data_append(gstring * g, uschar * s)
+{
+return g; /*dummy*/
+}
+
+
+
+/* import private key from PEM string in memory.
+Only handles RSA keys.
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_signing_init(const uschar * privkey_pem, es_ctx * sign_ctx)
+{
+uschar * s1, * s2;
+blob der;
+long alen;
+int rc;
+
+/*XXX will need extension to _spot_ as well as handle a
+non-RSA key? I think...
+So... this is not a PrivateKeyInfo - which would have a field
+identifying the keytype - PrivateKeyAlgorithmIdentifier -
+but a plain RSAPrivateKey (wrapped in PEM-headers. Can we
+use those as a type tag? What forms are there? "BEGIN EC PRIVATE KEY" (cf. ec(1ssl))
+
+How does OpenSSL PEM_read_bio_PrivateKey() deal with it?
+gnutls_x509_privkey_import() ?
+*/
+
+/*
+ * RSAPrivateKey ::= SEQUENCE
+ * version Version,
+ * modulus INTEGER, -- n
+ * publicExponent INTEGER, -- e
+ * privateExponent INTEGER, -- d
+ * prime1 INTEGER, -- p
+ * prime2 INTEGER, -- q
+ * exponent1 INTEGER, -- d mod (p-1)
+ * exponent2 INTEGER, -- d mod (q-1)
+ * coefficient INTEGER, -- (inverse of q) mod p
+ * otherPrimeInfos OtherPrimeInfos OPTIONAL
+
+ * ECPrivateKey ::= SEQUENCE {
+ * version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
+ * privateKey OCTET STRING,
+ * parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
+ * publicKey [1] BIT STRING OPTIONAL
+ * }
+ * Hmm, only 1 useful item, and not even an integer? Wonder how we might use it...
+
+- actually, gnutls_x509_privkey_import() appears to require a curve name parameter
+ value for that is an OID? a local-only integer (it's an enum in GnuTLS)?
+
+
+Useful cmds:
+ ssh-keygen -t ecdsa -f foo.privkey
+ ssh-keygen -t ecdsa -b384 -f foo.privkey
+ ssh-keygen -t ecdsa -b521 -f foo.privkey
+ ssh-keygen -t ed25519 -f foo.privkey
+
+ < foo openssl pkcs8 -in /dev/stdin -inform PEM -nocrypt -topk8 -outform DER | od -x
+
+ openssl asn1parse -in foo -inform PEM -dump
+ openssl asn1parse -in foo -inform PEM -dump -stroffset 24 (??)
+(not good for ed25519)
+
+ */
+
+if ( !(s1 = Ustrstr(CS privkey_pem, "-----BEGIN RSA PRIVATE KEY-----"))
+ || !(s2 = Ustrstr(CS (s1+=31), "-----END RSA PRIVATE KEY-----" ))
+ )
+ return US"Bad PEM wrapper";
+
+*s2 = '\0';
+
+if ((rc = b64decode(s1, &der.data) < 0))
+ return US"Bad PEM-DER b64 decode";
+der.len = rc;
+
+/* untangle asn.1 */
+
+/* sequence; just move past the header */
+if ((rc = as_tag(&der, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL))
+ != ASN1_SUCCESS) goto asn_err;
+
+/* integer version; move past the header, check is zero */
+if ((rc = as_tag(&der, 0, ASN1_TAG_INTEGER, &alen)) != ASN1_SUCCESS)
+ goto asn_err;
+if (alen != 1 || *der.data != 0)
+ return US"Bad version number";
+der.data++; der.len--;
+
+if ( (s1 = as_mpi(&der, &sign_ctx->n))
+ || (s1 = as_mpi(&der, &sign_ctx->e))
+ || (s1 = as_mpi(&der, &sign_ctx->d))
+ || (s1 = as_mpi(&der, &sign_ctx->p))
+ || (s1 = as_mpi(&der, &sign_ctx->q))
+ || (s1 = as_mpi(&der, &sign_ctx->dp))
+ || (s1 = as_mpi(&der, &sign_ctx->dq))
+ || (s1 = as_mpi(&der, &sign_ctx->qp))
+ )
+ return s1;
+
+#ifdef extreme_debug
+DEBUG(D_acl) debug_printf_indent("rsa_signing_init:\n");
+ {
+ uschar * s;
+ gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->n);
+ debug_printf_indent(" N : %s\n", s);
+ gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->e);
+ debug_printf_indent(" E : %s\n", s);
+ gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->d);
+ debug_printf_indent(" D : %s\n", s);
+ gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->p);
+ debug_printf_indent(" P : %s\n", s);
+ gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->q);
+ debug_printf_indent(" Q : %s\n", s);
+ gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->dp);
+ debug_printf_indent(" DP: %s\n", s);
+ gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->dq);
+ debug_printf_indent(" DQ: %s\n", s);
+ gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->qp);
+ debug_printf_indent(" QP: %s\n", s);
+ }
+#endif
+
+sign_ctx->keytype = KEYTYPE_RSA;
+return NULL;
+
+asn_err: return US asn1_strerror(rc);
+}
+
+
+
+/* allocate mem for signature (when signing) */
+/* sign already-hashed data.
+
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, blob * data, blob * sig)
+{
+char * sexp_hash;
+gcry_sexp_t s_hash = NULL, s_key = NULL, s_sig = NULL;
+gcry_mpi_t m_sig;
+uschar * errstr;
+gcry_error_t gerr;
+
+/*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 hash so should not be
+re-hashing. Has this been tested?
+
+Will need extension for non-RSA sugning algos. */
+
+switch (hash)
+ {
+ case HASH_SHA1: sexp_hash = "(data(flags pkcs1)(hash sha1 %b))"; break;
+ case HASH_SHA2_256: sexp_hash = "(data(flags pkcs1)(hash sha256 %b))"; break;
+ default: return US"nonhandled hash type";
+ }
+
+#define SIGSPACE 128
+sig->data = store_get(SIGSPACE, GET_UNTAINTED);
+
+if (gcry_mpi_cmp (sign_ctx->p, sign_ctx->q) > 0)
+ {
+ gcry_mpi_swap (sign_ctx->p, sign_ctx->q);
+ gcry_mpi_invm (sign_ctx->qp, sign_ctx->p, sign_ctx->q);
+ }
+
+if ( (gerr = gcry_sexp_build (&s_key, NULL,
+ "(private-key (rsa (n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))",
+ sign_ctx->n, sign_ctx->e,
+ sign_ctx->d, sign_ctx->p,
+ sign_ctx->q, sign_ctx->qp))
+ || (gerr = gcry_sexp_build (&s_hash, NULL, sexp_hash,
+ (int) data->len, CS data->data))
+ || (gerr = gcry_pk_sign (&s_sig, s_hash, s_key))
+ )
+ return US gcry_strerror(gerr);
+
+/* gcry_sexp_dump(s_sig); */
+
+if ( !(s_sig = gcry_sexp_find_token(s_sig, "s", 0))
+ )
+ return US"no sig result";
+
+m_sig = gcry_sexp_nth_mpi(s_sig, 1, GCRYMPI_FMT_USG);
+
+#ifdef extreme_debug
+DEBUG(D_acl)
+ {
+ uschar * s;
+ gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, m_sig);
+ debug_printf_indent(" SG: %s\n", s);
+ }
+#endif
+
+gerr = gcry_mpi_print(GCRYMPI_FMT_USG, sig->data, SIGSPACE, &sig->len, m_sig);
+if (gerr)
+ {
+ debug_printf_indent("signature conversion from MPI to buffer failed\n");
+ return US gcry_strerror(gerr);
+ }
+#undef SIGSPACE
+
+return NULL;
+}
+
+
+/* import public key (from blob in memory)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_verify_init(blob * pubkey, keyformat fmt, ev_ctx * verify_ctx,
+ unsigned * bits)
+{
+/*
+in code sequence per b81207d2bfa92 rsa_parse_public_key() and asn1_get_mpi()
+*/
+uschar tag_class;
+int taglen;
+long alen;
+unsigned nbits;
+int rc;
+uschar * errstr;
+gcry_error_t gerr;
+uschar * stage = US"S1";
+
+if (fmt != KEYFMT_DER) return US"pubkey format not handled";
+
+/*
+sequence
+ sequence
+ OBJECT:rsaEncryption
+ NULL
+ BIT STRING:RSAPublicKey
+ sequence
+ INTEGER:Public modulus
+ INTEGER:Public exponent
+
+openssl rsa -in aux-fixed/dkim/dkim.private -pubout -outform DER | od -t x1 | head;
+openssl rsa -in aux-fixed/dkim/dkim.private -pubout | openssl asn1parse -dump;
+openssl rsa -in aux-fixed/dkim/dkim.private -pubout | openssl asn1parse -dump -offset 22;
+*/
+
+/* sequence; just move past the header */
+if ((rc = as_tag(pubkey, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL))
+ != ASN1_SUCCESS) goto asn_err;
+
+/* sequence; skip the entire thing */
+DEBUG(D_acl) stage = US"S2";
+if ((rc = as_tag(pubkey, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, &alen))
+ != ASN1_SUCCESS) goto asn_err;
+pubkey->data += alen; pubkey->len -= alen;
+
+
+/* bitstring: limit range to size of bitstring;
+move over header + content wrapper */
+DEBUG(D_acl) stage = US"BS";
+if ((rc = as_tag(pubkey, 0, ASN1_TAG_BIT_STRING, &alen)) != ASN1_SUCCESS)
+ goto asn_err;
+pubkey->len = alen;
+pubkey->data++; pubkey->len--;
+
+/* sequence; just move past the header */
+DEBUG(D_acl) stage = US"S3";
+if ((rc = as_tag(pubkey, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL))
+ != ASN1_SUCCESS) goto asn_err;
+
+/* read two integers */
+DEBUG(D_acl) stage = US"MPI";
+nbits = pubkey->len;
+if ((errstr = as_mpi(pubkey, &verify_ctx->n))) return errstr;
+nbits = (nbits - pubkey->len) * 8;
+if ((errstr = as_mpi(pubkey, &verify_ctx->e))) return errstr;
+
+#ifdef extreme_debug
+DEBUG(D_acl) debug_printf_indent("rsa_verify_init:\n");
+ {
+ uschar * s;
+ gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, verify_ctx->n);
+ debug_printf_indent(" N : %s\n", s);
+ gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, verify_ctx->e);
+ debug_printf_indent(" E : %s\n", s);
+ }
+
+#endif
+if (bits) *bits = nbits;
+return NULL;
+
+asn_err:
+DEBUG(D_acl) return string_sprintf("%s: %s", stage, asn1_strerror(rc));
+ return US asn1_strerror(rc);
+}
+
+
+/* verify signature (of hash)
+XXX though we appear to be doing a hash, too!
+(given pubkey & alleged sig)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, blob * data_hash, blob * sig)
+{
+/*
+cf. libgnutls 2.8.5 _wrap_gcry_pk_verify()
+*/
+char * sexp_hash;
+gcry_mpi_t m_sig;
+gcry_sexp_t s_sig = NULL, s_hash = NULL, s_pkey = NULL;
+gcry_error_t gerr;
+uschar * stage;
+
+/*XXX needs extension for SHA512 */
+switch (hash)
+ {
+ case HASH_SHA1: sexp_hash = "(data(flags pkcs1)(hash sha1 %b))"; break;
+ case HASH_SHA2_256: sexp_hash = "(data(flags pkcs1)(hash sha256 %b))"; 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))
+ || (stage = US"data sexp build",
+ gerr = gcry_sexp_build (&s_hash, NULL, sexp_hash,
+ (int) data_hash->len, CS data_hash->data))
+ || (stage = US"sig mpi scan",
+ gerr = gcry_mpi_scan(&m_sig, GCRYMPI_FMT_USG, sig->data, sig->len, NULL))
+ || (stage = US"sig sexp build",
+ gerr = gcry_sexp_build (&s_sig, NULL, "(sig-val(rsa(s%m)))", m_sig))
+ || (stage = US"verify",
+ gerr = gcry_pk_verify (s_sig, s_hash, s_pkey))
+ )
+ {
+ DEBUG(D_acl) debug_printf_indent("verify: error in stage '%s'\n", stage);
+ return US gcry_strerror(gerr);
+ }
+
+if (s_sig) gcry_sexp_release (s_sig);
+if (s_hash) gcry_sexp_release (s_hash);
+if (s_pkey) gcry_sexp_release (s_pkey);
+gcry_mpi_release (m_sig);
+gcry_mpi_release (verify_ctx->n);
+gcry_mpi_release (verify_ctx->e);
+
+return NULL;
+}
+
+
+
+
+#elif defined(SIGN_OPENSSL)
+/******************************************************************************/
+
+void
+exim_dkim_init(void)
+{
+ERR_load_crypto_strings();
+}
+
+
+/* accumulate data (was gnutls-only but now needed for OpenSSL non-EC too
+because now using hash-and-sign interface) */
+gstring *
+exim_dkim_data_append(gstring * g, uschar * s)
+{
+return string_cat(g, s);
+}
+
+
+/* import private key from PEM string in memory.
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_signing_init(const uschar * privkey_pem, es_ctx * sign_ctx)
+{
+BIO * bp = BIO_new_mem_buf((void *)privkey_pem, -1);
+
+if (!(sign_ctx->key = PEM_read_bio_PrivateKey(bp, NULL, NULL, NULL)))
+ return string_sprintf("privkey PEM-block import: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+sign_ctx->keytype =
+#ifdef SIGN_HAVE_ED25519
+ EVP_PKEY_type(EVP_PKEY_id(sign_ctx->key)) == EVP_PKEY_ED25519
+ ? KEYTYPE_ED25519 : KEYTYPE_RSA;
+#else
+ KEYTYPE_RSA;
+#endif
+return NULL;
+}
+
+
+
+/* allocate mem for signature (when signing) */
+/* hash & sign data. Incremental not supported.
+
+Return: NULL for success with the signaature in the sig blob, or an error string */
+
+const uschar *
+exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, blob * data, blob * sig)
+{
+const EVP_MD * md;
+EVP_MD_CTX * ctx;
+size_t siglen;
+
+switch (hash)
+ {
+ case HASH_NULL: md = NULL; break; /* Ed25519 signing */
+ 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";
+ }
+
+#ifdef SIGN_HAVE_ED25519
+if ( (ctx = EVP_MD_CTX_new())
+ && EVP_DigestSignInit(ctx, NULL, md, NULL, sign_ctx->key) > 0
+ && EVP_DigestSign(ctx, NULL, &siglen, NULL, 0) > 0
+ && (sig->data = store_get(siglen, GET_UNTAINTED))
+
+ /* Obtain the signature (slen could change here!) */
+ && EVP_DigestSign(ctx, sig->data, &siglen, data->data, data->len) > 0
+ )
+ {
+ EVP_MD_CTX_destroy(ctx);
+ sig->len = siglen;
+ return NULL;
+ }
+#else
+/*XXX renamed to EVP_MD_CTX_new() in 1.1.0 */
+if ( (ctx = EVP_MD_CTX_create())
+ && EVP_DigestSignInit(ctx, NULL, md, NULL, sign_ctx->key) > 0
+ && EVP_DigestSignUpdate(ctx, data->data, data->len) > 0
+ && EVP_DigestSignFinal(ctx, NULL, &siglen) > 0
+ && (sig->data = store_get(siglen, GET_UNTAINTED))
+
+ /* Obtain the signature (slen could change here!) */
+ && EVP_DigestSignFinal(ctx, sig->data, &siglen) > 0
+ )
+ {
+ EVP_MD_CTX_destroy(ctx);
+ sig->len = siglen;
+ return NULL;
+ }
+#endif
+
+if (ctx) EVP_MD_CTX_destroy(ctx);
+return US ERR_error_string(ERR_get_error(), NULL);
+}
+
+
+
+/* import public key (from blob in memory)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_verify_init(blob * pubkey, keyformat fmt, ev_ctx * verify_ctx,
+ unsigned * bits)
+{
+const uschar * s = pubkey->data;
+uschar * ret = NULL;
+
+switch(fmt)
+ {
+ case KEYFMT_DER:
+ /*XXX hmm, we never free this */
+ if (!(verify_ctx->key = d2i_PUBKEY(NULL, &s, pubkey->len)))
+ ret = US ERR_error_string(ERR_get_error(), NULL);
+ break;
+#ifdef SIGN_HAVE_ED25519
+ case KEYFMT_ED25519_BARE:
+ if (!(verify_ctx->key = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL,
+ s, pubkey->len)))
+ ret = US ERR_error_string(ERR_get_error(), NULL);
+ break;
+#endif
+ default:
+ ret = US"pubkey format not handled";
+ break;
+ }
+
+if (!ret && bits) *bits = EVP_PKEY_bits(verify_ctx->key);
+return ret;
+}
+
+
+
+
+/* verify signature (of hash, except Ed25519 where of-data)
+(given pubkey & alleged sig)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, blob * data, blob * sig)
+{
+const EVP_MD * md;
+
+switch (hash)
+ {
+ case HASH_NULL: md = NULL; break;
+ 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";
+ }
+
+#ifdef SIGN_HAVE_ED25519
+if (!md)
+ {
+ EVP_MD_CTX * ctx;
+
+ if ((ctx = EVP_MD_CTX_new()))
+ {
+ if ( EVP_DigestVerifyInit(ctx, NULL, md, NULL, verify_ctx->key) > 0
+ && EVP_DigestVerify(ctx, sig->data, sig->len, data->data, data->len) > 0
+ )
+ { EVP_MD_CTX_free(ctx); return NULL; }
+ EVP_MD_CTX_free(ctx);
+ }
+ }
+else
+#endif
+ {
+ EVP_PKEY_CTX * ctx;
+
+ if ((ctx = EVP_PKEY_CTX_new(verify_ctx->key, NULL)))
+ {
+ if ( 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->data, data->len) == 1
+ )
+ { EVP_PKEY_CTX_free(ctx); return NULL; }
+ EVP_PKEY_CTX_free(ctx);
+
+ DEBUG(D_tls)
+ if (Ustrcmp(ERR_reason_error_string(ERR_peek_error()), "wrong signature length") == 0)
+ debug_printf("sig len (from msg hdr): %d, expected (from dns pubkey) %d\n",
+ (int) sig->len, EVP_PKEY_size(verify_ctx->key));
+ }
+ }
+
+return US ERR_error_string(ERR_get_error(), NULL);
+}
+
+
+
+#endif
+/******************************************************************************/
+
+#endif /*DISABLE_DKIM*/
+#endif /*MACRO_PREDEF*/
+/* End of File */
diff --git a/src/pdkim/signing.h b/src/pdkim/signing.h
new file mode 100644
index 0000000..ed6f397
--- /dev/null
+++ b/src/pdkim/signing.h
@@ -0,0 +1,97 @@
+/*
+ * PDKIM - a RFC4871 (DKIM) implementation
+ *
+ * Copyright (C) 1995 - 2020 Exim maintainers
+ *
+ * RSA signing/verification interface
+ */
+
+#include "../exim.h"
+
+#ifndef DISABLE_DKIM /* entire file */
+
+#include "crypt_ver.h"
+
+#ifdef SIGN_OPENSSL
+# include <openssl/rsa.h>
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+#elif defined(SIGN_GNUTLS)
+# include <gnutls/gnutls.h>
+# include <gnutls/x509.h>
+# include <gnutls/abstract.h>
+#elif defined(SIGN_GCRYPT)
+# include <gcrypt.h>
+# include <libtasn1.h>
+#endif
+
+#include "../blob.h"
+
+typedef enum {
+ KEYTYPE_RSA,
+ KEYTYPE_ED25519
+} keytype;
+
+typedef enum {
+ KEYFMT_DER, /* an asn.1 structure */
+ KEYFMT_ED25519_BARE /* just the key */
+} keyformat;
+
+
+#ifdef SIGN_OPENSSL
+
+typedef struct {
+ keytype keytype;
+ EVP_PKEY * key;
+} es_ctx;
+
+typedef struct {
+ keytype keytype;
+ EVP_PKEY * key;
+} ev_ctx;
+
+#elif defined(SIGN_GNUTLS)
+
+typedef struct {
+ keytype keytype;
+ gnutls_privkey_t key;
+} es_ctx;
+
+typedef struct {
+ keytype keytype;
+ gnutls_pubkey_t key;
+} ev_ctx;
+
+#elif defined(SIGN_GCRYPT)
+
+typedef struct {
+ keytype keytype;
+ gcry_mpi_t n;
+ gcry_mpi_t e;
+ gcry_mpi_t d;
+ gcry_mpi_t p;
+ gcry_mpi_t q;
+ gcry_mpi_t dp;
+ gcry_mpi_t dq;
+ gcry_mpi_t qp;
+} es_ctx;
+
+typedef struct {
+ keytype keytype;
+ gcry_mpi_t n;
+ gcry_mpi_t e;
+} ev_ctx;
+
+#endif
+
+
+extern void exim_dkim_init(void);
+extern gstring * exim_dkim_data_append(gstring *, uschar *);
+
+extern const uschar * exim_dkim_signing_init(const uschar *, es_ctx *);
+extern const uschar * exim_dkim_sign(es_ctx *, hashmethod, blob *, blob *);
+extern const uschar * exim_dkim_verify_init(blob *, keyformat, ev_ctx *, unsigned *);
+extern const uschar * exim_dkim_verify(ev_ctx *, hashmethod, blob *, blob *);
+
+#endif /*DISABLE_DKIM*/
+/* End of File */