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

dkim: Add fuzzing tests

This patch adds two fuzzing tests to the dkim package:

1) Fuzz the sign-verify cycle, checking we can verify any message we
   sign.
2) Fuzz the message we verify, returning valid keys for any domain so it
   gets past the key fetching, and exercises a substantial part of the
   verification process.

The first test already found two issues, which were fixed in the
previous patches.
This commit is contained in:
Alberto Bertogli
2026-10-09 21:09:21 +01:00
parent b3b1f39d85
commit 4b0b31a749
3 changed files with 107 additions and 0 deletions
+17
View File
@@ -86,6 +86,23 @@ func equalErrors(a, b error) bool {
return a.Error() == b.Error()
}
// Add the messages from the file-based tests to the fuzzing corpus.
func addTestdataCorpus(f *testing.F) {
f.Helper()
msgfs, err := filepath.Glob("testdata/*.msg")
if err != nil {
f.Fatalf("error finding test files: %v", err)
}
for _, msgf := range msgfs {
c, err := os.ReadFile(msgf)
if err != nil {
f.Fatalf("error reading %q: %v", msgf, err)
}
f.Add(toCRLF(string(c)))
}
}
func mustReadFile(t *testing.T, path string) string {
t.Helper()
contents, err := os.ReadFile(path)
+55
View File
@@ -1,6 +1,7 @@
package dkim
import (
"context"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
@@ -376,3 +377,57 @@ func TestFormatHeaders(t *testing.T) {
}
}
}
func FuzzSignAndVerify(f *testing.F) {
f.Add(basicMessage)
f.Add("From: a@b\r\n\r\n")
f.Add("")
// Repeated headers with different values.
f.Add("From: a@b\r\nTo: c@d\r\n e@f\r\nTo: g@h\r\n\r\nbody\r\n")
// Non-ASCII header name (U+017F is a long s).
f.Add("From: a@b\r\nſubject: x\r\n\r\nbody\r\n")
// Messages from the file-based tests, which have more realistic headers,
// including existing DKIM signatures.
addTestdataCorpus(f)
// Use a fixed key, as generating one for each run would be too slow.
priv := ed25519.NewKeyFromSeed(make([]byte, ed25519.SeedSize))
pub := priv.Public().(ed25519.PublicKey)
ctx := WithLookupTXTFunc(context.Background(),
makeLookupTXT(map[string][]string{
"test._domainkey.example.com": {
"v=DKIM1; k=ed25519; p=" +
base64.StdEncoding.EncodeToString(pub),
},
}))
s := &Signer{
Domain: "example.com",
Selector: "test",
Signer: priv,
}
f.Fuzz(func(t *testing.T, msg string) {
sig, err := s.Sign(ctx, msg)
if err != nil {
return
}
// Any message we can sign, we must be able to verify.
// Our signature is the first one, so it's always verified even if
// the message has other signatures.
res, err := VerifyMessage(ctx, addSig(sig, msg))
if err != nil {
t.Fatalf("VerifyMessage error: %v", err)
}
if len(res.Results) == 0 {
t.Fatalf("signature not found when verifying")
}
if r := res.Results[0]; r.State != SUCCESS {
t.Errorf("signature did not verify: %v / %v", r.State, r.Error)
}
})
}
+35
View File
@@ -520,3 +520,38 @@ func TestAuthenticationResults(t *testing.T) {
}
}
}
func FuzzVerifyMessage(f *testing.F) {
addTestdataCorpus(f)
// Return valid keys for every domain, so we can get past the key lookup
// and exercise the rest of the verification. The ed25519 key is the one
// used by testdata/01-rfc8463.
ctx := WithLookupTXTFunc(context.Background(),
func(ctx context.Context, domain string) ([]string, error) {
return []string{
"v=DKIM1; k=ed25519; p=" + exampleEd25519KeyB64,
"v=DKIM1; k=rsa; p=" + exampleRSAKeyB64,
}, nil
})
f.Fuzz(func(t *testing.T, msg string) {
res, err := VerifyMessage(ctx, msg)
if err != nil {
return
}
if res.Found != uint(len(res.Results)) {
t.Errorf("found %d signatures, but got %d results",
res.Found, len(res.Results))
}
if res.Valid > res.Found {
t.Errorf("%d valid signatures, but only %d found",
res.Valid, res.Found)
}
// This is always called on the result when receiving a message, so
// exercise it too.
res.AuthenticationResults()
})
}