Federated via graft
0

Configure Feed

Select the types of activity you want to include in your feed.

graft-source / internal / activitypub / httpsig_test.go
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}