DKIM: rename internal signing api
[exim.git] / src / src / pdkim / pdkim.c
index 0ae075f712c4f3c1ad4a4f13b9e465a8d6ad9a86..441f96cb55b2217cac5c4b54fa8aedfad65e6d23 100644 (file)
@@ -42,7 +42,7 @@
 #endif
 
 #include "pdkim.h"
-#include "rsa.h"
+#include "signing.h"
 
 #define PDKIM_SIGNATURE_VERSION     "1"
 #define PDKIM_PUB_RECORD_VERSION    US "DKIM1"
@@ -83,11 +83,13 @@ const uschar * pdkim_canons[] = {
   US"relaxed",
   NULL
 };
+/*XXX currently unused */
 const uschar * pdkim_hashes[] = {
   US"sha256",
   US"sha1",
   NULL
 };
+/*XXX currently unused */
 const uschar * pdkim_keytypes[] = {
   US"rsa",
   NULL
@@ -132,6 +134,7 @@ 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";
@@ -142,20 +145,20 @@ switch(ext_status)
   }
 }
 
-const char *
+const uschar *
 pdkim_errstr(int status)
 {
 switch(status)
   {
-  case PDKIM_OK:               return "OK";
-  case PDKIM_FAIL:             return "FAIL";
-  case PDKIM_ERR_RSA_PRIVKEY:  return "RSA_PRIVKEY";
-  case PDKIM_ERR_RSA_SIGNING:  return "RSA SIGNING";
-  case PDKIM_ERR_LONG_LINE:    return "RSA_LONG_LINE";
-  case PDKIM_ERR_BUFFER_TOO_SMALL:     return "BUFFER_TOO_SMALL";
-  case PDKIM_SIGN_PRIVKEY_WRAP:        return "PRIVKEY_WRAP";
-  case PDKIM_SIGN_PRIVKEY_B64D:        return "PRIVKEY_B64D";
-  default: return "(unknown)";
+  case PDKIM_OK:               return US"OK";
+  case PDKIM_FAIL:             return US"FAIL";
+  case PDKIM_ERR_RSA_PRIVKEY:  return US"RSA_PRIVKEY";
+  case PDKIM_ERR_RSA_SIGNING:  return US"RSA SIGNING";
+  case PDKIM_ERR_LONG_LINE:    return US"RSA_LONG_LINE";
+  case PDKIM_ERR_BUFFER_TOO_SMALL:     return US"BUFFER_TOO_SMALL";
+  case PDKIM_SIGN_PRIVKEY_WRAP:        return US"PRIVKEY_WRAP";
+  case PDKIM_SIGN_PRIVKEY_B64D:        return US"PRIVKEY_B64D";
+  default: return US"(unknown)";
   }
 }
 
@@ -288,7 +291,7 @@ found:
 /* Performs "relaxed" canonicalization of a header. */
 
 static uschar *
