diff options
| author | Gab <24553253+gabrix73@users.noreply.github.com> | 2025-10-06 04:15:18 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-10-06 04:15:18 +0200 |
| commit | 74f06db22b9ec8cb1591520cd7256ce15d9c4e1e (patch) | |
| tree | ad6e6a1f96d73d6def5f9541bd7222724304e07d | |
| parent | 965050012c4a0a3d7dd0f060f6bd0e5cfce85a9d (diff) | |
| download | noshitalk-74f06db22b9ec8cb1591520cd7256ce15d9c4e1e.tar.gz noshitalk-74f06db22b9ec8cb1591520cd7256ce15d9c4e1e.tar.xz noshitalk-74f06db22b9ec8cb1591520cd7256ce15d9c4e1e.zip | |
Update noshitalk-server.go
| -rw-r--r-- | noshitalk-server.go | 980 |
1 files changed, 733 insertions, 247 deletions
diff --git a/noshitalk-server.go b/noshitalk-server.go index 8c3d15a..92e6f47 100644 --- a/noshitalk-server.go +++ b/noshitalk-server.go @@ -1,21 +1,20 @@ package main import ( + "bytes" + "compress/zlib" "crypto/aes" "crypto/cipher" "crypto/ecdh" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" + "crypto/hmac" + cryptorand "crypto/rand" + "crypto/sha256" + "encoding/binary" + "encoding/hex" "encoding/json" - "encoding/pem" "fmt" "io" - "log" - "math/big" + "math/rand" "net" "os" "os/signal" @@ -34,74 +33,161 @@ type Client struct { quit chan struct{} lastActivity time.Time mu sync.Mutex + + // Visibility control + isVisible bool + + // Key rotation + currentKeyPair *ecdh.PrivateKey + nextKeyPair *ecdh.PrivateKey + sessionKeys [][]byte + keyRotationCount uint32 + lastRotation time.Time + messageCount uint32 +} + +type FileTransfer struct { + FileID string + Sender *Client + Receiver *Client + StartTime time.Time + TotalChunks int + Received int + Completed bool } type Message struct { From string `json:"from"` + To string `json:"to,omitempty"` Content string `json:"content"` Time string `json:"time"` Type string `json:"type"` } +type UserListMessage struct { + Type string `json:"type"` + Users []string `json:"users"` + Time string `json:"time"` +} + +type FileOfferMessage struct { + Type string `json:"type"` + From string `json:"from"` + To string `json:"to"` + FileID string `json:"file_id"` + Filename string `json:"filename"` + Size int64 `json:"size"` + TotalChunks int `json:"total_chunks"` + SHA256 string `json:"sha256"` + Compressed bool `json:"compressed"` + Time string `json:"time"` +} + +type FileChunkMessage struct { + Type string `json:"type"` + FileID string `json:"file_id"` + ChunkIndex int `json:"chunk_index"` + TotalChunks int `json:"total_chunks"` + Data string `json:"data"` + Time string `json:"time"` +} + +type FileResponseMessage struct { + Type string `json:"type"` + FileID string `json:"file_id"` + From string `json:"from"` + Status string `json:"status"` + Message string `json:"message"` + SHA256 string `json:"sha256,omitempty"` + Time string `json:"time"` +} + +type KeyRotationMessage struct { + Type string `json:"type"` + PublicKey []byte `json:"public_key"` + Nonce []byte `json:"nonce"` + Signature []byte `json:"signature"` + Time string `json:"time"` +} + var ( - clients = make(map[net.Conn]*Client) - clientsMux sync.RWMutex - version = "0.1" + clients = make(map[net.Conn]*Client) + clientsMux sync.RWMutex + fileTransfers = make(map[string]*FileTransfer) + transfersMux sync.RWMutex + version = "1.0-enhanced" ) -const port = "0.0.0.0:8083" +const ( + handshakeTimeout = 30 * time.Second + protocolVersion = 2 + messageBlockSize = 256 + minRandomDelay = 50 + maxRandomDelay = 200 + + // Enhanced file transfer settings + fileChunkSize = 256 * 1024 // 256KB chunks + maxFileSize = 500 * 1024 * 1024 // 500MB max + maxConcurrentTransfers = 3 + + // Key rotation settings + rotateAfterMessages = 100 + rotateAfterTime = 5 * time.Minute +) func main() { - fmt.Printf("š NoshiTalk Server v%s - Maximum Security\n", version) + fmt.Printf("š NoshiTalk Server v%s - Enhanced Security Edition\n", version) fmt.Printf("ā ļø Zero logs, zero traces, auto-wipe post-session\n") - + fmt.Printf("š§
Designed for Tor Hidden Service - localhost only\n") + fmt.Printf("š Traffic obfuscation: Padding + Random delays\n") + fmt.Printf("š Key rotation enabled (every %d messages or %v)\n", rotateAfterMessages, rotateAfterTime) + fmt.Printf("š» Ghost mode enabled by default\n") + secureSetup() defer secureShutdown("Session completed") - // Privacy-focused - no IP disclosure - fmt.Printf("š§
Tor Hidden Service Only - No direct connections\n") - fmt.Printf("š Listening on port: 8083 (accessible only via Tor)\n") - fmt.Printf("\n") + listenAddr := "127.0.0.1:8083" + fmt.Printf("š Listening on: localhost:8083 (Tor Hidden Service backend)\n") + fmt.Printf("ā ļø Server NOT exposed directly - Tor proxy required\n\n") - listener, err := tls.Listen("tcp", port, getTLSConfig()) + listener, err := net.Listen("tcp", listenAddr) if err != nil { - fmt.Printf("ā TLS listen error: %v\n", err) - secureShutdown(fmt.Sprintf("TLS listen error: %v", err)) + fmt.Printf("ā Listen error: %v\n", err) + secureShutdown(fmt.Sprintf("Listen error: %v", err)) } fmt.Printf("š Server started successfully\n") - fmt.Printf("š Waiting for connections...\n") + fmt.Printf("š Waiting for connections...\n\n") stop := make(chan os.Signal, 1) signal.Notify(stop, os.Interrupt, syscall.SIGTERM) - go func() { <-stop fmt.Printf("\nš Received shutdown signal\n") secureShutdown("Operator requested shutdown") }() - // Monitor goroutine - check for idle connections + // Monitor goroutine go func() { ticker := time.NewTicker(60 * time.Second) defer ticker.Stop() for range ticker.C { clientsMux.RLock() activeCount := len(clients) - if activeCount > 0 { - fmt.Printf("š Active connections: %d\n", activeCount) - // Check for idle connections (optional) - now := time.Now() - for _, client := range clients { - client.mu.Lock() - idleTime := now.Sub(client.lastActivity) - client.mu.Unlock() - if idleTime > 5*time.Minute { - fmt.Printf("ā° [%s] Idle for %v\n", client.identity, idleTime) - } + visibleCount := 0 + for _, client := range clients { + if client.isVisible { + visibleCount++ } } + if activeCount > 0 { + fmt.Printf("š Active connections: %d (visible: %d, ghost: %d)\n", + activeCount, visibleCount, activeCount-visibleCount) + } clientsMux.RUnlock() + + // Cleanup old transfers + cleanupOldTransfers() } }() @@ -112,101 +198,14 @@ func main() { continue } - fmt.Printf("šÆ New connection received\n") + fmt.Printf("šÆ New connection from %s\n", conn.RemoteAddr()) go handleClient(conn) } } -func getTLSConfig() *tls.Config { - if _, err := os.Stat("server_ec.crt"); os.IsNotExist(err) { - fmt.Println("š§ Generating self-signed certificates for testing...") - if err := generateTestCerts(); err != nil { - log.Fatal("Error generating test certificates:", err) - } - fmt.Println("ā
Certificates generated successfully") - } - - cert, err := tls.LoadX509KeyPair("server_ec.crt", "server_ec.key") - if err != nil { - log.Fatal("Error loading ECC server certificates:", err) - } - - return &tls.Config{ - Certificates: []tls.Certificate{cert}, - ClientAuth: tls.NoClientCert, - MinVersion: tls.VersionTLS12, - CipherSuites: []uint16{ - tls.TLS_AES_256_GCM_SHA384, - tls.TLS_CHACHA20_POLY1305_SHA256, - tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - }, - CurvePreferences: []tls.CurveID{ - tls.X25519, - tls.CurveP256, - tls.CurveP384, - }, - } -} - -func generateTestCerts() error { - priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - return err - } - - template := x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{ - Organization: []string{"NoshiTalk"}, - Country: []string{"US"}, - Province: []string{""}, - Locality: []string{"San Francisco"}, - StreetAddress: []string{""}, - PostalCode: []string{""}, - }, - NotBefore: time.Now(), - NotAfter: time.Now().Add(365 * 24 * time.Hour), - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1)}, - DNSNames: []string{"localhost"}, - BasicConstraintsValid: true, - } - - certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) - if err != nil { - return err - } - - certOut, err := os.Create("server_ec.crt") - if err != nil { - return err - } - defer certOut.Close() - - if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certDER}); err != nil { - return err - } - - keyOut, err := os.Create("server_ec.key") - if err != nil { - return err - } - defer keyOut.Close() - - privBytes, err := x509.MarshalECPrivateKey(priv) - if err != nil { - return err - } - - return pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: privBytes}) -} - -func getClientIdentity(conn net.Conn) (string, error) { - id := make([]byte, 8) - rand.Read(id) - return fmt.Sprintf("user_%x", id), nil +func deriveIdentity(publicKey []byte) string { + hash := sha256.Sum256(publicKey) + return fmt.Sprintf("anon_%s", hex.EncodeToString(hash[:8])) } func handleClient(conn net.Conn) { @@ -216,49 +215,34 @@ func handleClient(conn net.Conn) { removeClient(conn) }() - identity, err := getClientIdentity(conn) - if err != nil { - fmt.Printf("ā Error getting client identity: %v\n", err) - return - } - - fmt.Printf("š Client authenticated: %s\n", identity) - curve := ecdh.X25519() - privateKey, err := curve.GenerateKey(rand.Reader) + privateKey, err := curve.GenerateKey(cryptorand.Reader) if err != nil { - fmt.Printf("ā [%s] Private key generation failed: %v\n", identity, err) + fmt.Printf("ā Key generation failed: %v\n", err) return } privateKeyBuffer := memguard.NewBufferFromBytes(privateKey.Bytes()) defer privateKeyBuffer.Destroy() - fmt.Printf("š„ [%s] Waiting for client public key...\n", identity) + fmt.Printf("š„ Waiting for client public key...\n") clientPubKeyBytes := make([]byte, 32) - - // Set timeout for initial handshake + conn.SetReadDeadline(time.Now().Add(30 * time.Second)) - - totalRead := 0 - for totalRead < 32 { - n, err := conn.Read(clientPubKeyBytes[totalRead:]) - if err != nil { - fmt.Printf("ā [%s] Error reading client public key (read %d/32 bytes): %v\n", identity, totalRead, err) - return - } - totalRead += n - fmt.Printf("š„ [%s] Read %d bytes, total: %d/32\n", identity, n, totalRead) + n, err := io.ReadFull(conn, clientPubKeyBytes) + if err != nil { + fmt.Printf("ā Error reading client public key: %v\n", err) + return } - - // Clear the deadline after handshake conn.SetReadDeadline(time.Time{}) - - fmt.Printf("ā
[%s] Received complete client public key (%d bytes)\n", identity, totalRead) + + identity := deriveIdentity(clientPubKeyBytes) + fmt.Printf("š Client identity derived: %s\n", identity) + fmt.Printf("ā
[%s] Received client public key (%d bytes)\n", identity, n) clientPubKey, err := curve.NewPublicKey(clientPubKeyBytes) if err != nil { - fmt.Printf("ā [%s] Error parsing client public key: %v\n", identity, err) + fmt.Printf("ā [%s] Invalid client public key: %v\n", identity, err) return } @@ -268,7 +252,6 @@ func handleClient(conn net.Conn) { fmt.Printf("ā [%s] Error sending server public key: %v\n", identity, err) return } - fmt.Printf("ā
[%s] Sent server public key\n", identity) fmt.Printf("š¢ [%s] Calculating shared secret...\n", identity) sharedSecret, err := privateKey.ECDH(clientPubKey) @@ -277,11 +260,14 @@ func handleClient(conn net.Conn) { return } - sharedSecretBuffer := memguard.NewBufferFromBytes(sharedSecret) - defer sharedSecretBuffer.Destroy() + fmt.Printf("š [%s] Starting mutual authentication...\n", identity) + if err := performMutualAuth(conn, sharedSecret, identity); err != nil { + fmt.Printf("ā [%s] Authentication failed: %v\n", identity, err) + return + } fmt.Printf("š [%s] Setting up AES-GCM...\n", identity) - block, err := aes.NewCipher(sharedSecret) + block, err := aes.NewCipher(sharedSecret[:32]) if err != nil { fmt.Printf("ā [%s] AES cipher creation failed: %v\n", identity, err) return @@ -293,6 +279,9 @@ func handleClient(conn net.Conn) { return } + sharedSecretBuffer := memguard.NewBufferFromBytes(sharedSecret) + defer sharedSecretBuffer.Destroy() + client := &Client{ conn: conn, identity: identity, @@ -300,108 +289,193 @@ func handleClient(conn net.Conn) { sharedSecret: sharedSecretBuffer.Seal(), quit: make(chan struct{}), lastActivity: time.Now(), + isVisible: false, // Ghost mode by default + currentKeyPair: privateKey, + lastRotation: time.Now(), + messageCount: 0, } addClient(conn, client) - fmt.Printf("ā
[%s] Client fully initialized and added to active connections\n", identity) - broadcastSystemMessage(fmt.Sprintf("š¢ %s connected", identity)) + fmt.Printf("ā
[%s] Client initialized (ghost mode)\n", identity) + + // Send welcome message only to the new client + welcomeMsg := Message{ + From: "System", + Content: fmt.Sprintf("š¢ Connected as %s (ghost mode). Use /reveal to become visible, /ghost to hide.", identity), + Time: time.Now().Format("15:04:05"), + Type: "system", + } + msgJSON, _ := json.Marshal(welcomeMsg) + client.sendEncryptedMessage(string(msgJSON)) + + // Send personal user list (only themselves initially) + sendPersonalUserList(client) fmt.Printf("š” [%s] Starting message handling...\n", identity) client.receiveMessages() - fmt.Printf("š“ [%s] Client disconnected\n", identity) - broadcastSystemMessage(fmt.Sprintf("š“ %s disconnected", identity)) + fmt.Printf("š“ [%s] Client disconnected\n", identity) + + // Only notify if user was visible + if client.isVisible { + broadcastSystemMessage(fmt.Sprintf("š“ %s disconnected", identity)) + } } func addClient(conn net.Conn, client *Client) { clientsMux.Lock() clients[conn] = client + totalClients := len(clients) clientsMux.Unlock() + + fmt.Printf("ā [%s] Added to clients map. Total clients: %d\n", client.identity, totalClients) } func removeClient(conn net.Conn) { clientsMux.Lock() - delete(clients, conn) - clientsMux.Unlock() + client, exists := clients[conn] + if exists && client.isVisible { + delete(clients, conn) + clientsMux.Unlock() + // Only update lists if user was visible + broadcastUserListToVisible() + } else { + delete(clients, conn) + clientsMux.Unlock() + } } -func broadcastSystemMessage(message string) { +func sendPersonalUserList(client *Client) { + // Send only visible users to this client + clientsMux.RLock() + visibleUsers := []string{} + for _, c := range clients { + if c.isVisible || c == client { + visibleUsers = append(visibleUsers, c.identity) + } + } + clientsMux.RUnlock() + + userListMsg := UserListMessage{ + Type: "user_list", + Users: visibleUsers, + Time: time.Now().Format("15:04:05"), + } + + msgJSON, _ := json.Marshal(userListMsg) + client.sendEncryptedMessage(string(msgJSON)) +} + +func broadcastUserListToVisible() { clientsMux.RLock() defer clientsMux.RUnlock() - systemMsg := Message{ - From: "System", - Content: message, - Time: time.Now().Format("15:04:05"), - Type: "system", + visibleUsers := []string{} + for _, c := range clients { + if c.isVisible { + visibleUsers = append(visibleUsers, c.identity) + } } - msgJSON, _ := json.Marshal(systemMsg) + userListMsg := UserListMessage{ + Type: "user_list", + Users: visibleUsers, + Time: time.Now().Format("15:04:05"), + } + + msgJSON, _ := json.Marshal(userListMsg) + // Send only to visible users for _, client := range clients { - client.sendEncryptedMessage(string(msgJSON)) + if client.isVisible { + client.sendEncryptedMessage(string(msgJSON)) + } } } -func broadcastUserMessage(sender *Client, message string) { - clientsMux.RLock() - defer clientsMux.RUnlock() +func (c *Client) checkKeyRotation() { + c.mu.Lock() + defer c.mu.Unlock() - fmt.Printf("š¢ [%s] Starting broadcast to %d clients\n", sender.identity, len(clients)) + needRotation := false + if c.messageCount >= rotateAfterMessages { + needRotation = true + fmt.Printf("š [%s] Key rotation triggered (message count: %d)\n", c.identity, c.messageCount) + } + if time.Since(c.lastRotation) > rotateAfterTime { + needRotation = true + fmt.Printf("š [%s] Key rotation triggered (time elapsed: %v)\n", c.identity, time.Since(c.lastRotation)) + } - userMsg := Message{ - From: sender.identity, - Content: message, - Time: time.Now().Format("15:04:05"), - Type: "message", + if needRotation { + c.performKeyRotation() } +} - msgJSON, err := json.Marshal(userMsg) +func (c *Client) performKeyRotation() { + // Generate new key pair + curve := ecdh.X25519() + newKeyPair, err := curve.GenerateKey(cryptorand.Reader) if err != nil { - fmt.Printf("ā [%s] JSON marshal error: %v\n", sender.identity, err) + fmt.Printf("ā [%s] Key rotation failed: %v\n", c.identity, err) return } - sentCount := 0 - for _, client := range clients { - if client != sender { - fmt.Printf("š¤ Sending to %s\n", client.identity) - err := client.sendEncryptedMessage(string(msgJSON)) - if err != nil { - fmt.Printf("ā Failed to send to %s: %v\n", client.identity, err) - } else { - sentCount++ - } - } + c.nextKeyPair = newKeyPair + c.keyRotationCount++ + c.messageCount = 0 + c.lastRotation = time.Now() + + // Send new public key to client + rotMsg := KeyRotationMessage{ + Type: "key_rotation", + PublicKey: newKeyPair.PublicKey().Bytes(), + Nonce: make([]byte, 12), + Time: time.Now().Format("15:04:05"), } - fmt.Printf("ā
[%s] Broadcast completed - sent to %d clients\n", sender.identity, sentCount) + cryptorand.Read(rotMsg.Nonce) + + msgJSON, _ := json.Marshal(rotMsg) + c.sendEncryptedMessage(string(msgJSON)) + + fmt.Printf("ā
[%s] Key rotation completed (rotation #%d)\n", c.identity, c.keyRotationCount) +} + +func compressData(data []byte) ([]byte, error) { + var buf bytes.Buffer + w := zlib.NewWriter(&buf) + _, err := w.Write(data) + w.Close() + return buf.Bytes(), err +} + +func decompressData(data []byte) ([]byte, error) { + r, err := zlib.NewReader(bytes.NewReader(data)) + if err != nil { + return nil, err + } + defer r.Close() + return io.ReadAll(r) } func (c *Client) receiveMessages() { defer func() { if r := recover(); r != nil { - fmt.Printf("š„ [%s] PANIC in receiveMessages: %v\n", c.identity, r) + fmt.Printf("š„ [%s] PANIC: %v\n", c.identity, r) } - fmt.Printf("š [%s] Exiting receiveMessages\n", c.identity) close(c.quit) }() - buf := make([]byte, 8192) + buf := make([]byte, 512*1024) // Larger buffer for big file chunks fmt.Printf("š” [%s] Starting receive loop\n", c.identity) - // NO read deadline for persistent connections - c.conn.SetReadDeadline(time.Time{}) - for { n, err := c.conn.Read(buf) if err != nil { if err == io.EOF { - fmt.Printf("š“ [%s] Client closed connection cleanly\n", c.identity) - } else if netErr, ok := err.(net.Error); ok && netErr.Timeout() { - fmt.Printf("ā±ļø [%s] Read timeout - connection idle\n", c.identity) - continue // Continue on timeout + fmt.Printf("š“ [%s] Connection closed\n", c.identity) } else { fmt.Printf("ā [%s] Read error: %v\n", c.identity, err) } @@ -409,13 +483,12 @@ func (c *Client) receiveMessages() { } if n == 0 { - fmt.Printf("ā ļø [%s] Read 0 bytes\n", c.identity) continue } - // Update last activity c.mu.Lock() c.lastActivity = time.Now() + c.messageCount++ c.mu.Unlock() fmt.Printf("š„ [%s] Received %d bytes\n", c.identity, n) @@ -426,87 +499,500 @@ func (c *Client) receiveMessages() { continue } - fmt.Printf("š [%s] Message: %s\n", c.identity, message) + // Check for key rotation + c.checkKeyRotation() - // Handle special commands + // Handle commands if message == "/quit" { - fmt.Printf("š [%s] Quit received\n", c.identity) return } - - if message == "/ping" { - fmt.Printf("š [%s] Ping received, sending pong\n", c.identity) - err := c.sendEncryptedMessage("/pong") - if err != nil { - fmt.Printf("ā [%s] Failed to send pong: %v\n", c.identity, err) - } else { - fmt.Printf("ā
[%s] Pong sent successfully\n", c.identity) + + if message == "/reveal" { + c.mu.Lock() + wasVisible := c.isVisible + c.isVisible = true + c.mu.Unlock() + + if !wasVisible { + fmt.Printf("š»āš¤ [%s] Became visible\n", c.identity) + broadcastSystemMessage(fmt.Sprintf("š¤ %s joined the chat", c.identity)) + broadcastUserListToVisible() + } + continue + } + + if message == "/ghost" { + c.mu.Lock() + wasVisible := c.isVisible + c.isVisible = false + c.mu.Unlock() + + if wasVisible { + fmt.Printf("š¤āš» [%s] Went ghost\n", c.identity) + broadcastSystemMessage(fmt.Sprintf("š» %s went invisible", c.identity)) + broadcastUserListToVisible() } continue } + + if message == "/users" { + sendPersonalUserList(c) + continue + } + + // Handle file transfer messages + var genericMsg map[string]interface{} + if err := json.Unmarshal([]byte(message), &genericMsg); err == nil { + msgType, ok := genericMsg["type"].(string) + if ok { + switch msgType { + case "file_offer": + var fileOffer FileOfferMessage + if err := json.Unmarshal([]byte(message), &fileOffer); err == nil { + routeFileOfferEnhanced(c, &fileOffer) + continue + } + case "file_chunk": + var fileChunk FileChunkMessage + if err := json.Unmarshal([]byte(message), &fileChunk); err == nil { + routeFileChunkEnhanced(c, &fileChunk) + continue + } + case "file_accept", "file_reject", "file_complete": + var fileResp FileResponseMessage + if err := json.Unmarshal([]byte(message), &fileResp); err == nil { + routeFileResponseEnhanced(c, &fileResp) + continue + } + } + } + } + + // Handle private messages + if len(message) > 4 && message[:4] == "/pm " { + parts := splitPrivateMessage(message) + if len(parts) < 2 { + errorMsg := Message{ + From: "System", + Content: "ā Usage: /pm username message", + Time: time.Now().Format("15:04:05"), + Type: "error", + } + msgJSON, _ := json.Marshal(errorMsg) + c.sendEncryptedMessage(string(msgJSON)) + continue + } + sendPrivateMessage(c, parts[0], parts[1]) + continue + } + + // Broadcast only if visible + if c.isVisible { + broadcastUserMessage(c, message) + } else { + // Ghost users can't broadcast + errorMsg := Message{ + From: "System", + Content: "š» You are in ghost mode. Use /reveal to send messages.", + Time: time.Now().Format("15:04:05"), + Type: "error", + } + msgJSON, _ := json.Marshal(errorMsg) + c.sendEncryptedMessage(string(msgJSON)) + } + } +} + +func routeFileOfferEnhanced(sender *Client, offer *FileOfferMessage) { + // Check concurrent transfers limit + transfersMux.RLock() + activeTransfers := 0 + for _, transfer := range fileTransfers { + if !transfer.Completed && transfer.Sender == sender { + activeTransfers++ + } + } + transfersMux.RUnlock() + + if activeTransfers >= maxConcurrentTransfers { + errorMsg := Message{ + From: "System", + Content: fmt.Sprintf("ā Max concurrent transfers (%d) reached", maxConcurrentTransfers), + Time: time.Now().Format("15:04:05"), + Type: "error", + } + msgJSON, _ := json.Marshal(errorMsg) + sender.sendEncryptedMessage(string(msgJSON)) + return + } + + clientsMux.RLock() + var targetClient *Client + for _, client := range clients { + if client.identity == offer.To { + targetClient = client + break + } + } + clientsMux.RUnlock() + + if targetClient == nil { + errorMsg := Message{ + From: "System", + Content: fmt.Sprintf("ā User '%s' not found", offer.To), + Time: time.Now().Format("15:04:05"), + Type: "error", + } + msgJSON, _ := json.Marshal(errorMsg) + sender.sendEncryptedMessage(string(msgJSON)) + return + } + + // Create transfer tracking + transfer := &FileTransfer{ + FileID: offer.FileID, + Sender: sender, + Receiver: targetClient, + StartTime: time.Now(), + TotalChunks: offer.TotalChunks, + Received: 0, + Completed: false, + } + + transfersMux.Lock() + fileTransfers[offer.FileID] = transfer + transfersMux.Unlock() + + offer.From = sender.identity + offer.Time = time.Now().Format("15:04:05") + + fmt.Printf("š [%s ā %s] File offer: %s (%d bytes, %d chunks, compressed: %v)\n", + sender.identity, offer.To, offer.Filename, offer.Size, offer.TotalChunks, offer.Compressed) + + offerJSON, _ := json.Marshal(offer) + targetClient.sendEncryptedMessage(string(offerJSON)) +} + +func routeFileChunkEnhanced(sender *Client, chunk *FileChunkMessage) { + transfersMux.RLock() + transfer, exists := fileTransfers[chunk.FileID] + transfersMux.RUnlock() + + if !exists { + fmt.Printf("ā ļø [%s] Unknown file transfer: %s\n", sender.identity, chunk.FileID[:8]) + return + } + + if transfer.Sender != sender { + fmt.Printf("ā ļø [%s] Unauthorized chunk sender for: %s\n", sender.identity, chunk.FileID[:8]) + return + } + + chunk.Time = time.Now().Format("15:04:05") + + // Update progress + transfersMux.Lock() + transfer.Received++ + if transfer.Received >= transfer.TotalChunks { + transfer.Completed = true + duration := time.Since(transfer.StartTime) + fmt.Printf("ā
[%s] File transfer completed in %v\n", sender.identity, duration) + } + transfersMux.Unlock() + + fmt.Printf("š¦ [%s] File chunk %d/%d (FileID: %s)\n", + sender.identity, chunk.ChunkIndex+1, chunk.TotalChunks, chunk.FileID[:8]) + + chunkJSON, _ := json.Marshal(chunk) + transfer.Receiver.sendEncryptedMessage(string(chunkJSON)) +} + +func routeFileResponseEnhanced(sender *Client, resp *FileResponseMessage) { + transfersMux.RLock() + transfer, exists := fileTransfers[resp.FileID] + transfersMux.RUnlock() + + if exists { + resp.From = sender.identity + resp.Time = time.Now().Format("15:04:05") + + fmt.Printf("š¬ [%s] File response: %s (FileID: %s)\n", + sender.identity, resp.Status, resp.FileID[:8]) + + respJSON, _ := json.Marshal(resp) - // Broadcast regular messages - fmt.Printf("š¬ [%s] Broadcasting: %s\n", c.identity, message) - broadcastUserMessage(c, message) - fmt.Printf("š [%s] Continuing to wait for next message\n", c.identity) + // Send to the other party + if sender == transfer.Receiver { + transfer.Sender.sendEncryptedMessage(string(respJSON)) + } else { + transfer.Receiver.sendEncryptedMessage(string(respJSON)) + } + + // Cleanup if rejected or completed + if resp.Status == "rejected" || resp.Status == "completed" { + transfersMux.Lock() + delete(fileTransfers, resp.FileID) + transfersMux.Unlock() + } } } -func (c *Client) sendEncryptedMessage(message string) error { - if c.conn == nil { - return fmt.Errorf("connection is nil") +func cleanupOldTransfers() { + transfersMux.Lock() + defer transfersMux.Unlock() + + now := time.Now() + for fileID, transfer := range fileTransfers { + if now.Sub(transfer.StartTime) > 30*time.Minute { + fmt.Printf("šļø Cleaning up old transfer: %s\n", fileID[:8]) + delete(fileTransfers, fileID) + } } - - if c.gcm == nil { - return fmt.Errorf("gcm cipher is nil") +} + +func sendPrivateMessage(sender *Client, targetUsername string, message string) { + clientsMux.RLock() + defer clientsMux.RUnlock() + + var targetClient *Client + for _, client := range clients { + if client.identity == targetUsername { + targetClient = client + break + } } - + + if targetClient == nil { + errorMsg := Message{ + From: "System", + Content: fmt.Sprintf("ā User '%s' not found", targetUsername), + Time: time.Now().Format("15:04:05"), + Type: "error", + } + msgJSON, _ := json.Marshal(errorMsg) + sender.sendEncryptedMessage(string(msgJSON)) + return + } + + privateMsg := Message{ + From: sender.identity, + To: targetUsername, + Content: message, + Time: time.Now().Format("15:04:05"), + Type: "private", + } + + msgJSON, _ := json.Marshal(privateMsg) + + fmt.Printf("š [%s ā %s] Private message\n", sender.identity, targetUsername) + targetClient.sendEncryptedMessage(string(msgJSON)) + sender.sendEncryptedMessage(string(msgJSON)) +} + +func broadcastSystemMessage(message string) { + clientsMux.RLock() + defer clientsMux.RUnlock() + + systemMsg := Message{ + From: "System", + Content: message, + Time: time.Now().Format("15:04:05"), + Type: "system", + } + + msgJSON, _ := json.Marshal(systemMsg) + + for _, client := range clients { + if client.isVisible { + client.sendEncryptedMessage(string(msgJSON)) + } + } +} + +func broadcastUserMessage(sender *Client, message string) { + clientsMux.RLock() + defer clientsMux.RUnlock() + + visibleCount := 0 + for _, client := range clients { + if client.isVisible { + visibleCount++ + } + } + + fmt.Printf("š¢ [%s] Broadcasting to %d visible clients\n", sender.identity, visibleCount-1) + + userMsg := Message{ + From: sender.identity, + Content: message, + Time: time.Now().Format("15:04:05"), + Type: "message", + } + + msgJSON, _ := json.Marshal(userMsg) + + for _, client := range clients { + if client != sender && client.isVisible { + fmt.Printf("š¤ Sending to %s\n", client.identity) + client.sendEncryptedMessage(string(msgJSON)) + } + } +} + +func padMessage(plaintext []byte) []byte { + currentLen := len(plaintext) + paddedLen := ((currentLen / messageBlockSize) + 1) * messageBlockSize + padLen := paddedLen - currentLen + + result := make([]byte, 2+paddedLen) + binary.BigEndian.PutUint16(result[0:2], uint16(currentLen)) + copy(result[2:], plaintext) + + if padLen > 0 { + padding := make([]byte, padLen) + cryptorand.Read(padding) + copy(result[2+currentLen:], padding) + } + + return result +} + +func unpadMessage(paddedData []byte) ([]byte, error) { + if len(paddedData) < 2 { + return nil, fmt.Errorf("padded data too short") + } + + originalLen := binary.BigEndian.Uint16(paddedData[0:2]) + if int(originalLen) > len(paddedData)-2 { + return nil, fmt.Errorf("invalid padding length") + } + + return paddedData[2 : 2+originalLen], nil +} + +func randomDelay() { + delay := minRandomDelay + rand.Intn(maxRandomDelay-minRandomDelay) + time.Sleep(time.Duration(delay) * time.Millisecond) +} + +func (c *Client) sendEncryptedMessage(message string) error { + if c.conn == nil || c.gcm == nil { + return fmt.Errorf("not ready") + } + + randomDelay() + + paddedMessage := padMessage([]byte(message)) + nonce := make([]byte, 12) - if _, err := io.ReadFull(rand.Reader, nonce); err != nil { - return fmt.Errorf("nonce generation failed: %v", err) + if _, err := io.ReadFull(cryptorand.Reader, nonce); err != nil { + return fmt.Errorf("nonce failed: %v", err) } - ciphertext := c.gcm.Seal(nil, nonce, []byte(message), nil) + ciphertext := c.gcm.Seal(nil, nonce, paddedMessage, nil) data := append(nonce, ciphertext...) - - fmt.Printf("š¤ [%s] Sending encrypted message (%d bytes): %s\n", c.identity, len(data), message) - + + fmt.Printf("š¤ [%s] Sending %d bytes (padded)\n", c.identity, len(data)) + n, err := c.conn.Write(data) if err != nil { - return fmt.Errorf("write failed: %v", err) - } - - if n != len(data) { - return fmt.Errorf("incomplete write: wrote %d of %d bytes", n, len(data)) + return err } - - fmt.Printf("ā
[%s] Successfully wrote %d bytes\n", c.identity, n) + + fmt.Printf("ā
[%s] Sent %d bytes\n", c.identity, n) return nil } func (c *Client) decryptMessage(data []byte) (string, error) { if len(data) < 12 { - return "", fmt.Errorf("message too short: %d bytes", len(data)) + return "", fmt.Errorf("too short") } nonce := data[:12] ciphertext := data[12:] - plaintext, err := c.gcm.Open(nil, nonce, ciphertext, nil) + paddedPlaintext, err := c.gcm.Open(nil, nonce, ciphertext, nil) if err != nil { - return "", fmt.Errorf("decryption failed: %v", err) + return "", err + } + + plaintext, err := unpadMessage(paddedPlaintext) + if err != nil { + return "", fmt.Errorf("unpad failed: %v", err) } return string(plaintext), nil } +func splitPrivateMessage(msg string) []string { + content := msg[4:] + spaceIdx := -1 + for i, ch := range content { + if ch == ' ' { + spaceIdx = i + break + } + } + + if spaceIdx == -1 { + return []string{content} + } + + username := content[:spaceIdx] + message := content[spaceIdx+1:] + + return []string{username, message} +} + func secureSetup() { memguard.CatchInterrupt() } +func performMutualAuth(conn net.Conn, sharedSecret []byte, identity string) error { + conn.SetDeadline(time.Now().Add(handshakeTimeout)) + defer conn.SetDeadline(time.Time{}) + + serverChallenge := make([]byte, 32) + if _, err := cryptorand.Read(serverChallenge); err != nil { + return fmt.Errorf("challenge generation failed: %v", err) + } + + if _, err := conn.Write(serverChallenge); err != nil { + return fmt.Errorf("challenge send failed: %v", err) + } + + clientResponse := make([]byte, 32) + if _, err := io.ReadFull(conn, clientResponse); err != nil { + return fmt.Errorf("client response read failed: %v", err) + } + + clientChallenge := make([]byte, 32) + if _, err := io.ReadFull(conn, clientChallenge); err != nil { + return fmt.Errorf("client challenge read failed: %v", err) + } + + h := hmac.New(sha256.New, sharedSecret) + h.Write(serverChallenge) + expectedClientMAC := h.Sum(nil) + + if !hmac.Equal(clientResponse, expectedClientMAC) { + return fmt.Errorf("client authentication failed") + } + + h.Reset() + h.Write(clientChallenge) + serverResponse := h.Sum(nil) + + if _, err := conn.Write(serverResponse); err != nil { + return fmt.Errorf("server response send failed: %v", err) + } + + return nil +} + func secureShutdown(reason string) { fmt.Printf("\nš Server shutdown: %s\n", reason) - + clientsMux.Lock() for _, client := range clients { client.conn.Close() |
