1
0
mirror of https://blitiri.com.ar/repos/chasquid synced 2026-10-29 15:02:30 +00:00

dkim: Fix work amplification on h= tag processing

When verifying emails, for each entry in the h= tag, we would scan all
headers, leading to quadratic behaviour.

Similarly, when signing, for each instance of a signed header we would
scan all headers.

This could lead to unauthenticated DoS via work amplification.

This patch fixes the problem by going through the headers only once, so
behaviour is now linear, and also limiting the size of the h= tag for
defense in depth.

The limit is in line with what other implementations use, so it should
not cause any interoperability problems.

Thanks to John JEAN <john@wargan.com> for finding and reporting this
problem. The detailed report can be found in
https://johnjean.io/advisories/chasquid-unauthenticated-remote-dos/.
This commit is contained in:
Alberto Bertogli
2026-10-09 01:05:32 +01:00
parent 5c4193a8b6
commit b153955d5d
6 changed files with 151 additions and 14 deletions
+36
View File
@@ -129,6 +129,42 @@ func TestSignEd25519(t *testing.T) {
}
}
func TestSignManyHeaders(t *testing.T) {
// Regression test for signing being quadratic on the number of instances
// of a header. This takes milliseconds, while the quadratic
// implementation took minutes with this input. We don't check the
// timing, so a regression will show up as this test being very slow.
const n = 50_000
_, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("ed25519.GenerateKey: %v", err)
}
s := &Signer{
Domain: "example.com",
Selector: "test",
Signer: priv,
}
message := "From: from@example.com\r\n" +
strings.Repeat("To: to@example.com\r\n", n) +
"\r\nHi.\r\n"
sig, err := s.Sign(t.Context(), message)
if err != nil {
t.Fatalf("Sign: %v", err)
}
// All instances must be in h=, plus the extra one we always add.
hTag := regexp.MustCompile(`h=([^;]*);`).FindStringSubmatch(sig)
if hTag == nil {
t.Fatalf("h= not found in signature: %q", sig)
}
if got := strings.Count(hTag[1], "to"); got != n+1 {
t.Errorf("wanted %d instances of 'to' in h=, got %d", n+1, got)
}
}
func addSig(sig, message string) string {
return "DKIM-Signature: " +
strings.ReplaceAll(sig, "\r\n", "\r\n\t") +