-pdkim_relax_header(const uschar * header, int crlf)
+pdkim_relax_header(const uschar * header, BOOL append_crlf)
 {
 BOOL past_field_name = FALSE;
 BOOL seen_wsp = FALSE;
@@ -299,8 +302,8 @@ uschar * q = relaxed;
 for (p = header; *p; p++)
   {
   uschar c = *p;
-  /* Ignore CR & LF */
-  if (c == '\r' || c == '\n')
+
+  if (c == '\r' || c == '\n')  /* Ignore CR & LF */
     continue;
   if (c == '\t' || c == ' ')
     {
@@ -312,8 +315,8 @@ for (p = header; *p; p++)
   else
     if (!past_field_name && c == ':')
       {
-      if (seen_wsp) q--;       /* This removes WSP before the colon */
-      seen_wsp = TRUE;         /* This removes WSP after the colon */
+      if (seen_wsp) q--;       /* This removes WSP immediately before the colon */
+      seen_wsp = TRUE;         /* This removes WSP immediately after the colon */
       past_field_name = TRUE;
       }
     else
@@ -326,7 +329,7 @@ for (p = header; *p; p++)
 
 if (q > relaxed && q[-1] == ' ') q--; /* Squash eventual trailing SP */
 
-if (crlf) { *q++ = '\r'; *q++ = '\n'; }
+if (append_crlf) { *q++ = '\r'; *q++ = '\n'; }
 *q = '\0';
 return relaxed;
 }
@@ -335,10 +338,10 @@ return relaxed;
 /* -------------------------------------------------------------------------- */
 #define PDKIM_QP_ERROR_DECODE -1
 
-static uschar *
-pdkim_decode_qp_char(uschar *qp_p, int *c)
+static const uschar *
+pdkim_decode_qp_char(const uschar *qp_p, int *c)
 {
-uschar *initial_pos = qp_p;
+const uschar *initial_pos = qp_p;
 
 /* Advance one char */
 qp_p++;
@@ -361,11 +364,11 @@ return initial_pos;
 /* -------------------------------------------------------------------------- */
 
 static uschar *
-pdkim_decode_qp(uschar * str)
+pdkim_decode_qp(const uschar * str)
 {
 int nchar = 0;
 uschar * q;
-uschar * p = str;
+const uschar * p = str;
 uschar * n = store_get(Ustrlen(str)+1);
 
 *n = '\0';
@@ -393,7 +396,7 @@ return n;
 /* -------------------------------------------------------------------------- */
 
 static void
-pdkim_decode_base64(uschar *str, blob * b)
+pdkim_decode_base64(const uschar * str, blob * b)
 {
 int dlen;
 dlen = b64decode(str, &b->data);
@@ -416,7 +419,7 @@ return b64encode(b->data, b->len);
 static pdkim_signature *
 pdkim_parse_sig_header(pdkim_ctx *ctx, uschar * raw_hdr)
 {
-pdkim_signature *sig ;
+pdkim_signature * sig;
 uschar *p, *q;
 uschar * cur_tag = NULL; int ts = 0, tl = 0;
 uschar * cur_val = NULL; int vs = 0, vl = 0;
@@ -504,6 +507,7 @@ for (p = raw_hdr; ; p++)
              Ustrcmp(cur_val, PDKIM_SIGNATURE_VERSION) == 0 ? 1 : -1;
            break;
          case 'a':
+/*XXX this searches a list of combined (algo + hash-method)s */
            for (i = 0; pdkim_algos[i]; i++)
              if (Ustrcmp(cur_val, pdkim_algos[i]) == 0)
                {
@@ -582,8 +586,13 @@ DEBUG(D_acl)
          "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
   }
 
-exim_sha_init(&sig->body_hash_ctx,
-              sig->algo == PDKIM_ALGO_RSA_SHA1 ? HASH_SHA1 : HASH_SHA256);
+/*XXX hash method: extend for sha512 */
+if (!exim_sha_init(&sig->body_hash_ctx,
+              sig->algo == PDKIM_ALGO_RSA_SHA1 ? HASH_SHA1 : HASH_SHA256))
+  {
+  DEBUG(D_acl) debug_printf("PDKIM: hash init internal error\n");
+  return NULL;
+  }
 return sig;
 }
 
@@ -593,103 +602,58 @@ return sig;
 static pdkim_pubkey *
 pdkim_parse_pubkey_record(pdkim_ctx *ctx, const uschar *raw_record)
 {
-pdkim_pubkey *pub;
-const uschar *p;
-uschar * cur_tag = NULL; int ts = 0, tl = 0;
-uschar * cur_val = NULL; int vs = 0, vl = 0;
-int where = PDKIM_HDR_LIMBO;
+const uschar * ele;
+int sep = ';';
+pdkim_pubkey * pub;
 
 pub = store_get(sizeof(pdkim_pubkey));
 memset(pub, 0, sizeof(pdkim_pubkey));
 
-for (p = raw_record; ; p++)
+while ((ele = string_nextinlist(&raw_record, &sep, NULL, 0)))
+  {
+  const uschar * val;
+
+  if ((val = Ustrchr(ele, '=')))
     {
-    uschar c = *p;
+    int taglen = val++ - ele;
 
-    /* Ignore FWS */
-    if (c != '\r' && c != '\n') switch (where)
+    DEBUG(D_acl) debug_printf(" %.*s=%s\n", taglen, ele, val);
+    switch (ele[0])
       {
-      case PDKIM_HDR_LIMBO:            /* In limbo, just wait for a tag-char to appear */
-       if (!(c >= 'a' && c <= 'z'))
-         break;
-       where = PDKIM_HDR_TAG;
-       /*FALLTHROUGH*/
-
-      case PDKIM_HDR_TAG:
-       if (c >= 'a' && c <= 'z')
-         cur_tag = string_catn(cur_tag, &ts, &tl, p, 1);
-
-       if (c == '=')
-         {
-         cur_tag[tl] = '\0';
-         where = PDKIM_HDR_VALUE;
-         }
-       break;
-
-      case PDKIM_HDR_VALUE:
-       if (c == ';' || c == '\0')
-         {
-         if (tl && vl)
-           {
-           cur_val[vl] = '\0';
-           pdkim_strtrim(cur_val);
-           DEBUG(D_acl) debug_printf(" %s=%s\n", cur_tag, cur_val);
-
-           switch (cur_tag[0])
-             {
-             case 'v':
-               pub->version = string_copy(cur_val); break;
-             case 'h':
-             case 'k':
-/* This field appears to never be used. Also, unclear why
-a 'k' (key-type_ would go in this field name.  There is a field
-"keytype", also never used.
-               pub->hashes = string_copy(cur_val);
-*/
+      case 'v': pub->version = val;                    break;
+      case 'h': pub->hashes = val;                     break;
+      case 'k': break;
+      case 'g': pub->granularity = val;                        break;
+      case 'n': pub->notes = pdkim_decode_qp(val);     break;
+      case 'p': pdkim_decode_base64(val, &pub->key);   break;
+      case 's': pub->srvtype = val;                    break;
+      case 't': if (Ustrchr(val, 'y')) pub->testing = 1;
+               if (Ustrchr(val, 's')) pub->no_subdomaining = 1;
                break;
-             case 'g':
-               pub->granularity = string_copy(cur_val); break;
-             case 'n':
-               pub->notes = pdkim_decode_qp(cur_val); break;
-             case 'p':
-               pdkim_decode_base64(US cur_val, &pub->key); break;
-             case 's':
-               pub->srvtype = string_copy(cur_val); break;
-             case 't':
-               if (Ustrchr(cur_val, 'y') != NULL) pub->testing = 1;
-               if (Ustrchr(cur_val, 's') != NULL) pub->no_subdomaining = 1;
-               break;
-             default:
-               DEBUG(D_acl) debug_printf(" Unknown tag encountered\n");
-               break;
-             }
-           }
-         tl = 0;
-         vl = 0;
-         where = PDKIM_HDR_LIMBO;
-         }
-       else
-         cur_val = string_catn(cur_val, &vs, &vl, p, 1);
-       break;
+      default:  DEBUG(D_acl) debug_printf(" Unknown tag encountered\n"); break;
       }
-
-    if (c == '\0') break;
     }
+  }
 
 /* Set fallback defaults */
 if (!pub->version    ) pub->version     = string_copy(PDKIM_PUB_RECORD_VERSION);
-else if (Ustrcmp(pub->version, PDKIM_PUB_RECORD_VERSION) != 0) return NULL;
+else if (Ustrcmp(pub->version, PDKIM_PUB_RECORD_VERSION) != 0)
+  {
+  DEBUG(D_acl) debug_printf(" Bad v= field\n");
+  return NULL;
+  }
 
-if (!pub->granularity) pub->granularity = string_copy(US"*");
+if (!pub->granularity) pub->granularity = US"*";
 /*
-if (!pub->keytype    ) pub->keytype     = string_copy(US"rsa");
+if (!pub->keytype    ) pub->keytype     = US"rsa";
 */
-if (!pub->srvtype    ) pub->srvtype     = string_copy(US"*");
+if (!pub->srvtype    ) pub->srvtype     = US"*";
 
 /* p= is required */
 if (pub->key.data)
   return pub;
 
+DEBUG(D_acl) debug_printf(" Missing p= field\n");
 return NULL;
 }
 
@@ -927,44 +891,41 @@ return PDKIM_OK;
 #define DKIM_SIGNATURE_HEADERNAME "DKIM-Signature:"
 
 static int
-pdkim_header_complete(pdkim_ctx *ctx)
+pdkim_header_complete(pdkim_ctx * ctx)
 {
+pdkim_signature * sig, * last_sig;
+
 /* Special case: The last header can have an extra \r appended */
 if ( (ctx->cur_header_len > 1) &&
      (ctx->cur_header[(ctx->cur_header_len)-1] == '\r') )
   --ctx->cur_header_len;
 ctx->cur_header[ctx->cur_header_len] = '\0';
 
-ctx->num_headers++;
-if (ctx->num_headers > PDKIM_MAX_HEADERS) goto BAIL;
+if (++ctx->num_headers > PDKIM_MAX_HEADERS) goto BAIL;
 
 /* SIGNING -------------------------------------------------------------- */
 if (ctx->flags & PDKIM_MODE_SIGN)
-  {
-  pdkim_signature *sig;
-
   for (sig = ctx->sig; sig; sig = sig->next)                   /* Traverse all signatures */
 
     /* Add header to the signed headers list (in reverse order) */
     sig->headers = pdkim_prepend_stringlist(sig->headers,
                                  ctx->cur_header);
-  }
 
 /* VERIFICATION ----------------------------------------------------------- */
 /* DKIM-Signature: headers are added to the verification list */
 else
   {
+#ifdef notdef
   DEBUG(D_acl)
     {
     debug_printf("PDKIM >> raw hdr: ");
-    pdkim_quoteprint(CUS ctx->cur_header, Ustrlen(ctx->cur_header));
+    pdkim_quoteprint(CUS ctx->cur_header, ctx->cur_header_len);
     }
+#endif
   if (strncasecmp(CCS ctx->cur_header,
                  DKIM_SIGNATURE_HEADERNAME,
                  Ustrlen(DKIM_SIGNATURE_HEADERNAME)) == 0)
     {
-    pdkim_signature * new_sig, * last_sig;
-
     /* Create and chain new signature block.  We could error-check for all
     required tags here, but prefer to create the internal sig and expicitly
     fail verification of it later. */
@@ -972,14 +933,14 @@ else
     DEBUG(D_acl) debug_printf(
        "PDKIM >> Found sig, trying to parse >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
 
-    new_sig = pdkim_parse_sig_header(ctx, ctx->cur_header);
+    sig = pdkim_parse_sig_header(ctx, ctx->cur_header);
 
     if (!(last_sig = ctx->sig))
-      ctx->sig = new_sig;
+      ctx->sig = sig;
     else
       {
       while (last_sig->next) last_sig = last_sig->next;
-      last_sig->next = new_sig;
+      last_sig->next = sig;
       }
     }
 
@@ -988,8 +949,7 @@ else
   }
 
 BAIL:
-*ctx->cur_header = '\0';
-ctx->cur_header_len = 0;       /* leave buffer for reuse */
+ctx->cur_header[ctx->cur_header_len = 0] = '\0';       /* leave buffer for reuse */
 return PDKIM_OK;
 }
 
@@ -999,7 +959,7 @@ return PDKIM_OK;
 #define HEADER_BUFFER_FRAG_SIZE 256
 
 DLLEXPORT int
-pdkim_feed(pdkim_ctx *ctx, char *data, int len)
+pdkim_feed(pdkim_ctx * ctx, uschar * data, int len)
 {
 int p, rc;
 
@@ -1050,13 +1010,13 @@ else for (p = 0; p<len; p++)
        if ((rc = pdkim_header_complete(ctx)) != PDKIM_OK)
          return rc;
 
-       ctx->flags = ctx->flags & ~(PDKIM_SEEN_LF|PDKIM_SEEN_CR) | PDKIM_PAST_HDRS;
+       ctx->flags = (ctx->flags & ~(PDKIM_SEEN_LF|PDKIM_SEEN_CR)) | PDKIM_PAST_HDRS;
        DEBUG(D_acl) debug_printf(
            "PDKIM >> Body data for hash, canonicalized >>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
        continue;
        }
       else
-       ctx->flags = ctx->flags & ~PDKIM_SEEN_CR | PDKIM_SEEN_LF;
+       ctx->flags = (ctx->flags & ~PDKIM_SEEN_CR) | PDKIM_SEEN_LF;
       }
     else if (ctx->flags & PDKIM_SEEN_LF)
       {
@@ -1229,6 +1189,7 @@ col = hdr_len;
 
 /* Required and static bits */
 hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"a=",
+/*XXX this is a combo of algo and hash-method */
                    pdkim_algos[sig->algo]);
 hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"q=",
                    pdkim_querymethods[sig->querymethod]);
@@ -1297,11 +1258,23 @@ if (sig->bodylength >= 0)
   }
 
 /* Preliminary or final version? */
-base64_b = final ? pdkim_encode_base64(&sig->sighash) : US"";
-hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"b=", base64_b);
+if (final)
+  {
+  base64_b = pdkim_encode_base64(&sig->sighash);
+  hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"b=", base64_b);
 
-/* add trailing semicolon: I'm not sure if this is actually needed */
-hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, NULL, US";", US"");
+  /* add trailing semicolon: I'm not sure if this is actually needed */
+  hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, NULL, US";", US"");
+  }
+else
+  {
+  /* To satisfy the rule "all surrounding whitespace [...] deleted"
+  ( RFC 6376 section 3.7 ) we ensure there is no whitespace here.  Otherwise
+  the headcat routine could insert a linebreak which the relaxer would reduce
+  to a single space preceding the terminating semicolon, resulting in an
+  incorrect header-hash. */
+  hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"b=;", US"");
+  }
 
 hdr[hdr_len] = '\0';
 return hdr;
