package transport import ( "bytes" "context" "strings" "testing" "time" "github.com/libp2p/go-libp2p" "github.com/Yenn503/NecropolisC2/pkg/cryptography" ) // minimalHost creates a bare libp2p host for unit tests. No DHT, no relay. func minimalHost(t *testing.T) (*Node, context.Context, context.CancelFunc) { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) h, err := libp2p.New(libp2p.ListenAddrStrings("/ip4/127.0.0.1/tcp/0")) if err != nil { t.Fatalf("create host: %v", err) } t.Cleanup(func() { h.Close(); cancel() }) return &Node{Host: h, ctx: ctx, cancel: func() {}}, ctx, cancel } func TestCreateEnvelopeStoresToken(t *testing.T) { op, err := cryptography.GenerateOperatorKey() if err != nil { t.Fatalf("generate operator key: %v", err) } n, ctx, _ := minimalHost(t) _ = ctx m := NewOperatorMessenger(ctx, n, op) m.SetAuthToken(op.AuthToken) env := m.CreateEnvelope(MsgTypePs, []byte("test")) if len(env.Token) != 32 { t.Fatalf("token missing from envelope: got %d bytes, want 32", len(env.Token)) } if !bytes.Equal(env.Token, op.AuthToken) { t.Fatal("envelope token != operator auth token") } if env.ID == 0 { t.Fatal("envelope ID is zero — random generation failed") } } func TestReplayProtection(t *testing.T) { op, err := cryptography.GenerateOperatorKey() if err != nil { t.Fatalf("generate operator key: %v", err) } n, ctx, _ := minimalHost(t) m := NewOperatorMessenger(ctx, n, op) env1 := m.CreateEnvelope(MsgTypePs, []byte("test")) env2 := m.CreateEnvelope(MsgTypePs, []byte("test")) env2.ID = env1.ID // force same ID if m.IsReplay(env1.ID) { t.Fatal("first envelope flagged as replay") } if !m.IsReplay(env1.ID) { t.Fatal("same ID not detected as replay on second check") } if !m.IsReplay(env2.ID) { t.Fatal("replay envelope with same ID not detected") } } func TestAuthTokenRejection(t *testing.T) { op, err := cryptography.GenerateOperatorKey() if err != nil { t.Fatalf("generate operator key: %v", err) } n, ctx, _ := minimalHost(t) m := NewOperatorMessenger(ctx, n, op) m.SetAuthToken(op.AuthToken) env := m.CreateEnvelope(MsgTypePs, []byte("test")) if len(env.Token) != 32 { t.Fatalf("token not set: got %d bytes", len(env.Token)) } // Simulate implant-side check: wrong token should be rejected wrongToken := make([]byte, 32) for i := range wrongToken { wrongToken[i] = 0xFF } if bytes.Equal(env.Token, wrongToken) { t.Fatal("tampered token accidentally matches") } if bytes.Equal(env.Token, op.AuthToken) { // expected — token is correct. Now verify wrong token fails env.Token = wrongToken if bytes.Equal(env.Token, op.AuthToken) { t.Fatal("failed to set wrong token") } } } func TestEnvelopeSignAndVerify(t *testing.T) { op, err := cryptography.GenerateOperatorKey() if err != nil { t.Fatalf("generate operator key: %v", err) } n, ctx, _ := minimalHost(t) m := NewOperatorMessenger(ctx, n, op) data := []byte("necropolis command data") env := m.CreateEnvelope(MsgTypeExecute, data) signingData, err := EnvelopeSigningBytes(env) if err != nil { t.Fatalf("signing bytes: %v", err) } sig, err := op.PrivateKey.Sign(signingData) if err != nil { t.Fatalf("sign: %v", err) } env.Signature = sig ok, err := cryptography.Verify(op.PublicKey, signingData, sig) if err != nil { t.Fatalf("verify: %v", err) } if !ok { t.Fatal("signature verification failed") } // Tamper with data, re-signing bytes change, verify should fail data[0] ^= 0xFF env.Data = data signingData2, _ := EnvelopeSigningBytes(env) ok2, _ := cryptography.Verify(op.PublicKey, signingData2, sig) if ok2 { t.Fatal("tampered data passed signature verification") } } func TestTopicFormats(t *testing.T) { op, err := cryptography.GenerateOperatorKey() if err != nil { t.Fatalf("generate operator key: %v", err) } n, ctx, _ := minimalHost(t) m := NewOperatorMessenger(ctx, n, op) m.SetOperatorID(op.PeerID) opID := op.PeerID.String() if m.BeaconTopic() != "/b/"+opID+"/bx" { t.Fatalf("beacon topic: got %s", m.BeaconTopic()) } if m.CommandTopic() != "/c/"+opID+"/cx" { t.Fatalf("command topic: got %s", m.CommandTopic()) } taskTopic := m.TaskTopic("test-implant-id") if !strings.HasPrefix(taskTopic, "/b/") || !strings.HasSuffix(taskTopic, "/tx/test-implant-id") { t.Fatalf("task topic: got %s", taskTopic) } } func TestBoxEncryptDecryptViaEnvelope(t *testing.T) { op, err := cryptography.GenerateOperatorKey() if err != nil { t.Fatalf("generate operator key: %v", err) } n, ctx, _ := minimalHost(t) m := NewOperatorMessenger(ctx, n, op) plain := []byte("secret implant command") env := m.CreateEnvelope(MsgTypeExecute, plain) // Encrypt the data field if op.BoxKeys == nil { t.Skip("no box keys") } encrypted, err := cryptography.EncryptMessage(env.Data, op.BoxKeys.PublicKey) if err != nil { t.Fatalf("encrypt: %v", err) } if bytes.Equal(encrypted, plain) { t.Fatal("encrypted data equals plaintext") } env.Data = encrypted // Decrypt decrypted, err := cryptography.DecryptMessage(env.Data, op.BoxKeys) if err != nil { t.Fatalf("decrypt: %v", err) } if !bytes.Equal(decrypted, plain) { t.Fatalf("decrypted != plain: got %q, want %q", decrypted, plain) } }