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