mirror of
https://blitiri.com.ar/repos/chasquid
synced 2025-12-23 15:37:01 +00:00
dkim: Implement internal dkim signing and verification
This patch implements internal DKIM signing and verification.
This commit is contained in:
257
internal/dkim/sign_test.go
Normal file
257
internal/dkim/sign_test.go
Normal file
@@ -0,0 +1,257 @@
|
||||
package dkim
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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 := context.Background()
|
||||
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": []string{
|
||||
"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 := context.Background()
|
||||
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": []string{
|
||||
"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(context.Background(), "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(context.Background(), 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user