125 lines
4.6 KiB
Go
125 lines
4.6 KiB
Go
package dkim
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"mailgoserver/internal/dnspublish"
|
|
)
|
|
|
|
// TestGenerateGlobalDKIMKeyWithoutHostnameSkipsPublish confirms regenerating with no
|
|
// hostname/creds configured just rotates the key, no publish attempt.
|
|
func TestGenerateGlobalDKIMKeyWithoutHostnameSkipsPublish(t *testing.T) {
|
|
database := openTestDKIMDB(t)
|
|
mgr := New(database, 1024)
|
|
|
|
publishErr, err := mgr.GenerateGlobalDKIMKey("", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if publishErr != nil {
|
|
t.Fatalf("expected no publish attempt with no hostname/creds, got %v", publishErr)
|
|
}
|
|
key, err := mgr.GetActiveGlobalDKIMKey()
|
|
if err != nil || key == nil {
|
|
t.Fatalf("expected a global key to exist, got %v %v", key, err)
|
|
}
|
|
}
|
|
|
|
// TestGenerateGlobalDKIMKeyPublishesAndRotates confirms a real publish happens against
|
|
// the configured hostname, and a second regenerate deactivates the first key.
|
|
func TestGenerateGlobalDKIMKeyPublishesAndRotates(t *testing.T) {
|
|
var bodies []string
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case r.URL.Path == "/zones":
|
|
w.Write([]byte(`{"success":true,"errors":[],"result":[{"id":"zone1"}]}`))
|
|
case r.URL.Path == "/zones/zone1/dns_records" && r.Method == http.MethodGet:
|
|
w.Write([]byte(`{"success":true,"errors":[],"result":[]}`))
|
|
case r.URL.Path == "/zones/zone1/dns_records" && r.Method == http.MethodPost:
|
|
body, _ := io.ReadAll(r.Body)
|
|
bodies = append(bodies, string(body))
|
|
w.Write([]byte(`{"success":true,"errors":[],"result":{"id":"rec1"}}`))
|
|
default:
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}
|
|
}))
|
|
defer srv.Close()
|
|
orig := dnspublish.CloudflareAPIBase
|
|
dnspublish.CloudflareAPIBase = srv.URL
|
|
defer func() { dnspublish.CloudflareAPIBase = orig }()
|
|
|
|
database := openTestDKIMDB(t)
|
|
mgr := New(database, 1024)
|
|
creds := &dnspublish.Credentials{Provider: "cloudflare", ZoneName: "example.com", CloudflareAPIToken: "tok"}
|
|
|
|
publishErr, err := mgr.GenerateGlobalDKIMKey("dkim.example.com", creds)
|
|
if err != nil || publishErr != nil {
|
|
t.Fatalf("first generate: err=%v publishErr=%v", err, publishErr)
|
|
}
|
|
first, _ := mgr.GetActiveGlobalDKIMKey()
|
|
if first == nil {
|
|
t.Fatal("expected an active global key after first generate")
|
|
}
|
|
if len(bodies) != 1 || !strings.Contains(bodies[0], "_domainkey.dkim.example.com") {
|
|
t.Fatalf("expected a publish call naming the configured hostname, got %v", bodies)
|
|
}
|
|
|
|
publishErr, err = mgr.GenerateGlobalDKIMKey("dkim.example.com", creds)
|
|
if err != nil || publishErr != nil {
|
|
t.Fatalf("second generate: err=%v publishErr=%v", err, publishErr)
|
|
}
|
|
second, _ := mgr.GetActiveGlobalDKIMKey()
|
|
if second == nil || second.ID == first.ID {
|
|
t.Fatalf("expected a distinct, newly active key after rotation: first=%+v second=%+v", first, second)
|
|
}
|
|
}
|
|
|
|
// TestGetActiveDKIMKeySwapsToGlobalKeyMaterial confirms a domain with UseGlobalDKIM
|
|
// signs with the global key's material while keeping its own selector, and falls back
|
|
// to its own key when no global key exists yet.
|
|
func TestGetActiveDKIMKeySwapsToGlobalKeyMaterial(t *testing.T) {
|
|
database := openTestDKIMDB(t)
|
|
domainID, err := database.CreateDomain("useglobal.example")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
mgr := New(database, 1024)
|
|
if ok, err := mgr.GenerateDKIMKeypair("useglobal.example", "own-sel", true); err != nil || !ok {
|
|
t.Fatalf("seed domain key: ok=%v err=%v", ok, err)
|
|
}
|
|
ownKey, _ := mgr.GetActiveDKIMKey("useglobal.example")
|
|
|
|
if err := database.SetDomainUseGlobalDKIM(domainID, true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// No global key generated yet — falls back to the domain's own material.
|
|
fallback, err := mgr.GetActiveDKIMKey("useglobal.example")
|
|
if err != nil || fallback == nil || fallback.PrivateKey != ownKey.PrivateKey || fallback.Selector != "own-sel" {
|
|
t.Fatalf("expected fallback to the domain's own key material, got %+v", fallback)
|
|
}
|
|
|
|
if _, err := mgr.GenerateGlobalDKIMKey("", nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
globalKey, _ := mgr.GetActiveGlobalDKIMKey()
|
|
|
|
swapped, err := mgr.GetActiveDKIMKey("useglobal.example")
|
|
if err != nil || swapped == nil {
|
|
t.Fatalf("GetActiveDKIMKey: %v %v", swapped, err)
|
|
}
|
|
if swapped.Selector != "own-sel" {
|
|
t.Fatalf("expected the domain's own selector preserved, got %q", swapped.Selector)
|
|
}
|
|
if swapped.PrivateKey != globalKey.PrivateKey || swapped.PublicKey != globalKey.PublicKey {
|
|
t.Fatalf("expected the global key's material substituted in, got %+v want private=%q public=%q", swapped, globalKey.PrivateKey, globalKey.PublicKey)
|
|
}
|
|
if swapped.PrivateKey == ownKey.PrivateKey {
|
|
t.Fatal("expected the domain's own private key to no longer be used once global mode is on")
|
|
}
|
|
}
|