update readme
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user