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