ARC: enhance debug for signing; explicitly init signing context
[exim.git] / src / src / arc.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4 /* Experimental ARC support for Exim
5    Copyright (c) Jeremy Harris 2018
6    License: GPL
7 */
8
9 #include "exim.h"
10 #ifdef EXPERIMENTAL_ARC
11 # if !defined SUPPORT_SPF
12 #  error SPF must also be enabled for ARC
13 # elif defined DISABLE_DKIM
14 #  error DKIM must also be enabled for ARC
15 # else
16
17 #  include "functions.h"
18 #  include "pdkim/pdkim.h"
19 #  include "pdkim/signing.h"
20
21 extern pdkim_ctx * dkim_verify_ctx;
22 extern pdkim_ctx dkim_sign_ctx;
23
24 /******************************************************************************/
25
26 typedef struct hdr_rlist {
27   struct hdr_rlist *    prev;
28   BOOL                  used;
29   header_line *         h;
30 } hdr_rlist;
31
32 typedef struct arc_line {
33   header_line * complete;       /* including the header name; nul-term */
34   uschar *      relaxed;
35
36   /* identified tag contents */
37   /*XXX t= for AS? */
38   blob          i;
39   blob          cv;
40   blob          a;
41   blob          b;
42   blob          bh;
43   blob          d;
44   blob          h;
45   blob          s;
46   blob          c;
47   blob          l;
48
49   /* tag content sub-portions */
50   blob          a_algo;
51   blob          a_hash;
52
53   blob          c_head;
54   blob          c_body;
55
56   /* modified copy of b= field in line */
57   blob          rawsig_no_b_val;
58 } arc_line;
59
60 typedef struct arc_set {
61   struct arc_set *      next;
62   struct arc_set *      prev;
63
64   unsigned              instance;
65   arc_line *            hdr_aar;
66   arc_line *            hdr_ams;
67   arc_line *            hdr_as;
68
69   const uschar *        ams_verify_done;
70   BOOL                  ams_verify_passed;
71 } arc_set;
72
73 typedef struct arc_ctx {
74   arc_set *     arcset_chain;
75   arc_set *     arcset_chain_last;
76 } arc_ctx;
77
78 #define ARC_HDR_AAR     US"ARC-Authentication-Results:"
79 #define ARC_HDRLEN_AAR  27
80 #define ARC_HDR_AMS     US"ARC-Message-Signature:"
81 #define ARC_HDRLEN_AMS  22
82 #define ARC_HDR_AS      US"ARC-Seal:"
83 #define ARC_HDRLEN_AS   9
84 #define HDR_AR          US"Authentication-Results:"
85 #define HDRLEN_AR       23
86
87 static hdr_rlist * headers_rlist;
88 static arc_ctx arc_sign_ctx = { NULL };
89
90
91 /******************************************************************************/
92
93
94 /* Get the instance number from the header.
95 Return 0 on error */
96 static unsigned
97 arc_instance_from_hdr(const arc_line * al)
98 {
99 const uschar * s = al->i.data;
100 if (!s || !al->i.len) return 0;
101 return (unsigned) atoi(CCS s);
102 }
103
104
105 static uschar *
106 skip_fws(uschar * s)
107 {
108 uschar c = *s;
109 while (c && (c == ' ' || c == '\t' || c == '\n' || c == '\r')) c = *++s;
110 return s;
111 }
112
113
114 /* Locate instance struct on chain, inserting a new one if
115 needed.  The chain is in increasing-instance-number order
116 by the "next" link, and we have a "prev" link also.
117 */
118
119 static arc_set *
120 arc_find_set(arc_ctx * ctx, unsigned i)
121 {
122 arc_set ** pas, * as, * next, * prev;
123
124 for (pas = &ctx->arcset_chain, prev = NULL, next = ctx->arcset_chain;
125      as = *pas; pas = &as->next)
126   {
127   if (as->instance > i) break;
128   if (as->instance == i)
129     {
130     DEBUG(D_acl) debug_printf("ARC: existing instance %u\n", i);
131     return as;
132     }
133   next = as->next;
134   prev = as;
135   }
136
137 DEBUG(D_acl) debug_printf("ARC: new instance %u\n", i);
138 *pas = as = store_get(sizeof(arc_set));
139 memset(as, 0, sizeof(arc_set));
140 as->next = next;
141 as->prev = prev;
142 as->instance = i;
143 if (next)
144   next->prev = as;
145 else
146   ctx->arcset_chain_last = as;
147 return as;
148 }
149
150
151
152 /* Insert a tag content into the line structure.
153 Note this is a reference to existing data, not a copy.
154 Check for already-seen tag.
155 The string-pointer is on the '=' for entry.  Update it past the
156 content (to the ;) on return;
157 */
158
159 static uschar *
160 arc_insert_tagvalue(arc_line * al, unsigned loff, uschar ** ss)
161 {
162 uschar * s = *ss;
163 uschar c = *++s;
164 blob * b = (blob *)(US al + loff);
165 size_t len = 0;
166
167 /* [FWS] tag-value [FWS] */
168
169 if (b->data) return US"fail";
170 s = skip_fws(s);                                                /* FWS */
171
172 b->data = s;
173 while ((c = *s) && c != ';') { len++; s++; }
174 *ss = s;
175 while (len && ((c = s[-1]) == ' ' || c == '\t' || c == '\n' || c == '\r'))
176   { s--; len--; }                                               /* FWS */
177 b->len = len;
178 return NULL;
179 }
180
181
182 /* Inspect a header line, noting known tag fields.
183 Check for duplicates. */
184
185 static uschar *
186 arc_parse_line(arc_line * al, header_line * h, unsigned off, BOOL instance_only)
187 {
188 uschar * s = h->text + off;
189 uschar * r = NULL;      /* compiler-quietening */
190 uschar c;
191
192 al->complete = h;
193
194 if (!instance_only)
195   {
196   al->rawsig_no_b_val.data = store_get(h->slen + 1);
197   memcpy(al->rawsig_no_b_val.data, h->text, off);       /* copy the header name blind */
198   r = al->rawsig_no_b_val.data + off;
199   al->rawsig_no_b_val.len = off;
200   }
201
202 /* tag-list  =  tag-spec *( ";" tag-spec ) [ ";" ] */
203
204 while ((c = *s))
205   {
206   char tagchar;
207   uschar * t;
208   unsigned i = 0;
209   uschar * fieldstart = s;
210   uschar * bstart = NULL, * bend;
211
212   /* tag-spec  =  [FWS] tag-name [FWS] "=" [FWS] tag-value [FWS] */
213
214   s = skip_fws(s);                                              /* FWS */
215   if (!*s) break;
216 /* debug_printf("%s: consider '%s'\n", __FUNCTION__, s); */
217   tagchar = *s++;
218   s = skip_fws(s);                                              /* FWS */
219   if (!*s) break;
220
221   if (!instance_only || tagchar == 'i') switch (tagchar)
222     {
223     case 'a':                           /* a= AMS algorithm */
224       {
225       if (*s != '=') return US"no 'a' value";
226       if (arc_insert_tagvalue(al, offsetof(arc_line, a), &s)) return US"a tag dup";
227
228       /* substructure: algo-hash   (eg. rsa-sha256) */
229
230       t = al->a_algo.data = al->a.data;
231       while (*t != '-')
232         if (!*t++ || ++i > al->a.len) return US"no '-' in 'a' value";
233       al->a_algo.len = i;
234       if (*t++ != '-') return US"no '-' in 'a' value";
235       al->a_hash.data = t;
236       al->a_hash.len = al->a.len - i - 1;
237       }
238       break;
239     case 'b':
240       {
241       gstring * g = NULL;
242
243       switch (*s)
244         {
245         case '=':                       /* b= AMS signature */
246           if (al->b.data) return US"already b data";
247           bstart = s+1;
248
249           /* The signature can have FWS inserted in the content;
250           make a stripped copy */
251
252           while ((c = *++s) && c != ';')
253             if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
254               g = string_catn(g, s, 1);
255           al->b.data = string_from_gstring(g);
256           al->b.len = g->ptr;
257           gstring_reset_unused(g);
258           bend = s;
259           break;
260         case 'h':                       /* bh= AMS body hash */
261           s = skip_fws(++s);                                    /* FWS */
262           if (*s != '=') return US"no bh value";
263           if (al->bh.data) return US"already bh data";
264
265           /* The bodyhash can have FWS inserted in the content;
266           make a stripped copy */
267
268           while ((c = *++s) && c != ';')
269             if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
270               g = string_catn(g, s, 1);
271           al->bh.data = string_from_gstring(g);
272           al->bh.len = g->ptr;
273           gstring_reset_unused(g);
274           break;
275         default:
276           return US"b? tag";
277         }
278       }
279       break;
280     case 'c':
281       switch (*s)
282         {
283         case '=':                       /* c= AMS canonicalisation */
284           if (arc_insert_tagvalue(al, offsetof(arc_line, c), &s)) return US"c tag dup";
285
286           /* substructure: head/body   (eg. relaxed/simple)) */
287
288           t = al->c_head.data = al->c.data;
289           while (isalpha(*t))
290             if (!*t++ || ++i > al->a.len) break;
291           al->c_head.len = i;
292           if (*t++ == '/')              /* /body is optional */
293             {
294             al->c_body.data = t;
295             al->c_body.len = al->c.len - i - 1;
296             }
297           else
298             {
299             al->c_body.data = US"simple";
300             al->c_body.len = 6;
301             }
302           break;
303         case 'v':                       /* cv= AS validity */
304           if (*++s != '=') return US"cv tag val";
305           if (arc_insert_tagvalue(al, offsetof(arc_line, cv), &s)) return US"cv tag dup";
306           break;
307         default:
308           return US"c? tag";
309         }
310       break;
311     case 'd':                           /* d= AMS domain */
312       if (*s != '=') return US"d tag val";
313       if (arc_insert_tagvalue(al, offsetof(arc_line, d), &s)) return US"d tag dup";
314       break;
315     case 'h':                           /* h= AMS headers */
316       if (*s != '=') return US"h tag val";
317       if (arc_insert_tagvalue(al, offsetof(arc_line, h), &s)) return US"h tag dup";
318       break;
319     case 'i':                           /* i= ARC set instance */
320       if (*s != '=') return US"i tag val";
321       if (arc_insert_tagvalue(al, offsetof(arc_line, i), &s)) return US"i tag dup";
322       if (instance_only) goto done;
323       break;
324     case 'l':                           /* l= bodylength */
325       if (*s != '=') return US"l tag val";
326       if (arc_insert_tagvalue(al, offsetof(arc_line, l), &s)) return US"l tag dup";
327       break;
328     case 's':                           /* s= AMS selector */
329       if (*s != '=') return US"s tag val";
330       if (arc_insert_tagvalue(al, offsetof(arc_line, s), &s)) return US"s tag dup";
331       break;
332     }
333
334   while ((c = *s) && c != ';') s++;
335   if (c) s++;                           /* ; after tag-spec */
336
337   /* for all but the b= tag, copy the field including FWS.  For the b=,
338   drop the tag content. */
339
340   if (!instance_only)
341     if (bstart)
342       {
343       size_t n = bstart - fieldstart;
344       memcpy(r, fieldstart, n);         /* FWS "b=" */
345       r += n;
346       al->rawsig_no_b_val.len += n;
347       n = s - bend;
348       memcpy(r, bend, n);               /* FWS ";" */
349       r += n;
350       al->rawsig_no_b_val.len += n;
351       }
352     else
353       {
354       size_t n = s - fieldstart;
355       memcpy(r, fieldstart, n);
356       r += n;
357       al->rawsig_no_b_val.len += n;
358       }
359   }
360
361 if (!instance_only)
362   *r = '\0';
363
364 done:
365 /* debug_printf("%s: finshed\n", __FUNCTION__); */
366 return NULL;
367 }
368
369
370 /* Insert one header line in the correct set of the chain,
371 adding instances as needed and checking for duplicate lines.
372 */
373
374 static uschar *
375 arc_insert_hdr(arc_ctx * ctx, header_line * h, unsigned off, unsigned hoff,
376   BOOL instance_only)
377 {
378 int i;
379 arc_set * as;
380 arc_line * al = store_get(sizeof(arc_line)), ** alp;
381 uschar * e;
382
383 memset(al, 0, sizeof(arc_line));
384
385 if ((e = arc_parse_line(al, h, off, instance_only)))
386   {
387   DEBUG(D_acl) if (e) debug_printf("ARC: %s\n", e);
388   return US"line parse";
389   }
390 if (!(i = arc_instance_from_hdr(al)))   return US"instance find";
391 if (!(as = arc_find_set(ctx, i)))       return US"set find";
392 if (*(alp = (arc_line **)(US as + hoff))) return US"dup hdr";
393
394 *alp = al;
395 return NULL;
396 }
397
398
399
400
401 static const uschar *
402 arc_try_header(arc_ctx * ctx, header_line * h, BOOL instance_only)
403 {
404 const uschar * e;
405
406 /*debug_printf("consider hdr '%s'\n", h->text);*/
407 if (strncmpic(ARC_HDR_AAR, h->text, ARC_HDRLEN_AAR) == 0)
408   {
409   DEBUG(D_acl)
410     {
411     int len = h->slen;
412     uschar * s;
413     for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
414       s--, len--;
415     debug_printf("ARC: found AAR: %.*s\n", len, h->text);
416     }
417   if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AAR, offsetof(arc_set, hdr_aar),
418                           TRUE)))
419     {
420     DEBUG(D_acl) debug_printf("inserting AAR: %s\n", e);
421     return US"inserting AAR";
422     }
423   }
424 else if (strncmpic(ARC_HDR_AMS, h->text, ARC_HDRLEN_AMS) == 0)
425   {
426   arc_line * ams;
427
428   DEBUG(D_acl)
429     {
430     int len = h->slen;
431     uschar * s;
432     for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
433       s--, len--;
434     debug_printf("ARC: found AMS: %.*s\n", len, h->text);
435     }
436   if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AMS, offsetof(arc_set, hdr_ams),
437                           instance_only)))
438     {
439     DEBUG(D_acl) debug_printf("inserting AMS: %s\n", e);
440     return US"inserting AMS";
441     }
442
443   /* defaults */
444   /*XXX dubious selection of ams here */
445   ams = ctx->arcset_chain->hdr_ams;
446   if (!ams->c.data)
447     {
448     ams->c_head.data = US"simple"; ams->c_head.len = 6;
449     ams->c_body = ams->c_head;
450     }
451   }
452 else if (strncmpic(ARC_HDR_AS, h->text, ARC_HDRLEN_AS) == 0)
453   {
454   DEBUG(D_acl)
455     {
456     int len = h->slen;
457     uschar * s;
458     for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
459       s--, len--;
460     debug_printf("ARC: found AS: %.*s\n", len, h->text);
461     }
462   if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AS, offsetof(arc_set, hdr_as),
463                           instance_only)))
464     {
465     DEBUG(D_acl) debug_printf("inserting AS: %s\n", e);
466     return US"inserting AS";
467     }
468   }
469 return NULL;
470 }
471
472
473
474 /* Gather the chain of arc sets from the headers.
475 Check for duplicates while that is done.  Also build the
476 reverse-order headers list;
477
478 Return: ARC state if determined, eg. by lack of any ARC chain.
479 */
480
481 static const uschar *
482 arc_vfy_collect_hdrs(arc_ctx * ctx)
483 {
484 header_line * h;
485 hdr_rlist * r = NULL, * rprev = NULL;
486 const uschar * e;
487
488 DEBUG(D_acl) debug_printf("ARC: collecting arc sets\n");
489 for (h = header_list; h; h = h->next)
490   {
491   r = store_get(sizeof(hdr_rlist));
492   r->prev = rprev;
493   r->used = FALSE;
494   r->h = h;
495   rprev = r;
496
497   if ((e = arc_try_header(ctx, h, FALSE)))
498     {
499     arc_state_reason = string_sprintf("collecting headers: %s", e);
500     return US"fail";
501     }
502   }
503 headers_rlist = r;
504
505 if (!ctx->arcset_chain) return US"none";
506 return NULL;
507 }
508
509
510 static BOOL
511 arc_cv_match(arc_line * al, const uschar * s)
512 {
513 return Ustrncmp(s, al->cv.data, al->cv.len) == 0;
514 }
515
516 /******************************************************************************/
517
518 /* Return the hash of headers from the message that the AMS claims it
519 signed.
520 */
521
522 static void
523 arc_get_verify_hhash(arc_ctx * ctx, arc_line * ams, blob * hhash)
524 {
525 const uschar * headernames = string_copyn(ams->h.data, ams->h.len);
526 const uschar * hn;
527 int sep = ':';
528 hdr_rlist * r;
529 BOOL relaxed = Ustrncmp(US"relaxed", ams->c_head.data, ams->c_head.len) == 0;
530 int hashtype = pdkim_hashname_to_hashtype(
531                     ams->a_hash.data, ams->a_hash.len);
532 hctx hhash_ctx;
533 const uschar * s;
534 int len;
535
536 if (!exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
537   {
538   DEBUG(D_acl)
539       debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
540   return;
541   }
542
543 /* For each headername in the list from the AMS (walking in order)
544 walk the message headers in reverse order, adding to the hash any
545 found for the first time. For that last point, maintain used-marks
546 on the list of message headers. */
547
548 DEBUG(D_acl) debug_printf("ARC: AMS header data for verification:\n");
549
550 for (r = headers_rlist; r; r = r->prev)
551   r->used = FALSE;
552 while ((hn = string_nextinlist(&headernames, &sep, NULL, 0)))
553   for (r = headers_rlist; r; r = r->prev)
554     if (  !r->used
555        && strncasecmp(CCS (s = r->h->text), CCS hn, Ustrlen(hn)) == 0
556        )
557       {
558       if (relaxed) s = pdkim_relax_header_n(s, r->h->slen, TRUE);
559
560       len = Ustrlen(s);
561       DEBUG(D_acl) pdkim_quoteprint(s, len);
562       exim_sha_update(&hhash_ctx, s, Ustrlen(s));
563       r->used = TRUE;
564       break;
565       }
566
567 /* Finally add in the signature header (with the b= tag stripped); no CRLF */
568
569 s = ams->rawsig_no_b_val.data, len = ams->rawsig_no_b_val.len;
570 if (relaxed)
571   len = Ustrlen(s = pdkim_relax_header_n(s, len, FALSE));
572 DEBUG(D_acl) pdkim_quoteprint(s, len);
573 exim_sha_update(&hhash_ctx, s, len);
574
575 exim_sha_finish(&hhash_ctx, hhash);
576 DEBUG(D_acl)
577   { debug_printf("ARC: header hash: "); pdkim_hexprint(hhash->data, hhash->len); }
578 return;
579 }
580
581
582
583
584 static pdkim_pubkey *
585 arc_line_to_pubkey(arc_line * al)
586 {
587 uschar * dns_txt;
588 pdkim_pubkey * p;
589
590 if (!(dns_txt = dkim_exim_query_dns_txt(string_sprintf("%.*s._domainkey.%.*s",
591           al->s.len, al->s.data, al->d.len, al->d.data))))
592   {
593   DEBUG(D_acl) debug_printf("pubkey dns lookup fail\n");
594   return NULL;
595   }
596
597 if (  !(p = pdkim_parse_pubkey_record(dns_txt))
598    || (Ustrcmp(p->srvtype, "*") != 0 && Ustrcmp(p->srvtype, "email") != 0)
599    )
600   {
601   DEBUG(D_acl) debug_printf("pubkey dns lookup format error\n");
602   return NULL;
603   }
604
605 /* If the pubkey limits use to specified hashes, reject unusable
606 signatures. XXX should we have looked for multiple dns records? */
607
608 if (p->hashes)
609   {
610   const uschar * list = p->hashes, * ele;
611   int sep = ':';
612
613   while ((ele = string_nextinlist(&list, &sep, NULL, 0)))
614     if (Ustrncmp(ele, al->a_hash.data, al->a_hash.len) == 0) break;
615   if (!ele)
616     {
617     DEBUG(D_acl) debug_printf("pubkey h=%s vs sig a=%.*s\n",
618                               p->hashes, (int)al->a.len, al->a.data);
619     return NULL;
620     }
621   }
622 return p;
623 }
624
625
626
627
628 static pdkim_bodyhash *
629 arc_ams_setup_vfy_bodyhash(arc_line * ams)
630 {
631 int canon_head, canon_body;
632 long bodylen;
633
634 if (!ams->c.data) ams->c.data = US"simple";     /* RFC 6376 (DKIM) default */
635 pdkim_cstring_to_canons(ams->c.data, ams->c.len, &canon_head, &canon_body);
636 bodylen = ams->l.data
637         ? strtol(CS string_copyn(ams->l.data, ams->l.len), NULL, 10) : -1;
638
639 return pdkim_set_bodyhash(dkim_verify_ctx,
640         pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len),
641         canon_body,
642         bodylen);
643 }
644
645
646
647 /* Verify an AMS. This is a DKIM-sig header, but with an ARC i= tag
648 and without a DKIM v= tag.
649 */
650
651 static const uschar *
652 arc_ams_verify(arc_ctx * ctx, arc_set * as)
653 {
654 arc_line * ams = as->hdr_ams;
655 pdkim_bodyhash * b;
656 pdkim_pubkey * p;
657 blob sighash;
658 blob hhash;
659 ev_ctx vctx;
660 int hashtype;
661 const uschar * errstr;
662
663 as->ams_verify_done = US"in-progress";
664
665 /* Check the AMS has all the required tags:
666    "a="  algorithm
667    "b="  signature
668    "bh=" body hash
669    "d="  domain (for key lookup)
670    "h="  headers (included in signature)
671    "s="  key-selector (for key lookup)
672 */
673 if (  !ams->a.data || !ams->b.data || !ams->bh.data || !ams->d.data
674    || !ams->h.data || !ams->s.data)
675   {
676   as->ams_verify_done = arc_state_reason = US"required tag missing";
677   return US"fail";
678   }
679
680
681 /* The bodyhash should have been created earlier, and the dkim code should
682 have managed calculating it during message input.  Find the reference to it. */
683
684 if (!(b = arc_ams_setup_vfy_bodyhash(ams)))
685   {
686   as->ams_verify_done = arc_state_reason = US"internal hash setup error";
687   return US"fail";
688   }
689
690 DEBUG(D_acl)
691   {
692   debug_printf("ARC i=%d AMS   Body bytes hashed: %lu\n"
693                "              Body %.*s computed: ",
694                as->instance, b->signed_body_bytes,
695                (int)ams->a_hash.len, ams->a_hash.data);
696   pdkim_hexprint(CUS b->bh.data, b->bh.len);
697   }
698
699 /* We know the bh-tag blob is of a nul-term string, so safe as a string */
700
701 if (  !ams->bh.data
702    || (pdkim_decode_base64(ams->bh.data, &sighash), sighash.len != b->bh.len)
703    || memcmp(sighash.data, b->bh.data, b->bh.len) != 0
704    )
705   {
706   DEBUG(D_acl)
707     {
708     debug_printf("ARC i=%d AMS Body hash from headers: ", as->instance);
709     pdkim_hexprint(sighash.data, sighash.len);
710     debug_printf("ARC i=%d AMS Body hash did NOT match\n", as->instance);
711     }
712   return as->ams_verify_done = arc_state_reason = US"AMS body hash miscompare";
713   }
714
715 DEBUG(D_acl) debug_printf("ARC i=%d AMS Body hash compared OK\n", as->instance);
716
717 /* Get the public key from DNS */
718
719 if (!(p = arc_line_to_pubkey(ams)))
720   return as->ams_verify_done = arc_state_reason = US"pubkey problem";
721
722 /* We know the b-tag blob is of a nul-term string, so safe as a string */
723 pdkim_decode_base64(ams->b.data, &sighash);
724
725 arc_get_verify_hhash(ctx, ams, &hhash);
726
727 /* Setup the interface to the signing library */
728
729 if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx)))
730   {
731   DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
732   as->ams_verify_done = arc_state_reason = US"internal sigverify init error";
733   return US"fail";
734   }
735
736 hashtype = pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len);
737
738 if ((errstr = exim_dkim_verify(&vctx,
739           pdkim_hashes[hashtype].exim_hashmethod, &hhash, &sighash)))
740   {
741   DEBUG(D_acl) debug_printf("ARC i=%d AMS verify %s\n", as->instance, errstr);
742   return as->ams_verify_done = arc_state_reason = US"AMS sig nonverify";
743   }
744
745 DEBUG(D_acl) debug_printf("ARC i=%d AMS verify pass\n", as->instance);
746 as->ams_verify_passed = TRUE;
747 return NULL;
748 }
749
750
751
752 /* Check the sets are instance-continuous and that all
753 members are present.  Check that no arc_seals are "fail".
754 Set the highest instance number global.
755 Verify the latest AMS.
756 */
757 static uschar *
758 arc_headers_check(arc_ctx * ctx)
759 {
760 arc_set * as;
761 int inst;
762 BOOL ams_fail_found = FALSE;
763 uschar * ret = NULL;
764
765 if (!(as = ctx->arcset_chain))
766   return US"none";
767
768 for(inst = 0; as; as = as->next)
769   {
770   if (  as->instance != ++inst
771      || !as->hdr_aar || !as->hdr_ams || !as->hdr_as
772      || arc_cv_match(as->hdr_as, US"fail")
773      )
774     {
775     arc_state_reason = string_sprintf("i=%d"
776       " (cv, sequence or missing header)", as->instance);
777     DEBUG(D_acl) debug_printf("ARC chain fail at %s\n", arc_state_reason);
778     return US"fail";
779     }
780
781   /* Evaluate the oldest-pass AMS validation while we're here.
782   It does not affect the AS chain validation but is reported as
783   auxilary info. */
784
785   if (!ams_fail_found)
786     if (arc_ams_verify(ctx, as))
787       ams_fail_found = TRUE;
788     else
789       arc_oldest_pass = inst;
790   arc_state_reason = NULL;
791   }
792
793 arc_received = ctx->arcset_chain_last;
794 arc_received_instance = inst;
795 if (ret)
796   return ret;
797
798 /* We can skip the latest-AMS validation, if we already did it. */
799
800 as = ctx->arcset_chain_last;
801 if (as->ams_verify_done && !as->ams_verify_passed)
802   {
803   arc_state_reason = as->ams_verify_done;
804   return US"fail";
805   }
806 if (!!arc_ams_verify(ctx, as))
807   return US"fail";
808
809 return NULL;
810 }
811
812
813 /******************************************************************************/
814 static const uschar *
815 arc_seal_verify(arc_ctx * ctx, arc_set * as)
816 {
817 arc_line * hdr_as = as->hdr_as;
818 arc_set * as2;
819 int hashtype;
820 hctx hhash_ctx;
821 blob hhash_computed;
822 blob sighash;
823 ev_ctx vctx;
824 pdkim_pubkey * p;
825 const uschar * errstr;
826
827 DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d\n", as->instance);
828 /*
829        1.  If the value of the "cv" tag on that seal is "fail", the
830            chain state is "fail" and the algorithm stops here.  (This
831            step SHOULD be skipped if the earlier step (2.1) was
832            performed) [it was]
833
834        2.  In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
835            == "none" && i != 1)) then the chain state is "fail" and the
836            algorithm stops here (note that the ordering of the logic is
837            structured for short-circuit evaluation).
838 */
839
840 if (  as->instance == 1 && !arc_cv_match(hdr_as, US"none")
841    || arc_cv_match(hdr_as, US"none") && as->instance != 1
842    )
843   {
844   arc_state_reason = US"seal cv state";
845   return US"fail";
846   }
847
848 /*
849        3.  Initialize a hash function corresponding to the "a" tag of
850            the ARC-Seal.
851 */
852
853 hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len);
854
855 if (!exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
856   {
857   DEBUG(D_acl)
858       debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
859   arc_state_reason = US"seal hash setup error";
860   return US"fail";
861   }
862
863 /*
864        4.  Compute the canonicalized form of the ARC header fields, in
865            the order described in Section 5.4.2, using the "relaxed"
866            header canonicalization defined in Section 3.4.2 of
867            [RFC6376].  Pass the canonicalized result to the hash
868            function.
869
870 Headers are CRLF-separated, but the last one is not crlf-terminated.
871 */
872
873 DEBUG(D_acl) debug_printf("ARC: AS header data for verification:\n");
874 for (as2 = ctx->arcset_chain;
875      as2 && as2->instance <= as->instance;
876      as2 = as2->next)
877   {
878   arc_line * al;
879   uschar * s;
880   int len;
881
882   al = as2->hdr_aar;
883   if (!(s = al->relaxed))
884     al->relaxed = s = pdkim_relax_header_n(al->complete->text,
885                                             al->complete->slen, TRUE);
886   len = Ustrlen(s);
887   DEBUG(D_acl) pdkim_quoteprint(s, len);
888   exim_sha_update(&hhash_ctx, s, len);
889
890   al = as2->hdr_ams;
891   if (!(s = al->relaxed))
892     al->relaxed = s = pdkim_relax_header_n(al->complete->text,
893                                             al->complete->slen, TRUE);
894   len = Ustrlen(s);
895   DEBUG(D_acl) pdkim_quoteprint(s, len);
896   exim_sha_update(&hhash_ctx, s, len);
897
898   al = as2->hdr_as;
899   if (as2->instance == as->instance)
900     s = pdkim_relax_header_n(al->rawsig_no_b_val.data,
901                                         al->rawsig_no_b_val.len, FALSE);
902   else if (!(s = al->relaxed))
903     al->relaxed = s = pdkim_relax_header_n(al->complete->text,
904                                             al->complete->slen, TRUE);
905   len = Ustrlen(s);
906   DEBUG(D_acl) pdkim_quoteprint(s, len);
907   exim_sha_update(&hhash_ctx, s, len);
908   }
909
910 /*
911        5.  Retrieve the final digest from the hash function.
912 */
913
914 exim_sha_finish(&hhash_ctx, &hhash_computed);
915 DEBUG(D_acl)
916   {
917   debug_printf("ARC i=%d AS Header %.*s computed: ",
918     as->instance, (int)hdr_as->a_hash.len, hdr_as->a_hash.data);
919   pdkim_hexprint(hhash_computed.data, hhash_computed.len);
920   }
921
922
923 /*
924        6.  Retrieve the public key identified by the "s" and "d" tags in
925            the ARC-Seal, as described in Section 4.1.6.
926 */
927
928 if (!(p = arc_line_to_pubkey(hdr_as)))
929   return US"pubkey problem";
930
931 /*
932        7.  Determine whether the signature portion ("b" tag) of the ARC-
933            Seal and the digest computed above are valid according to the
934            public key.  (See also Section Section 8.4 for failure case
935            handling)
936
937        8.  If the signature is not valid, the chain state is "fail" and
938            the algorithm stops here.
939 */
940
941 /* We know the b-tag blob is of a nul-term string, so safe as a string */
942 pdkim_decode_base64(hdr_as->b.data, &sighash);
943
944 if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx)))
945   {
946   DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
947   return US"fail";
948   }
949
950 hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len);
951
952 if ((errstr = exim_dkim_verify(&vctx,
953               pdkim_hashes[hashtype].exim_hashmethod,
954               &hhash_computed, &sighash)))
955   {
956   DEBUG(D_acl)
957     debug_printf("ARC i=%d AS headers verify: %s\n", as->instance, errstr);
958   arc_state_reason = US"seal sigverify error";
959   return US"fail";
960   }
961
962 DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d pass\n", as->instance);
963 return NULL;
964 }
965
966
967 static const uschar *
968 arc_verify_seals(arc_ctx * ctx)
969 {
970 arc_set * as = ctx->arcset_chain;
971
972 if (!as)
973   return US"none";
974
975 while (as)
976   {
977   if (arc_seal_verify(ctx, as)) return US"fail";
978   as = as->next;
979   }
980 DEBUG(D_acl) debug_printf("ARC: AS vfy overall pass\n");
981 return NULL;
982 }
983 /******************************************************************************/
984
985 /* Do ARC verification.  Called from DATA ACL, on a verify = arc
986 condition.  No arguments; we are checking globals.
987
988 Return:  The ARC state, or NULL on error.
989 */
990
991 const uschar *
992 acl_verify_arc(void)
993 {
994 arc_ctx ctx = { NULL };
995 const uschar * res;
996
997 if (!dkim_verify_ctx)
998   {
999   DEBUG(D_acl) debug_printf("ARC: no DKIM verify context\n");
1000   return NULL;
1001   }
1002
1003 /* AS evaluation, per
1004 https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-10#section-6
1005 */
1006 /* 1.  Collect all ARC sets currently on the message.  If there were
1007        none, the ARC state is "none" and the algorithm stops here.
1008 */
1009
1010 if ((res = arc_vfy_collect_hdrs(&ctx)))
1011   goto out;
1012
1013 /* 2.  If the form of any ARC set is invalid (e.g., does not contain
1014        exactly one of each of the three ARC-specific header fields),
1015        then the chain state is "fail" and the algorithm stops here.
1016
1017        1.  To avoid the overhead of unnecessary computation and delay
1018            from crypto and DNS operations, the cv value for all ARC-
1019            Seal(s) MAY be checked at this point.  If any of the values
1020            are "fail", then the overall state of the chain is "fail" and
1021            the algorithm stops here.
1022
1023    3.  Conduct verification of the ARC-Message-Signature header field
1024        bearing the highest instance number.  If this verification fails,
1025        then the chain state is "fail" and the algorithm stops here.
1026 */
1027
1028 if ((res = arc_headers_check(&ctx)))
1029   goto out;
1030
1031 /* 4.  For each ARC-Seal from the "N"th instance to the first, apply the
1032        following logic:
1033
1034        1.  If the value of the "cv" tag on that seal is "fail", the
1035            chain state is "fail" and the algorithm stops here.  (This
1036            step SHOULD be skipped if the earlier step (2.1) was
1037            performed)
1038
1039        2.  In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
1040            == "none" && i != 1)) then the chain state is "fail" and the
1041            algorithm stops here (note that the ordering of the logic is
1042            structured for short-circuit evaluation).
1043
1044        3.  Initialize a hash function corresponding to the "a" tag of
1045            the ARC-Seal.
1046
1047        4.  Compute the canonicalized form of the ARC header fields, in
1048            the order described in Section 5.4.2, using the "relaxed"
1049            header canonicalization defined in Section 3.4.2 of
1050            [RFC6376].  Pass the canonicalized result to the hash
1051            function.
1052
1053        5.  Retrieve the final digest from the hash function.
1054
1055        6.  Retrieve the public key identified by the "s" and "d" tags in
1056            the ARC-Seal, as described in Section 4.1.6.
1057
1058        7.  Determine whether the signature portion ("b" tag) of the ARC-
1059            Seal and the digest computed above are valid according to the
1060            public key.  (See also Section Section 8.4 for failure case
1061            handling)
1062
1063        8.  If the signature is not valid, the chain state is "fail" and
1064            the algorithm stops here.
1065
1066    5.  If all seals pass validation, then the chain state is "pass", and
1067        the algorithm is complete.
1068 */
1069
1070 if ((res = arc_verify_seals(&ctx)))
1071   goto out;
1072
1073 res = US"pass";
1074
1075 out:
1076   return res;
1077 }
1078
1079 /******************************************************************************/
1080
1081 /* Prepend the header to the rlist */
1082
1083 static hdr_rlist *
1084 arc_rlist_entry(hdr_rlist * list, const uschar * s, int len)
1085 {
1086 hdr_rlist * r = store_get(sizeof(hdr_rlist) + sizeof(header_line));
1087 header_line * h = r->h = (header_line *)(r+1);
1088
1089 r->prev = list;
1090 r->used = FALSE;
1091 h->next = NULL;
1092 h->type = 0;
1093 h->slen = len;
1094 h->text = US s;
1095
1096 /* This works for either NL or CRLF lines; also nul-termination */
1097 while (*++s)
1098   if (*s == '\n' && s[1] != '\t' && s[1] != ' ') break;
1099 s++;            /* move past end of line */
1100
1101 return r;
1102 }
1103
1104
1105 /* Walk the given headers strings identifying each header, and construct
1106 a reverse-order list.
1107 */
1108
1109 static hdr_rlist *
1110 arc_sign_scan_headers(arc_ctx * ctx, gstring * sigheaders)
1111 {
1112 const uschar * s;
1113 hdr_rlist * rheaders = NULL;
1114
1115 s = sigheaders ? sigheaders->s : NULL;
1116 if (s) while (*s)
1117   {
1118   const uschar * s2 = s;
1119
1120   /* This works for either NL or CRLF lines; also nul-termination */
1121   while (*++s2)
1122     if (*s2 == '\n' && s2[1] != '\t' && s2[1] != ' ') break;
1123   s2++;         /* move past end of line */
1124
1125   rheaders = arc_rlist_entry(rheaders, s, s2 - s);
1126   s = s2;
1127   }
1128 return rheaders;
1129 }
1130
1131
1132
1133 /* Return the A-R content, without identity, with line-ending and
1134 NUL termination. */
1135
1136 static BOOL
1137 arc_sign_find_ar(header_line * headers, const uschar * identity, blob * ret)
1138 {
1139 header_line * h;
1140 int ilen = Ustrlen(identity);
1141
1142 ret->data = NULL;
1143 for(h = headers; h; h = h->next)
1144   {
1145   uschar * s = h->text, c;
1146   int len = h->slen;
1147
1148   if (Ustrncmp(s, HDR_AR, HDRLEN_AR) != 0) continue;
1149   s += HDRLEN_AR, len -= HDRLEN_AR;             /* header name */
1150   while (  len > 0
1151         && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
1152     s++, len--;                                 /* FWS */
1153   if (Ustrncmp(s, identity, ilen) != 0) continue;
1154   s += ilen; len -= ilen;                       /* identity */
1155   if (len <= 0) continue;
1156   if ((c = *s) && c == ';') s++, len--;         /* identity terminator */
1157   while (  len > 0
1158         && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
1159     s++, len--;                                 /* FWS */
1160   if (len <= 0) continue;
1161   ret->data = s;
1162   ret->len = len;
1163   return TRUE;
1164   }
1165 return FALSE;
1166 }
1167
1168
1169
1170 /* Append a constructed AAR including CRLF.  Add it to the arc_ctx too.  */
1171
1172 static gstring *
1173 arc_sign_append_aar(gstring * g, arc_ctx * ctx,
1174   const uschar * identity, int instance, blob * ar)
1175 {
1176 int aar_off = g ? g->ptr : 0;
1177 arc_set * as = store_get(sizeof(arc_set) + sizeof(arc_line) + sizeof(header_line));
1178 arc_line * al = (arc_line *)(as+1);
1179 header_line * h = (header_line *)(al+1);
1180
1181 g = string_catn(g, ARC_HDR_AAR, ARC_HDRLEN_AAR);
1182 g = string_cat(g, string_sprintf(" i=%d; %s;\r\n\t", instance, identity));
1183 g = string_catn(g, US ar->data, ar->len);
1184
1185 h->slen = g->ptr - aar_off;
1186 h->text = g->s + aar_off;
1187 al->complete = h;
1188 as->next = NULL;
1189 as->prev = ctx->arcset_chain_last;
1190 as->instance = instance;
1191 as->hdr_aar = al;
1192 if (instance == 1)
1193   ctx->arcset_chain = as;
1194 else
1195   ctx->arcset_chain_last->next = as;
1196 ctx->arcset_chain_last = as;
1197
1198 DEBUG(D_transport) debug_printf("ARC: AAR '%.*s'\n", h->slen - 2, h->text);
1199 return g;
1200 }
1201
1202
1203
1204 static BOOL
1205 arc_sig_from_pseudoheader(gstring * hdata, int hashtype, const uschar * privkey,
1206   blob * sig, const uschar * why)
1207 {
1208 hashmethod hm = /*sig->keytype == KEYTYPE_ED25519*/ FALSE
1209   ? HASH_SHA2_512 : pdkim_hashes[hashtype].exim_hashmethod;
1210 blob hhash;
1211 es_ctx sctx;
1212 const uschar * errstr;
1213
1214 DEBUG(D_transport)
1215   {
1216   hctx hhash_ctx;
1217   debug_printf("ARC: %s header data for signing:\n", why);
1218   pdkim_quoteprint(hdata->s, hdata->ptr);
1219
1220   (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
1221   exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
1222   exim_sha_finish(&hhash_ctx, &hhash);
1223   debug_printf("ARC: header hash: "); pdkim_hexprint(hhash.data, hhash.len);
1224   }
1225
1226 if (FALSE /*need hash for Ed25519 or GCrypt signing*/ )
1227   {
1228   hctx hhash_ctx;
1229   (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
1230   exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
1231   exim_sha_finish(&hhash_ctx, &hhash);
1232   }
1233 else
1234   {
1235   hhash.data = hdata->s;
1236   hhash.len = hdata->ptr;
1237   }
1238
1239 if (  (errstr = exim_dkim_signing_init(privkey, &sctx))
1240    || (errstr = exim_dkim_sign(&sctx, hm, &hhash, sig)))
1241   {
1242   log_write(0, LOG_MAIN|LOG_PANIC, "ARC: %s signing: %s\n", why, errstr);
1243   return FALSE;
1244   }
1245 return TRUE;
1246 }
1247
1248
1249
1250 static gstring *
1251 arc_sign_append_sig(gstring * g, blob * sig)
1252 {
1253 /*debug_printf("%s: raw sig ", __FUNCTION__); pdkim_hexprint(sig->data, sig->len);*/
1254 sig->data = pdkim_encode_base64(sig);
1255 sig->len = Ustrlen(sig->data);
1256 for (;;)
1257   {
1258   int len = MIN(sig->len, 74);
1259   g = string_catn(g, sig->data, len);
1260   if ((sig->len -= len) == 0) break;
1261   sig->data += len;
1262   g = string_catn(g, US"\r\n\t  ", 5);
1263   }
1264 g = string_catn(g, US";\r\n", 3);
1265 gstring_reset_unused(g);
1266 string_from_gstring(g);
1267 return g;
1268 }
1269
1270
1271 /* Append a constructed AMS including CRLF.  Add it to the arc_ctx too. */
1272
1273 static gstring *
1274 arc_sign_append_ams(gstring * g, arc_ctx * ctx, int instance,
1275   const uschar * identity, const uschar * selector, blob * bodyhash,
1276   hdr_rlist * rheaders, const uschar * privkey)
1277 {
1278 uschar * s;
1279 gstring * hdata = NULL;
1280 int col;
1281 int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6);       /*XXX hardwired */
1282 blob sig;
1283 int ams_off;
1284 arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line));
1285 header_line * h = (header_line *)(al+1);
1286
1287 /* debug_printf("%s\n", __FUNCTION__); */
1288
1289 /* Construct the to-be-signed AMS pseudo-header: everything but the sig. */
1290
1291 ams_off = g->ptr;
1292 g = string_append(g, 10,
1293       ARC_HDR_AMS,
1294       US" i=", string_sprintf("%d", instance),
1295       US"; a=rsa-sha256; c=relaxed; d=", identity,              /*XXX hardwired */
1296       US"; s=", selector,
1297       US";\r\n\tbh=", pdkim_encode_base64(bodyhash),
1298       US";\r\n\th=");
1299
1300 for(col = 3; rheaders; rheaders = rheaders->prev)
1301   {
1302   const uschar * hnames = US"DKIM-Signature:" PDKIM_DEFAULT_SIGN_HEADERS;
1303   uschar * name, * htext = rheaders->h->text;
1304   int sep = ':';
1305
1306   /* Spot headers of interest */
1307
1308   while ((name = string_nextinlist(&hnames, &sep, NULL, 0)))
1309     {
1310     int len = Ustrlen(name);
1311     if (strncasecmp(CCS htext, CCS name, len) == 0)
1312       {
1313       /* If too long, fold line in h= field */
1314
1315       if (col + len > 78) g = string_catn(g, US"\r\n\t  ", 5), col = 3;
1316
1317       /* Add name to h= list */
1318
1319       g = string_catn(g, name, len);
1320       g = string_catn(g, US":", 1);
1321       col += len + 1;
1322
1323       /* Accumulate header for hashing/signing */
1324
1325       hdata = string_cat(hdata,
1326                 pdkim_relax_header_n(htext, rheaders->h->slen, TRUE));  /*XXX hardwired */
1327       break;
1328       }
1329     }
1330   }
1331
1332 /* Lose the last colon from the h= list */
1333
1334 if (g->s[g->ptr - 1] == ':') g->ptr--;
1335
1336 g = string_catn(g, US";\r\n\tb=;", 7);
1337
1338 /* Include the pseudo-header in the accumulation */
1339
1340 s = pdkim_relax_header_n(g->s + ams_off, g->ptr - ams_off, FALSE);
1341 hdata = string_cat(hdata, s);
1342
1343 /* Calculate the signature from the accumulation */
1344 /*XXX does that need further relaxation? there are spaces embedded in the b= strings! */
1345
1346 if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AMS"))
1347   return NULL;
1348
1349 /* Lose the trailing semicolon from the psuedo-header, and append the signature
1350 (folded over lines) and termination to complete it. */
1351
1352 g->ptr--;
1353 g = arc_sign_append_sig(g, &sig);
1354
1355 h->slen = g->ptr - ams_off;
1356 h->text = g->s + ams_off;
1357 al->complete = h;
1358 ctx->arcset_chain_last->hdr_ams = al;
1359
1360 DEBUG(D_transport) debug_printf("ARC: AMS '%.*s'\n", h->slen - 2, h->text);
1361 return g;
1362 }
1363
1364
1365
1366 /* Look for an arc= result in an A-R header blob.  We know that its data
1367 happens to be a NUL-term string. */
1368
1369 static uschar *
1370 arc_ar_cv_status(blob * ar)
1371 {
1372 const uschar * resinfo = ar->data;
1373 int sep = ';';
1374 uschar * methodspec, * s;
1375
1376 while ((methodspec = string_nextinlist(&resinfo, &sep, NULL, 0)))
1377   if (Ustrncmp(methodspec, US"arc=", 4) == 0)
1378     {
1379     uschar c;
1380     for (s = methodspec += 4;
1381          (c = *s) && c != ';' && c != ' ' && c != '\r' && c != '\n'; ) s++;
1382     return string_copyn(methodspec, s - methodspec);
1383     }
1384 return US"none";
1385 }
1386
1387
1388
1389 /* Build the AS header and prepend it */
1390
1391 static gstring *
1392 arc_sign_prepend_as(gstring * arcset_interim, arc_ctx * ctx,
1393   int instance, const uschar * identity, const uschar * selector, blob * ar,
1394   const uschar * privkey)
1395 {
1396 gstring * arcset;
1397 arc_set * as;
1398 uschar * status = arc_ar_cv_status(ar);
1399 arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line));
1400 header_line * h = (header_line *)(al+1);
1401
1402 gstring * hdata = NULL;
1403 int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6);       /*XXX hardwired */
1404 blob sig;
1405
1406 /*
1407 - Generate AS
1408   - no body coverage
1409   - no h= tag; implicit coverage
1410   - arc status from A-R
1411     - if fail:
1412       - coverage is just the new ARC set
1413         including self (but with an empty b= in self)
1414     - if non-fail:
1415       - all ARC set headers, set-number order, aar then ams then as,
1416         including self (but with an empty b= in self)
1417 */
1418
1419 /* Construct the AS except for the signature */
1420
1421 arcset = string_append(NULL, 10,
1422           ARC_HDR_AS,
1423           US" i=", string_sprintf("%d", instance),
1424           US"; cv=", status,
1425           US"; a=rsa-sha256; d=", identity,                     /*XXX hardwired */
1426           US"; s=", selector,                                   /*XXX same as AMS */
1427           US";\r\n\t b=;");
1428
1429 h->slen = arcset->ptr;
1430 h->text = arcset->s;
1431 al->complete = h;
1432 ctx->arcset_chain_last->hdr_as = al;
1433
1434 /* For any but "fail" chain-verify status, walk the entire chain in order by
1435 instance.  For fail, only the new arc-set.  Accumulate the elements walked. */
1436
1437 for (as = Ustrcmp(status, US"fail") == 0
1438         ? ctx->arcset_chain_last : ctx->arcset_chain;
1439      as; as = as->next)
1440   {
1441   /* Accumulate AAR then AMS then AS.  Relaxed canonicalisation
1442   is required per standard. */
1443
1444   h = as->hdr_aar->complete;
1445   hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
1446   h = as->hdr_ams->complete;
1447   hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
1448   h = as->hdr_as->complete;
1449   hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, !!as->next));
1450   }
1451
1452 /* Calculate the signature from the accumulation */
1453
1454 if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AS"))
1455   return NULL;
1456
1457 /* Lose the trailing semicolon */
1458 arcset->ptr--;
1459 arcset = arc_sign_append_sig(arcset, &sig);
1460 DEBUG(D_transport) debug_printf("ARC: AS  '%.*s'\n", arcset->ptr - 2, arcset->s);
1461
1462 /* Finally, append the AMS and AAR to the new AS */
1463
1464 return string_catn(arcset, arcset_interim->s, arcset_interim->ptr);
1465 }
1466
1467
1468 /**************************************/
1469
1470 /* Return pointer to pdkim_bodyhash for given hash method, creating new
1471 method if needed.
1472 */
1473
1474 void *
1475 arc_ams_setup_sign_bodyhash(void)
1476 {
1477 int canon_head, canon_body;
1478
1479 DEBUG(D_transport) debug_printf("ARC: requesting bodyhash\n");
1480 pdkim_cstring_to_canons(US"relaxed", 7, &canon_head, &canon_body);      /*XXX hardwired */
1481 return pdkim_set_bodyhash(&dkim_sign_ctx,
1482         pdkim_hashname_to_hashtype(US"sha256", 6),                      /*XXX hardwired */
1483         canon_body,
1484         -1);
1485 }
1486
1487
1488
1489 void
1490 arc_sign_init(void)
1491 {
1492 memset(&arc_sign_ctx, 0, sizeof(arc_sign_ctx));
1493 }
1494
1495
1496
1497 /* A "normal" header line, identified by DKIM processing.  These arrive before
1498 the call to arc_sign(), which carries any newly-created DKIM headers - and
1499 those go textually before the normal ones in the message.
1500
1501 We have to take the feed from DKIM as, in the transport-filter case, the
1502 headers are not in memory at the time of the call to arc_sign().
1503
1504 Take a copy of the header and construct a reverse-order list.
1505 Also parse ARC-chain headers and build the chain struct, retaining pointers
1506 into the copies.
1507 */
1508
1509 static const uschar *
1510 arc_header_sign_feed(gstring * g)
1511 {
1512 uschar * s = string_copyn(g->s, g->ptr);
1513 headers_rlist = arc_rlist_entry(headers_rlist, s, g->ptr);
1514 return arc_try_header(&arc_sign_ctx, headers_rlist->h, TRUE);
1515 }
1516
1517
1518
1519 /* ARC signing.  Called from the smtp transport, if the arc_sign option is set.
1520 The dkim_exim_sign() function has already been called, so will have hashed the
1521 message body for us so long as we requested a hash previously.
1522
1523 Arguments:
1524   signspec      Three-element colon-sep list: identity, selector, privkey
1525                 Already expanded
1526   sigheaders    Any signature headers already generated, eg. by DKIM, or NULL
1527   errstr        Error string
1528
1529 Return value
1530   Set of headers to prepend to the message, including the supplied sigheaders
1531   but not the plainheaders.
1532 */
1533
1534 gstring *
1535 arc_sign(const uschar * signspec, gstring * sigheaders, uschar ** errstr)
1536 {
1537 const uschar * identity, * selector, * privkey;
1538 int sep = 0;
1539 header_line * headers;
1540 hdr_rlist * rheaders;
1541 blob ar;
1542 int instance;
1543 gstring * g = NULL;
1544 pdkim_bodyhash * b;
1545
1546 /* Parse the signing specification */
1547
1548 identity = string_nextinlist(&signspec, &sep, NULL, 0);
1549 selector = string_nextinlist(&signspec, &sep, NULL, 0);
1550 if (  !*identity | !*selector
1551    || !(privkey = string_nextinlist(&signspec, &sep, NULL, 0)) || !*privkey)
1552   {
1553   log_write(0, LOG_MAIN|LOG_PANIC, "ARC: bad signing-specification (%s)",
1554     !*identity ? "identity" : !*selector ? "selector" : "private-key");
1555   return NULL;
1556   }
1557 if (*privkey == '/' && !(privkey = expand_file_big_buffer(privkey)))
1558   return NULL;
1559
1560 DEBUG(D_transport) debug_printf("ARC: sign for %s\n", identity);
1561
1562 /* Make an rlist of any new DKIM headers, then add the "normals" rlist to it.
1563 Then scan the list for an A-R header. */
1564
1565 string_from_gstring(sigheaders);
1566 if ((rheaders = arc_sign_scan_headers(&arc_sign_ctx, sigheaders)))
1567   {
1568   hdr_rlist ** rp;
1569   for (rp = &rheaders; *rp; ) rp = &(*rp)->prev;
1570   *rp = headers_rlist;
1571   headers_rlist = rheaders;
1572   }
1573 else
1574   rheaders = headers_rlist;
1575
1576 /* Finally, build a normal-order headers list */
1577 /*XXX only needed for hunt-the-AR? */
1578   {
1579   header_line * hnext = NULL;
1580   for (; rheaders; hnext = rheaders->h, rheaders = rheaders->prev)
1581     rheaders->h->next = hnext;
1582   headers = hnext;
1583   }
1584
1585 if (!(arc_sign_find_ar(headers, identity, &ar)))
1586   {
1587   log_write(0, LOG_MAIN|LOG_PANIC, "ARC: no Authentication-Results header for signing");
1588   return sigheaders ? sigheaders : string_get(0);
1589   }
1590
1591 /* We previously built the data-struct for the existing ARC chain, if any, using a headers
1592 feed from the DKIM module.  Use that to give the instance number for the ARC set we are
1593 about to build. */
1594
1595 DEBUG(D_transport)
1596   if (arc_sign_ctx.arcset_chain_last)
1597     debug_printf("ARC: existing chain highest instance: %d\n",
1598       arc_sign_ctx.arcset_chain_last->instance);
1599   else
1600     debug_printf("ARC: no existing chain\n");
1601
1602 instance = arc_sign_ctx.arcset_chain_last ? arc_sign_ctx.arcset_chain_last->instance + 1 : 1;
1603
1604 /*
1605 - Generate AAR
1606   - copy the A-R; prepend i= & identity
1607 */
1608
1609 g = arc_sign_append_aar(g, &arc_sign_ctx, identity, instance, &ar);
1610
1611 /*
1612 - Generate AMS
1613   - Looks fairly like a DKIM sig
1614   - Cover all DKIM sig headers as well as the usuals
1615     - ? oversigning?
1616   - Covers the data
1617   - we must have requested a suitable bodyhash previously
1618 */
1619
1620 b = arc_ams_setup_sign_bodyhash();
1621 g = arc_sign_append_ams(g, &arc_sign_ctx, instance, identity, selector,
1622       &b->bh, headers_rlist, privkey);
1623
1624 /*
1625 - Generate AS
1626   - no body coverage
1627   - no h= tag; implicit coverage
1628   - arc status from A-R
1629     - if fail:
1630       - coverage is just the new ARC set
1631         including self (but with an empty b= in self)
1632     - if non-fail:
1633       - all ARC set headers, set-number order, aar then ams then as,
1634         including self (but with an empty b= in self)
1635 */
1636
1637 g = arc_sign_prepend_as(g, &arc_sign_ctx, instance, identity, selector, &ar, privkey);
1638
1639 /* Finally, append the dkim headers and return the lot. */
1640
1641 g = string_catn(g, sigheaders->s, sigheaders->ptr);
1642 (void) string_from_gstring(g);
1643 gstring_reset_unused(g);
1644 return g;
1645 }
1646
1647
1648 /******************************************************************************/
1649
1650 /* Check to see if the line is an AMS and if so, set up to validate it.
1651 Called from the DKIM input processing.  This must be done now as the message
1652 body data is hashed during input.
1653
1654 We call the DKIM code to request a body-hash; it has the facility already
1655 and the hash parameters might be common with other requests.
1656 */
1657
1658 static const uschar *
1659 arc_header_vfy_feed(gstring * g)
1660 {
1661 header_line h;
1662 arc_line al;
1663 pdkim_bodyhash * b;
1664 uschar * errstr;
1665
1666 if (!dkim_verify_ctx) return US"no dkim context";
1667
1668 if (strncmpic(ARC_HDR_AMS, g->s, ARC_HDRLEN_AMS) != 0) return US"not AMS";
1669
1670 DEBUG(D_receive) debug_printf("ARC: spotted AMS header\n");
1671 /* Parse the AMS header */
1672
1673 h.next = NULL;
1674 h.slen = g->size;
1675 h.text = g->s;
1676 memset(&al, 0, sizeof(arc_line));
1677 if ((errstr = arc_parse_line(&al, &h, ARC_HDRLEN_AMS, FALSE)))
1678   {
1679   DEBUG(D_acl) if (errstr) debug_printf("ARC: %s\n", errstr);
1680   return US"line parsing error";
1681   }
1682
1683 /* defaults */
1684 if (!al.c.data)
1685   {
1686   al.c_body.data = US"simple"; al.c_body.len = 6;
1687   al.c_head = al.c_body;
1688   }
1689
1690 /* Ask the dkim code to calc a bodyhash with those specs */
1691
1692 if (!(b = arc_ams_setup_vfy_bodyhash(&al)))
1693   return US"dkim hash setup fail";
1694
1695 /* Discard the reference; search again at verify time, knowing that one
1696 should have been created here. */
1697
1698 return NULL;
1699 }
1700
1701
1702
1703 /* A header line has been identified by DKIM processing.
1704
1705 Arguments:
1706   g             Header line
1707   is_vfy        TRUE for verify mode or FALSE for signing mode
1708
1709 Return:
1710   NULL for success, or an error string (probably unused)
1711 */
1712
1713 const uschar *
1714 arc_header_feed(gstring * g, BOOL is_vfy)
1715 {
1716 return is_vfy ? arc_header_vfy_feed(g) : arc_header_sign_feed(g);
1717 }
1718
1719
1720
1721 /******************************************************************************/
1722
1723 /* Construct an Authenticate-Results header portion, for the ARC module */
1724
1725 gstring *
1726 authres_arc(gstring * g)
1727 {
1728 if (arc_state)
1729   {
1730   arc_line * highest_ams;
1731   int start = 0;                /* Compiler quietening */
1732   DEBUG(D_acl) start = g->ptr;
1733
1734   g = string_append(g, 2, US";\n\tarc=", arc_state);
1735   if (arc_received_instance > 0)
1736     {
1737     g = string_append(g, 3, US" (i=",
1738       string_sprintf("%d", arc_received_instance), US")");
1739     if (arc_state_reason)
1740       g = string_append(g, 3, US"(", arc_state_reason, US")");
1741     g = string_catn(g, US" header.s=", 10);
1742     highest_ams = arc_received->hdr_ams;
1743     g = string_catn(g, highest_ams->s.data, highest_ams->s.len);
1744
1745     g = string_append(g, 2,
1746       US" arc.oldest-pass=", string_sprintf("%d", arc_oldest_pass));
1747
1748     if (sender_host_address)
1749       g = string_append(g, 2, US" smtp.client-ip=", sender_host_address);
1750     }
1751   else if (arc_state_reason)
1752     g = string_append(g, 3, US" (", arc_state_reason, US")");
1753   DEBUG(D_acl) debug_printf("ARC:  authres '%.*s'\n",
1754                   g->ptr - start - 3, g->s + start + 3);
1755   }
1756 else
1757   DEBUG(D_acl) debug_printf("ARC:  no authres\n");
1758 return g;
1759 }
1760
1761
1762 # endif /* SUPPORT_SPF */
1763 #endif /* EXPERIMENTAL_ARC */
1764 /* vi: aw ai sw=2
1765  */