DKIM: add support for the SubjectPublicKeyInfo wrapped form of pubkey
[exim.git] / src / src / pdkim / pdkim.c
index eec1a9c1621268d94fbac86295b9547c3bea99d2..5688c78a87796ef4c9c6a1db98e73924f35b96ea 100644 (file)
@@ -2,7 +2,7 @@
  *  PDKIM - a RFC4871 (DKIM) implementation
  *
  *  Copyright (C) 2009 - 2016  Tom Kistner <tom@duncanthrax.net>
- *  Copyright (C) 2016 - 2017  Jeremy Harris <jgh@exim.org>
+ *  Copyright (C) 2016 - 2018  Jeremy Harris <jgh@exim.org>
  *
  *  http://duncanthrax.net/pdkim/
  *
@@ -71,11 +71,7 @@ const uschar * pdkim_canons[] = {
   NULL
 };
 
-typedef struct {
-  const uschar * dkim_hashname;
-  hashmethod    exim_hashmethod;
-} pdkim_hashtype;
-static const pdkim_hashtype pdkim_hashes[] = {
+const pdkim_hashtype pdkim_hashes[] = {
   { US"sha1",   HASH_SHA1 },
   { US"sha256", HASH_SHA2_256 },
   { US"sha512", HASH_SHA2_512 }
@@ -84,7 +80,7 @@ static const pdkim_hashtype pdkim_hashes[] = {
 const uschar * pdkim_keytypes[] = {
   [KEYTYPE_RSA] =      US"rsa",
 #ifdef SIGN_HAVE_ED25519
-  [KEYTYPE_ED25519] =  US"ed25519",            /* Works for 3.6.0 GnuTLS */
+  [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 */
@@ -125,6 +121,34 @@ return string_sprintf("%s-%s",
 }
 
 
+int
+pdkim_hashname_to_hashtype(const uschar * s, unsigned len)
+{
+int i;
+if (!len) len = Ustrlen(s);
+for (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)
+{
+int i;
+if (!len) len = Ustrlen(s);
+for (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)
@@ -177,7 +201,7 @@ switch(status)
 
 /* -------------------------------------------------------------------------- */
 /* Print debugging functions */
-static void
+void
 pdkim_quoteprint(const uschar *data, int len)
 {
 int i;
@@ -203,7 +227,7 @@ for (i = 0; i < len; i++)
 debug_printf("\n");
 }
 
-static void
+void
 pdkim_hexprint(const uschar *data, int len)
 {
 int i;
@@ -302,16 +326,16 @@ return PDKIM_FAIL;
 /* -------------------------------------------------------------------------- */
 /* Performs "relaxed" canonicalization of a header. */
 
-static uschar *
-pdkim_relax_header(const uschar * header, BOOL append_crlf)
+uschar *
+pdkim_relax_header_n(const uschar * header, int len, BOOL append_crlf)
 {
 BOOL past_field_name = FALSE;
 BOOL seen_wsp = FALSE;
 const uschar * p;
-uschar * relaxed = store_get(Ustrlen(header)+3);
+uschar * relaxed = store_get(len+3);
 uschar * q = relaxed;
 
-for (p = header; *p; p++)
+for (p = header; p - header < len; p++)
   {
   uschar c = *p;
 
@@ -347,6 +371,13 @@ 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
 
@@ -407,16 +438,15 @@ return n;
 
 /* -------------------------------------------------------------------------- */
 
-static void
+void
 pdkim_decode_base64(const uschar * str, blob * b)
 {
-int dlen;
-dlen = b64decode(str, &b->data);
+int dlen = b64decode(str, &b->data);
 if (dlen < 0) b->data = NULL;
 b->len = dlen;
 }
 
-static uschar *
+uschar *
 pdkim_encode_base64(blob * b)
 {
 return b64encode(b->data, b->len);
@@ -531,34 +561,23 @@ for (p = raw_hdr; ; p++)
            break;
          case 'a':                                     /* algorithm */
            {
-           uschar * s = Ustrchr(cur_val->s, '-');
-
-           for(i = 0; i < nelem(pdkim_keytypes); i++)
-             if (Ustrncmp(cur_val->s, pdkim_keytypes[i], s - cur_val->s) == 0)
-               { sig->keytype = i; break; }
-           if (sig->keytype < 0)
-             log_write(0, LOG_MAIN,
-               "DKIM: ignoring signature due to nonhandled keytype in a=%s",
-               cur_val->s);
-
-           for (++s, i = 0; i < nelem(pdkim_hashes); i++)
-             if (Ustrcmp(s, pdkim_hashes[i].dkim_hashname) == 0)
-               { sig->hashtype = i; break; }
-           if (sig->hashtype < 0)
-             log_write(0, LOG_MAIN,
-               "DKIM: ignoring signature due to nonhandled hashtype in a=%s",
-               cur_val);
-           break;
+           const uschar * list = cur_val->s;
+           int sep = '-';
+           uschar * elem;
+
+           if ((elem = string_nextinlist(&list, &sep, NULL, 0)))
+             for(i = 0; i < nelem(pdkim_keytypes); i++)
+               if (Ustrcmp(elem, pdkim_keytypes[i]) == 0)
+                 { sig->keytype = i; break; }
+           if ((elem = string_nextinlist(&list, &sep, NULL, 0)))
+             for (i = 0; i < nelem(pdkim_hashes); i++)
+               if (Ustrcmp(elem, pdkim_hashes[i].dkim_hashname) == 0)
+                 { sig->hashtype = i; break; }
            }
 
          case 'c':                                     /* canonicalization */
-           for (i = 0; pdkim_combined_canons[i].str; i++)
-             if (Ustrcmp(cur_val->s, pdkim_combined_canons[i].str) == 0)
-               {
-               sig->canon_headers = pdkim_combined_canons[i].canon_headers;
-               sig->canon_body    = pdkim_combined_canons[i].canon_body;
-               break;
-               }
+           pdkim_cstring_to_canons(cur_val->s, 0,
+                                   &sig->canon_headers, &sig->canon_body);
            break;
          case 'q':                             /* Query method (for pubkey)*/
            for (i = 0; pdkim_querymethods[i]; i++)
@@ -626,7 +645,7 @@ DEBUG(D_acl)
          "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
   }
 
-if (!pdkim_set_bodyhash(ctx, sig))
+if (!pdkim_set_sig_bodyhash(ctx, sig))
   return NULL;
 
 return sig;
@@ -635,8 +654,8 @@ return sig;
 
 /* -------------------------------------------------------------------------- */
 
-static pdkim_pubkey *
-pdkim_parse_pubkey_record(pdkim_ctx *ctx, const uschar *raw_record)
+pdkim_pubkey *
+pdkim_parse_pubkey_record(const uschar *raw_record)
 {
 const uschar * ele;
 int sep = ';';
@@ -710,7 +729,7 @@ if (b->canon_method == PDKIM_CANON_RELAXED)
   if (!relaxed_data)
     {
     BOOL seen_wsp = FALSE;
-    const uschar * p;
+    const uschar * p, * r;
     int q = 0;
 
     /* We want to be able to free this else we allocate
@@ -721,7 +740,7 @@ if (b->canon_method == PDKIM_CANON_RELAXED)
     relaxed_data = store_malloc(sizeof(blob) + orig_data->len+1);
     relaxed_data->data = US (relaxed_data+1);
 
-    for (p = orig_data->data; *p; p++)
+    for (p = orig_data->data, r = p + orig_data->len; p < r; p++)
       {
       char c = *p;
       if (c == '\r')
@@ -772,7 +791,11 @@ pdkim_bodyhash * b;
 pdkim_signature * sig;
 
 for (b = ctx->bodyhash; b; b = b->next)                /* Finish hashes */
+  {
+  DEBUG(D_acl) debug_printf("PDKIM: finish bodyhash %d/%d/%ld len %ld\n",
+           b->hashtype, b->canon_method, b->bodylength, b->signed_body_bytes);
   exim_sha_finish(&b->body_hash_ctx, &b->bh);
+  }
 
 /* Traverse all signatures */
 for (sig = ctx->sig; sig; sig = sig->next)
@@ -781,10 +804,10 @@ for (sig = ctx->sig; sig; sig = sig->next)
 
   DEBUG(D_acl)
     {
-    debug_printf("PDKIM [%s] Body bytes hashed: %lu\n"
+    debug_printf("PDKIM [%s] Body bytes (%s) hashed: %lu\n"
                 "PDKIM [%s] Body %s computed: ",
-               sig->domain, b->signed_body_bytes,
-               sig->domain, pdkim_hashes[sig->hashtype].dkim_hashname);
+       sig->domain, pdkim_canons[b->canon_method], b->signed_body_bytes,
+       sig->domain, pdkim_hashes[b->hashtype].dkim_hashname);
     pdkim_hexprint(CUS b->bh.data, b->bh.len);
     }
 
@@ -804,7 +827,7 @@ for (sig = ctx->sig; sig; sig = sig->next)
     if (  sig->bodyhash.data
        && memcmp(b->bh.data, sig->bodyhash.data, b->bh.len) == 0)
       {
-      DEBUG(D_acl) debug_printf("PDKIM [%s] Body hash verified OK\n", sig->domain);
+      DEBUG(D_acl) debug_printf("PDKIM [%s] Body hash compared OK\n", sig->domain);
       }
     else
       {
@@ -848,6 +871,7 @@ 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)
@@ -939,6 +963,11 @@ if ( (ctx->cur_header->ptr > 1) &&
   --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 -------------------------------------------------------------- */
@@ -1307,6 +1336,28 @@ 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("PDKIM: unexpected pubkey len %lu\n", 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)
@@ -1318,7 +1369,7 @@ pdkim_pubkey * p;
 
 dns_txt_name = string_sprintf("%s._domainkey.%s.", sig->selector, sig->domain);
 
-if (  !(dns_txt_reply = ctx->dns_txt_callback(CS dns_txt_name))
+if (  !(dns_txt_reply = ctx->dns_txt_callback(dns_txt_name))
    || dns_txt_reply[0] == '\0'
    )
   {
@@ -1337,7 +1388,7 @@ DEBUG(D_acl)
   pdkim_quoteprint(CUS dns_txt_reply, Ustrlen(dns_txt_reply));
   }
 
-if (  !(p = pdkim_parse_pubkey_record(ctx, CUS dns_txt_reply))
+if (  !(p = pdkim_parse_pubkey_record(CUS dns_txt_reply))
    || (Ustrcmp(p->srvtype, "*") != 0 && Ustrcmp(p->srvtype, "email") != 0)
    )
   {
@@ -1361,6 +1412,27 @@ DEBUG(D_acl) debug_printf(
 
 /* 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)
+  {
+  int i;
+  for(i = 0; i < nelem(pdkim_keytypes); i++)
+    if (Ustrcmp(p->keytype, pdkim_keytypes[i]) == 0)
+      { sig->keytype = i; goto k_ok; }
+  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;
+  }
+k_ok:
+
+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)))
@@ -1385,7 +1457,6 @@ pdkim_feed_finish(pdkim_ctx * ctx, pdkim_signature ** return_signatures,
 pdkim_bodyhash * b;
 pdkim_signature * sig;
 BOOL verify_pass = FALSE;
-es_ctx sctx;
 
 /* 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
@@ -1406,15 +1477,18 @@ else
   DEBUG(D_acl) debug_printf(
       "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\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);
+
 if (!ctx->sig)
   {
   DEBUG(D_acl) debug_printf("PDKIM: no signatures\n");
+  *return_signatures = NULL;
   return PDKIM_OK;
   }
 
-/* Build (and/or evaluate) body hash */
-pdkim_finish_bodyhash(ctx);
-
 for (sig = ctx->sig; sig; sig = sig->next)
   {
   hctx hhash_ctx;
@@ -1423,6 +1497,14 @@ for (sig = ctx->sig; sig; sig = sig->next)
   gstring * hdata = NULL;
   es_ctx sctx;
 
+  if (  !(ctx->flags & PDKIM_MODE_SIGN)
+     && sig->verify_status == PDKIM_VERIFY_FAIL)
+    {
+    DEBUG(D_acl)
+       debug_printf("PDKIM: [%s] abandoning this signature\n", sig->domain);
+    continue;
+    }
+
   /*XXX The hash of the headers is needed for GCrypt (for which we can do RSA
   suging only, as it happens) and for either GnuTLS and OpenSSL when we are
   signing with EC (specifically, Ed25519).  The former is because the GCrypt
@@ -1458,7 +1540,8 @@ for (sig = ctx->sig; sig; sig = sig->next)
        sig->sign_headers);
 
   DEBUG(D_acl) debug_printf(
-      "PDKIM >> Header data for hash, canonicalized, in sequence >>>>>>>>>>>>\n");
+      "PDKIM >> Header data for hash, canonicalized (%-7s), in sequence >>\n",
+       pdkim_canons[sig->canon_headers]);
 
 
   /* SIGNING ---------------------------------------------------------------- */
@@ -1479,7 +1562,7 @@ for (sig = ctx->sig; sig; sig = sig->next)
     the signature header  */
 
 /*XXX extend for non-RSA algos */
-    if ((*err = exim_dkim_signing_init(US sig->privkey, &sctx)))
+    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;
@@ -1595,8 +1678,8 @@ for (sig = ctx->sig; sig; sig = sig->next)
 
   DEBUG(D_acl)
     {
-    debug_printf(
-           "PDKIM >> Signed DKIM-Signature header, canonicalized >>>>>>>>>>>>>>>>>\n");
+    debug_printf("PDKIM >> Signed DKIM-Signature header, canonicalized (%-7s) >>>>>>>\n",
+           pdkim_canons[sig->canon_headers]);
     pdkim_quoteprint(CUS sig_hdr, Ustrlen(sig_hdr));
     debug_printf(
            "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
@@ -1622,7 +1705,12 @@ for (sig = ctx->sig; sig; sig = sig->next)
   if (ctx->flags & PDKIM_MODE_SIGN)
     {
     hashmethod hm = sig->keytype == KEYTYPE_ED25519
-      ? HASH_SHA2_512 : pdkim_hashes[sig->hashtype].exim_hashmethod;
+#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
@@ -1635,8 +1723,6 @@ for (sig = ctx->sig; sig; sig = sig->next)
       hhash.len = hdata->ptr;
       }
 
-/*XXX extend for non-RSA algos */
-/*- done for GnuTLS */
     if ((*err = exim_dkim_sign(&sctx, hm, &hhash, &sig->sighash)))
       {
       log_write(0, LOG_MAIN|LOG_PANIC, "signing: %s", *err);
@@ -1656,6 +1742,7 @@ for (sig = ctx->sig; sig; sig = sig->next)
   else
     {
     ev_ctx vctx;
+    hashmethod hm;
 
     /* Make sure we have all required signature tags */
     if (!(  sig->domain        && *sig->domain
@@ -1672,11 +1759,19 @@ for (sig = ctx->sig; sig; sig = sig->next)
       sig->verify_ext_status = PDKIM_VERIFY_INVALID_SIGNATURE_ERROR;
 
       DEBUG(D_acl) debug_printf(
-         " Error in DKIM-Signature header: tags missing or invalid\n"
-         "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+         " Error in DKIM-Signature header: tags missing or invalid (%s)\n"
+         "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\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)
       {
@@ -1724,12 +1819,17 @@ for (sig = ctx->sig; sig; sig = sig->next)
        }
       }
 
+    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 */
-/*XXX extend for non-RSA algos */
-/*- done for GnuTLS */
-    if ((*err = exim_dkim_verify(&vctx,
-                               pdkim_hashes[sig->hashtype].exim_hashmethod,
-                               &hhash, &sig->sighash)))
+
+    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;
@@ -1773,7 +1873,7 @@ return ctx->flags & PDKIM_MODE_SIGN  ||  verify_pass
 /* -------------------------------------------------------------------------- */
 
 DLLEXPORT pdkim_ctx *
-pdkim_init_verify(uschar * (*dns_txt_callback)(char *), BOOL dot_stuffing)
+pdkim_init_verify(uschar * (*dns_txt_callback)(uschar *), BOOL dot_stuffing)
 {
 pdkim_ctx * ctx;
 
@@ -1867,29 +1967,35 @@ return;
 
 
 /* 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.
+given needs.  Use an existing one if possible, or create a new one.
 
-Return: hashblob pointer, or NULL on error (only used as a boolean).
+Return: hashblob pointer, or NULL on error
 */
 pdkim_bodyhash *
-pdkim_set_bodyhash(pdkim_ctx * ctx, pdkim_signature * sig)
+pdkim_set_bodyhash(pdkim_ctx * ctx, int hashtype, int canon_method,
+       long bodylength)
 {
 pdkim_bodyhash * b;
 
 for (b = ctx->bodyhash; b; b = b->next)
-  if (  sig->hashtype == b->hashtype
-     && sig->canon_body == b->canon_method
-     && sig->bodylength == b->bodylength)
-    goto old;
+  if (  hashtype == b->hashtype
+     && canon_method == b->canon_method
+     && bodylength == b->bodylength)
+    {
+    DEBUG(D_receive) debug_printf("PDKIM: using existing bodyhash %d/%d/%ld\n",
+                                 hashtype, canon_method, bodylength);
+    return b;
+    }
 
+DEBUG(D_receive) debug_printf("PDKIM: new bodyhash %d/%d/%ld\n",
+                             hashtype, canon_method, bodylength);
 b = store_get(sizeof(pdkim_bodyhash));
 b->next = ctx->bodyhash;
-b->hashtype = sig->hashtype;
-b->canon_method = sig->canon_body;
-b->bodylength = sig->bodylength;
+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[sig->hashtype].exim_hashmethod))
+                 pdkim_hashes[hashtype].exim_hashmethod))
   {
   DEBUG(D_acl)
     debug_printf("PDKIM: hash init error, possibly nonhandled hashtype\n");
@@ -1898,8 +2004,21 @@ if (!exim_sha_init(&b->body_hash_ctx,            /*XXX hash method: extend for sha512 */
 b->signed_body_bytes = 0;
 b->num_buffered_blanklines = 0;
 ctx->bodyhash = b;
+return b;
+}
+
 
-old:
+/* 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;
 }
@@ -1910,7 +2029,7 @@ return b;
 
 void
 pdkim_init_context(pdkim_ctx * ctx, BOOL dot_stuffed,
-  uschar * (*dns_txt_callback)(char *))
+  uschar * (*dns_txt_callback)(uschar *))
 {
 memset(ctx, 0, sizeof(pdkim_ctx));
 ctx->flags = dot_stuffed ? PDKIM_MODE_SIGN | PDKIM_DOT_TERM : PDKIM_MODE_SIGN;