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