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