package submit import ( "crypto/ed25519" "crypto/rand" "crypto/sha1" "encoding/base64" "fmt" "net/url" "strings" "testing" "time" "n2usenet/internal/config" ) func TestParseAndValidateSignedSubmission(t *testing.T) { pub, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } body := "This is a valid signed test message." sig := ed25519.Sign(priv, []byte(body)) sigB64 := base64.StdEncoding.EncodeToString(sig) pubB64 := base64.StdEncoding.EncodeToString(pub) form := url.Values{} form.Set("from", "Tester ") form.Set("newsgroups", "alt.test") form.Set("subject", "Test") form.Set("xhashcash", mineTestHashcash(t, "tester@example.net", 8)) form.Set("message", body+"\n\n--- Digital Signature ---\n"+sigB64) form.Set("x-ed25519-pub", pubB64) form.Set("x-ed25519-sig", sigB64) sub, err := ParseAndValidate(form, config.SecurityConfig{ MinHashcashBits: 8, MinMessageBytes: 10, MaxMessageBytes: 1024, MaxNewsgroups: 3, }) if err != nil { t.Fatalf("ParseAndValidate returned error: %v", err) } if sub.SignedText != body { t.Fatalf("signed payload mismatch: %q", sub.SignedText) } } func TestParseAndValidateRejectsBadSignature(t *testing.T) { pub, _, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } _, otherPriv, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } body := "This is a valid sized test message." sig := ed25519.Sign(otherPriv, []byte("different")) form := url.Values{} form.Set("from", "Tester ") form.Set("newsgroups", "alt.test") form.Set("subject", "Test") form.Set("xhashcash", mineTestHashcash(t, "tester@example.net", 8)) form.Set("message", body+"\n\n--- Digital Signature ---\n"+base64.StdEncoding.EncodeToString(sig)) form.Set("x-ed25519-pub", base64.StdEncoding.EncodeToString(pub)) form.Set("x-ed25519-sig", base64.StdEncoding.EncodeToString(sig)) _, err = ParseAndValidate(form, config.SecurityConfig{ MinHashcashBits: 8, MinMessageBytes: 10, MaxMessageBytes: 1024, MaxNewsgroups: 3, }) if err == nil { t.Fatal("expected bad signature to be rejected") } } func TestBuildMessageRequiresFaceWhenConfigured(t *testing.T) { sub := Submission{ From: "Tester ", FromAddress: "tester@example.net", Newsgroups: []string{"alt.test"}, Subject: "Test", Message: "This is a body.", PublicKeyB64: base64.StdEncoding.EncodeToString(make([]byte, ed25519.PublicKeySize)), SignatureB64: base64.StdEncoding.EncodeToString(make([]byte, ed25519.SignatureSize)), } _, _, err := BuildMessage(sub, "mail2news@mail2news.tcpreset.net", "n2usenet@virebent.art", "example.net", "/missing/identicons-cli", true) if err == nil { t.Fatal("expected missing Face generator to reject message") } } func mineTestHashcash(t *testing.T, resource string, bits int) string { t.Helper() date := time.Now().UTC().Format("060102150405") prefix := fmt.Sprintf("1:%d:%s:%s::test:", bits, date, resource) target := bits / 4 for i := 0; i < 1_000_000; i++ { token := fmt.Sprintf("%s%d", prefix, i) sum := sha1.Sum([]byte(token)) if strings.HasPrefix(fmt.Sprintf("%x", sum[:]), strings.Repeat("0", target)) { return token } } t.Fatal("failed to mine test hashcash") return "" }