1
0
mirror of https://blitiri.com.ar/repos/chasquid synced 2026-10-29 15:02:30 +00:00
Files
go-chasquid-smtp/internal/dkim/sign_test.go
T
Alberto Bertogli 8b19760f4e test: Use modern testing features instead of custom helpers
Many of our tests rely on helpers that predate features which are now
available in the standard library.

This patch replaces them with the standard equivalents:

- MustTempDir/RemoveIfOk -> t.ArtifactDir() or t.TempDir() (depending on
  whether it is useful to keep the data on request).
- GetFreePort + waitForServer -> opening the listeners before starting
  the servers. To support this, localrpc.Server gains a Serve method. We
  may re-introduce these in the future if more use cases appear.
- WaitFor and real-time timing checks -> testing/synctest.
- context.Background() -> t.Context().
- Benchmark b.N loops -> b.Loop().
- reflect.DeepEqual -> cmp.Diff, and proto.Equal for protobufs.
2026-10-03 10:34:35 +01:00

257 lines
6.3 KiB
Go

package dkim
import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"errors"
"regexp"
"strings"
"testing"
)
var basicMessage = toCRLF(
`Received: from client1.football.example.com [192.0.2.1]
by submitserver.example.com with SUBMISSION;
Fri, 11 Jul 2003 21:01:54 -0700 (PDT)
From: Joe SixPack <joe@football.example.com>
To: Suzie Q <suzie@shopping.example.net>
Subject: Is dinner ready?
Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT)
Message-ID: <20030712040037.46341.5F8J@football.example.com>
Hi.
We lost the game. Are you hungry yet?
Joe.
`)
func TestSignRSA(t *testing.T) {
ctx := t.Context()
ctx = WithTraceFunc(ctx, t.Logf)
// Generate a new key pair.
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatalf("rsa.GenerateKey: %v", err)
}
pub, err := x509.MarshalPKIXPublicKey(priv.Public())
if err != nil {
t.Fatalf("MarshalPKIXPublicKey: %v", err)
}
ctx = WithLookupTXTFunc(ctx, makeLookupTXT(map[string][]string{
"test._domainkey.example.com": {
"v=DKIM1; p=" + base64.StdEncoding.EncodeToString(pub),
},
}))
s := &Signer{
Domain: "example.com",
Selector: "test",
Signer: priv,
}
sig, err := s.Sign(ctx, basicMessage)
if err != nil {
t.Fatalf("Sign: %v", err)
}
// Verify the signature.
res, err := VerifyMessage(ctx, addSig(sig, basicMessage))
if err != nil || res.Valid != 1 {
t.Errorf("VerifyMessage: wanted 1 valid / nil; got %v / %v", res, err)
}
// Compare the reproducible parts against a known-good header.
want := regexp.MustCompile(
"v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n" +
"d=example.com; s=test; t=\\d+;\r\n" +
"h=from:subject:date:to:message-id:from:subject:date:to:cc:message-id;\r\n" +
"bh=[A-Za-z0-9+/]+=*;\r\n" +
"b=[A-Za-z0-9+/ \r\n]+=*;")
if !want.MatchString(sig) {
t.Errorf("Unexpected signature:")
t.Errorf(" Want: %q (regexp)", want)
t.Errorf(" Got: %q", sig)
}
}
func TestSignEd25519(t *testing.T) {
ctx := t.Context()
ctx = WithTraceFunc(ctx, t.Logf)
// Generate a new key pair.
pub, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("ed25519.GenerateKey: %v", err)
}
ctx = WithLookupTXTFunc(ctx, 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,
}
sig, err := s.Sign(ctx, basicMessage)
if err != nil {
t.Fatalf("Sign: %v", err)
}
// Verify the signature.
res, err := VerifyMessage(ctx, addSig(sig, basicMessage))
if err != nil || res.Valid != 1 {
t.Errorf("VerifyMessage: wanted 1 valid / nil; got %v / %v", res, err)
}
// Compare the reproducible parts against a known-good header.
want := regexp.MustCompile(
"v=1; a=ed25519-sha256; c=relaxed/relaxed;\r\n" +
"d=example.com; s=test; t=\\d+;\r\n" +
"h=from:subject:date:to:message-id:from:subject:date:to:cc:message-id;\r\n" +
"bh=[A-Za-z0-9+/]+=*;\r\n" +
"b=[A-Za-z0-9+/ \r\n]+=*;")
if !want.MatchString(sig) {
t.Errorf("Unexpected signature:")
t.Errorf(" Want: %q (regexp)", want)
t.Errorf(" Got: %q", sig)
}
}
func addSig(sig, message string) string {
return "DKIM-Signature: " +
strings.ReplaceAll(sig, "\r\n", "\r\n\t") +
"\r\n" + message
}
func TestSignBadMessage(t *testing.T) {
s := &Signer{
Domain: "example.com",
Selector: "test",
}
_, err := s.Sign(t.Context(), "Bad message")
if err == nil {
t.Errorf("Sign: wanted error; got nil")
}
}
func TestSignBadAlgorithm(t *testing.T) {
s := &Signer{
Domain: "example.com",
Selector: "test",
}
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatalf("ecdsa.GenerateKey: %v", err)
}
s.Signer = priv
_, err = s.Sign(t.Context(), basicMessage)
if !errors.Is(err, errUnsupportedKeyType) {
t.Errorf("Sign: wanted unsupported key type; got %v", err)
}
}
func TestBreakLongLines(t *testing.T) {
cases := []struct {
in string
want string
}{
{"1234567890", "1234567890"},
{
"xxxxxxxx10xxxxxxxx20xxxxxxxx30xxxxxxxx40" +
"xxxxxxxx50xxxxxxxx60xxxxxxxx70",
"xxxxxxxx10xxxxxxxx20xxxxxxxx30xxxxxxxx40" +
"xxxxxxxx50xxxxxxxx60xxxxxxxx70",
},
{
"xxxxxxxx10xxxxxxxx20xxxxxxxx30xxxxxxxx40" +
"xxxxxxxx50xxxxxxxx60xxxxxxxx70123",
"xxxxxxxx10xxxxxxxx20xxxxxxxx30xxxxxxxx40" +
"xxxxxxxx50xxxxxxxx60xxxxxxxx70\r\n 123",
},
{
"xxxxxxxx10xxxxxxxx20xxxxxxxx30xxxxxxxx40" +
"xxxxxxxx50xxxxxxxx60xxxxxxxx70xxxxxxxx80" +
"xxxxxxxx90xxxxxxx100xxxxxxx110xxxxxxx120" +
"xxxxxxx130xxxxxxx140xxxxxxx150xxxxxxx160",
"xxxxxxxx10xxxxxxxx20xxxxxxxx30xxxxxxxx40" +
"xxxxxxxx50xxxxxxxx60xxxxxxxx70\r\n " +
"xxxxxxxx80xxxxxxxx90xxxxxxx100xxxxxxx110" +
"xxxxxxx120xxxxxxx130xxxxxxx140\r\n " +
"xxxxxxx150xxxxxxx160",
},
}
for i, c := range cases {
got := breakLongLines(c.in)
if got != c.want {
t.Errorf("%d: breakLongLines(%q):", i, c.in)
t.Errorf(" want %q", c.want)
t.Errorf(" got %q", got)
}
}
}
func TestFormatHeaders(t *testing.T) {
cases := []struct {
in []string
want string
}{
{[]string{"From"}, "from"},
{
[]string{"From", "Subject", "Date"},
"from:subject:date",
},
{
[]string{"from", "subject", "date", "to", "message-id",
"from", "subject", "date", "to", "cc", "in-reply-to",
"message-id"},
"from:subject:date:to:message-id:" +
"from:subject:date:to:cc:in-reply-to\r\n" +
" :message-id",
},
{
[]string{"from", "subject", "date", "to", "message-id",
"from", "subject", "date", "to", "cc", "xxxxxxxxxxxx70"},
"from:subject:date:to:message-id:" +
"from:subject:date:to:cc:xxxxxxxxxxxx70",
},
{
[]string{"from", "subject", "date", "to", "message-id",
"from", "subject", "date", "to", "cc", "xxxxxxxxxxxx701"},
"from:subject:date:to:message-id:from:subject:date:to:cc\r\n" +
" :xxxxxxxxxxxx701",
},
{
[]string{"from", "subject", "date", "to", "message-id",
"from", "subject", "date", "to", "cc", "xxxxxxxxxxxx70",
"1"},
"from:subject:date:to:message-id:" +
"from:subject:date:to:cc:xxxxxxxxxxxx70\r\n" +
" :1",
},
}
for i, c := range cases {
got := formatHeaders(c.in)
if got != c.want {
t.Errorf("%d: formatHeaders(%q):", i, c.in)
t.Errorf(" want %q", c.want)
t.Errorf(" got %q", got)
}
}
}