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