Hello Bron,

I'm trying to parse this into a coherent attack vector. What could a bad actor do with this? Launder a bad messages as good somehow? Launder reputation?
Thank you for working through the scenario concretely. I follow your analysis of it: if C applies the recipe and validates the preceding Message-Instance, a modified body cannot be laundered into a passing chain. There is no smuggling vector in that case and I will not press that framing. The issue is not that an attacker can launder content. The issue is that the verdict on a single message depends on which parser the operator runs. Two conforming parsers may resolve the same signed bytes differently (RFC 8259 leaves duplicate key resolution undefined, so taking the first and taking the last are both conformant) and the verification outcome diverges accordingly. This is not bad coding on either side; it is an ambiguity the encoding carries. In a protocol whose purpose is authentication, a verdict that depends on the implementation rather than on the message is a problem of guarantee, not of exploit: it enables no laundering, but it leaves a property the specification does not pin down.

Incorrect in DKIM2's case. Each hop walks the whole chain, and independently verifies the previous hop's work. One weak hop doesn't weaken the later validators.
I take your point. That speaks to a weak-link argument; the one I am putting is different. The divergence is not between hops. Every hop running parser A reaches one conclusion and every hop running parser B reaches the other and re-verifying at each hop reproduces the same result rather than exposing a discrepancy. Verification looks between hops; nothing looks between implementations. The consequence is selective deliverability: a message valid for part of the network and invalid for the rest, with the composer choosing which part.

Assuming all hops are honest is a chump's game, see ARC.
I am not assuming that; the check is what establishes it. A one-step rollback to i-1 verifies that the predecessor declared the modifications it actually made, against a header hash that predecessor signed. It is the dishonest hop the check exists for, and an undeclared modification fails it. Nor does the guarantee depend on any particular hop performing the check. The predecessor's hash is signed and the successor's hash and modification declarations are signed too, so that link remains checkable by any later party. This covers the case of a hop that declares some of its changes and not others: the header hash it signs covers the whole non-excluded header set, so anything it injected or altered without declaring is inside what it signed and the rollback of its declarations no longer reconstructs the hash its predecessor signed. The mismatch is attributable to that hop by its own signature. A hop that skips the check forgoes early detection and takes responsibility for what it re-signs, but destroys no evidence: a final verifier can still walk the whole chain as a sequence of independent one-step rollbacks, each over two adjacent header states, each completing and releasing before the next, with no body reconstructed at any point. The ARC comparison does not carry over. In ARC an intermediary attests to what it says it saw, with nothing for a later party to check it against. Here the declaration is checked against a hash the previous party signed.

It MUST be both. Otherwise the security hole is big enough to drive a truck through.
This is the reason I am raising it at all. The composition you describe as necessary is not in the document. Section 11 does state that when a Verifier is determining a particular DKIM2-Signature it MUST first apply the relevant Recipes and ignore fields added later; but that governs how a signature is evaluated once a Verifier has chosen to evaluate it, not which signatures must be evaluated. Section 10.1 says only that a Verifier SHOULD check the most recent signature and its Message-Instance and Section 10.3 makes the checking of all the DKIM2-Signature fields conditional on the Verifier wanting chain of custody, explosion, feedback or reputation information. Section 11.1 permits ceasing verification at the first failure. The document describes how hash values are validated (Section 11.7) but does not state the composition as a requirement: nothing says that a Verifier reporting a result must have validated each reconstruction against the hashes of the Message-Instance it reconstructs. Section 10.2 states the opposite in one case - where only the first form of the message is of interest, all the Recipes can be applied in turn and only one hash value checked, the correctness of the intermediate hash values being not relevant to that assessment. So the composition you describe as mandatory is not stated as a requirement anywhere I can find. If it is essential, and I agree with you that it is, it belongs in the specification rather than on this list.

I mean, it's possible. It's really bad coding, but I guess it's possible.
You may say this is an implementation matter. But if the composition is essential for security and the specification does not require it, then an implementation can omit it and remain conformant. That is not an implementation matter; it is a gap in the specification.

What would you accept as a rebuttal other than throwing away JSON? What
proof would be sufficient to persuade you?
Not the removal of JSON: text in the document. I would propose adding to Section 10 a requirement along these lines: "A Verifier that applies a Recipe to reconstruct a prior state of the message MUST validate that reconstruction against the hash values declared in the corresponding Message-Instance header field, and MUST NOT report a verification result that derives from a reconstruction it has not validated in this way." And, more generally, a statement that where the encoding admits more than one conformant resolution of the same input, the specification defines which one applies and a Verifier that encounters an input for which no resolution is defined reports PERMERROR rather than choosing. Forbidding duplicate keys closes one such case; it does not establish the rule and the rule is what determines whether the next case is closed before or after it is found in deployment. That is the shape of what would satisfy me. Note that what I am asking for concerns the verification procedure as a whole; it is not specific to body recipes, which are a separate question I am not raising here.

You especially can't fall back to blanket statements when said security analysis has been done by somebody else and they showed why it's not a problem in this case.
I would be glad to read it - a message reference or a document section would let me address it directly.

Regards,
Vittorio Moccia


Il 2026-09-16 23:30 Bron Gondwana ha scritto:
On Wed, Sep 16, 2026, at 04:51, Vittorio wrote:

Concretely, with duplicate keys: a recipe object containing two
entries
for the same header field name is not rejected by a conforming
parser
and RFC 8259 leaves the resolution undefined. One implementation
takes
the first, another the last. Both then reconstruct a different
previous
header state from the same signed bytes and compare it against the
hash
declared by the preceding hop. One reconstruction matches; the other

does not.

I'm trying to parse this into a coherent attack vector.  What could a
bad actor do with this?  Launder a bad messages as good somehow?
Launder reputation?

Consider a message from A to C via B.  B being the canonical "Bad
actor".  A is sending an invoice with bank account ABC123 and B wants
to change it to say "bank account XYZ789" and still validate as being
from A.

So here's B's goal.  Create two variants of the message.
1) original reconstructs as "bank account XYZ789" - aka, B didn't
modify the bank account.
2) original reconstructs as "bank account ABC123" - aka, A's
Message-Instance validates.

Those, as far as I can see it, are the kinds of things that an
attacker might want to change.  They can't change the content their
OWN signature signed, because that doesn't even go through a recipe.
That's hashed directly into the Message-Instance they sign, and sent
to C.

So the risk here is that C somehow has a validator which runs two
disjoint passes - one applies the recipe and checks that it didn't
change anything risky but doesn't check the earlier Message-Instance
applies, the other applies the recipe and validates the earlier
Message-Instance but doesn't compare the content.

It MUST be both.  Otherwise the security hole is big enough to drive a
truck through.  Either you blindly trust any changes B made (in which
case you'll pay the invoice to the wrong account) or you blindly trust
the recipe without validating the earlier Message-Instance (in which
case B can just lie, no need to create a recipe which even tries to be
valid).

I mean, it's possible.  It's really bad coding, but I guess it's
possible.  Luckily Richard's coming-soon change to make any duplicate
key be a syntax error and grounds for rejecting the message will solve
it.  Unless both validators are bogus in which case see the earlier
failure cases.

Bron.

_______________________________________________
Ietf-dkim mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to