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:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user