diff --git a/pkg/client/tls_test.go b/pkg/client/tls_test.go index 236168a..8fc1d5e 100644 --- a/pkg/client/tls_test.go +++ b/pkg/client/tls_test.go @@ -7,13 +7,16 @@ import ( "crypto/tls" "crypto/x509" "crypto/x509/pkix" + "encoding/hex" "encoding/pem" "math/big" "net" + "sync" "testing" "time" "github.com/enmanuel/unibus/pkg/client" + "github.com/enmanuel/unibus/pkg/frame" "github.com/enmanuel/unibus/pkg/membership" "github.com/enmanuel/unibus/pkg/room" ) @@ -111,3 +114,72 @@ func TestNatsTLS(t *testing.T) { t.Fatalf("client without the CA must fail the TLS handshake") } } + +// TestSecureBusEndToEnd is the headline golden of issue 0001: with ALL three +// layers active at once — control-plane request signing (enforce), NATS nkey +// auth, and TLS — two registered peers run an encrypted room end to end. A +// creates a Matrix-policy room, invites B, A publishes and B decrypts. This +// proves the layers compose: signed HTTP control plane + authenticated, +// encrypted data plane + E2E room content. +func TestSecureBusEndToEnd(t *testing.T) { + serverTLS, caPool := genTestCA(t) + h := bootHarness(t, membership.AuthEnforce, true, serverTLS) + waitHealth(t, h.ctrlURL) + + clientTLS := &tls.Config{RootCAs: caPool, MinVersion: tls.VersionTLS12} + secure := func(t *testing.T, handle string) (*client.Client, membership.AuthMode) { + id := mustIdentity(t) + if err := h.store.AddUser(hex.EncodeToString(id.SignPub), handle, membership.RoleMember); err != nil { + t.Fatalf("register %s: %v", handle, err) + } + c, err := client.NewWithOptions(h.natsURL, h.ctrlURL, id, client.Options{UseNkey: true, TLS: clientTLS}) + if err != nil { + t.Fatalf("connect %s securely: %v", handle, err) + } + return c, 0 + } + + a, _ := secure(t, "alice") + defer a.Close() + b, _ := secure(t, "bob") + defer b.Close() + + roomID, err := a.CreateRoom("room.secure", room.ModeMatrix) + if err != nil { + t.Fatalf("A create encrypted room over secure bus: %v", err) + } + if err := a.Invite(roomID, b.Endpoint()); err != nil { + t.Fatalf("A invite B: %v", err) + } + if err := b.Join(roomID); err != nil { + t.Fatalf("B join: %v", err) + } + + var mu sync.Mutex + var got []string + sub, err := b.Subscribe(roomID, func(_ frame.Frame, plaintext []byte) { + mu.Lock() + got = append(got, string(plaintext)) + mu.Unlock() + }) + if err != nil { + t.Fatalf("B subscribe: %v", err) + } + defer sub.Unsubscribe() + time.Sleep(150 * time.Millisecond) + + const msg = "mensaje sobre bus seguro (auth+TLS+E2E)" + if err := a.Publish(roomID, []byte(msg)); err != nil { + t.Fatalf("A publish: %v", err) + } + if !waitFor(&mu, &got, func(rs []string) bool { + for _, r := range rs { + if r == msg { + return true + } + } + return false + }, 2*time.Second) { + t.Fatalf("B did not receive/decrypt the message over the secured bus; got %v", snapshot(&mu, &got)) + } +}