Federated via graft
2.8 kB
85 lines
1package activitypub
2
3import (
4 "bytes"
5 "crypto/rsa"
6 "io"
7 "net/http"
8 "net/http/httptest"
9 "testing"
10)
11
12// TestSignVerifyRoundTrip exercises the actual code path a remote server
13// runs: sign a POST the way PostSigned does, receive it through an
14// httptest server, and verify it the way the inbox handler does. This is
15// the one piece of this package genuinely worth distrusting until proven —
16// HTTP Signatures are notoriously easy to get subtly wrong (header casing,
17// request-target format, digest encoding) in a way that only shows up
18// against a real remote server's strict verifier.
19func TestSignVerifyRoundTrip(t *testing.T) {
20 _, pub, priv := testKeyPair(t)
21
22 body := []byte(`{"type":"Follow","actor":"https://example.social/users/alice","object":"https://graft.example.org/actors/testrepo"}`)
23
24 var verifyErr error
25 var gotBody []byte
26 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
27 var err error
28 gotBody, err = io.ReadAll(r.Body)
29 if err != nil {
30 t.Errorf("read body: %v", err)
31 }
32 verifyErr = VerifyRequest(r, gotBody, pub)
33 w.WriteHeader(http.StatusAccepted)
34 }))
35 defer srv.Close()
36
37 client := srv.Client()
38 if err := PostSigned(client, srv.URL+"/actors/testrepo/inbox", "https://example.social/users/alice#main-key", priv, body); err != nil {
39 t.Fatalf("PostSigned: %v", err)
40 }
41 if verifyErr != nil {
42 t.Fatalf("VerifyRequest: %v", verifyErr)
43 }
44 if !bytes.Equal(gotBody, body) {
45 t.Fatalf("body mismatch: got %q want %q", gotBody, body)
46 }
47}
48
49// TestVerifyRejectsTamperedBody confirms a body modified in transit (or by
50// a malicious intermediary) fails verification via the Digest mismatch,
51// even though the Signature header itself is untouched.
52func TestVerifyRejectsTamperedBody(t *testing.T) {
53 _, pub, priv := testKeyPair(t)
54 body := []byte(`{"type":"Follow","actor":"https://example.social/users/alice"}`)
55
56 req, err := http.NewRequest(http.MethodPost, "https://graft.example.org/actors/testrepo/inbox", bytes.NewReader(body))
57 if err != nil {
58 t.Fatal(err)
59 }
60 if err := SignRequest(req, body, "https://example.social/users/alice#main-key", priv); err != nil {
61 t.Fatal(err)
62 }
63
64 tampered := []byte(`{"type":"Follow","actor":"https://attacker.example/users/mallory"}`)
65 if err := VerifyRequest(req, tampered, pub); err == nil {
66 t.Fatal("expected verification to fail on tampered body, got nil error")
67 }
68}
69
70func testKeyPair(t *testing.T) (privPEM string, pub *rsa.PublicKey, priv *rsa.PrivateKey) {
71 t.Helper()
72 privateP, publicP, err := GenerateKeyPair()
73 if err != nil {
74 t.Fatalf("GenerateKeyPair: %v", err)
75 }
76 privKey, err := ParsePrivateKey(privateP)
77 if err != nil {
78 t.Fatalf("ParsePrivateKey: %v", err)
79 }
80 pubKey, err := ParsePublicKey(publicP)
81 if err != nil {
82 t.Fatalf("ParsePublicKey: %v", err)
83 }
84 return privateP, pubKey, privKey
85}