1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
4 /* Experimental ARC support for Exim
5 Copyright (c) Jeremy Harris 2018
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
17 # include "functions.h"
18 # include "pdkim/pdkim.h"
19 # include "pdkim/signing.h"
21 extern pdkim_ctx * dkim_verify_ctx;
22 extern pdkim_ctx dkim_sign_ctx;
24 /******************************************************************************/
26 typedef struct hdr_rlist {
27 struct hdr_rlist * prev;
32 typedef struct arc_line {
33 header_line * complete; /* including the header name; nul-term */
36 /* identified tag contents */
49 /* tag content sub-portions */
56 /* modified copy of b= field in line */
60 typedef struct arc_set {
61 struct arc_set * next;
62 struct arc_set * prev;
69 const uschar * ams_verify_done;
70 BOOL ams_verify_passed;
73 typedef struct arc_ctx {
74 arc_set * arcset_chain;
75 arc_set * arcset_chain_last;
78 #define ARC_HDR_AAR US"ARC-Authentication-Results:"
79 #define ARC_HDRLEN_AAR 27
80 #define ARC_HDR_AMS US"ARC-Message-Signature:"
81 #define ARC_HDRLEN_AMS 22
82 #define ARC_HDR_AS US"ARC-Seal:"
83 #define ARC_HDRLEN_AS 9
84 #define HDR_AR US"Authentication-Results:"
87 static hdr_rlist * headers_rlist;
88 static arc_ctx arc_sign_ctx = { NULL };
91 /******************************************************************************/
94 /* Get the instance number from the header.
97 arc_instance_from_hdr(const arc_line * al)
99 const uschar * s = al->i.data;
100 if (!s || !al->i.len) return 0;
101 return (unsigned) atoi(CCS s);
109 while (c && (c == ' ' || c == '\t' || c == '\n' || c == '\r')) c = *++s;
114 /* Locate instance struct on chain, inserting a new one if
115 needed. The chain is in increasing-instance-number order
116 by the "next" link, and we have a "prev" link also.
120 arc_find_set(arc_ctx * ctx, unsigned i)
122 arc_set ** pas, * as, * next, * prev;
124 for (pas = &ctx->arcset_chain, prev = NULL, next = ctx->arcset_chain;
125 as = *pas; pas = &as->next)
127 if (as->instance > i) break;
128 if (as->instance == i)
130 DEBUG(D_acl) debug_printf("ARC: existing instance %u\n", i);
137 DEBUG(D_acl) debug_printf("ARC: new instance %u\n", i);
138 *pas = as = store_get(sizeof(arc_set));
139 memset(as, 0, sizeof(arc_set));
146 ctx->arcset_chain_last = as;
152 /* Insert a tag content into the line structure.
153 Note this is a reference to existing data, not a copy.
154 Check for already-seen tag.
155 The string-pointer is on the '=' for entry. Update it past the
156 content (to the ;) on return;
160 arc_insert_tagvalue(arc_line * al, unsigned loff, uschar ** ss)
164 blob * b = (blob *)(US al + loff);
167 /* [FWS] tag-value [FWS] */
169 if (b->data) return US"fail";
170 s = skip_fws(s); /* FWS */
173 while ((c = *s) && c != ';') { len++; s++; }
175 while (len && ((c = s[-1]) == ' ' || c == '\t' || c == '\n' || c == '\r'))
176 { s--; len--; } /* FWS */
182 /* Inspect a header line, noting known tag fields.
183 Check for duplicates. */
186 arc_parse_line(arc_line * al, header_line * h, unsigned off, BOOL instance_only)
188 uschar * s = h->text + off;
189 uschar * r = NULL; /* compiler-quietening */
196 al->rawsig_no_b_val.data = store_get(h->slen + 1);
197 memcpy(al->rawsig_no_b_val.data, h->text, off); /* copy the header name blind */
198 r = al->rawsig_no_b_val.data + off;
199 al->rawsig_no_b_val.len = off;
202 /* tag-list = tag-spec *( ";" tag-spec ) [ ";" ] */
209 uschar * fieldstart = s;
210 uschar * bstart = NULL, * bend;
212 /* tag-spec = [FWS] tag-name [FWS] "=" [FWS] tag-value [FWS] */
214 s = skip_fws(s); /* FWS */
216 /* debug_printf("%s: consider '%s'\n", __FUNCTION__, s); */
218 s = skip_fws(s); /* FWS */
221 if (!instance_only || tagchar == 'i') switch (tagchar)
223 case 'a': /* a= AMS algorithm */
225 if (*s != '=') return US"no 'a' value";
226 if (arc_insert_tagvalue(al, offsetof(arc_line, a), &s)) return US"a tag dup";
228 /* substructure: algo-hash (eg. rsa-sha256) */
230 t = al->a_algo.data = al->a.data;
232 if (!*t++ || ++i > al->a.len) return US"no '-' in 'a' value";
234 if (*t++ != '-') return US"no '-' in 'a' value";
236 al->a_hash.len = al->a.len - i - 1;
245 case '=': /* b= AMS signature */
246 if (al->b.data) return US"already b data";
249 /* The signature can have FWS inserted in the content;
250 make a stripped copy */
252 while ((c = *++s) && c != ';')
253 if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
254 g = string_catn(g, s, 1);
255 al->b.data = string_from_gstring(g);
257 gstring_reset_unused(g);
260 case 'h': /* bh= AMS body hash */
261 s = skip_fws(++s); /* FWS */
262 if (*s != '=') return US"no bh value";
263 if (al->bh.data) return US"already bh data";
265 /* The bodyhash can have FWS inserted in the content;
266 make a stripped copy */
268 while ((c = *++s) && c != ';')
269 if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
270 g = string_catn(g, s, 1);
271 al->bh.data = string_from_gstring(g);
273 gstring_reset_unused(g);
283 case '=': /* c= AMS canonicalisation */
284 if (arc_insert_tagvalue(al, offsetof(arc_line, c), &s)) return US"c tag dup";
286 /* substructure: head/body (eg. relaxed/simple)) */
288 t = al->c_head.data = al->c.data;
290 if (!*t++ || ++i > al->a.len) break;
292 if (*t++ == '/') /* /body is optional */
295 al->c_body.len = al->c.len - i - 1;
299 al->c_body.data = US"simple";
303 case 'v': /* cv= AS validity */
304 if (*++s != '=') return US"cv tag val";
305 if (arc_insert_tagvalue(al, offsetof(arc_line, cv), &s)) return US"cv tag dup";
311 case 'd': /* d= AMS domain */
312 if (*s != '=') return US"d tag val";
313 if (arc_insert_tagvalue(al, offsetof(arc_line, d), &s)) return US"d tag dup";
315 case 'h': /* h= AMS headers */
316 if (*s != '=') return US"h tag val";
317 if (arc_insert_tagvalue(al, offsetof(arc_line, h), &s)) return US"h tag dup";
319 case 'i': /* i= ARC set instance */
320 if (*s != '=') return US"i tag val";
321 if (arc_insert_tagvalue(al, offsetof(arc_line, i), &s)) return US"i tag dup";
322 if (instance_only) goto done;
324 case 'l': /* l= bodylength */
325 if (*s != '=') return US"l tag val";
326 if (arc_insert_tagvalue(al, offsetof(arc_line, l), &s)) return US"l tag dup";
328 case 's': /* s= AMS selector */
329 if (*s != '=') return US"s tag val";
330 if (arc_insert_tagvalue(al, offsetof(arc_line, s), &s)) return US"s tag dup";
334 while ((c = *s) && c != ';') s++;
335 if (c) s++; /* ; after tag-spec */
337 /* for all but the b= tag, copy the field including FWS. For the b=,
338 drop the tag content. */
343 size_t n = bstart - fieldstart;
344 memcpy(r, fieldstart, n); /* FWS "b=" */
346 al->rawsig_no_b_val.len += n;
348 memcpy(r, bend, n); /* FWS ";" */
350 al->rawsig_no_b_val.len += n;
354 size_t n = s - fieldstart;
355 memcpy(r, fieldstart, n);
357 al->rawsig_no_b_val.len += n;
365 /* debug_printf("%s: finshed\n", __FUNCTION__); */
370 /* Insert one header line in the correct set of the chain,
371 adding instances as needed and checking for duplicate lines.
375 arc_insert_hdr(arc_ctx * ctx, header_line * h, unsigned off, unsigned hoff,
380 arc_line * al = store_get(sizeof(arc_line)), ** alp;
383 memset(al, 0, sizeof(arc_line));
385 if ((e = arc_parse_line(al, h, off, instance_only)))
387 DEBUG(D_acl) if (e) debug_printf("ARC: %s\n", e);
388 return US"line parse";
390 if (!(i = arc_instance_from_hdr(al))) return US"instance find";
391 if (!(as = arc_find_set(ctx, i))) return US"set find";
392 if (*(alp = (arc_line **)(US as + hoff))) return US"dup hdr";
401 static const uschar *
402 arc_try_header(arc_ctx * ctx, header_line * h, BOOL instance_only)
406 /*debug_printf("consider hdr '%s'\n", h->text);*/
407 if (strncmpic(ARC_HDR_AAR, h->text, ARC_HDRLEN_AAR) == 0)
413 for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
415 debug_printf("ARC: found AAR: %.*s\n", len, h->text);
417 if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AAR, offsetof(arc_set, hdr_aar),
420 DEBUG(D_acl) debug_printf("inserting AAR: %s\n", e);
421 return US"inserting AAR";
424 else if (strncmpic(ARC_HDR_AMS, h->text, ARC_HDRLEN_AMS) == 0)
432 for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
434 debug_printf("ARC: found AMS: %.*s\n", len, h->text);
436 if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AMS, offsetof(arc_set, hdr_ams),
439 DEBUG(D_acl) debug_printf("inserting AMS: %s\n", e);
440 return US"inserting AMS";
444 /*XXX dubious selection of ams here */
445 ams = ctx->arcset_chain->hdr_ams;
448 ams->c_head.data = US"simple"; ams->c_head.len = 6;
449 ams->c_body = ams->c_head;
452 else if (strncmpic(ARC_HDR_AS, h->text, ARC_HDRLEN_AS) == 0)
458 for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
460 debug_printf("ARC: found AS: %.*s\n", len, h->text);
462 if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AS, offsetof(arc_set, hdr_as),
465 DEBUG(D_acl) debug_printf("inserting AS: %s\n", e);
466 return US"inserting AS";
474 /* Gather the chain of arc sets from the headers.
475 Check for duplicates while that is done. Also build the
476 reverse-order headers list;
478 Return: ARC state if determined, eg. by lack of any ARC chain.
481 static const uschar *
482 arc_vfy_collect_hdrs(arc_ctx * ctx)
485 hdr_rlist * r = NULL, * rprev = NULL;
488 DEBUG(D_acl) debug_printf("ARC: collecting arc sets\n");
489 for (h = header_list; h; h = h->next)
491 r = store_get(sizeof(hdr_rlist));
497 if ((e = arc_try_header(ctx, h, FALSE)))
499 arc_state_reason = string_sprintf("collecting headers: %s", e);
505 if (!ctx->arcset_chain) return US"none";
511 arc_cv_match(arc_line * al, const uschar * s)
513 return Ustrncmp(s, al->cv.data, al->cv.len) == 0;
516 /******************************************************************************/
518 /* Return the hash of headers from the message that the AMS claims it
523 arc_get_verify_hhash(arc_ctx * ctx, arc_line * ams, blob * hhash)
525 const uschar * headernames = string_copyn(ams->h.data, ams->h.len);
529 BOOL relaxed = Ustrncmp(US"relaxed", ams->c_head.data, ams->c_head.len) == 0;
530 int hashtype = pdkim_hashname_to_hashtype(
531 ams->a_hash.data, ams->a_hash.len);
536 if (!exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
539 debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
543 /* For each headername in the list from the AMS (walking in order)
544 walk the message headers in reverse order, adding to the hash any
545 found for the first time. For that last point, maintain used-marks
546 on the list of message headers. */
548 DEBUG(D_acl) debug_printf("ARC: AMS header data for verification:\n");
550 for (r = headers_rlist; r; r = r->prev)
552 while ((hn = string_nextinlist(&headernames, &sep, NULL, 0)))
553 for (r = headers_rlist; r; r = r->prev)
555 && strncasecmp(CCS (s = r->h->text), CCS hn, Ustrlen(hn)) == 0
558 if (relaxed) s = pdkim_relax_header_n(s, r->h->slen, TRUE);
561 DEBUG(D_acl) pdkim_quoteprint(s, len);
562 exim_sha_update(&hhash_ctx, s, Ustrlen(s));
567 /* Finally add in the signature header (with the b= tag stripped); no CRLF */
569 s = ams->rawsig_no_b_val.data, len = ams->rawsig_no_b_val.len;
571 len = Ustrlen(s = pdkim_relax_header_n(s, len, FALSE));
572 DEBUG(D_acl) pdkim_quoteprint(s, len);
573 exim_sha_update(&hhash_ctx, s, len);
575 exim_sha_finish(&hhash_ctx, hhash);
577 { debug_printf("ARC: header hash: "); pdkim_hexprint(hhash->data, hhash->len); }
584 static pdkim_pubkey *
585 arc_line_to_pubkey(arc_line * al)
590 if (!(dns_txt = dkim_exim_query_dns_txt(string_sprintf("%.*s._domainkey.%.*s",
591 al->s.len, al->s.data, al->d.len, al->d.data))))
593 DEBUG(D_acl) debug_printf("pubkey dns lookup fail\n");
597 if ( !(p = pdkim_parse_pubkey_record(dns_txt))
598 || (Ustrcmp(p->srvtype, "*") != 0 && Ustrcmp(p->srvtype, "email") != 0)
601 DEBUG(D_acl) debug_printf("pubkey dns lookup format error\n");
605 /* If the pubkey limits use to specified hashes, reject unusable
606 signatures. XXX should we have looked for multiple dns records? */
610 const uschar * list = p->hashes, * ele;
613 while ((ele = string_nextinlist(&list, &sep, NULL, 0)))
614 if (Ustrncmp(ele, al->a_hash.data, al->a_hash.len) == 0) break;
617 DEBUG(D_acl) debug_printf("pubkey h=%s vs sig a=%.*s\n",
618 p->hashes, (int)al->a.len, al->a.data);
628 static pdkim_bodyhash *
629 arc_ams_setup_vfy_bodyhash(arc_line * ams)
631 int canon_head, canon_body;
634 if (!ams->c.data) ams->c.data = US"simple"; /* RFC 6376 (DKIM) default */
635 pdkim_cstring_to_canons(ams->c.data, ams->c.len, &canon_head, &canon_body);
636 bodylen = ams->l.data
637 ? strtol(CS string_copyn(ams->l.data, ams->l.len), NULL, 10) : -1;
639 return pdkim_set_bodyhash(dkim_verify_ctx,
640 pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len),
647 /* Verify an AMS. This is a DKIM-sig header, but with an ARC i= tag
648 and without a DKIM v= tag.
651 static const uschar *
652 arc_ams_verify(arc_ctx * ctx, arc_set * as)
654 arc_line * ams = as->hdr_ams;
661 const uschar * errstr;
663 as->ams_verify_done = US"in-progress";
665 /* Check the AMS has all the required tags:
669 "d=" domain (for key lookup)
670 "h=" headers (included in signature)
671 "s=" key-selector (for key lookup)
673 if ( !ams->a.data || !ams->b.data || !ams->bh.data || !ams->d.data
674 || !ams->h.data || !ams->s.data)
676 as->ams_verify_done = arc_state_reason = US"required tag missing";
681 /* The bodyhash should have been created earlier, and the dkim code should
682 have managed calculating it during message input. Find the reference to it. */
684 if (!(b = arc_ams_setup_vfy_bodyhash(ams)))
686 as->ams_verify_done = arc_state_reason = US"internal hash setup error";
692 debug_printf("ARC i=%d AMS Body bytes hashed: %lu\n"
693 " Body %.*s computed: ",
694 as->instance, b->signed_body_bytes,
695 (int)ams->a_hash.len, ams->a_hash.data);
696 pdkim_hexprint(CUS b->bh.data, b->bh.len);
699 /* We know the bh-tag blob is of a nul-term string, so safe as a string */
702 || (pdkim_decode_base64(ams->bh.data, &sighash), sighash.len != b->bh.len)
703 || memcmp(sighash.data, b->bh.data, b->bh.len) != 0
708 debug_printf("ARC i=%d AMS Body hash from headers: ", as->instance);
709 pdkim_hexprint(sighash.data, sighash.len);
710 debug_printf("ARC i=%d AMS Body hash did NOT match\n", as->instance);
712 return as->ams_verify_done = arc_state_reason = US"AMS body hash miscompare";
715 DEBUG(D_acl) debug_printf("ARC i=%d AMS Body hash compared OK\n", as->instance);
717 /* Get the public key from DNS */
719 if (!(p = arc_line_to_pubkey(ams)))
720 return as->ams_verify_done = arc_state_reason = US"pubkey problem";
722 /* We know the b-tag blob is of a nul-term string, so safe as a string */
723 pdkim_decode_base64(ams->b.data, &sighash);
725 arc_get_verify_hhash(ctx, ams, &hhash);
727 /* Setup the interface to the signing library */
729 if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx)))
731 DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
732 as->ams_verify_done = arc_state_reason = US"internal sigverify init error";
736 hashtype = pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len);
738 if ((errstr = exim_dkim_verify(&vctx,
739 pdkim_hashes[hashtype].exim_hashmethod, &hhash, &sighash)))
741 DEBUG(D_acl) debug_printf("ARC i=%d AMS verify %s\n", as->instance, errstr);
742 return as->ams_verify_done = arc_state_reason = US"AMS sig nonverify";
745 DEBUG(D_acl) debug_printf("ARC i=%d AMS verify pass\n", as->instance);
746 as->ams_verify_passed = TRUE;
752 /* Check the sets are instance-continuous and that all
753 members are present. Check that no arc_seals are "fail".
754 Set the highest instance number global.
755 Verify the latest AMS.
758 arc_headers_check(arc_ctx * ctx)
762 BOOL ams_fail_found = FALSE;
764 if (!(as = ctx->arcset_chain))
767 for(inst = 0; as; as = as->next)
769 if ( as->instance != ++inst
770 || !as->hdr_aar || !as->hdr_ams || !as->hdr_as
771 || arc_cv_match(as->hdr_as, US"fail")
774 arc_state_reason = string_sprintf("i=%d"
775 " (cv, sequence or missing header)", as->instance);
776 DEBUG(D_acl) debug_printf("ARC chain fail at %s\n", arc_state_reason);
780 /* Evaluate the oldest-pass AMS validation while we're here.
781 It does not affect the AS chain validation but is reported as
785 if (arc_ams_verify(ctx, as))
786 ams_fail_found = TRUE;
788 arc_oldest_pass = inst;
789 arc_state_reason = NULL;
792 arc_received = ctx->arcset_chain_last;
793 arc_received_instance = inst;
795 /* We can skip the latest-AMS validation, if we already did it. */
797 as = ctx->arcset_chain_last;
798 if (!as->ams_verify_passed)
800 if (as->ams_verify_done)
802 arc_state_reason = as->ams_verify_done;
805 if (!!arc_ams_verify(ctx, as))
812 /******************************************************************************/
813 static const uschar *
814 arc_seal_verify(arc_ctx * ctx, arc_set * as)
816 arc_line * hdr_as = as->hdr_as;
824 const uschar * errstr;
826 DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d\n", as->instance);
828 1. If the value of the "cv" tag on that seal is "fail", the
829 chain state is "fail" and the algorithm stops here. (This
830 step SHOULD be skipped if the earlier step (2.1) was
833 2. In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
834 == "none" && i != 1)) then the chain state is "fail" and the
835 algorithm stops here (note that the ordering of the logic is
836 structured for short-circuit evaluation).
839 if ( as->instance == 1 && !arc_cv_match(hdr_as, US"none")
840 || arc_cv_match(hdr_as, US"none") && as->instance != 1
843 arc_state_reason = US"seal cv state";
848 3. Initialize a hash function corresponding to the "a" tag of
852 hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len);
854 if (!exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
857 debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
858 arc_state_reason = US"seal hash setup error";
863 4. Compute the canonicalized form of the ARC header fields, in
864 the order described in Section 5.4.2, using the "relaxed"
865 header canonicalization defined in Section 3.4.2 of
866 [RFC6376]. Pass the canonicalized result to the hash
869 Headers are CRLF-separated, but the last one is not crlf-terminated.
872 DEBUG(D_acl) debug_printf("ARC: AS header data for verification:\n");
873 for (as2 = ctx->arcset_chain;
874 as2 && as2->instance <= as->instance;
882 if (!(s = al->relaxed))
883 al->relaxed = s = pdkim_relax_header_n(al->complete->text,
884 al->complete->slen, TRUE);
886 DEBUG(D_acl) pdkim_quoteprint(s, len);
887 exim_sha_update(&hhash_ctx, s, len);
890 if (!(s = al->relaxed))
891 al->relaxed = s = pdkim_relax_header_n(al->complete->text,
892 al->complete->slen, TRUE);
894 DEBUG(D_acl) pdkim_quoteprint(s, len);
895 exim_sha_update(&hhash_ctx, s, len);
898 if (as2->instance == as->instance)
899 s = pdkim_relax_header_n(al->rawsig_no_b_val.data,
900 al->rawsig_no_b_val.len, FALSE);
901 else if (!(s = al->relaxed))
902 al->relaxed = s = pdkim_relax_header_n(al->complete->text,
903 al->complete->slen, TRUE);
905 DEBUG(D_acl) pdkim_quoteprint(s, len);
906 exim_sha_update(&hhash_ctx, s, len);
910 5. Retrieve the final digest from the hash function.
913 exim_sha_finish(&hhash_ctx, &hhash_computed);
916 debug_printf("ARC i=%d AS Header %.*s computed: ",
917 as->instance, (int)hdr_as->a_hash.len, hdr_as->a_hash.data);
918 pdkim_hexprint(hhash_computed.data, hhash_computed.len);
923 6. Retrieve the public key identified by the "s" and "d" tags in
924 the ARC-Seal, as described in Section 4.1.6.
927 if (!(p = arc_line_to_pubkey(hdr_as)))
928 return US"pubkey problem";
931 7. Determine whether the signature portion ("b" tag) of the ARC-
932 Seal and the digest computed above are valid according to the
933 public key. (See also Section Section 8.4 for failure case
936 8. If the signature is not valid, the chain state is "fail" and
937 the algorithm stops here.
940 /* We know the b-tag blob is of a nul-term string, so safe as a string */
941 pdkim_decode_base64(hdr_as->b.data, &sighash);
943 if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx)))
945 DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
949 hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len);
951 if ((errstr = exim_dkim_verify(&vctx,
952 pdkim_hashes[hashtype].exim_hashmethod,
953 &hhash_computed, &sighash)))
956 debug_printf("ARC i=%d AS headers verify: %s\n", as->instance, errstr);
957 arc_state_reason = US"seal sigverify error";
961 DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d pass\n", as->instance);
966 static const uschar *
967 arc_verify_seals(arc_ctx * ctx)
969 arc_set * as = ctx->arcset_chain;
976 if (arc_seal_verify(ctx, as)) return US"fail";
979 DEBUG(D_acl) debug_printf("ARC: AS vfy overall pass\n");
982 /******************************************************************************/
984 /* Do ARC verification. Called from DATA ACL, on a verify = arc
985 condition. No arguments; we are checking globals.
987 Return: The ARC state, or NULL on error.
993 arc_ctx ctx = { NULL };
996 if (!dkim_verify_ctx)
998 DEBUG(D_acl) debug_printf("ARC: no DKIM verify context\n");
1002 /* AS evaluation, per
1003 https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-10#section-6
1005 /* 1. Collect all ARC sets currently on the message. If there were
1006 none, the ARC state is "none" and the algorithm stops here.
1009 if ((res = arc_vfy_collect_hdrs(&ctx)))
1012 /* 2. If the form of any ARC set is invalid (e.g., does not contain
1013 exactly one of each of the three ARC-specific header fields),
1014 then the chain state is "fail" and the algorithm stops here.
1016 1. To avoid the overhead of unnecessary computation and delay
1017 from crypto and DNS operations, the cv value for all ARC-
1018 Seal(s) MAY be checked at this point. If any of the values
1019 are "fail", then the overall state of the chain is "fail" and
1020 the algorithm stops here.
1022 3. Conduct verification of the ARC-Message-Signature header field
1023 bearing the highest instance number. If this verification fails,
1024 then the chain state is "fail" and the algorithm stops here.
1027 if ((res = arc_headers_check(&ctx)))
1030 /* 4. For each ARC-Seal from the "N"th instance to the first, apply the
1033 1. If the value of the "cv" tag on that seal is "fail", the
1034 chain state is "fail" and the algorithm stops here. (This
1035 step SHOULD be skipped if the earlier step (2.1) was
1038 2. In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
1039 == "none" && i != 1)) then the chain state is "fail" and the
1040 algorithm stops here (note that the ordering of the logic is
1041 structured for short-circuit evaluation).
1043 3. Initialize a hash function corresponding to the "a" tag of
1046 4. Compute the canonicalized form of the ARC header fields, in
1047 the order described in Section 5.4.2, using the "relaxed"
1048 header canonicalization defined in Section 3.4.2 of
1049 [RFC6376]. Pass the canonicalized result to the hash
1052 5. Retrieve the final digest from the hash function.
1054 6. Retrieve the public key identified by the "s" and "d" tags in
1055 the ARC-Seal, as described in Section 4.1.6.
1057 7. Determine whether the signature portion ("b" tag) of the ARC-
1058 Seal and the digest computed above are valid according to the
1059 public key. (See also Section Section 8.4 for failure case
1062 8. If the signature is not valid, the chain state is "fail" and
1063 the algorithm stops here.
1065 5. If all seals pass validation, then the chain state is "pass", and
1066 the algorithm is complete.
1069 if ((res = arc_verify_seals(&ctx)))
1078 /******************************************************************************/
1080 /* Prepend the header to the rlist */
1083 arc_rlist_entry(hdr_rlist * list, const uschar * s, int len)
1085 hdr_rlist * r = store_get(sizeof(hdr_rlist) + sizeof(header_line));
1086 header_line * h = r->h = (header_line *)(r+1);
1095 /* This works for either NL or CRLF lines; also nul-termination */
1097 if (*s == '\n' && s[1] != '\t' && s[1] != ' ') break;
1098 s++; /* move past end of line */
1104 /* Walk the given headers strings identifying each header, and construct
1105 a reverse-order list.
1109 arc_sign_scan_headers(arc_ctx * ctx, gstring * sigheaders)
1112 hdr_rlist * rheaders = NULL;
1114 s = sigheaders ? sigheaders->s : NULL;
1117 const uschar * s2 = s;
1119 /* This works for either NL or CRLF lines; also nul-termination */
1121 if (*s2 == '\n' && s2[1] != '\t' && s2[1] != ' ') break;
1122 s2++; /* move past end of line */
1124 rheaders = arc_rlist_entry(rheaders, s, s2 - s);
1132 /* Return the A-R content, without identity, with line-ending and
1136 arc_sign_find_ar(header_line * headers, const uschar * identity, blob * ret)
1139 int ilen = Ustrlen(identity);
1142 for(h = headers; h; h = h->next)
1144 uschar * s = h->text, c;
1147 if (Ustrncmp(s, HDR_AR, HDRLEN_AR) != 0) continue;
1148 s += HDRLEN_AR, len -= HDRLEN_AR; /* header name */
1150 && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
1151 s++, len--; /* FWS */
1152 if (Ustrncmp(s, identity, ilen) != 0) continue;
1153 s += ilen; len -= ilen; /* identity */
1154 if (len <= 0) continue;
1155 if ((c = *s) && c == ';') s++, len--; /* identity terminator */
1157 && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
1158 s++, len--; /* FWS */
1159 if (len <= 0) continue;
1169 /* Append a constructed AAR including CRLF. Add it to the arc_ctx too. */
1172 arc_sign_append_aar(gstring * g, arc_ctx * ctx,
1173 const uschar * identity, int instance, blob * ar)
1175 int aar_off = g ? g->ptr : 0;
1176 arc_set * as = store_get(sizeof(arc_set) + sizeof(arc_line) + sizeof(header_line));
1177 arc_line * al = (arc_line *)(as+1);
1178 header_line * h = (header_line *)(al+1);
1180 g = string_catn(g, ARC_HDR_AAR, ARC_HDRLEN_AAR);
1181 g = string_cat(g, string_sprintf(" i=%d; %s;\r\n\t", instance, identity));
1182 g = string_catn(g, US ar->data, ar->len);
1184 h->slen = g->ptr - aar_off;
1185 h->text = g->s + aar_off;
1188 as->prev = ctx->arcset_chain_last;
1189 as->instance = instance;
1192 ctx->arcset_chain = as;
1194 ctx->arcset_chain_last->next = as;
1195 ctx->arcset_chain_last = as;
1197 DEBUG(D_transport) debug_printf("ARC: AAR '%.*s'\n", h->slen - 2, h->text);
1204 arc_sig_from_pseudoheader(gstring * hdata, int hashtype, const uschar * privkey,
1205 blob * sig, const uschar * why)
1207 hashmethod hm = /*sig->keytype == KEYTYPE_ED25519*/ FALSE
1208 ? HASH_SHA2_512 : pdkim_hashes[hashtype].exim_hashmethod;
1211 const uschar * errstr;
1216 debug_printf("ARC: %s header data for signing:\n", why);
1217 pdkim_quoteprint(hdata->s, hdata->ptr);
1219 (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
1220 exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
1221 exim_sha_finish(&hhash_ctx, &hhash);
1222 debug_printf("ARC: header hash: "); pdkim_hexprint(hhash.data, hhash.len);
1225 if (FALSE /*need hash for Ed25519 or GCrypt signing*/ )
1228 (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
1229 exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
1230 exim_sha_finish(&hhash_ctx, &hhash);
1234 hhash.data = hdata->s;
1235 hhash.len = hdata->ptr;
1238 if ( (errstr = exim_dkim_signing_init(privkey, &sctx))
1239 || (errstr = exim_dkim_sign(&sctx, hm, &hhash, sig)))
1241 log_write(0, LOG_MAIN, "ARC: %s signing: %s\n", why, errstr);
1250 arc_sign_append_sig(gstring * g, blob * sig)
1252 /*debug_printf("%s: raw sig ", __FUNCTION__); pdkim_hexprint(sig->data, sig->len);*/
1253 sig->data = pdkim_encode_base64(sig);
1254 sig->len = Ustrlen(sig->data);
1257 int len = MIN(sig->len, 74);
1258 g = string_catn(g, sig->data, len);
1259 if ((sig->len -= len) == 0) break;
1261 g = string_catn(g, US"\r\n\t ", 5);
1263 g = string_catn(g, US";\r\n", 3);
1264 gstring_reset_unused(g);
1265 string_from_gstring(g);
1270 /* Append a constructed AMS including CRLF. Add it to the arc_ctx too. */
1273 arc_sign_append_ams(gstring * g, arc_ctx * ctx, int instance,
1274 const uschar * identity, const uschar * selector, blob * bodyhash,
1275 hdr_rlist * rheaders, const uschar * privkey)
1278 gstring * hdata = NULL;
1280 int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6); /*XXX hardwired */
1283 arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line));
1284 header_line * h = (header_line *)(al+1);
1286 /* debug_printf("%s\n", __FUNCTION__); */
1288 /* Construct the to-be-signed AMS pseudo-header: everything but the sig. */
1291 g = string_append(g, 10,
1293 US" i=", string_sprintf("%d", instance),
1294 US"; a=rsa-sha256; c=relaxed; d=", identity, /*XXX hardwired */
1296 US";\r\n\tbh=", pdkim_encode_base64(bodyhash),
1299 for(col = 3; rheaders; rheaders = rheaders->prev)
1301 const uschar * hnames = US"DKIM-Signature:" PDKIM_DEFAULT_SIGN_HEADERS;
1302 uschar * name, * htext = rheaders->h->text;
1305 /* Spot headers of interest */
1307 while ((name = string_nextinlist(&hnames, &sep, NULL, 0)))
1309 int len = Ustrlen(name);
1310 if (strncasecmp(CCS htext, CCS name, len) == 0)
1312 /* If too long, fold line in h= field */
1314 if (col + len > 78) g = string_catn(g, US"\r\n\t ", 5), col = 3;
1316 /* Add name to h= list */
1318 g = string_catn(g, name, len);
1319 g = string_catn(g, US":", 1);
1322 /* Accumulate header for hashing/signing */
1324 hdata = string_cat(hdata,
1325 pdkim_relax_header_n(htext, rheaders->h->slen, TRUE)); /*XXX hardwired */
1331 /* Lose the last colon from the h= list */
1333 if (g->s[g->ptr - 1] == ':') g->ptr--;
1335 g = string_catn(g, US";\r\n\tb=;", 7);
1337 /* Include the pseudo-header in the accumulation */
1339 s = pdkim_relax_header_n(g->s + ams_off, g->ptr - ams_off, FALSE);
1340 hdata = string_cat(hdata, s);
1342 /* Calculate the signature from the accumulation */
1343 /*XXX does that need further relaxation? there are spaces embedded in the b= strings! */
1345 if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AMS"))
1348 /* Lose the trailing semicolon from the psuedo-header, and append the signature
1349 (folded over lines) and termination to complete it. */
1352 g = arc_sign_append_sig(g, &sig);
1354 h->slen = g->ptr - ams_off;
1355 h->text = g->s + ams_off;
1357 ctx->arcset_chain_last->hdr_ams = al;
1359 DEBUG(D_transport) debug_printf("ARC: AMS '%.*s'\n", h->slen - 2, h->text);
1365 /* Look for an arc= result in an A-R header blob. We know that its data
1366 happens to be a NUL-term string. */
1369 arc_ar_cv_status(blob * ar)
1371 const uschar * resinfo = ar->data;
1373 uschar * methodspec, * s;
1375 while ((methodspec = string_nextinlist(&resinfo, &sep, NULL, 0)))
1376 if (Ustrncmp(methodspec, US"arc=", 4) == 0)
1379 for (s = methodspec += 4;
1380 (c = *s) && c != ';' && c != ' ' && c != '\r' && c != '\n'; ) s++;
1381 return string_copyn(methodspec, s - methodspec);
1388 /* Build the AS header and prepend it */
1391 arc_sign_prepend_as(gstring * arcset_interim, arc_ctx * ctx,
1392 int instance, const uschar * identity, const uschar * selector, blob * ar,
1393 const uschar * privkey)
1397 uschar * status = arc_ar_cv_status(ar);
1398 arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line));
1399 header_line * h = (header_line *)(al+1);
1401 gstring * hdata = NULL;
1402 int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6); /*XXX hardwired */
1408 - no h= tag; implicit coverage
1409 - arc status from A-R
1411 - coverage is just the new ARC set
1412 including self (but with an empty b= in self)
1414 - all ARC set headers, set-number order, aar then ams then as,
1415 including self (but with an empty b= in self)
1418 /* Construct the AS except for the signature */
1420 arcset = string_append(NULL, 10,
1422 US" i=", string_sprintf("%d", instance),
1424 US"; a=rsa-sha256; d=", identity, /*XXX hardwired */
1425 US"; s=", selector, /*XXX same as AMS */
1428 h->slen = arcset->ptr;
1429 h->text = arcset->s;
1431 ctx->arcset_chain_last->hdr_as = al;
1433 /* For any but "fail" chain-verify status, walk the entire chain in order by
1434 instance. For fail, only the new arc-set. Accumulate the elements walked. */
1436 for (as = Ustrcmp(status, US"fail") == 0
1437 ? ctx->arcset_chain_last : ctx->arcset_chain;
1440 /* Accumulate AAR then AMS then AS. Relaxed canonicalisation
1441 is required per standard. */
1443 h = as->hdr_aar->complete;
1444 hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
1445 h = as->hdr_ams->complete;
1446 hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
1447 h = as->hdr_as->complete;
1448 hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, !!as->next));
1451 /* Calculate the signature from the accumulation */
1453 if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AS"))
1456 /* Lose the trailing semicolon */
1458 arcset = arc_sign_append_sig(arcset, &sig);
1459 DEBUG(D_transport) debug_printf("ARC: AS '%.*s'\n", arcset->ptr - 2, arcset->s);
1461 /* Finally, append the AMS and AAR to the new AS */
1463 return string_catn(arcset, arcset_interim->s, arcset_interim->ptr);
1467 /**************************************/
1469 /* Return pointer to pdkim_bodyhash for given hash method, creating new
1474 arc_ams_setup_sign_bodyhash(void)
1476 int canon_head, canon_body;
1478 DEBUG(D_transport) debug_printf("ARC: requesting bodyhash\n");
1479 pdkim_cstring_to_canons(US"relaxed", 7, &canon_head, &canon_body); /*XXX hardwired */
1480 return pdkim_set_bodyhash(&dkim_sign_ctx,
1481 pdkim_hashname_to_hashtype(US"sha256", 6), /*XXX hardwired */
1491 memset(&arc_sign_ctx, 0, sizeof(arc_sign_ctx));
1496 /* A "normal" header line, identified by DKIM processing. These arrive before
1497 the call to arc_sign(), which carries any newly-created DKIM headers - and
1498 those go textually before the normal ones in the message.
1500 We have to take the feed from DKIM as, in the transport-filter case, the
1501 headers are not in memory at the time of the call to arc_sign().
1503 Take a copy of the header and construct a reverse-order list.
1504 Also parse ARC-chain headers and build the chain struct, retaining pointers
1508 static const uschar *
1509 arc_header_sign_feed(gstring * g)
1511 uschar * s = string_copyn(g->s, g->ptr);
1512 headers_rlist = arc_rlist_entry(headers_rlist, s, g->ptr);
1513 return arc_try_header(&arc_sign_ctx, headers_rlist->h, TRUE);
1518 /* ARC signing. Called from the smtp transport, if the arc_sign option is set.
1519 The dkim_exim_sign() function has already been called, so will have hashed the
1520 message body for us so long as we requested a hash previously.
1523 signspec Three-element colon-sep list: identity, selector, privkey
1525 sigheaders Any signature headers already generated, eg. by DKIM, or NULL
1529 Set of headers to prepend to the message, including the supplied sigheaders
1530 but not the plainheaders.
1534 arc_sign(const uschar * signspec, gstring * sigheaders, uschar ** errstr)
1536 const uschar * identity, * selector, * privkey;
1538 header_line * headers;
1539 hdr_rlist * rheaders;
1545 /* Parse the signing specification */
1547 identity = string_nextinlist(&signspec, &sep, NULL, 0);
1548 selector = string_nextinlist(&signspec, &sep, NULL, 0);
1549 if ( !*identity | !*selector
1550 || !(privkey = string_nextinlist(&signspec, &sep, NULL, 0)) || !*privkey)
1552 log_write(0, LOG_MAIN, "ARC: bad signing-specification (%s)",
1553 !*identity ? "identity" : !*selector ? "selector" : "private-key");
1554 return sigheaders ? sigheaders : string_get(0);
1556 if (*privkey == '/' && !(privkey = expand_file_big_buffer(privkey)))
1557 return sigheaders ? sigheaders : string_get(0);
1559 DEBUG(D_transport) debug_printf("ARC: sign for %s\n", identity);
1561 /* Make an rlist of any new DKIM headers, then add the "normals" rlist to it.
1562 Then scan the list for an A-R header. */
1564 string_from_gstring(sigheaders);
1565 if ((rheaders = arc_sign_scan_headers(&arc_sign_ctx, sigheaders)))
1568 for (rp = &headers_rlist; *rp; ) rp = &(*rp)->prev;
1572 /* Finally, build a normal-order headers list */
1573 /*XXX only needed for hunt-the-AR? */
1574 /*XXX also, we really should be accepting any number of ADMD-matching ARs */
1576 header_line * hnext = NULL;
1577 for (rheaders = headers_rlist; rheaders;
1578 hnext = rheaders->h, rheaders = rheaders->prev)
1579 rheaders->h->next = hnext;
1583 if (!(arc_sign_find_ar(headers, identity, &ar)))
1585 log_write(0, LOG_MAIN, "ARC: no Authentication-Results header for signing");
1586 return sigheaders ? sigheaders : string_get(0);
1589 /* We previously built the data-struct for the existing ARC chain, if any, using a headers
1590 feed from the DKIM module. Use that to give the instance number for the ARC set we are
1594 if (arc_sign_ctx.arcset_chain_last)
1595 debug_printf("ARC: existing chain highest instance: %d\n",
1596 arc_sign_ctx.arcset_chain_last->instance);
1598 debug_printf("ARC: no existing chain\n");
1600 instance = arc_sign_ctx.arcset_chain_last ? arc_sign_ctx.arcset_chain_last->instance + 1 : 1;
1604 - copy the A-R; prepend i= & identity
1607 g = arc_sign_append_aar(g, &arc_sign_ctx, identity, instance, &ar);
1611 - Looks fairly like a DKIM sig
1612 - Cover all DKIM sig headers as well as the usuals
1615 - we must have requested a suitable bodyhash previously
1618 b = arc_ams_setup_sign_bodyhash();
1619 g = arc_sign_append_ams(g, &arc_sign_ctx, instance, identity, selector,
1620 &b->bh, headers_rlist, privkey);
1625 - no h= tag; implicit coverage
1626 - arc status from A-R
1628 - coverage is just the new ARC set
1629 including self (but with an empty b= in self)
1631 - all ARC set headers, set-number order, aar then ams then as,
1632 including self (but with an empty b= in self)
1635 g = arc_sign_prepend_as(g, &arc_sign_ctx, instance, identity, selector, &ar, privkey);
1637 /* Finally, append the dkim headers and return the lot. */
1639 g = string_catn(g, sigheaders->s, sigheaders->ptr);
1640 (void) string_from_gstring(g);
1641 gstring_reset_unused(g);
1646 /******************************************************************************/
1648 /* Check to see if the line is an AMS and if so, set up to validate it.
1649 Called from the DKIM input processing. This must be done now as the message
1650 body data is hashed during input.
1652 We call the DKIM code to request a body-hash; it has the facility already
1653 and the hash parameters might be common with other requests.
1656 static const uschar *
1657 arc_header_vfy_feed(gstring * g)
1664 if (!dkim_verify_ctx) return US"no dkim context";
1666 if (strncmpic(ARC_HDR_AMS, g->s, ARC_HDRLEN_AMS) != 0) return US"not AMS";
1668 DEBUG(D_receive) debug_printf("ARC: spotted AMS header\n");
1669 /* Parse the AMS header */
1674 memset(&al, 0, sizeof(arc_line));
1675 if ((errstr = arc_parse_line(&al, &h, ARC_HDRLEN_AMS, FALSE)))
1677 DEBUG(D_acl) if (errstr) debug_printf("ARC: %s\n", errstr);
1678 return US"line parsing error";
1684 al.c_body.data = US"simple"; al.c_body.len = 6;
1685 al.c_head = al.c_body;
1688 /* Ask the dkim code to calc a bodyhash with those specs */
1690 if (!(b = arc_ams_setup_vfy_bodyhash(&al)))
1691 return US"dkim hash setup fail";
1693 /* Discard the reference; search again at verify time, knowing that one
1694 should have been created here. */
1701 /* A header line has been identified by DKIM processing.
1705 is_vfy TRUE for verify mode or FALSE for signing mode
1708 NULL for success, or an error string (probably unused)
1712 arc_header_feed(gstring * g, BOOL is_vfy)
1714 return is_vfy ? arc_header_vfy_feed(g) : arc_header_sign_feed(g);
1719 /******************************************************************************/
1721 /* Construct an Authenticate-Results header portion, for the ARC module */
1724 authres_arc(gstring * g)
1728 arc_line * highest_ams;
1729 int start = 0; /* Compiler quietening */
1730 DEBUG(D_acl) start = g->ptr;
1732 g = string_append(g, 2, US";\n\tarc=", arc_state);
1733 if (arc_received_instance > 0)
1735 g = string_append(g, 3, US" (i=",
1736 string_sprintf("%d", arc_received_instance), US")");
1737 if (arc_state_reason)
1738 g = string_append(g, 3, US"(", arc_state_reason, US")");
1739 g = string_catn(g, US" header.s=", 10);
1740 highest_ams = arc_received->hdr_ams;
1741 g = string_catn(g, highest_ams->s.data, highest_ams->s.len);
1743 g = string_append(g, 2,
1744 US" arc.oldest-pass=", string_sprintf("%d", arc_oldest_pass));
1746 if (sender_host_address)
1747 g = string_append(g, 2, US" smtp.client-ip=", sender_host_address);
1749 else if (arc_state_reason)
1750 g = string_append(g, 3, US" (", arc_state_reason, US")");
1751 DEBUG(D_acl) debug_printf("ARC: authres '%.*s'\n",
1752 g->ptr - start - 3, g->s + start + 3);
1755 DEBUG(D_acl) debug_printf("ARC: no authres\n");
1760 # endif /* SUPPORT_SPF */
1761 #endif /* EXPERIMENTAL_ARC */