mirror of
https://blitiri.com.ar/repos/chasquid
synced 2025-12-22 15:27:02 +00:00
dkim: Implement internal dkim signing and verification
This patch implements internal DKIM signing and verification.
This commit is contained in:
248
internal/dkim/dns_test.go
Normal file
248
internal/dkim/dns_test.go
Normal file
@@ -0,0 +1,248 @@
|
||||
package dkim
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ed25519"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
)
|
||||
|
||||
func TestLookupError(t *testing.T) {
|
||||
testErr := errors.New("lookup error")
|
||||
errLookupF := func(ctx context.Context, name string) ([]string, error) {
|
||||
return nil, testErr
|
||||
}
|
||||
ctx := WithLookupTXTFunc(context.Background(), errLookupF)
|
||||
|
||||
pks, err := findPublicKeys(ctx, "example.com", "selector")
|
||||
if pks != nil || err != testErr {
|
||||
t.Errorf("findPublicKeys expected nil / lookup error, got %v / %v",
|
||||
pks, err)
|
||||
}
|
||||
}
|
||||
|
||||
// RSA key from the RFC example.
|
||||
// https://datatracker.ietf.org/doc/html/rfc6376#appendix-C
|
||||
const exampleRSAKeyB64 = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQ" +
|
||||
"KBgQDwIRP/UC3SBsEmGqZ9ZJW3/DkMoGeLnQg1fWn7/zYt" +
|
||||
"IxN2SnFCjxOCKG9v3b4jYfcTNh5ijSsq631uBItLa7od+v" +
|
||||
"/RtdC2UzJ1lWT947qR+Rcac2gbto/NMqJ0fzfVjH4OuKhi" +
|
||||
"tdY9tf6mcwGjaNBcWToIMmPSPDdQPNUYckcQ2QIDAQAB"
|
||||
|
||||
var exampleRSAKeyBuf, _ = base64.StdEncoding.DecodeString(exampleRSAKeyB64)
|
||||
var exampleRSAKey, _ = x509.ParsePKCS1PublicKey(exampleRSAKeyBuf)
|
||||
|
||||
// Ed25519 key from the RFC example.
|
||||
// https://datatracker.ietf.org/doc/html/rfc8463#appendix-A.2
|
||||
const exampleEd25519KeyB64 = "11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo="
|
||||
|
||||
var exampleEd25519KeyBuf, _ = base64.StdEncoding.DecodeString(
|
||||
exampleEd25519KeyB64)
|
||||
var exampleEd25519Key = ed25519.PublicKey(exampleEd25519KeyBuf)
|
||||
|
||||
var results = map[string][]string{}
|
||||
var resultErr = map[string]error{}
|
||||
|
||||
func testLookupTXT(ctx context.Context, name string) ([]string, error) {
|
||||
return results[name], resultErr[name]
|
||||
}
|
||||
|
||||
func TestSkipBadRecords(t *testing.T) {
|
||||
ctx := WithLookupTXTFunc(context.Background(), testLookupTXT)
|
||||
results["selector._domainkey.example.com"] = []string{
|
||||
"not a tag",
|
||||
"v=DKIM1; p=" + exampleRSAKeyB64,
|
||||
}
|
||||
defer clear(results)
|
||||
|
||||
pks, err := findPublicKeys(ctx, "example.com", "selector")
|
||||
if err != nil {
|
||||
t.Errorf("findPublicKeys expected nil, got %v", err)
|
||||
}
|
||||
if len(pks) != 1 {
|
||||
t.Errorf("findPublicKeys expected 1 key, got %v", len(pks))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePublicKey(t *testing.T) {
|
||||
cases := []struct {
|
||||
in string
|
||||
pk *publicKey
|
||||
err error
|
||||
}{
|
||||
// Invalid records.
|
||||
{"not a tag", nil, errInvalidTag},
|
||||
{"v=DKIM666;", nil, errInvalidVersion},
|
||||
{"p=abc~*#def", nil, base64.CorruptInputError(3)},
|
||||
{"k=blah; p=" + exampleRSAKeyB64, nil, errUnsupportedKeyType},
|
||||
|
||||
// Error parsing the keys.
|
||||
{"p=", nil, errInvalidRSAPublicKey},
|
||||
|
||||
// RSA key but the contents are a (valid) ECDSA key.
|
||||
{"p=" +
|
||||
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIT0qsh+0jdY" +
|
||||
"DhK5+rSedhT7W/5rTRiulhphqtuplGFAyNiSh9I5t6MsrIu" +
|
||||
"xFQV7A/cWAt8qcbVscT3Q2l6iu3w==",
|
||||
nil, errNotRSAPublicKey},
|
||||
|
||||
// Valid RSA key, that is too short.
|
||||
{"p=" +
|
||||
"MEgCQQCo9+BpMRYQ/dL3DS2CyJxRF+j6ctbT3/Qp84+KeFh" +
|
||||
"nii7NT7fELilKUSnxS30WAvQCCo2yU1orfgqr41mM70MBAg" +
|
||||
"MBAAE=", nil, errRSAKeyTooSmall},
|
||||
|
||||
// Invalid ed25519 key.
|
||||
{"k=ed25519; p=MFkwEwYH", nil, errInvalidEd25519Key},
|
||||
|
||||
// Valid.
|
||||
{"p=" + exampleRSAKeyB64,
|
||||
&publicKey{K: keyTypeRSA, P: exampleRSAKeyBuf}, nil},
|
||||
{"k=rsa ; p=" + exampleRSAKeyB64,
|
||||
&publicKey{K: keyTypeRSA, P: exampleRSAKeyBuf}, nil},
|
||||
{
|
||||
"k=rsa; h=sha256; p=" + exampleRSAKeyB64,
|
||||
&publicKey{
|
||||
K: keyTypeRSA,
|
||||
H: []crypto.Hash{crypto.SHA256},
|
||||
P: exampleRSAKeyBuf},
|
||||
nil,
|
||||
},
|
||||
{"t=s; p=" + exampleRSAKeyB64,
|
||||
&publicKey{
|
||||
K: keyTypeRSA,
|
||||
P: exampleRSAKeyBuf,
|
||||
T: []string{"s"},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{"t = s : y; p=" + exampleRSAKeyB64,
|
||||
&publicKey{
|
||||
K: keyTypeRSA,
|
||||
P: exampleRSAKeyBuf,
|
||||
T: []string{"s", "y"},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
// We should ignore unrecognized hash algorithms.
|
||||
"k=rsa; h=sha1:xxx123:sha256; p=" + exampleRSAKeyB64,
|
||||
&publicKey{
|
||||
K: keyTypeRSA,
|
||||
H: []crypto.Hash{crypto.SHA256},
|
||||
P: exampleRSAKeyBuf},
|
||||
nil,
|
||||
},
|
||||
{"k=ed25519; p=" + exampleEd25519KeyB64,
|
||||
&publicKey{K: keyTypeEd25519, P: exampleEd25519KeyBuf}, nil},
|
||||
}
|
||||
|
||||
for i, c := range cases {
|
||||
pk, err := parsePublicKey(c.in)
|
||||
diff := cmp.Diff(c.pk, pk,
|
||||
cmpopts.IgnoreUnexported(publicKey{}),
|
||||
cmpopts.EquateEmpty(),
|
||||
)
|
||||
if diff != "" {
|
||||
t.Errorf("%d: parsePublicKey(%q) key: (-want +got)\n%s",
|
||||
i, c.in, diff)
|
||||
}
|
||||
if !errors.Is(err, c.err) {
|
||||
t.Errorf("%d: parsePublicKey(%q) error: want %v, got %v",
|
||||
i, c.in, c.err, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicKeyMatches(t *testing.T) {
|
||||
cases := []struct {
|
||||
pk *publicKey
|
||||
kt keyType
|
||||
h crypto.Hash
|
||||
ok bool
|
||||
}{
|
||||
{
|
||||
&publicKey{K: keyTypeRSA},
|
||||
keyTypeRSA, crypto.SHA256,
|
||||
true,
|
||||
},
|
||||
{
|
||||
&publicKey{K: keyTypeRSA, H: []crypto.Hash{crypto.SHA1}},
|
||||
keyTypeRSA, crypto.SHA1,
|
||||
true,
|
||||
},
|
||||
{
|
||||
&publicKey{K: keyTypeRSA, H: []crypto.Hash{crypto.SHA1}},
|
||||
keyTypeRSA, crypto.SHA256,
|
||||
false,
|
||||
},
|
||||
{
|
||||
&publicKey{K: keyTypeRSA, H: []crypto.Hash{crypto.SHA1}},
|
||||
keyTypeEd25519, crypto.SHA1,
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, c := range cases {
|
||||
if ok := c.pk.Matches(c.kt, c.h); ok != c.ok {
|
||||
t.Errorf("%d: matches(%v, %v) = %v, want %v",
|
||||
i, c.kt, c.h, ok, c.ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStrictDomainCheck(t *testing.T) {
|
||||
cases := []struct {
|
||||
t string
|
||||
ok bool
|
||||
}{
|
||||
{"", false},
|
||||
{"y", false},
|
||||
{"x:y", false},
|
||||
{":x::y", false},
|
||||
{"s", true},
|
||||
{"y:s", true},
|
||||
{" y: s", true},
|
||||
{"y:s:x", true},
|
||||
}
|
||||
|
||||
for i, c := range cases {
|
||||
pkS := "k=ed25519; p=" + exampleEd25519KeyB64 + "; t=" + c.t
|
||||
pk, err := parsePublicKey(pkS)
|
||||
if err != nil {
|
||||
t.Fatalf("%d: parsePublicKey(%q) = %v", i, pkS, err)
|
||||
}
|
||||
if ok := pk.StrictDomainCheck(); ok != c.ok {
|
||||
t.Errorf("%d: strictDomainCheck(t=%q) = %v, want %v",
|
||||
i, c.t, ok, c.ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzParsePublicKey(f *testing.F) {
|
||||
// Add some initial corpus from the tests above.
|
||||
f.Add("not a tag")
|
||||
f.Add("v=DKIM666;")
|
||||
f.Add("p=abc~*#def")
|
||||
f.Add("k=blah; p=" + exampleRSAKeyB64)
|
||||
f.Add("p=")
|
||||
f.Add("k=ed25519; p=")
|
||||
f.Add("k=ed25519; p=MFkwEwYH")
|
||||
f.Add("p=" + exampleEd25519KeyB64)
|
||||
f.Add("k=rsa ; p=" + exampleRSAKeyB64)
|
||||
f.Add("v=DKIM1; p=" + exampleRSAKeyB64)
|
||||
f.Add("t=s; p=" + exampleRSAKeyB64)
|
||||
f.Add("t = s : y; p=" + exampleRSAKeyB64)
|
||||
f.Add("k=rsa; h=sha256; p=" + exampleRSAKeyB64)
|
||||
f.Add("k=rsa; h=sha1:xxx123:sha256; p=" + exampleRSAKeyB64)
|
||||
|
||||
f.Fuzz(func(t *testing.T, in string) {
|
||||
parsePublicKey(in)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user