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]