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;
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)))
502 if (!ctx->arcset_chain) return US"none";
508 arc_cv_match(arc_line * al, const uschar * s)
510 return Ustrncmp(s, al->cv.data, al->cv.len) == 0;
513 /******************************************************************************/
515 /* Return the hash of headers from the message that the AMS claims it
520 arc_get_verify_hhash(arc_ctx * ctx, arc_line * ams, blob * hhash)
522 const uschar * headernames = string_copyn(ams->h.data, ams->h.len);
526 BOOL relaxed = Ustrncmp(US"relaxed", ams->c_head.data, ams->c_head.len) == 0;
527 int hashtype = pdkim_hashname_to_hashtype(
528 ams->a_hash.data, ams->a_hash.len);
533 if (!exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
536 debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
540 /* For each headername in the list from the AMS (walking in order)
541 walk the message headers in reverse order, adding to the hash any
542 found for the first time. For that last point, maintain used-marks
543 on the list of message headers. */
545 DEBUG(D_acl) debug_printf("ARC: AMS header data for verification:\n");
547 for (r = headers_rlist; r; r = r->prev)
549 while ((hn = string_nextinlist(&headernames, &sep, NULL, 0)))
550 for (r = headers_rlist; r; r = r->prev)
552 && strncasecmp(CCS (s = r->h->text), CCS hn, Ustrlen(hn)) == 0
555 if (relaxed) s = pdkim_relax_header_n(s, r->h->slen, TRUE);
558 DEBUG(D_acl) pdkim_quoteprint(s, len);
559 exim_sha_update(&hhash_ctx, s, Ustrlen(s));
564 /* Finally add in the signature header (with the b= tag stripped) */
566 s = ams->rawsig_no_b_val.data, len = ams->rawsig_no_b_val.len;
568 len = Ustrlen(s = pdkim_relax_header_n(s, len, TRUE));
569 DEBUG(D_acl) pdkim_quoteprint(s, len);
570 exim_sha_update(&hhash_ctx, s, len);
572 exim_sha_finish(&hhash_ctx, hhash);
574 { debug_printf("ARC: header hash: "); pdkim_hexprint(hhash->data, hhash->len); }
581 static pdkim_pubkey *
582 arc_line_to_pubkey(arc_line * al)
587 if (!(dns_txt = dkim_exim_query_dns_txt(string_sprintf("%.*s._domainkey.%.*s",
588 al->s.len, al->s.data, al->d.len, al->d.data))))
590 DEBUG(D_acl) debug_printf("pubkey dns lookup fail\n");
594 if ( !(p = pdkim_parse_pubkey_record(dns_txt))
595 || (Ustrcmp(p->srvtype, "*") != 0 && Ustrcmp(p->srvtype, "email") != 0)
598 DEBUG(D_acl) debug_printf("pubkey dns lookup format error\n");
602 /* If the pubkey limits use to specified hashes, reject unusable
603 signatures. XXX should we have looked for multiple dns records? */
607 const uschar * list = p->hashes, * ele;
610 while ((ele = string_nextinlist(&list, &sep, NULL, 0)))
611 if (Ustrncmp(ele, al->a_hash.data, al->a_hash.len) == 0) break;
614 DEBUG(D_acl) debug_printf("pubkey h=%s vs sig a=%.*s\n",
615 p->hashes, (int)al->a.len, al->a.data);
625 static pdkim_bodyhash *
626 arc_ams_setup_vfy_bodyhash(arc_line * ams)
628 int canon_head, canon_body;
631 pdkim_cstring_to_canons(ams->c.data, ams->c.len, &canon_head, &canon_body);
632 bodylen = ams->l.data
633 ? strtol(CS string_copyn(ams->l.data, ams->l.len), NULL, 10) : -1;
635 return pdkim_set_bodyhash(dkim_verify_ctx,
636 pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len),
643 /* Verify an AMS. This is a DKIM-sig header, but with an ARC i= tag
644 and without a DKIM v= tag.
648 arc_ams_verify(arc_ctx * ctx, arc_set * as)
650 arc_line * ams = as->hdr_ams;
657 const uschar * errstr;
659 as->ams_verify_done = TRUE;
661 /* Check the AMS has all the required tags:
665 "d=" domain (for key lookup)
666 "h=" headers (included in signature)
667 "s=" key-selector (for key lookup)
669 if ( !ams->a.data || !ams->b.data || !ams->bh.data || !ams->d.data
670 || !ams->h.data || !ams->s.data)
674 /* The bodyhash should have been created earlier, and the dkim code should
675 have managed calculating it during message input. Find the reference to it. */
677 if (!(b = arc_ams_setup_vfy_bodyhash(ams)))
682 debug_printf("ARC i=%d AMS Body bytes hashed: %lu\n"
683 " Body %.*s computed: ",
684 as->instance, b->signed_body_bytes,
685 (int)ams->a_hash.len, ams->a_hash.data);
686 pdkim_hexprint(CUS b->bh.data, b->bh.len);
689 /* We know the bh-tag blob is of a nul-term string, so safe as a string */
692 || (pdkim_decode_base64(ams->bh.data, &sighash), sighash.len != b->bh.len)
693 || memcmp(sighash.data, b->bh.data, b->bh.len) != 0
698 debug_printf("ARC i=%d AMS Body hash from headers: ", as->instance);
699 pdkim_hexprint(sighash.data, sighash.len);
700 debug_printf("ARC i=%d AMS Body hash did NOT match\n", as->instance);
702 return US"body hash compare mismatch";
705 DEBUG(D_acl) debug_printf("ARC i=%d AMS Body hash compared OK\n", as->instance);
707 /* Get the public key from DNS */
709 if (!(p = arc_line_to_pubkey(ams)))
710 return US"pubkey problem";
712 /* We know the b-tag blob is of a nul-term string, so safe as a string */
713 pdkim_decode_base64(ams->b.data, &sighash);
715 arc_get_verify_hhash(ctx, ams, &hhash);
717 /* Setup the interface to the signing library */
719 if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx)))
721 DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
725 hashtype = pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len);
727 if ((errstr = exim_dkim_verify(&vctx,
728 pdkim_hashes[hashtype].exim_hashmethod, &hhash, &sighash)))
730 DEBUG(D_acl) debug_printf("ARC i=%d AMS verify %s\n", as->instance, errstr);
731 return US"ams sig verify fail";
734 DEBUG(D_acl) debug_printf("ARC i=%d AMS verify pass\n", as->instance);
735 as->ams_verify_passed = TRUE;
741 /* Check the sets are instance-continuous and that all
742 members are present. Check that no arc_seals are "fail".
743 Set the highest instance number global.
744 Verify the latest AMS.
747 arc_headers_check(arc_ctx * ctx)
751 BOOL ams_fail_found = FALSE;
754 if (!(as = ctx->arcset_chain))
757 for(inst = 0; as; as = as->next)
759 if ( as->instance != ++inst
760 || !as->hdr_aar || !as->hdr_ams || !as->hdr_as
761 || arc_cv_match(as->hdr_as, US"fail")
764 DEBUG(D_acl) debug_printf("ARC i=%d fail"
765 " (cv, sequence or missing header)\n", as->instance);
769 /* Evaluate the oldest-pass AMS validation while we're here.
770 It does not affect the AS chain validation but is reported as
774 if (arc_ams_verify(ctx, as))
775 ams_fail_found = TRUE;
777 arc_oldest_pass = inst;
780 arc_received = ctx->arcset_chain_last;
781 arc_received_instance = inst;
785 /* We can skip the latest-AMS validation, if we already did it. */
787 as = ctx->arcset_chain_last;
788 if (as->ams_verify_done ? !as->ams_verify_passed : !!arc_ams_verify(ctx, as))
795 /******************************************************************************/
796 static const uschar *
797 arc_seal_verify(arc_ctx * ctx, arc_set * as)
799 arc_line * hdr_as = as->hdr_as;
807 const uschar * errstr;
809 DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d\n", as->instance);
811 1. If the value of the "cv" tag on that seal is "fail", the
812 chain state is "fail" and the algorithm stops here. (This
813 step SHOULD be skipped if the earlier step (2.1) was
816 2. In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
817 == "none" && i != 1)) then the chain state is "fail" and the
818 algorithm stops here (note that the ordering of the logic is
819 structured for short-circuit evaluation).
822 if ( as->instance == 1 && !arc_cv_match(hdr_as, US"none")
823 || arc_cv_match(hdr_as, US"none") && as->instance != 1
828 3. Initialize a hash function corresponding to the "a" tag of
832 hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len);
834 if (!exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
837 debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
842 4. Compute the canonicalized form of the ARC header fields, in
843 the order described in Section 5.4.2, using the "relaxed"
844 header canonicalization defined in Section 3.4.2 of
845 [RFC6376]. Pass the canonicalized result to the hash
849 DEBUG(D_acl) debug_printf("ARC: AS header data for verification:\n");
850 for (as2 = ctx->arcset_chain;
851 as2 && as2->instance <= as->instance;
859 if (!(s = al->relaxed))
860 al->relaxed = s = pdkim_relax_header_n(al->complete->text,
861 al->complete->slen, TRUE);
863 DEBUG(D_acl) pdkim_quoteprint(s, len);
864 exim_sha_update(&hhash_ctx, s, len);
867 if (!(s = al->relaxed))
868 al->relaxed = s = pdkim_relax_header_n(al->complete->text,
869 al->complete->slen, TRUE);
871 DEBUG(D_acl) pdkim_quoteprint(s, len);
872 exim_sha_update(&hhash_ctx, s, len);
875 if (as2->instance == as->instance)
876 s = pdkim_relax_header_n(al->rawsig_no_b_val.data,
877 al->rawsig_no_b_val.len, TRUE);
878 else if (!(s = al->relaxed))
879 al->relaxed = s = pdkim_relax_header_n(al->complete->text,
880 al->complete->slen, TRUE);
882 DEBUG(D_acl) pdkim_quoteprint(s, len);
883 exim_sha_update(&hhash_ctx, s, len);
887 5. Retrieve the final digest from the hash function.
890 exim_sha_finish(&hhash_ctx, &hhash_computed);
893 debug_printf("ARC i=%d AS Header %.*s computed: ",
894 as->instance, (int)hdr_as->a_hash.len, hdr_as->a_hash.data);
895 pdkim_hexprint(hhash_computed.data, hhash_computed.len);
900 6. Retrieve the public key identified by the "s" and "d" tags in
901 the ARC-Seal, as described in Section 4.1.6.
904 if (!(p = arc_line_to_pubkey(hdr_as)))
905 return US"pubkey problem";
908 7. Determine whether the signature portion ("b" tag) of the ARC-
909 Seal and the digest computed above are valid according to the
910 public key. (See also Section Section 8.4 for failure case
913 8. If the signature is not valid, the chain state is "fail" and
914 the algorithm stops here.
917 /* We know the b-tag blob is of a nul-term string, so safe as a string */
918 pdkim_decode_base64(hdr_as->b.data, &sighash);
920 if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx)))
922 DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
926 hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len);
928 if ((errstr = exim_dkim_verify(&vctx,
929 pdkim_hashes[hashtype].exim_hashmethod,
930 &hhash_computed, &sighash)))
933 debug_printf("ARC i=%d AS headers verify: %s\n", as->instance, errstr);
937 DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d pass\n", as->instance);
942 static const uschar *
943 arc_verify_seals(arc_ctx * ctx)
945 arc_set * as = ctx->arcset_chain;
952 if (arc_seal_verify(ctx, as)) return US"fail";
955 DEBUG(D_acl) debug_printf("ARC: AS vfy overall pass\n");
958 /******************************************************************************/
960 /* Do ARC verification. Called from DATA ACL, on a verify = arc
961 condition. No arguments; we are checking globals.
963 Return: The ARC state, or NULL on error.
969 arc_ctx ctx = { NULL };
972 /* AS evaluation, per
973 https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-10#section-6
975 /* 1. Collect all ARC sets currently on the message. If there were
976 none, the ARC state is "none" and the algorithm stops here.
979 if ((res = arc_vfy_collect_hdrs(&ctx)))
982 /* 2. If the form of any ARC set is invalid (e.g., does not contain
983 exactly one of each of the three ARC-specific header fields),
984 then the chain state is "fail" and the algorithm stops here.
986 1. To avoid the overhead of unnecessary computation and delay
987 from crypto and DNS operations, the cv value for all ARC-
988 Seal(s) MAY be checked at this point. If any of the values
989 are "fail", then the overall state of the chain is "fail" and
990 the algorithm stops here.
992 3. Conduct verification of the ARC-Message-Signature header field
993 bearing the highest instance number. If this verification fails,
994 then the chain state is "fail" and the algorithm stops here.
997 if ((res = arc_headers_check(&ctx)))
1000 /* 4. For each ARC-Seal from the "N"th instance to the first, apply the
1003 1. If the value of the "cv" tag on that seal is "fail", the
1004 chain state is "fail" and the algorithm stops here. (This
1005 step SHOULD be skipped if the earlier step (2.1) was
1008 2. In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
1009 == "none" && i != 1)) then the chain state is "fail" and the
1010 algorithm stops here (note that the ordering of the logic is
1011 structured for short-circuit evaluation).
1013 3. Initialize a hash function corresponding to the "a" tag of
1016 4. Compute the canonicalized form of the ARC header fields, in
1017 the order described in Section 5.4.2, using the "relaxed"
1018 header canonicalization defined in Section 3.4.2 of
1019 [RFC6376]. Pass the canonicalized result to the hash
1022 5. Retrieve the final digest from the hash function.
1024 6. Retrieve the public key identified by the "s" and "d" tags in
1025 the ARC-Seal, as described in Section 4.1.6.
1027 7. Determine whether the signature portion ("b" tag) of the ARC-
1028 Seal and the digest computed above are valid according to the
1029 public key. (See also Section Section 8.4 for failure case
1032 8. If the signature is not valid, the chain state is "fail" and
1033 the algorithm stops here.
1035 5. If all seals pass validation, then the chain state is "pass", and
1036 the algorithm is complete.
1039 if ((res = arc_verify_seals(&ctx)))
1048 /******************************************************************************/
1050 /* Prepend the header to the rlist */
1053 arc_rlist_entry(hdr_rlist * list, const uschar * s, int len)
1055 hdr_rlist * r = store_get(sizeof(hdr_rlist) + sizeof(header_line));
1056 header_line * h = r->h = (header_line *)(r+1);
1065 /* This works for either NL or CRLF lines; also nul-termination */
1067 if (*s == '\n' && s[1] != '\t' && s[1] != ' ') break;
1068 s++; /* move past end of line */
1074 /* Walk the given headers strings identifying each header, and construct
1075 a reverse-order list. Also parse ARC-chain headers and build the chain
1076 struct, retaining pointers into the string.
1080 arc_sign_scan_headers(arc_ctx * ctx, gstring * sigheaders)
1083 hdr_rlist * rheaders = NULL;
1085 s = sigheaders ? sigheaders->s : NULL;
1088 const uschar * s2 = s;
1090 /* This works for either NL or CRLF lines; also nul-termination */
1092 if (*s2 == '\n' && s2[1] != '\t' && s2[1] != ' ') break;
1093 s2++; /* move past end of line */
1095 rheaders = arc_rlist_entry(rheaders, s, s2 - s);
1103 /* Return the A-R content, without identity, with line-ending and
1107 arc_sign_find_ar(header_line * headers, const uschar * identity, blob * ret)
1110 int ilen = Ustrlen(identity);
1113 for(h = headers; h; h = h->next)
1115 uschar * s = h->text, c;
1118 if (Ustrncmp(s, HDR_AR, HDRLEN_AR) != 0) continue;
1119 s += HDRLEN_AR, len -= HDRLEN_AR; /* header name */
1121 && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
1122 s++, len--; /* FWS */
1123 if (Ustrncmp(s, identity, ilen) != 0) continue;
1124 s += ilen; len -= ilen; /* identity */
1125 if (len <= 0) continue;
1126 if ((c = *s) && c == ';') s++, len--; /* identity terminator */
1128 && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
1129 s++, len--; /* FWS */
1130 if (len <= 0) continue;
1140 /* Append a constructed AAR including CRLF. Add it to the arc_ctx too. */
1143 arc_sign_append_aar(gstring * g, arc_ctx * ctx,
1144 const uschar * identity, int instance, blob * ar)
1146 int aar_off = g ? g->ptr : 0;
1147 arc_set * as = store_get(sizeof(arc_set) + sizeof(arc_line) + sizeof(header_line));
1148 arc_line * al = (arc_line *)(as+1);
1149 header_line * h = (header_line *)(al+1);
1151 g = string_catn(g, ARC_HDR_AAR, ARC_HDRLEN_AAR);
1152 g = string_cat(g, string_sprintf(" i=%d; %s;\r\n\t", instance, identity));
1153 g = string_catn(g, US ar->data, ar->len);
1155 h->slen = g->ptr - aar_off;
1156 h->text = g->s + aar_off;
1159 as->prev = ctx->arcset_chain_last;
1160 as->instance = instance;
1163 ctx->arcset_chain = as;
1165 ctx->arcset_chain_last->next = as;
1166 ctx->arcset_chain_last = as;
1168 DEBUG(D_transport) debug_printf("ARC: AAR '%.*s'\n", h->slen - 2, h->text);
1175 arc_sig_from_pseudoheader(gstring * hdata, int hashtype, const uschar * privkey,
1176 blob * sig, const uschar * why)
1178 hashmethod hm = /*sig->keytype == KEYTYPE_ED25519*/ FALSE
1179 ? HASH_SHA2_512 : pdkim_hashes[hashtype].exim_hashmethod;
1182 const uschar * errstr;
1187 debug_printf("ARC: %s header data for signing:\n", why);
1188 pdkim_quoteprint(hdata->s, hdata->ptr);
1190 (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
1191 exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
1192 exim_sha_finish(&hhash_ctx, &hhash);
1193 debug_printf("ARC: header hash: "); pdkim_hexprint(hhash.data, hhash.len);
1196 if (FALSE /*need hash for Ed25519 or GCrypt signing*/ )
1199 (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
1200 exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
1201 exim_sha_finish(&hhash_ctx, &hhash);
1205 hhash.data = hdata->s;
1206 hhash.len = hdata->ptr;
1209 if ( (errstr = exim_dkim_signing_init(privkey, &sctx))
1210 || (errstr = exim_dkim_sign(&sctx, hm, &hhash, sig)))
1212 log_write(0, LOG_MAIN|LOG_PANIC, "ARC: %s signing: %s\n", why, errstr);
1221 arc_sign_append_sig(gstring * g, blob * sig)
1223 /*debug_printf("%s: raw sig ", __FUNCTION__); pdkim_hexprint(sig->data, sig->len);*/
1224 sig->data = pdkim_encode_base64(sig);
1225 sig->len = Ustrlen(sig->data);
1228 int len = MIN(sig->len, 74);
1229 g = string_catn(g, sig->data, len);
1230 if ((sig->len -= len) == 0) break;
1232 g = string_catn(g, US"\r\n\t ", 5);
1234 g = string_catn(g, US";\r\n", 3);
1235 gstring_reset_unused(g);
1236 string_from_gstring(g);
1241 /* Append a constructed AMS including CRLF. Add it to the arc_ctx too. */
1244 arc_sign_append_ams(gstring * g, arc_ctx * ctx, int instance,
1245 const uschar * identity, const uschar * selector, blob * bodyhash,
1246 hdr_rlist * rheaders, const uschar * privkey)
1249 gstring * hdata = NULL;
1251 int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6); /*XXX hardwired */
1254 arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line));
1255 header_line * h = (header_line *)(al+1);
1257 /* debug_printf("%s\n", __FUNCTION__); */
1259 /* Construct the to-be-signed AMS pseudo-header: everything but the sig. */
1262 g = string_append(g, 10,
1264 US" i=", string_sprintf("%d", instance),
1265 US"; a=rsa-sha256; c=relaxed; d=", identity, /*XXX hardwired */
1267 US";\r\n\tbh=", pdkim_encode_base64(bodyhash),
1270 for(col = 3; rheaders; rheaders = rheaders->prev)
1272 const uschar * hnames = US"DKIM-Signature:" PDKIM_DEFAULT_SIGN_HEADERS;
1273 uschar * name, * htext = rheaders->h->text;
1276 /* Spot headers of interest */
1278 while ((name = string_nextinlist(&hnames, &sep, NULL, 0)))
1280 int len = Ustrlen(name);
1281 if (strncasecmp(CCS htext, CCS name, len) == 0)
1283 /* If too long, fold line in h= field */
1285 if (col + len > 78) g = string_catn(g, US"\r\n\t ", 5), col = 3;
1287 /* Add name to h= list */
1289 g = string_catn(g, name, len);
1290 g = string_catn(g, US":", 1);
1293 /* Accumulate header for hashing/signing */
1295 hdata = string_cat(hdata,
1296 pdkim_relax_header_n(htext, rheaders->h->slen, TRUE)); /*XXX hardwired */
1302 /* Lose the last colon from the h= list */
1304 if (g->s[g->ptr - 1] == ':') g->ptr--;
1306 g = string_catn(g, US";\r\n\tb=;", 7);
1308 /* Include the pseudo-header in the accumulation */
1309 /*XXX should that be prepended rather than appended? */
1310 /*XXX also need to include at the verify stage */
1312 s = pdkim_relax_header_n(g->s + ams_off, g->ptr - ams_off, TRUE);
1313 hdata = string_cat(hdata, s);
1315 /* Calculate the signature from the accumulation */
1316 /*XXX does that need further relaxation? there are spaces embedded in the b= strings! */
1318 if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AMS"))
1321 /* Lose the trailing semicolon from the psuedo-header, and append the signature
1322 (folded over lines) and termination to complete it. */
1325 g = arc_sign_append_sig(g, &sig);
1327 h->slen = g->ptr - ams_off;
1328 h->text = g->s + ams_off;
1330 ctx->arcset_chain_last->hdr_ams = al;
1332 DEBUG(D_transport) debug_printf("ARC: AMS '%.*s'\n", h->slen - 2, h->text);
1338 /* Look for an arc= result in an A-R header blob. We know that its data
1339 happens to be a NUL-term string. */
1342 arc_ar_cv_status(blob * ar)
1344 const uschar * resinfo = ar->data;
1346 uschar * methodspec, * s;
1348 while ((methodspec = string_nextinlist(&resinfo, &sep, NULL, 0)))
1349 if (Ustrncmp(methodspec, US"arc=", 4) == 0)
1352 for (s = methodspec += 4;
1353 (c = *s) && c != ';' && c != ' ' && c != '\r' && c != '\n'; ) s++;
1354 return string_copyn(methodspec, s - methodspec);
1361 /* Build the AS header and prepend it */
1364 arc_sign_prepend_as(gstring * arcset_interim, arc_ctx * ctx,
1365 int instance, const uschar * identity, const uschar * selector, blob * ar,
1366 const uschar * privkey)
1370 uschar * status = arc_ar_cv_status(ar);
1371 arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line));
1372 header_line * h = (header_line *)(al+1);
1374 gstring * hdata = NULL;
1375 int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6); /*XXX hardwired */
1381 - no h= tag; implicit coverage
1382 - arc status from A-R
1384 - coverage is just the new ARC set
1385 including self (but with an empty b= in self)
1387 - all ARC set headers, set-number order, aar then ams then as,
1388 including self (but with an empty b= in self)
1391 /* Construct the AS except for the signature */
1393 arcset = string_append(NULL, 10,
1395 US" i=", string_sprintf("%d", instance),
1397 US"; a=rsa-sha256; c=relaxed; d=", identity, /*XXX hardwired */
1398 US"; s=", selector, /*XXX same as AMS */
1401 h->slen = arcset->ptr;
1402 h->text = arcset->s;
1404 ctx->arcset_chain_last->hdr_as = al;
1406 /* For any but "fail" chain-verify status, walk the entire chain in order by
1407 instance. For fail, only the new arc-set. Accumulate the elements walked. */
1409 for (as = Ustrcmp(status, US"fail") == 0
1410 ? ctx->arcset_chain_last : ctx->arcset_chain;
1413 /* Accumulate AAR then AMS then AS. Relaxed canonicalisation
1414 is required per standard. */
1416 h = as->hdr_aar->complete;
1417 hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
1418 h = as->hdr_ams->complete;
1419 hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
1420 h = as->hdr_as->complete;
1421 hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
1424 /* Calculate the signature from the accumulation */
1426 if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AS"))
1429 /* Lose the trailing semicolon */
1431 arcset = arc_sign_append_sig(arcset, &sig);
1432 DEBUG(D_transport) debug_printf("ARC: AS '%.*s'\n", arcset->ptr - 2, arcset->s);
1434 /* Finally, append the AMS and AAR to the new AS */
1436 return string_catn(arcset, arcset_interim->s, arcset_interim->ptr);
1440 /**************************************/
1442 /* Return pointer to pdkim_bodyhash for given hash method, creating new
1447 arc_ams_setup_sign_bodyhash(void)
1449 int canon_head, canon_body;
1451 DEBUG(D_transport) debug_printf("ARC: requesting bodyhash\n");
1452 pdkim_cstring_to_canons(US"relaxed", 7, &canon_head, &canon_body); /*XXX hardwired */
1453 return pdkim_set_bodyhash(&dkim_sign_ctx,
1454 pdkim_hashname_to_hashtype(US"sha256", 6), /*XXX hardwired */
1461 /* A "normal" header line, identified by DKIM processing. These arrive before
1462 the call to arc_sign(), which carries any newly-created DKIM headers - and
1463 those go textually before the normal ones in the message.
1465 We have to take the feed from DKIM as, in the transport-filter case, the
1466 headers are not in memory at the time of the call to arc_sign().
1468 Take a copy of the header and construct a reverse-order list.
1469 Also parse ARC-chain headers and build the chain struct, retaining pointers
1473 static const uschar *
1474 arc_header_sign_feed(gstring * g)
1476 uschar * s = string_copyn(g->s, g->ptr);
1477 headers_rlist = arc_rlist_entry(headers_rlist, s, g->ptr);
1478 return arc_try_header(&arc_sign_ctx, headers_rlist->h, TRUE);
1483 /* ARC signing. Called from the smtp transport, if the arc_sign option is set.
1484 The dkim_exim_sign() function has already been called, so will have hashed the
1485 message body for us so long as we requested a hash previously.
1488 signspec Three-element colon-sep list: identity, selector, privkey
1490 sigheaders Any signature headers already generated, eg. by DKIM, or NULL
1494 Set of headers to prepend to the message, including the supplied sigheaders
1495 but not the plainheaders.
1499 arc_sign(const uschar * signspec, gstring * sigheaders, uschar ** errstr)
1501 const uschar * identity, * selector, * privkey;
1503 header_line * headers;
1504 hdr_rlist * rheaders;
1510 /* Parse the signing specification */
1512 identity = string_nextinlist(&signspec, &sep, NULL, 0);
1513 selector = string_nextinlist(&signspec, &sep, NULL, 0);
1514 if ( !*identity | !*selector
1515 || !(privkey = string_nextinlist(&signspec, &sep, NULL, 0)) || !*privkey)
1517 log_write(0, LOG_MAIN|LOG_PANIC, "ARC: bad signing-specification");
1520 if (*privkey == '/' && !(privkey = expand_file_big_buffer(privkey)))
1523 DEBUG(D_transport) debug_printf("ARC: sign for %s\n", identity);
1526 - scan headers for existing ARC chain & A-R (with matching system-identfier)
1527 - paniclog & skip on problems (no A-R)
1530 /* Make an rlist of any new DKIM headers, then add the "normals" rlist to it */
1532 string_from_gstring(sigheaders);
1533 if ((rheaders = arc_sign_scan_headers(&arc_sign_ctx, sigheaders)))
1536 for (rp = &rheaders; *rp; ) rp = &(*rp)->prev;
1537 *rp = headers_rlist;
1538 headers_rlist = rheaders;
1541 rheaders = headers_rlist;
1542 /* Finally, build a normal-order headers list */
1543 /*XXX only needed for hunt-the-AR? */
1545 header_line * hnext = NULL;
1546 for (; rheaders; hnext = rheaders->h, rheaders = rheaders->prev)
1547 rheaders->h->next = hnext;
1551 instance = arc_sign_ctx.arcset_chain_last ? arc_sign_ctx.arcset_chain_last->instance + 1 : 1;
1553 if (!(arc_sign_find_ar(headers, identity, &ar)))
1555 log_write(0, LOG_MAIN|LOG_PANIC, "ARC: no Authentication-Results header for signing");
1556 return sigheaders ? sigheaders : string_get(0);
1561 - copy the A-R; prepend i= & identity
1564 g = arc_sign_append_aar(g, &arc_sign_ctx, identity, instance, &ar);
1568 - Looks fairly like a DKIM sig
1569 - Cover all DKIM sig headers as well as the usuals
1572 - we must have requested a suitable bodyhash previously
1575 b = arc_ams_setup_sign_bodyhash();
1576 g = arc_sign_append_ams(g, &arc_sign_ctx, instance, identity, selector,
1577 &b->bh, headers_rlist, privkey);
1582 - no h= tag; implicit coverage
1583 - arc status from A-R
1585 - coverage is just the new ARC set
1586 including self (but with an empty b= in self)
1588 - all ARC set headers, set-number order, aar then ams then as,
1589 including self (but with an empty b= in self)
1592 g = arc_sign_prepend_as(g, &arc_sign_ctx, instance, identity, selector, &ar, privkey);
1594 /* Finally, append the dkim headers and return the lot. */
1596 g = string_catn(g, sigheaders->s, sigheaders->ptr);
1597 (void) string_from_gstring(g);
1598 gstring_reset_unused(g);
1603 /******************************************************************************/
1605 /* Check to see if the line is an AMS and if so, set up to validate it.
1606 Called from the DKIM input processing. This must be done now as the message
1607 body data is hashed during input.
1609 We call the DKIM code to request a body-hash; it has the facility already
1610 and the hash parameters might be common with other requests.
1613 static const uschar *
1614 arc_header_vfy_feed(gstring * g)
1621 if (!dkim_verify_ctx) return US"no dkim context";
1623 if (strncmpic(ARC_HDR_AMS, g->s, ARC_HDRLEN_AMS) != 0) return US"not AMS";
1625 DEBUG(D_receive) debug_printf("ARC: spotted AMS header\n");
1626 /* Parse the AMS header */
1631 memset(&al, 0, sizeof(arc_line));
1632 if ((errstr = arc_parse_line(&al, &h, ARC_HDRLEN_AMS, FALSE)))
1634 DEBUG(D_acl) if (errstr) debug_printf("ARC: %s\n", errstr);
1635 return US"line parsing error";
1641 al.c_body.data = US"simple"; al.c_body.len = 6;
1642 al.c_head = al.c_body;
1645 /* Ask the dkim code to calc a bodyhash with those specs */
1647 if (!(b = arc_ams_setup_vfy_bodyhash(&al)))
1648 return US"dkim hash setup fail";
1650 /* Discard the reference; search again at verify time, knowing that one
1651 should have been created here. */
1658 /* A header line has been identified by DKIM processing.
1662 is_vfy TRUE for verify mode or FALSE for signing mode
1665 NULL for success, or an error string (probably unused)
1669 arc_header_feed(gstring * g, BOOL is_vfy)
1671 return is_vfy ? arc_header_vfy_feed(g) : arc_header_sign_feed(g);
1676 /******************************************************************************/
1678 /* Construct an Authenticate-Results header portion, for the ARC module */
1681 authres_arc(gstring * g)
1685 arc_line * highest_ams;
1686 int start = 0; /* Compiler quietening */
1687 DEBUG(D_acl) start = g->ptr;
1689 g = string_append(g, 2, US";\n\tarc=", arc_state);
1690 if (arc_received_instance > 0)
1692 g = string_append(g, 3, US" (i=",
1693 string_sprintf("%d", arc_received_instance), US") header.s=");
1694 highest_ams = arc_received->hdr_ams;
1695 g = string_catn(g, highest_ams->s.data, highest_ams->s.len);
1697 g = string_append(g, 2,
1698 US" arc.oldest-pass=", string_sprintf("%d", arc_oldest_pass));
1700 if (sender_host_address)
1701 g = string_append(g, 2, US" smtp.client-ip=", sender_host_address);
1703 DEBUG(D_acl) debug_printf("ARC: authres '%.*s'\n",
1704 g->ptr - start - 3, g->s + start + 3);
1707 DEBUG(D_acl) debug_printf("ARC: no authres\n");
1712 # endif /* SUPPORT_SPF */
1713 #endif /* EXPERIMENTAL_ARC */