06ca9373616f1eedefb7afc80ea497e7b884c447
[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 pdkim_cstring_to_canons(ams->c.data, ams->c.len, &canon_head, &canon_body);
635 bodylen = ams->l.data
636 ? strtol(CS string_copyn(ams->l.data, ams->l.len), NULL, 10) : -1;
637
638 return pdkim_set_bodyhash(dkim_verify_ctx,
639 pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len),
640 canon_body,
641 bodylen);
642 }
643
644
645
646 /* Verify an AMS. This is a DKIM-sig header, but with an ARC i= tag
647 and without a DKIM v= tag.
648 */
649
650 static const uschar *
651 arc_ams_verify(arc_ctx * ctx, arc_set * as)
652 {
653 arc_line * ams = as->hdr_ams;
654 pdkim_bodyhash * b;
655 pdkim_pubkey * p;
656 blob sighash;
657 blob hhash;
658 ev_ctx vctx;
659 int hashtype;
660 const uschar * errstr;
661
662 as->ams_verify_done = US"in-progress";
663
664 /* Check the AMS has all the required tags:
665 "a=" algorithm
666 "b=" signature
667 "bh=" body hash
668 "d=" domain (for key lookup)
669 "h=" headers (included in signature)
670 "s=" key-selector (for key lookup)
671 */
672 if ( !ams->a.data || !ams->b.data || !ams->bh.data || !ams->d.data
673 || !ams->h.data || !ams->s.data)
674 {
675 as->ams_verify_done = arc_state_reason = US"required tag missing";
676 return US"fail";
677 }
678
679
680 /* The bodyhash should have been created earlier, and the dkim code should
681 have managed calculating it during message input. Find the reference to it. */
682
683 if (!(b = arc_ams_setup_vfy_bodyhash(ams)))
684 {
685 as->ams_verify_done = arc_state_reason = US"internal hash setup error";
686 return US"fail";
687 }
688
689 DEBUG(D_acl)
690 {
691 debug_printf("ARC i=%d AMS Body bytes hashed: %lu\n"
692 " Body %.*s computed: ",
693 as->instance, b->signed_body_bytes,
694 (int)ams->a_hash.len, ams->a_hash.data);
695 pdkim_hexprint(CUS b->bh.data, b->bh.len);
696 }
697
698 /* We know the bh-tag blob is of a nul-term string, so safe as a string */
699
700 if ( !ams->bh.data
701 || (pdkim_decode_base64(ams->bh.data, &sighash), sighash.len != b->bh.len)
702 || memcmp(sighash.data, b->bh.data, b->bh.len) != 0
703 )
704 {
705 DEBUG(D_acl)
706 {
707 debug_printf("ARC i=%d AMS Body hash from headers: ", as->instance);
708 pdkim_hexprint(sighash.data, sighash.len);
709 debug_printf("ARC i=%d AMS Body hash did NOT match\n", as->instance);
710 }
711 return as->ams_verify_done = arc_state_reason = US"AMS body hash miscompare";
712 }
713
714 DEBUG(D_acl) debug_printf("ARC i=%d AMS Body hash compared OK\n", as->instance);
715
716 /* Get the public key from DNS */
717
718 if (!(p = arc_line_to_pubkey(ams)))
719 return as->ams_verify_done = arc_state_reason = US"pubkey problem";
720
721 /* We know the b-tag blob is of a nul-term string, so safe as a string */
722 pdkim_decode_base64(ams->b.data, &sighash);
723
724 arc_get_verify_hhash(ctx, ams, &hhash);
725
726 /* Setup the interface to the signing library */
727
728 if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx)))
729 {
730 DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
731 as->ams_verify_done = arc_state_reason = US"internal sigverify init error";
732 return US"fail";
733 }
734
735 hashtype = pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len);
736
737 if ((errstr = exim_dkim_verify(&vctx,
738 pdkim_hashes[hashtype].exim_hashmethod, &hhash, &sighash)))
739 {
740 DEBUG(D_acl) debug_printf("ARC i=%d AMS verify %s\n", as->instance, errstr);
741 return as->ams_verify_done = arc_state_reason = US"AMS sig nonverify";
742 }
743
744 DEBUG(D_acl) debug_printf("ARC i=%d AMS verify pass\n", as->instance);
745 as->ams_verify_passed = TRUE;
746 return NULL;
747 }
748
749
750
751 /* Check the sets are instance-continuous and that all
752 members are present. Check that no arc_seals are "fail".
753 Set the highest instance number global.
754 Verify the latest AMS.
755 */
756 static uschar *
757 arc_headers_check(arc_ctx * ctx)
758 {
759 arc_set * as;
760 int inst;
761 BOOL ams_fail_found = FALSE;
762 uschar * ret = NULL;
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 fail"
775 " (cv, sequence or missing header)", as->instance);
776 DEBUG(D_acl) debug_printf("ARC %s\n", arc_state_reason);
777 ret = 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 if (ret)
795 return ret;
796
797 /* We can skip the latest-AMS validation, if we already did it. */
798
799 as = ctx->arcset_chain_last;
800 if (as->ams_verify_done && !as->ams_verify_passed)
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. Also parse ARC-chain headers and build the chain
1106 struct, retaining pointers into the string.
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 /* A "normal" header line, identified by DKIM processing. These arrive before
1490 the call to arc_sign(), which carries any newly-created DKIM headers - and
1491 those go textually before the normal ones in the message.
1492
1493 We have to take the feed from DKIM as, in the transport-filter case, the
1494 headers are not in memory at the time of the call to arc_sign().
1495
1496 Take a copy of the header and construct a reverse-order list.
1497 Also parse ARC-chain headers and build the chain struct, retaining pointers
1498 into the copies.
1499 */
1500
1501 static const uschar *
1502 arc_header_sign_feed(gstring * g)
1503 {
1504 uschar * s = string_copyn(g->s, g->ptr);
1505 headers_rlist = arc_rlist_entry(headers_rlist, s, g->ptr);
1506 return arc_try_header(&arc_sign_ctx, headers_rlist->h, TRUE);
1507 }
1508
1509
1510
1511 /* ARC signing. Called from the smtp transport, if the arc_sign option is set.
1512 The dkim_exim_sign() function has already been called, so will have hashed the
1513 message body for us so long as we requested a hash previously.
1514
1515 Arguments:
1516 signspec Three-element colon-sep list: identity, selector, privkey
1517 Already expanded
1518 sigheaders Any signature headers already generated, eg. by DKIM, or NULL
1519 errstr Error string
1520
1521 Return value
1522 Set of headers to prepend to the message, including the supplied sigheaders
1523 but not the plainheaders.
1524 */
1525
1526 gstring *
1527 arc_sign(const uschar * signspec, gstring * sigheaders, uschar ** errstr)
1528 {
1529 const uschar * identity, * selector, * privkey;
1530 int sep = 0;
1531 header_line * headers;
1532 hdr_rlist * rheaders;
1533 blob ar;
1534 int instance;
1535 gstring * g = NULL;
1536 pdkim_bodyhash * b;
1537
1538 /* Parse the signing specification */
1539
1540 identity = string_nextinlist(&signspec, &sep, NULL, 0);
1541 selector = string_nextinlist(&signspec, &sep, NULL, 0);
1542 if ( !*identity | !*selector
1543 || !(privkey = string_nextinlist(&signspec, &sep, NULL, 0)) || !*privkey)
1544 {
1545 log_write(0, LOG_MAIN|LOG_PANIC, "ARC: bad signing-specification (%s)",
1546 !*identity ? "identity" : !*selector ? "selector" : "private-key");
1547 return NULL;
1548 }
1549 if (*privkey == '/' && !(privkey = expand_file_big_buffer(privkey)))
1550 return NULL;
1551
1552 DEBUG(D_transport) debug_printf("ARC: sign for %s\n", identity);
1553
1554 /*
1555 - scan headers for existing ARC chain & A-R (with matching system-identfier)
1556 - paniclog & skip on problems (no A-R)
1557 */
1558
1559 /* Make an rlist of any new DKIM headers, then add the "normals" rlist to it */
1560
1561 string_from_gstring(sigheaders);
1562 if ((rheaders = arc_sign_scan_headers(&arc_sign_ctx, sigheaders)))
1563 {
1564 hdr_rlist ** rp;
1565 for (rp = &rheaders; *rp; ) rp = &(*rp)->prev;
1566 *rp = headers_rlist;
1567 headers_rlist = rheaders;
1568 }
1569 else
1570 rheaders = headers_rlist;
1571 /* Finally, build a normal-order headers list */
1572 /*XXX only needed for hunt-the-AR? */
1573 {
1574 header_line * hnext = NULL;
1575 for (; rheaders; hnext = rheaders->h, rheaders = rheaders->prev)
1576 rheaders->h->next = hnext;
1577 headers = hnext;
1578 }
1579
1580 instance = arc_sign_ctx.arcset_chain_last ? arc_sign_ctx.arcset_chain_last->instance + 1 : 1;
1581
1582 if (!(arc_sign_find_ar(headers, identity, &ar)))
1583 {
1584 log_write(0, LOG_MAIN|LOG_PANIC, "ARC: no Authentication-Results header for signing");
1585 return sigheaders ? sigheaders : string_get(0);
1586 }
1587
1588 /*
1589 - Generate AAR
1590 - copy the A-R; prepend i= & identity
1591 */
1592
1593 g = arc_sign_append_aar(g, &arc_sign_ctx, identity, instance, &ar);
1594
1595 /*
1596 - Generate AMS
1597 - Looks fairly like a DKIM sig
1598 - Cover all DKIM sig headers as well as the usuals
1599 - ? oversigning?
1600 - Covers the data
1601 - we must have requested a suitable bodyhash previously
1602 */
1603
1604 b = arc_ams_setup_sign_bodyhash();
1605 g = arc_sign_append_ams(g, &arc_sign_ctx, instance, identity, selector,
1606 &b->bh, headers_rlist, privkey);
1607
1608 /*
1609 - Generate AS
1610 - no body coverage
1611 - no h= tag; implicit coverage
1612 - arc status from A-R
1613 - if fail:
1614 - coverage is just the new ARC set
1615 including self (but with an empty b= in self)
1616 - if non-fail:
1617 - all ARC set headers, set-number order, aar then ams then as,
1618 including self (but with an empty b= in self)
1619 */
1620
1621 g = arc_sign_prepend_as(g, &arc_sign_ctx, instance, identity, selector, &ar, privkey);
1622
1623 /* Finally, append the dkim headers and return the lot. */
1624
1625 g = string_catn(g, sigheaders->s, sigheaders->ptr);
1626 (void) string_from_gstring(g);
1627 gstring_reset_unused(g);
1628 return g;
1629 }
1630
1631
1632 /******************************************************************************/
1633
1634 /* Check to see if the line is an AMS and if so, set up to validate it.
1635 Called from the DKIM input processing. This must be done now as the message
1636 body data is hashed during input.
1637
1638 We call the DKIM code to request a body-hash; it has the facility already
1639 and the hash parameters might be common with other requests.
1640 */
1641
1642 static const uschar *
1643 arc_header_vfy_feed(gstring * g)
1644 {
1645 header_line h;
1646 arc_line al;
1647 pdkim_bodyhash * b;
1648 uschar * errstr;
1649
1650 if (!dkim_verify_ctx) return US"no dkim context";
1651
1652 if (strncmpic(ARC_HDR_AMS, g->s, ARC_HDRLEN_AMS) != 0) return US"not AMS";
1653
1654 DEBUG(D_receive) debug_printf("ARC: spotted AMS header\n");
1655 /* Parse the AMS header */
1656
1657 h.next = NULL;
1658 h.slen = g->size;
1659 h.text = g->s;
1660 memset(&al, 0, sizeof(arc_line));
1661 if ((errstr = arc_parse_line(&al, &h, ARC_HDRLEN_AMS, FALSE)))
1662 {
1663 DEBUG(D_acl) if (errstr) debug_printf("ARC: %s\n", errstr);
1664 return US"line parsing error";
1665 }
1666
1667 /* defaults */
1668 if (!al.c.data)
1669 {
1670 al.c_body.data = US"simple"; al.c_body.len = 6;
1671 al.c_head = al.c_body;
1672 }
1673
1674 /* Ask the dkim code to calc a bodyhash with those specs */
1675
1676 if (!(b = arc_ams_setup_vfy_bodyhash(&al)))
1677 return US"dkim hash setup fail";
1678
1679 /* Discard the reference; search again at verify time, knowing that one
1680 should have been created here. */
1681
1682 return NULL;
1683 }
1684
1685
1686
1687 /* A header line has been identified by DKIM processing.
1688
1689 Arguments:
1690 g Header line
1691 is_vfy TRUE for verify mode or FALSE for signing mode
1692
1693 Return:
1694 NULL for success, or an error string (probably unused)
1695 */
1696
1697 const uschar *
1698 arc_header_feed(gstring * g, BOOL is_vfy)
1699 {
1700 return is_vfy ? arc_header_vfy_feed(g) : arc_header_sign_feed(g);
1701 }
1702
1703
1704
1705 /******************************************************************************/
1706
1707 /* Construct an Authenticate-Results header portion, for the ARC module */
1708
1709 gstring *
1710 authres_arc(gstring * g)
1711 {
1712 if (arc_state)
1713 {
1714 arc_line * highest_ams;
1715 int start = 0; /* Compiler quietening */
1716 DEBUG(D_acl) start = g->ptr;
1717
1718 g = string_append(g, 2, US";\n\tarc=", arc_state);
1719 if (arc_received_instance > 0)
1720 {
1721 g = string_append(g, 3, US" (i=",
1722 string_sprintf("%d", arc_received_instance), US")");
1723 if (arc_state_reason)
1724 g = string_append(g, 3, US"(", arc_state_reason, US")");
1725 g = string_catn(g, US" header.s=", 10);
1726 highest_ams = arc_received->hdr_ams;
1727 g = string_catn(g, highest_ams->s.data, highest_ams->s.len);
1728
1729 g = string_append(g, 2,
1730 US" arc.oldest-pass=", string_sprintf("%d", arc_oldest_pass));
1731
1732 if (sender_host_address)
1733 g = string_append(g, 2, US" smtp.client-ip=", sender_host_address);
1734 }
1735 DEBUG(D_acl) debug_printf("ARC: authres '%.*s'\n",
1736 g->ptr - start - 3, g->s + start + 3);
1737 }
1738 else
1739 DEBUG(D_acl) debug_printf("ARC: no authres\n");
1740 return g;
1741 }
1742
1743
1744 # endif /* SUPPORT_SPF */
1745 #endif /* EXPERIMENTAL_ARC */
1746 /* vi: aw ai sw=2
1747 */