ARC: fix signing when DKIM-signing is also being done
[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
764 if (!(as = ctx->arcset_chain))
765   return US"none";
766
767 for(inst = 0; as; as = as->next)
768   {
769   if (  as->instance != ++inst
770      || !as->hdr_aar || !as->hdr_ams || !as->hdr_as
771      || arc_cv_match(as->hdr_as, US"fail")
772      )
773     {
774     arc_state_reason = string_sprintf("i=%d"
775       " (cv, sequence or missing header)", as->instance);
776     DEBUG(D_acl) debug_printf("ARC chain fail at %s\n", arc_state_reason);
777     return US"fail";
778     }
779
780   /* Evaluate the oldest-pass AMS validation while we're here.
781   It does not affect the AS chain validation but is reported as
782   auxilary info. */
783
784   if (!ams_fail_found)
785     if (arc_ams_verify(ctx, as))
786       ams_fail_found = TRUE;
787     else
788       arc_oldest_pass = inst;
789   arc_state_reason = NULL;
790   }
791
792 arc_received = ctx->arcset_chain_last;
793 arc_received_instance = inst;
794
795 /* We can skip the latest-AMS validation, if we already did it. */
796
797 as = ctx->arcset_chain_last;
798 if (!as->ams_verify_passed)
799   {
800   if (as->ams_verify_done)
801     {
802     arc_state_reason = as->ams_verify_done;
803     return US"fail";
804     }
805   if (!!arc_ams_verify(ctx, as))
806     return US"fail";
807   }
808 return NULL;
809 }
810
811
812 /******************************************************************************/
813 static const uschar *
814 arc_seal_verify(arc_ctx * ctx, arc_set * as)
815 {
816 arc_line * hdr_as = as->hdr_as;
817 arc_set * as2;
818 int hashtype;
819 hctx hhash_ctx;
820 blob hhash_computed;
821 blob sighash;
822 ev_ctx vctx;
823 pdkim_pubkey * p;
824 const uschar * errstr;
825
826 DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d\n", as->instance);
827 /*
828        1.  If the value of the "cv" tag on that seal is "fail", the
829            chain state is "fail" and the algorithm stops here.  (This
830            step SHOULD be skipped if the earlier step (2.1) was
831            performed) [it was]
832
833        2.  In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
834            == "none" && i != 1)) then the chain state is "fail" and the
835            algorithm stops here (note that the ordering of the logic is
836            structured for short-circuit evaluation).
837 */
838
839 if (  as->instance == 1 && !arc_cv_match(hdr_as, US"none")
840    || arc_cv_match(hdr_as, US"none") && as->instance != 1
841    )
842   {
843   arc_state_reason = US"seal cv state";
844   return US"fail";
845   }
846
847 /*
848        3.  Initialize a hash function corresponding to the "a" tag of
849            the ARC-Seal.
850 */
851
852 hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len);
853
854 if (!exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
855   {
856   DEBUG(D_acl)
857       debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
858   arc_state_reason = US"seal hash setup error";
859   return US"fail";
860   }
861
862 /*
863        4.  Compute the canonicalized form of the ARC header fields, in
864            the order described in Section 5.4.2, using the "relaxed"
865            header canonicalization defined in Section 3.4.2 of
866            [RFC6376].  Pass the canonicalized result to the hash
867            function.
868
869 Headers are CRLF-separated, but the last one is not crlf-terminated.
870 */
871
872 DEBUG(D_acl) debug_printf("ARC: AS header data for verification:\n");
873 for (as2 = ctx->arcset_chain;
874      as2 && as2->instance <= as->instance;
875      as2 = as2->next)
876   {
877   arc_line * al;
878   uschar * s;
879   int len;
880
881   al = as2->hdr_aar;
882   if (!(s = al->relaxed))
883     al->relaxed = s = pdkim_relax_header_n(al->complete->text,
884                                             al->complete->slen, TRUE);
885   len = Ustrlen(s);
886   DEBUG(D_acl) pdkim_quoteprint(s, len);
887   exim_sha_update(&hhash_ctx, s, len);
888
889   al = as2->hdr_ams;
890   if (!(s = al->relaxed))
891     al->relaxed = s = pdkim_relax_header_n(al->complete->text,
892                                             al->complete->slen, TRUE);
893   len = Ustrlen(s);
894   DEBUG(D_acl) pdkim_quoteprint(s, len);
895   exim_sha_update(&hhash_ctx, s, len);
896
897   al = as2->hdr_as;
898   if (as2->instance == as->instance)
899     s = pdkim_relax_header_n(al->rawsig_no_b_val.data,
900                                         al->rawsig_no_b_val.len, FALSE);
901   else if (!(s = al->relaxed))
902     al->relaxed = s = pdkim_relax_header_n(al->complete->text,
903                                             al->complete->slen, TRUE);
904   len = Ustrlen(s);
905   DEBUG(D_acl) pdkim_quoteprint(s, len);
906   exim_sha_update(&hhash_ctx, s, len);
907   }
908
909 /*
910        5.  Retrieve the final digest from the hash function.
911 */
912
913 exim_sha_finish(&hhash_ctx, &hhash_computed);
914 DEBUG(D_acl)
915   {
916   debug_printf("ARC i=%d AS Header %.*s computed: ",
917     as->instance, (int)hdr_as->a_hash.len, hdr_as->a_hash.data);
918   pdkim_hexprint(hhash_computed.data, hhash_computed.len);
919   }
920
921
922 /*
923        6.  Retrieve the public key identified by the "s" and "d" tags in
924            the ARC-Seal, as described in Section 4.1.6.
925 */
926
927 if (!(p = arc_line_to_pubkey(hdr_as)))
928   return US"pubkey problem";
929
930 /*
931        7.  Determine whether the signature portion ("b" tag) of the ARC-
932            Seal and the digest computed above are valid according to the
933            public key.  (See also Section Section 8.4 for failure case
934            handling)
935
936        8.  If the signature is not valid, the chain state is "fail" and
937            the algorithm stops here.
938 */
939
940 /* We know the b-tag blob is of a nul-term string, so safe as a string */
941 pdkim_decode_base64(hdr_as->b.data, &sighash);
942
943 if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx)))
944   {
945   DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
946   return US"fail";
947   }
948
949 hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len);
950
951 if ((errstr = exim_dkim_verify(&vctx,
952               pdkim_hashes[hashtype].exim_hashmethod,
953               &hhash_computed, &sighash)))
954   {
955   DEBUG(D_acl)
956     debug_printf("ARC i=%d AS headers verify: %s\n", as->instance, errstr);
957   arc_state_reason = US"seal sigverify error";
958   return US"fail";
959   }
960
961 DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d pass\n", as->instance);
962 return NULL;
963 }
964
965
966 static const uschar *
967 arc_verify_seals(arc_ctx * ctx)
968 {
969 arc_set * as = ctx->arcset_chain;
970
971 if (!as)
972   return US"none";
973
974 while (as)
975   {
976   if (arc_seal_verify(ctx, as)) return US"fail";
977   as = as->next;
978   }
979 DEBUG(D_acl) debug_printf("ARC: AS vfy overall pass\n");
980 return NULL;
981 }
982 /******************************************************************************/
983
984 /* Do ARC verification.  Called from DATA ACL, on a verify = arc
985 condition.  No arguments; we are checking globals.
986
987 Return:  The ARC state, or NULL on error.
988 */
989
990 const uschar *
991 acl_verify_arc(void)
992 {
993 arc_ctx ctx = { NULL };
994 const uschar * res;
995
996 if (!dkim_verify_ctx)
997   {
998   DEBUG(D_acl) debug_printf("ARC: no DKIM verify context\n");
999   return NULL;
1000   }
1001
1002 /* AS evaluation, per
1003 https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-10#section-6
1004 */
1005 /* 1.  Collect all ARC sets currently on the message.  If there were
1006        none, the ARC state is "none" and the algorithm stops here.
1007 */
1008
1009 if ((res = arc_vfy_collect_hdrs(&ctx)))
1010   goto out;
1011
1012 /* 2.  If the form of any ARC set is invalid (e.g., does not contain
1013        exactly one of each of the three ARC-specific header fields),
1014        then the chain state is "fail" and the algorithm stops here.
1015
1016        1.  To avoid the overhead of unnecessary computation and delay
1017            from crypto and DNS operations, the cv value for all ARC-
1018            Seal(s) MAY be checked at this point.  If any of the values
1019            are "fail", then the overall state of the chain is "fail" and
1020            the algorithm stops here.
1021
1022    3.  Conduct verification of the ARC-Message-Signature header field
1023        bearing the highest instance number.  If this verification fails,
1024        then the chain state is "fail" and the algorithm stops here.
1025 */
1026
1027 if ((res = arc_headers_check(&ctx)))
1028   goto out;
1029
1030 /* 4.  For each ARC-Seal from the "N"th instance to the first, apply the
1031        following logic:
1032
1033        1.  If the value of the "cv" tag on that seal is "fail", the
1034            chain state is "fail" and the algorithm stops here.  (This
1035            step SHOULD be skipped if the earlier step (2.1) was
1036            performed)
1037
1038        2.  In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
1039            == "none" && i != 1)) then the chain state is "fail" and the
1040            algorithm stops here (note that the ordering of the logic is
1041            structured for short-circuit evaluation).
1042
1043        3.  Initialize a hash function corresponding to the "a" tag of
1044            the ARC-Seal.
1045
1046        4.  Compute the canonicalized form of the ARC header fields, in
1047            the order described in Section 5.4.2, using the "relaxed"
1048            header canonicalization defined in Section 3.4.2 of
1049            [RFC6376].  Pass the canonicalized result to the hash
1050            function.
1051
1052        5.  Retrieve the final digest from the hash function.
1053
1054        6.  Retrieve the public key identified by the "s" and "d" tags in
1055            the ARC-Seal, as described in Section 4.1.6.
1056
1057        7.  Determine whether the signature portion ("b" tag) of the ARC-
1058            Seal and the digest computed above are valid according to the
1059            public key.  (See also Section Section 8.4 for failure case
1060            handling)
1061
1062        8.  If the signature is not valid, the chain state is "fail" and
1063            the algorithm stops here.
1064
1065    5.  If all seals pass validation, then the chain state is "pass", and
1066        the algorithm is complete.
1067 */
1068
1069 if ((res = arc_verify_seals(&ctx)))
1070   goto out;
1071
1072 res = US"pass";
1073
1074 out:
1075   return res;
1076 }
1077
1078 /******************************************************************************/
1079
1080 /* Prepend the header to the rlist */
1081
1082 static hdr_rlist *
1083 arc_rlist_entry(hdr_rlist * list, const uschar * s, int len)
1084 {
1085 hdr_rlist * r = store_get(sizeof(hdr_rlist) + sizeof(header_line));
1086 header_line * h = r->h = (header_line *)(r+1);
1087
1088 r->prev = list;
1089 r->used = FALSE;
1090 h->next = NULL;
1091 h->type = 0;
1092 h->slen = len;
1093 h->text = US s;
1094
1095 /* This works for either NL or CRLF lines; also nul-termination */
1096 while (*++s)
1097   if (*s == '\n' && s[1] != '\t' && s[1] != ' ') break;
1098 s++;            /* move past end of line */
1099
1100 return r;
1101 }
1102
1103
1104 /* Walk the given headers strings identifying each header, and construct
1105 a reverse-order list.
1106 */
1107
1108 static hdr_rlist *
1109 arc_sign_scan_headers(arc_ctx * ctx, gstring * sigheaders)
1110 {
1111 const uschar * s;
1112 hdr_rlist * rheaders = NULL;
1113
1114 s = sigheaders ? sigheaders->s : NULL;
1115 if (s) while (*s)
1116   {
1117   const uschar * s2 = s;
1118
1119   /* This works for either NL or CRLF lines; also nul-termination */
1120   while (*++s2)
1121     if (*s2 == '\n' && s2[1] != '\t' && s2[1] != ' ') break;
1122   s2++;         /* move past end of line */
1123
1124   rheaders = arc_rlist_entry(rheaders, s, s2 - s);
1125   s = s2;
1126   }
1127 return rheaders;
1128 }
1129
1130
1131
1132 /* Return the A-R content, without identity, with line-ending and
1133 NUL termination. */
1134
1135 static BOOL
1136 arc_sign_find_ar(header_line * headers, const uschar * identity, blob * ret)
1137 {
1138 header_line * h;
1139 int ilen = Ustrlen(identity);
1140
1141 ret->data = NULL;
1142 for(h = headers; h; h = h->next)
1143   {
1144   uschar * s = h->text, c;
1145   int len = h->slen;
1146
1147   if (Ustrncmp(s, HDR_AR, HDRLEN_AR) != 0) continue;
1148   s += HDRLEN_AR, len -= HDRLEN_AR;             /* header name */
1149   while (  len > 0
1150         && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
1151     s++, len--;                                 /* FWS */
1152   if (Ustrncmp(s, identity, ilen) != 0) continue;
1153   s += ilen; len -= ilen;                       /* identity */
1154   if (len <= 0) continue;
1155   if ((c = *s) && c == ';') s++, len--;         /* identity terminator */
1156   while (  len > 0
1157         && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
1158     s++, len--;                                 /* FWS */
1159   if (len <= 0) continue;
1160   ret->data = s;
1161   ret->len = len;
1162   return TRUE;
1163   }
1164 return FALSE;
1165 }
1166
1167
1168
1169 /* Append a constructed AAR including CRLF.  Add it to the arc_ctx too.  */
1170
1171 static gstring *
1172 arc_sign_append_aar(gstring * g, arc_ctx * ctx,
1173   const uschar * identity, int instance, blob * ar)
1174 {
1175 int aar_off = g ? g->ptr : 0;
1176 arc_set * as = store_get(sizeof(arc_set) + sizeof(arc_line) + sizeof(header_line));
1177 arc_line * al = (arc_line *)(as+1);
1178 header_line * h = (header_line *)(al+1);
1179
1180 g = string_catn(g, ARC_HDR_AAR, ARC_HDRLEN_AAR);
1181 g = string_cat(g, string_sprintf(" i=%d; %s;\r\n\t", instance, identity));
1182 g = string_catn(g, US ar->data, ar->len);
1183
1184 h->slen = g->ptr - aar_off;
1185 h->text = g->s + aar_off;
1186 al->complete = h;
1187 as->next = NULL;
1188 as->prev = ctx->arcset_chain_last;
1189 as->instance = instance;
1190 as->hdr_aar = al;
1191 if (instance == 1)
1192   ctx->arcset_chain = as;
1193 else
1194   ctx->arcset_chain_last->next = as;
1195 ctx->arcset_chain_last = as;
1196
1197 DEBUG(D_transport) debug_printf("ARC: AAR '%.*s'\n", h->slen - 2, h->text);
1198 return g;
1199 }
1200
1201
1202
1203 static BOOL
1204 arc_sig_from_pseudoheader(gstring * hdata, int hashtype, const uschar * privkey,
1205   blob * sig, const uschar * why)
1206 {
1207 hashmethod hm = /*sig->keytype == KEYTYPE_ED25519*/ FALSE
1208   ? HASH_SHA2_512 : pdkim_hashes[hashtype].exim_hashmethod;
1209 blob hhash;
1210 es_ctx sctx;
1211 const uschar * errstr;
1212
1213 DEBUG(D_transport)
1214   {
1215   hctx hhash_ctx;
1216   debug_printf("ARC: %s header data for signing:\n", why);
1217   pdkim_quoteprint(hdata->s, hdata->ptr);
1218
1219   (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
1220   exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
1221   exim_sha_finish(&hhash_ctx, &hhash);
1222   debug_printf("ARC: header hash: "); pdkim_hexprint(hhash.data, hhash.len);
1223   }
1224
1225 if (FALSE /*need hash for Ed25519 or GCrypt signing*/ )
1226   {
1227   hctx hhash_ctx;
1228   (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
1229   exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
1230   exim_sha_finish(&hhash_ctx, &hhash);
1231   }
1232 else
1233   {
1234   hhash.data = hdata->s;
1235   hhash.len = hdata->ptr;
1236   }
1237
1238 if (  (errstr = exim_dkim_signing_init(privkey, &sctx))
1239    || (errstr = exim_dkim_sign(&sctx, hm, &hhash, sig)))
1240   {
1241   log_write(0, LOG_MAIN, "ARC: %s signing: %s\n", why, errstr);
1242   return FALSE;
1243   }
1244 return TRUE;
1245 }
1246
1247
1248
1249 static gstring *
1250 arc_sign_append_sig(gstring * g, blob * sig)
1251 {
1252 /*debug_printf("%s: raw sig ", __FUNCTION__); pdkim_hexprint(sig->data, sig->len);*/
1253 sig->data = pdkim_encode_base64(sig);
1254 sig->len = Ustrlen(sig->data);
1255 for (;;)
1256   {
1257   int len = MIN(sig->len, 74);
1258   g = string_catn(g, sig->data, len);
1259   if ((sig->len -= len) == 0) break;
1260   sig->data += len;
1261   g = string_catn(g, US"\r\n\t  ", 5);
1262   }
1263 g = string_catn(g, US";\r\n", 3);
1264 gstring_reset_unused(g);
1265 string_from_gstring(g);
1266 return g;
1267 }
1268
1269
1270 /* Append a constructed AMS including CRLF.  Add it to the arc_ctx too. */
1271
1272 static gstring *
1273 arc_sign_append_ams(gstring * g, arc_ctx * ctx, int instance,
1274   const uschar * identity, const uschar * selector, blob * bodyhash,
1275   hdr_rlist * rheaders, const uschar * privkey)
1276 {
1277 uschar * s;
1278 gstring * hdata = NULL;
1279 int col;
1280 int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6);       /*XXX hardwired */
1281 blob sig;
1282 int ams_off;
1283 arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line));
1284 header_line * h = (header_line *)(al+1);
1285
1286 /* debug_printf("%s\n", __FUNCTION__); */
1287
1288 /* Construct the to-be-signed AMS pseudo-header: everything but the sig. */
1289
1290 ams_off = g->ptr;
1291 g = string_append(g, 10,
1292       ARC_HDR_AMS,
1293       US" i=", string_sprintf("%d", instance),
1294       US"; a=rsa-sha256; c=relaxed; d=", identity,              /*XXX hardwired */
1295       US"; s=", selector,
1296       US";\r\n\tbh=", pdkim_encode_base64(bodyhash),
1297       US";\r\n\th=");
1298
1299 for(col = 3; rheaders; rheaders = rheaders->prev)
1300   {
1301   const uschar * hnames = US"DKIM-Signature:" PDKIM_DEFAULT_SIGN_HEADERS;
1302   uschar * name, * htext = rheaders->h->text;
1303   int sep = ':';
1304
1305   /* Spot headers of interest */
1306
1307   while ((name = string_nextinlist(&hnames, &sep, NULL, 0)))
1308     {
1309     int len = Ustrlen(name);
1310     if (strncasecmp(CCS htext, CCS name, len) == 0)
1311       {
1312       /* If too long, fold line in h= field */
1313
1314       if (col + len > 78) g = string_catn(g, US"\r\n\t  ", 5), col = 3;
1315
1316       /* Add name to h= list */
1317
1318       g = string_catn(g, name, len);
1319       g = string_catn(g, US":", 1);
1320       col += len + 1;
1321
1322       /* Accumulate header for hashing/signing */
1323
1324       hdata = string_cat(hdata,
1325                 pdkim_relax_header_n(htext, rheaders->h->slen, TRUE));  /*XXX hardwired */
1326       break;
1327       }
1328     }
1329   }
1330
1331 /* Lose the last colon from the h= list */
1332
1333 if (g->s[g->ptr - 1] == ':') g->ptr--;
1334
1335 g = string_catn(g, US";\r\n\tb=;", 7);
1336
1337 /* Include the pseudo-header in the accumulation */
1338
1339 s = pdkim_relax_header_n(g->s + ams_off, g->ptr - ams_off, FALSE);
1340 hdata = string_cat(hdata, s);
1341
1342 /* Calculate the signature from the accumulation */
1343 /*XXX does that need further relaxation? there are spaces embedded in the b= strings! */
1344
1345 if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AMS"))
1346   return NULL;
1347
1348 /* Lose the trailing semicolon from the psuedo-header, and append the signature
1349 (folded over lines) and termination to complete it. */
1350
1351 g->ptr--;
1352 g = arc_sign_append_sig(g, &sig);
1353
1354 h->slen = g->ptr - ams_off;
1355 h->text = g->s + ams_off;
1356 al->complete = h;
1357 ctx->arcset_chain_last->hdr_ams = al;
1358
1359 DEBUG(D_transport) debug_printf("ARC: AMS '%.*s'\n", h->slen - 2, h->text);
1360 return g;
1361 }
1362
1363
1364
1365 /* Look for an arc= result in an A-R header blob.  We know that its data
1366 happens to be a NUL-term string. */
1367
1368 static uschar *
1369 arc_ar_cv_status(blob * ar)
1370 {
1371 const uschar * resinfo = ar->data;
1372 int sep = ';';
1373 uschar * methodspec, * s;
1374
1375 while ((methodspec = string_nextinlist(&resinfo, &sep, NULL, 0)))
1376   if (Ustrncmp(methodspec, US"arc=", 4) == 0)
1377     {
1378     uschar c;
1379     for (s = methodspec += 4;
1380          (c = *s) && c != ';' && c != ' ' && c != '\r' && c != '\n'; ) s++;
1381     return string_copyn(methodspec, s - methodspec);
1382     }
1383 return US"none";
1384 }
1385
1386
1387
1388 /* Build the AS header and prepend it */
1389
1390 static gstring *
1391 arc_sign_prepend_as(gstring * arcset_interim, arc_ctx * ctx,
1392   int instance, const uschar * identity, const uschar * selector, blob * ar,
1393   const uschar * privkey)
1394 {
1395 gstring * arcset;
1396 arc_set * as;
1397 uschar * status = arc_ar_cv_status(ar);
1398 arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line));
1399 header_line * h = (header_line *)(al+1);
1400
1401 gstring * hdata = NULL;
1402 int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6);       /*XXX hardwired */
1403 blob sig;
1404
1405 /*
1406 - Generate AS
1407   - no body coverage
1408   - no h= tag; implicit coverage
1409   - arc status from A-R
1410     - if fail:
1411       - coverage is just the new ARC set
1412         including self (but with an empty b= in self)
1413     - if non-fail:
1414       - all ARC set headers, set-number order, aar then ams then as,
1415         including self (but with an empty b= in self)
1416 */
1417
1418 /* Construct the AS except for the signature */
1419
1420 arcset = string_append(NULL, 10,
1421           ARC_HDR_AS,
1422           US" i=", string_sprintf("%d", instance),
1423           US"; cv=", status,
1424           US"; a=rsa-sha256; d=", identity,                     /*XXX hardwired */
1425           US"; s=", selector,                                   /*XXX same as AMS */
1426           US";\r\n\t b=;");
1427
1428 h->slen = arcset->ptr;
1429 h->text = arcset->s;
1430 al->complete = h;
1431 ctx->arcset_chain_last->hdr_as = al;
1432
1433 /* For any but "fail" chain-verify status, walk the entire chain in order by
1434 instance.  For fail, only the new arc-set.  Accumulate the elements walked. */
1435
1436 for (as = Ustrcmp(status, US"fail") == 0
1437         ? ctx->arcset_chain_last : ctx->arcset_chain;
1438      as; as = as->next)
1439   {
1440   /* Accumulate AAR then AMS then AS.  Relaxed canonicalisation
1441   is required per standard. */
1442
1443   h = as->hdr_aar->complete;
1444   hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
1445   h = as->hdr_ams->complete;
1446   hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
1447   h = as->hdr_as->complete;
1448   hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, !!as->next));
1449   }
1450
1451 /* Calculate the signature from the accumulation */
1452
1453 if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AS"))
1454   return NULL;
1455
1456 /* Lose the trailing semicolon */
1457 arcset->ptr--;
1458 arcset = arc_sign_append_sig(arcset, &sig);
1459 DEBUG(D_transport) debug_printf("ARC: AS  '%.*s'\n", arcset->ptr - 2, arcset->s);
1460
1461 /* Finally, append the AMS and AAR to the new AS */
1462
1463 return string_catn(arcset, arcset_interim->s, arcset_interim->ptr);
1464 }
1465
1466
1467 /**************************************/
1468
1469 /* Return pointer to pdkim_bodyhash for given hash method, creating new
1470 method if needed.
1471 */
1472
1473 void *
1474 arc_ams_setup_sign_bodyhash(void)
1475 {
1476 int canon_head, canon_body;
1477
1478 DEBUG(D_transport) debug_printf("ARC: requesting bodyhash\n");
1479 pdkim_cstring_to_canons(US"relaxed", 7, &canon_head, &canon_body);      /*XXX hardwired */
1480 return pdkim_set_bodyhash(&dkim_sign_ctx,
1481         pdkim_hashname_to_hashtype(US"sha256", 6),                      /*XXX hardwired */
1482         canon_body,
1483         -1);
1484 }
1485
1486
1487
1488 void
1489 arc_sign_init(void)
1490 {
1491 memset(&arc_sign_ctx, 0, sizeof(arc_sign_ctx));
1492 }
1493
1494
1495
1496 /* A "normal" header line, identified by DKIM processing.  These arrive before
1497 the call to arc_sign(), which carries any newly-created DKIM headers - and
1498 those go textually before the normal ones in the message.
1499
1500 We have to take the feed from DKIM as, in the transport-filter case, the
1501 headers are not in memory at the time of the call to arc_sign().
1502
1503 Take a copy of the header and construct a reverse-order list.
1504 Also parse ARC-chain headers and build the chain struct, retaining pointers
1505 into the copies.
1506 */
1507
1508 static const uschar *
1509 arc_header_sign_feed(gstring * g)
1510 {
1511 uschar * s = string_copyn(g->s, g->ptr);
1512 headers_rlist = arc_rlist_entry(headers_rlist, s, g->ptr);
1513 return arc_try_header(&arc_sign_ctx, headers_rlist->h, TRUE);
1514 }
1515
1516
1517
1518 /* ARC signing.  Called from the smtp transport, if the arc_sign option is set.
1519 The dkim_exim_sign() function has already been called, so will have hashed the
1520 message body for us so long as we requested a hash previously.
1521
1522 Arguments:
1523   signspec      Three-element colon-sep list: identity, selector, privkey
1524                 Already expanded
1525   sigheaders    Any signature headers already generated, eg. by DKIM, or NULL
1526   errstr        Error string
1527
1528 Return value
1529   Set of headers to prepend to the message, including the supplied sigheaders
1530   but not the plainheaders.
1531 */
1532
1533 gstring *
1534 arc_sign(const uschar * signspec, gstring * sigheaders, uschar ** errstr)
1535 {
1536 const uschar * identity, * selector, * privkey;
1537 int sep = 0;
1538 header_line * headers;
1539 hdr_rlist * rheaders;
1540 blob ar;
1541 int instance;
1542 gstring * g = NULL;
1543 pdkim_bodyhash * b;
1544
1545 /* Parse the signing specification */
1546
1547 identity = string_nextinlist(&signspec, &sep, NULL, 0);
1548 selector = string_nextinlist(&signspec, &sep, NULL, 0);
1549 if (  !*identity | !*selector
1550    || !(privkey = string_nextinlist(&signspec, &sep, NULL, 0)) || !*privkey)
1551   {
1552   log_write(0, LOG_MAIN, "ARC: bad signing-specification (%s)",
1553     !*identity ? "identity" : !*selector ? "selector" : "private-key");
1554   return sigheaders ? sigheaders : string_get(0);
1555   }
1556 if (*privkey == '/' && !(privkey = expand_file_big_buffer(privkey)))
1557   return sigheaders ? sigheaders : string_get(0);
1558
1559 DEBUG(D_transport) debug_printf("ARC: sign for %s\n", identity);
1560
1561 /* Make an rlist of any new DKIM headers, then add the "normals" rlist to it.
1562 Then scan the list for an A-R header. */
1563
1564 string_from_gstring(sigheaders);
1565 if ((rheaders = arc_sign_scan_headers(&arc_sign_ctx, sigheaders)))
1566   {
1567   hdr_rlist ** rp;
1568   for (rp = &headers_rlist; *rp; ) rp = &(*rp)->prev;
1569   *rp = rheaders;
1570   }
1571
1572 /* Finally, build a normal-order headers list */
1573 /*XXX only needed for hunt-the-AR? */
1574 /*XXX also, we really should be accepting any number of ADMD-matching ARs */
1575   {
1576   header_line * hnext = NULL;
1577   for (rheaders = headers_rlist; rheaders;
1578        hnext = rheaders->h, rheaders = rheaders->prev)
1579     rheaders->h->next = hnext;
1580   headers = hnext;
1581   }
1582
1583 if (!(arc_sign_find_ar(headers, identity, &ar)))
1584   {
1585   log_write(0, LOG_MAIN, "ARC: no Authentication-Results header for signing");
1586   return sigheaders ? sigheaders : string_get(0);
1587   }
1588
1589 /* We previously built the data-struct for the existing ARC chain, if any, using a headers
1590 feed from the DKIM module.  Use that to give the instance number for the ARC set we are
1591 about to build. */
1592
1593 DEBUG(D_transport)
1594   if (arc_sign_ctx.arcset_chain_last)
1595     debug_printf("ARC: existing chain highest instance: %d\n",
1596       arc_sign_ctx.arcset_chain_last->instance);
1597   else
1598     debug_printf("ARC: no existing chain\n");
1599
1600 instance = arc_sign_ctx.arcset_chain_last ? arc_sign_ctx.arcset_chain_last->instance + 1 : 1;
1601
1602 /*
1603 - Generate AAR
1604   - copy the A-R; prepend i= & identity
1605 */
1606
1607 g = arc_sign_append_aar(g, &arc_sign_ctx, identity, instance, &ar);
1608
1609 /*
1610 - Generate AMS
1611   - Looks fairly like a DKIM sig
1612   - Cover all DKIM sig headers as well as the usuals
1613     - ? oversigning?
1614   - Covers the data
1615   - we must have requested a suitable bodyhash previously
1616 */
1617
1618 b = arc_ams_setup_sign_bodyhash();
1619 g = arc_sign_append_ams(g, &arc_sign_ctx, instance, identity, selector,
1620       &b->bh, headers_rlist, privkey);
1621
1622 /*
1623 - Generate AS
1624   - no body coverage
1625   - no h= tag; implicit coverage
1626   - arc status from A-R
1627     - if fail:
1628       - coverage is just the new ARC set
1629         including self (but with an empty b= in self)
1630     - if non-fail:
1631       - all ARC set headers, set-number order, aar then ams then as,
1632         including self (but with an empty b= in self)
1633 */
1634
1635 g = arc_sign_prepend_as(g, &arc_sign_ctx, instance, identity, selector, &ar, privkey);
1636
1637 /* Finally, append the dkim headers and return the lot. */
1638
1639 g = string_catn(g, sigheaders->s, sigheaders->ptr);
1640 (void) string_from_gstring(g);
1641 gstring_reset_unused(g);
1642 return g;
1643 }
1644
1645
1646 /******************************************************************************/
1647
1648 /* Check to see if the line is an AMS and if so, set up to validate it.
1649 Called from the DKIM input processing.  This must be done now as the message
1650 body data is hashed during input.
1651
1652 We call the DKIM code to request a body-hash; it has the facility already
1653 and the hash parameters might be common with other requests.
1654 */
1655
1656 static const uschar *
1657 arc_header_vfy_feed(gstring * g)
1658 {
1659 header_line h;
1660 arc_line al;
1661 pdkim_bodyhash * b;
1662 uschar * errstr;
1663
1664 if (!dkim_verify_ctx) return US"no dkim context";
1665
1666 if (strncmpic(ARC_HDR_AMS, g->s, ARC_HDRLEN_AMS) != 0) return US"not AMS";
1667
1668 DEBUG(D_receive) debug_printf("ARC: spotted AMS header\n");
1669 /* Parse the AMS header */
1670
1671 h.next = NULL;
1672 h.slen = g->size;
1673 h.text = g->s;
1674 memset(&al, 0, sizeof(arc_line));
1675 if ((errstr = arc_parse_line(&al, &h, ARC_HDRLEN_AMS, FALSE)))
1676   {
1677   DEBUG(D_acl) if (errstr) debug_printf("ARC: %s\n", errstr);
1678   return US"line parsing error";
1679   }
1680
1681 /* defaults */
1682 if (!al.c.data)
1683   {
1684   al.c_body.data = US"simple"; al.c_body.len = 6;
1685   al.c_head = al.c_body;
1686   }
1687
1688 /* Ask the dkim code to calc a bodyhash with those specs */
1689
1690 if (!(b = arc_ams_setup_vfy_bodyhash(&al)))
1691   return US"dkim hash setup fail";
1692
1693 /* Discard the reference; search again at verify time, knowing that one
1694 should have been created here. */
1695
1696 return NULL;
1697 }
1698
1699
1700
1701 /* A header line has been identified by DKIM processing.
1702
1703 Arguments:
1704   g             Header line
1705   is_vfy        TRUE for verify mode or FALSE for signing mode
1706
1707 Return:
1708   NULL for success, or an error string (probably unused)
1709 */
1710
1711 const uschar *
1712 arc_header_feed(gstring * g, BOOL is_vfy)
1713 {
1714 return is_vfy ? arc_header_vfy_feed(g) : arc_header_sign_feed(g);
1715 }
1716
1717
1718
1719 /******************************************************************************/
1720
1721 /* Construct an Authenticate-Results header portion, for the ARC module */
1722
1723 gstring *
1724 authres_arc(gstring * g)
1725 {
1726 if (arc_state)
1727   {
1728   arc_line * highest_ams;
1729   int start = 0;                /* Compiler quietening */
1730   DEBUG(D_acl) start = g->ptr;
1731
1732   g = string_append(g, 2, US";\n\tarc=", arc_state);
1733   if (arc_received_instance > 0)
1734     {
1735     g = string_append(g, 3, US" (i=",
1736       string_sprintf("%d", arc_received_instance), US")");
1737     if (arc_state_reason)
1738       g = string_append(g, 3, US"(", arc_state_reason, US")");
1739     g = string_catn(g, US" header.s=", 10);
1740     highest_ams = arc_received->hdr_ams;
1741     g = string_catn(g, highest_ams->s.data, highest_ams->s.len);
1742
1743     g = string_append(g, 2,
1744       US" arc.oldest-pass=", string_sprintf("%d", arc_oldest_pass));
1745
1746     if (sender_host_address)
1747       g = string_append(g, 2, US" smtp.client-ip=", sender_host_address);
1748     }
1749   else if (arc_state_reason)
1750     g = string_append(g, 3, US" (", arc_state_reason, US")");
1751   DEBUG(D_acl) debug_printf("ARC:  authres '%.*s'\n",
1752                   g->ptr - start - 3, g->s + start + 3);
1753   }
1754 else
1755   DEBUG(D_acl) debug_printf("ARC:  no authres\n");
1756 return g;
1757 }
1758
1759
1760 # endif /* SUPPORT_SPF */
1761 #endif /* EXPERIMENTAL_ARC */
1762 /* vi: aw ai sw=2
1763  */