summaryrefslogtreecommitdiffstats
path: root/internal/submit/validation_test.go
blob: d1284f1de660165d54ecf8d7cc0b8f26dc1446a3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
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 <tester@example.net>")
	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 <tester@example.net>")
	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 <tester@example.net>",
		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 ""
}