@@ -1311,11 +1284,11 @@ return hdr;
 /* -------------------------------------------------------------------------- */
 
 static pdkim_pubkey *
-pdkim_key_from_dns(pdkim_ctx * ctx, pdkim_signature * sig, ev_ctx * vctx)
+pdkim_key_from_dns(pdkim_ctx * ctx, pdkim_signature * sig, ev_ctx * vctx,
+  const uschar ** errstr)
 {
 uschar * dns_txt_name, * dns_txt_reply;
 pdkim_pubkey * p;
-const uschar * errstr;
 
 /* Fetch public key for signing domain, from DNS */
 
@@ -1364,9 +1337,9 @@ DEBUG(D_acl) debug_printf(
       "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
 
 /* Import public key */
-if ((errstr = exim_rsa_verify_init(&p->key, vctx)))
+if ((*errstr = exim_dkim_verify_init(&p->key, vctx)))
   {
-  DEBUG(D_acl) debug_printf("verify_init: %s\n", errstr);
+  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;
@@ -1379,7 +1352,8 @@ return p;
 /* -------------------------------------------------------------------------- */
 
 DLLEXPORT int
-pdkim_feed_finish(pdkim_ctx *ctx, pdkim_signature **return_signatures)
+pdkim_feed_finish(pdkim_ctx * ctx, pdkim_signature ** return_signatures,
+  const uschar ** err)
 {
 pdkim_signature *sig = ctx->sig;
 
@@ -1401,6 +1375,7 @@ pdkim_finish_bodyhash(ctx);
 
 while (sig)
   {
+/*XXX bool probably not enough */
   BOOL is_sha1 = sig->algo == PDKIM_ALGO_RSA_SHA1;
   hctx hhash_ctx;
   uschar * sig_hdr = US"";
@@ -1411,10 +1386,14 @@ while (sig)
   hdata.data = NULL;
   hdata.len = 0;
 
-  exim_sha_init(&hhash_ctx, is_sha1 ? HASH_SHA1 : HASH_SHA256);
+  if (!exim_sha_init(&hhash_ctx, is_sha1 ? HASH_SHA1 : HASH_SHA256))
+    {
+    DEBUG(D_acl) debug_printf("PDKIM: hask setup internal error\n");
+    break;
+    }
 
   DEBUG(D_acl) debug_printf(
-      "PDKIM >> Header data for hash, canonicalized, in sequence >>>>>>>>>>>>>>\n");
+      "PDKIM >> Header data for hash, canonicalized, in sequence >>>>>>>>>>>>\n");
 
   /* SIGNING ---------------------------------------------------------------- */
   /* When signing, walk through our header list and add them to the hash. As we
@@ -1442,14 +1421,14 @@ while (sig)
                        p->value, (q - US p->value) + (p->next ? 1 : 0));
 
        rh = sig->canon_headers == PDKIM_CANON_RELAXED
-         ? pdkim_relax_header(p->value, 1) /* cook header for relaxed canon */
+         ? pdkim_relax_header(p->value, TRUE) /* cook header for relaxed canon */
          : string_copy(CUS p->value);      /* just copy it for simple canon */
 
        /* Feed header to the hash algorithm */
        exim_sha_update(&hhash_ctx, CUS rh, Ustrlen(rh));
 
        /* Remember headers block for signing (when the library cannot do incremental)  */
-       (void) exim_rsa_data_append(&hdata, &hdata_alloc, rh);
+       (void) exim_dkim_data_append(&hdata, &hdata_alloc, rh);
 
        DEBUG(D_acl) pdkim_quoteprint(rh, Ustrlen(rh));
        }
@@ -1503,7 +1482,7 @@ while (sig)
            /* cook header for relaxed canon, or just copy it for simple  */
 
            uschar * rh = sig->canon_headers == PDKIM_CANON_RELAXED
-             ? pdkim_relax_header(hdrs->value, 1)
+             ? pdkim_relax_header(hdrs->value, TRUE)
              : string_copy(CUS hdrs->value);
 
            /* Feed header to the hash algorithm */
@@ -1527,7 +1506,7 @@ while (sig)
 
   /* Relax header if necessary */
   if (sig->canon_headers == PDKIM_CANON_RELAXED)
-    sig_hdr = pdkim_relax_header(sig_hdr, 0);
+    sig_hdr = pdkim_relax_header(sig_hdr, FALSE);
 
   DEBUG(D_acl)
     {
@@ -1549,19 +1528,20 @@ while (sig)
     }
 
   /* Remember headers block for signing (when the library cannot do incremental)  */
+/*XXX is this assuing algo == RSA? */
   if (ctx->flags & PDKIM_MODE_SIGN)
-    (void) exim_rsa_data_append(&hdata, &hdata_alloc, US sig_hdr);
+    (void) exim_dkim_data_append(&hdata, &hdata_alloc, US sig_hdr);
 
   /* SIGNING ---------------------------------------------------------------- */
   if (ctx->flags & PDKIM_MODE_SIGN)
     {
     es_ctx sctx;
-    const uschar * errstr;
 
     /* Import private key */
-    if ((errstr = exim_rsa_signing_init(US sig->rsa_privkey, &sctx)))
+/*XXX extend for non-RSA algos */
+    if ((*err = exim_dkim_signing_init(US sig->rsa_privkey, &sctx)))
       {
-      DEBUG(D_acl) debug_printf("signing_init: %s\n", errstr);
+      DEBUG(D_acl) debug_printf("signing_init: %s\n", *err);
       return PDKIM_ERR_RSA_PRIVKEY;
       }
 
@@ -1573,9 +1553,13 @@ while (sig)
     hdata = hhash;
 #endif
 
-    if ((errstr = exim_rsa_sign(&sctx, is_sha1, &hdata, &sig->sighash)))
+/*XXX extend for non-RSA algos */
+/*XXX oddly the dkim rfc does _not_ say what variant (sha1 or sha256) of
+RSA signing should be done.  We use the same variant as the hash-method. */
+
+    if ((*err = exim_dkim_sign(&sctx, is_sha1, &hdata, &sig->sighash)))
       {
-      DEBUG(D_acl) debug_printf("signing: %s\n", errstr);
+      DEBUG(D_acl) debug_printf("signing: %s\n", *err);
       return PDKIM_ERR_RSA_SIGNING;
       }
 
@@ -1592,8 +1576,6 @@ while (sig)
   else
     {
     ev_ctx vctx;
-    const uschar * errstr;
-    pdkim_pubkey * p;
 
     /* Make sure we have all required signature tags */
     if (!(  sig->domain        && *sig->domain
@@ -1626,13 +1608,33 @@ while (sig)
       goto NEXT_VERIFY;
       }
 
-    if (!(sig->pubkey = pdkim_key_from_dns(ctx, sig, &vctx)))
+    if (!(sig->pubkey = pdkim_key_from_dns(ctx, sig, &vctx, err)))
       goto NEXT_VERIFY;
 
+    /* If the pubkey limits to a list of specific hashes, ignore sigs that
+    do not have the hash part of the sig algorithm matching */
+
+    if (sig->pubkey->hashes)
+      {
+      const uschar * list = sig->pubkey->hashes, * ele;
+      int sep = ':';
+      while ((ele = string_nextinlist(&list, &sep, NULL, 0)))
+       if (Ustrcmp(ele, pdkim_algos[sig->algo] + 4) == 0) break;
+      if (!ele)
+       {
+       DEBUG(D_acl) debug_printf("pubkey h=%s vs sig a=%s\n",
+                               sig->pubkey->hashes, pdkim_algos[sig->algo]);
+       sig->verify_status =      PDKIM_VERIFY_FAIL;
+       sig->verify_ext_status =  PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH;
+       goto NEXT_VERIFY;
+       }
+      }
+
     /* Check the signature */
-    if ((errstr = exim_rsa_verify(&vctx, is_sha1, &hhash, &sig->sighash)))
+/*XXX needs extension for non-RSA */
+    if ((*err = exim_dkim_verify(&vctx, is_sha1, &hhash, &sig->sighash)))
       {
-      DEBUG(D_acl) debug_printf("headers verify: %s\n", errstr);
+      DEBUG(D_acl) debug_printf("headers verify: %s\n", *err);
       sig->verify_status =      PDKIM_VERIFY_FAIL;
       sig->verify_ext_status =  PDKIM_VERIFY_FAIL_MESSAGE;
       goto NEXT_VERIFY;
@@ -1688,9 +1690,13 @@ return ctx;
 
 /* -------------------------------------------------------------------------- */
 
+/*XXX ? needs extension to cover non-RSA algo?  Currently the "algo" is actually
+the combo of algo and hash-method */
+
 DLLEXPORT pdkim_ctx *
 pdkim_init_sign(char * domain, char * selector, char * rsa_privkey, int algo,
-  BOOL dot_stuffed, int(*dns_txt_callback)(char *, char *))
+  BOOL dot_stuffed, int(*dns_txt_callback)(char *, char *),
+  const uschar ** errstr)
 {
 pdkim_ctx * ctx;
 pdkim_signature * sig;
@@ -1717,17 +1723,23 @@ sig->selector = string_copy(US selector);
 sig->rsa_privkey = string_copy(US rsa_privkey);
 sig->algo = algo;
 
-exim_sha_init(&sig->body_hash_ctx,
-              algo == PDKIM_ALGO_RSA_SHA1 ? HASH_SHA1 : HASH_SHA256);
+/*XXX extend for sha512 */
+if (!exim_sha_init(&sig->body_hash_ctx,
+              algo == PDKIM_ALGO_RSA_SHA1 ? HASH_SHA1 : HASH_SHA256))
+  {
+  DEBUG(D_acl) debug_printf("PDKIM: hash setup internal error\n");
+  return NULL;
+  }
+
 DEBUG(D_acl)
   {
   pdkim_signature s = *sig;
   ev_ctx vctx;
 
-  debug_printf("PDKIM (checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
-  if (!pdkim_key_from_dns(ctx, &s, &vctx))
+  debug_printf("PDKIM (checking verify key)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
+  if (!pdkim_key_from_dns(ctx, &s, &vctx, errstr))
     debug_printf("WARNING: bad dkim key in dns\n");
-  debug_printf("PDKIM (finished checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+  debug_printf("PDKIM (finished checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
   }
 return ctx;
 }
@@ -1766,7 +1778,7 @@ return PDKIM_OK;
 void
 pdkim_init(void)
 {
-exim_rsa_init();
+exim_dkim_init();
 }