diff options
| author | Gab <24553253+gabrix73@users.noreply.github.com> | 2025-10-14 18:53:35 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-10-14 18:53:35 +0200 |
| commit | eed868fdfac863a5020e7cb3f5447236b6433f68 (patch) | |
| tree | febcb8c8bc5b03463a2a22ffe2d92c541ce96f92 | |
| parent | 96c3f07dc89bb0d6e6f378c3c8d49f7988b94313 (diff) | |
| download | nofuture-go-memguard-eed868fdfac863a5020e7cb3f5447236b6433f68.tar.gz nofuture-go-memguard-eed868fdfac863a5020e7cb3f5447236b6433f68.tar.xz nofuture-go-memguard-eed868fdfac863a5020e7cb3f5447236b6433f68.zip | |
Update nofuture.go
| -rw-r--r-- | nofuture.go | 715 |
1 files changed, 327 insertions, 388 deletions
diff --git a/nofuture.go b/nofuture.go index e4dab0d..5bc7c13 100644 --- a/nofuture.go +++ b/nofuture.go @@ -2,629 +2,569 @@ package main import ( "crypto/rand" + "crypto/sha256" "encoding/hex" "encoding/json" "fmt" + "io" "log" "net/http" - "os" - "runtime" "sync" "time" "github.com/awnumar/memguard" + "golang.org/x/crypto/chacha20poly1305" + "golang.org/x/crypto/nacl/box" ) -// Session represents a user session with protected keys +const ( + VERSION = "0.5.0" + MAX_MESSAGE_SIZE = 1024 * 1024 // 1MB + SESSION_TIMEOUT = 24 * time.Hour + MAX_SESSIONS = 1000 + RATE_LIMIT_WINDOW = time.Minute + MAX_REQUESTS = 60 +) + +// Session represents a user session with protected keys and plaintext type Session struct { ID string - PrivateKey *memguard.LockedBuffer - PublicKey *memguard.LockedBuffer - SharedSecret *memguard.LockedBuffer + PublicKey *memguard.LockedBuffer // NaCl box public key + PrivateKey *memguard.LockedBuffer // NaCl box private key + SharedSecret *memguard.LockedBuffer // Derived shared secret + Nonce *memguard.LockedBuffer // Session nonce CreatedAt time.Time + LastActivity time.Time + PairedWith string + mutex sync.RWMutex } -// PairedSession represents two connected sessions -type PairedSession struct { - SessionA string - SessionB string - PairedAt time.Time +// SessionManager handles all sessions with rate limiting +type SessionManager struct { + sessions map[string]*Session + mutex sync.RWMutex + rateLimiter map[string]*RateLimit + limiterMutex sync.RWMutex } -// StartSessionRequest represents the request to start a new session -type StartSessionRequest struct { - // Empty for now, could add user preferences later +type RateLimit struct { + requests int + resetTime time.Time } -// StartSessionResponse represents the response from starting a session +// Request/Response types type StartSessionResponse struct { SessionID string `json:"session_id"` - PublicKey string `json:"public_key"` Status string `json:"status"` + Version string `json:"version"` } -// PairSessionRequest represents the request to pair sessions type PairSessionRequest struct { SessionID string `json:"session_id"` BuddySessionID string `json:"buddy_session_id"` } -// PairSessionResponse represents the response from pairing sessions type PairSessionResponse struct { - Status string `json:"status"` - Message string `json:"message"` - BuddyPublicKey string `json:"buddy_public_key,omitempty"` + Status string `json:"status"` + Message string `json:"message"` } -// EncryptRequest represents the request to encrypt a message type EncryptRequest struct { SessionID string `json:"session_id"` Message string `json:"message"` } -// EncryptResponse represents the response from encrypting a message type EncryptResponse struct { EncryptedMessage string `json:"encrypted_message"` - Status string `json:"status"` + Status string `json:"status"` } -// DecryptRequest represents the request to decrypt a message type DecryptRequest struct { SessionID string `json:"session_id"` EncryptedMessage string `json:"encrypted_message"` } -// DecryptResponse represents the response from decrypting a message type DecryptResponse struct { DecryptedMessage string `json:"decrypted_message"` - Status string `json:"status"` + Status string `json:"status"` } -// EndSessionRequest represents the request to end a session type EndSessionRequest struct { SessionID string `json:"session_id"` } -// EndSessionResponse represents the response from ending a session type EndSessionResponse struct { Status string `json:"status"` Message string `json:"message"` } -// SecurityDemo represents the security demonstration data -type SecurityDemo struct { - Timestamp string `json:"timestamp"` - MemguardStatus string `json:"memguard_status"` - ProtectedPages int `json:"protected_pages"` - LockedMemoryKB int `json:"locked_memory_kb"` - AccessAttempts int `json:"access_attempts_blocked"` - SecurityTests map[string]string `json:"security_tests"` - LiveDemoResults []TestResult `json:"live_demo_results"` -} +var sessionManager *SessionManager -type TestResult struct { - TestName string `json:"test_name"` - Status string `json:"status"` - Result string `json:"result"` - Timestamp string `json:"timestamp"` - Details string `json:"details"` +func init() { + sessionManager = &SessionManager{ + sessions: make(map[string]*Session), + rateLimiter: make(map[string]*RateLimit), + } } -// Global variables -var ( - sessions = make(map[string]*Session) - pairedSessions = make(map[string]*PairedSession) - sessionsMutex sync.RWMutex - blockedAttempts int - demoSecrets *memguard.LockedBuffer -) +// Rate limiting +func (sm *SessionManager) checkRateLimit(ip string) bool { + sm.limiterMutex.Lock() + defer sm.limiterMutex.Unlock() -// Initialize demo secrets in protected memory -func initSecurityDemo() { - demoSecrets = memguard.NewBufferFromBytes([]byte("SUPER_SECRET_DEMO_KEY_12345")) - log.Printf("Demo secrets stored in protected memory") -} + now := time.Now() + limit, exists := sm.rateLimiter[ip] -// Security demonstration endpoint -func securityDemoHandler(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.Header().Set("Access-Control-Allow-Origin", "*") - - liveTests := performLiveSecurityTests() + if !exists || now.After(limit.resetTime) { + sm.rateLimiter[ip] = &RateLimit{ + requests: 1, + resetTime: now.Add(RATE_LIMIT_WINDOW), + } + return true + } - demo := SecurityDemo{ - Timestamp: time.Now().Format(time.RFC3339), - MemguardStatus: getMemguardStatus(), - ProtectedPages: getProtectedPageCount(), - LockedMemoryKB: getLockedMemorySize(), - AccessAttempts: blockedAttempts, - SecurityTests: getSecurityTestResults(), - LiveDemoResults: liveTests, + if limit.requests >= MAX_REQUESTS { + return false } - json.NewEncoder(w).Encode(demo) + limit.requests++ + return true } -// Challenge endpoint -func securityChallengeHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } +// Generate cryptographically secure session ID +func generateSessionID(publicKey []byte, nonce []byte) string { + hash := sha256.New() + hash.Write(publicKey) + hash.Write(nonce) + return hex.EncodeToString(hash.Sum(nil)) +} - var challenge struct { - TestType string `json:"test_type"` - } +// Session cleanup goroutine +func (sm *SessionManager) startCleanupRoutine() { + ticker := time.NewTicker(1 * time.Hour) + go func() { + for range ticker.C { + sm.cleanupExpiredSessions() + } + }() +} - if err := json.NewDecoder(r.Body).Decode(&challenge); err != nil { - http.Error(w, "Invalid JSON", http.StatusBadRequest) - return - } +func (sm *SessionManager) cleanupExpiredSessions() { + sm.mutex.Lock() + defer sm.mutex.Unlock() - result := performSpecificTest(challenge.TestType) - - w.Header().Set("Content-Type", "application/json") - w.Header().Set("Access-Control-Allow-Origin", "*") - json.NewEncoder(w).Encode(result) + now := time.Now() + for id, session := range sm.sessions { + if now.Sub(session.LastActivity) > SESSION_TIMEOUT { + session.destroy() + delete(sm.sessions, id) + log.Printf("Expired session cleaned up: %s", id) + } + } } -func performLiveSecurityTests() []TestResult { - tests := []TestResult{} - now := time.Now().Format("15:04:05") - - tests = append(tests, TestResult{ - TestName: "Memory Protection Check", - Status: "ACTIVE", - Result: "Memguard protection verified", - Timestamp: now, - Details: "Protected memory pages are locked and encrypted", - }) +// Destroy session - securely wipe all sensitive data +func (s *Session) destroy() { + s.mutex.Lock() + defer s.mutex.Unlock() - return tests + if s.PrivateKey != nil { + s.PrivateKey.Destroy() + } + if s.PublicKey != nil { + s.PublicKey.Destroy() + } + if s.SharedSecret != nil { + s.SharedSecret.Destroy() + } + if s.Nonce != nil { + s.Nonce.Destroy() + } } -func performSpecificTest(testType string) TestResult { - now := time.Now().Format("15:04:05") - blockedAttempts++ - - switch testType { - case "root_access": - return TestResult{ - TestName: "Root Access Attempt", - Status: "BLOCKED", - Result: "Root privileges cannot bypass memguard protection", - Timestamp: now, - Details: fmt.Sprintf("UID: %d attempted access - DENIED by mlock protection", os.Getuid()), - } - case "memory_dump": - return TestResult{ - TestName: "Memory Dump Attempt", - Status: "BLOCKED", - Result: "Memory dump blocked by kernel-level protection", - Timestamp: now, - Details: fmt.Sprintf("Cannot access protected regions in PID %d - mlock syscall prevents core dumps", os.Getpid()), - } - case "debugger_attach": - return TestResult{ - TestName: "Debugger Attach Test", - Status: "BLOCKED", - Result: "Debugger attachment denied", - Timestamp: now, - Details: "Memory protection prevents debugging access to encrypted pages", - } - case "process_scan": - return TestResult{ - TestName: "Memory Pattern Scan", - Status: "BLOCKED", - Result: "Secret data not found in scannable memory", - Timestamp: now, - Details: "Protected data is encrypted and locked - pattern scanning ineffective", - } - case "cold_boot": - return TestResult{ - TestName: "Cold Boot Attack Simulation", - Status: "BLOCKED", - Result: "Memory wiped on process termination", - Timestamp: now, - Details: "Memguard automatically destroys secrets on exit - no residual data", - } - case "swap_analysis": - return TestResult{ - TestName: "Swap File Analysis", - Status: "BLOCKED", - Result: "Protected memory never swapped to disk", - Timestamp: now, - Details: "mlock() prevents sensitive data from reaching swap partition", +// Middleware for CORS with restrictions +func corsMiddleware(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + origin := r.Header.Get("Origin") + + // Only allow same-origin or localhost for development + // In production, set this to your specific domain + if origin == "" || origin == "http://localhost:8080" || origin == "https://yourdomain.com" { + w.Header().Set("Access-Control-Allow-Origin", origin) } - default: - return TestResult{ - TestName: "Unknown Test", - Status: "ERROR", - Result: "Invalid test type", - Timestamp: now, - Details: "Test type not recognized", + + w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type") + w.Header().Set("Access-Control-Max-Age", "3600") + + if r.Method == "OPTIONS" { + w.WriteHeader(http.StatusOK) + return } - } -} -func getMemguardStatus() string { - if demoSecrets != nil { - return "ACTIVE" + next(w, r) } - return "INACTIVE" } -func getProtectedPageCount() int { - sessionsMutex.RLock() - defer sessionsMutex.RUnlock() - return len(sessions)*2 + 1 // Sessions + demo secrets -} +// Middleware for rate limiting +func rateLimitMiddleware(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ip := r.RemoteAddr -func getLockedMemorySize() int { - var memStats runtime.MemStats - runtime.ReadMemStats(&memStats) - return int(memStats.HeapAlloc / 1024) -} + if !sessionManager.checkRateLimit(ip) { + http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests) + return + } -func getSecurityTestResults() map[string]string { - return map[string]string{ - "memory_protection": "ENABLED", - "root_access": "DENIED", - "debugger_access": "BLOCKED", - "memory_dumps": "PREVENTED", - "cold_boot_attack": "MITIGATED", - "swap_analysis": "PROTECTED", + next(w, r) } } -// generateSessionID creates a secure random session ID -func generateSessionID() (string, error) { - bytes := make([]byte, 16) - if _, err := rand.Read(bytes); err != nil { - return "", err +// Middleware for request size limiting +func sizeLimitMiddleware(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + r.Body = http.MaxBytesReader(w, r.Body, MAX_MESSAGE_SIZE) + next(w, r) } - return hex.EncodeToString(bytes), nil } -// startSessionHandler handles starting a new session +// Start session handler func startSessionHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - w.Header().Set("Content-Type", "application/json") - // Generate session ID - sessionID, err := generateSessionID() - if err != nil { - http.Error(w, "Failed to generate session ID", http.StatusInternalServerError) + sessionManager.mutex.Lock() + if len(sessionManager.sessions) >= MAX_SESSIONS { + sessionManager.mutex.Unlock() + http.Error(w, "Maximum sessions reached", http.StatusServiceUnavailable) return } + sessionManager.mutex.Unlock() - // Generate quantum-safe key pair using memguard - privateKeyBytes := make([]byte, 32) - if _, err := rand.Read(privateKeyBytes); err != nil { - http.Error(w, "Failed to generate private key", http.StatusInternalServerError) + // Generate NaCl box key pair (Curve25519) + publicKey, privateKey, err := box.GenerateKey(rand.Reader) + if err != nil { + log.Printf("Key generation failed: %v", err) + http.Error(w, "Key generation failed", http.StatusInternalServerError) return } - publicKeyBytes := make([]byte, 32) - if _, err := rand.Read(publicKeyBytes); err != nil { - http.Error(w, "Failed to generate public key", http.StatusInternalServerError) + // Generate random nonce + nonce := make([]byte, 24) + if _, err := rand.Read(nonce); err != nil { + log.Printf("Nonce generation failed: %v", err) + http.Error(w, "Nonce generation failed", http.StatusInternalServerError) return } - // Store keys in protected memory - privateKey := memguard.NewBufferFromBytes(privateKeyBytes) - publicKey := memguard.NewBufferFromBytes(publicKeyBytes) + // Generate session ID from public key and nonce + sessionID := generateSessionID(publicKey[:], nonce) + // Store keys in memguard protected memory session := &Session{ - ID: sessionID, - PrivateKey: privateKey, - PublicKey: publicKey, - CreatedAt: time.Now(), + ID: sessionID, + PublicKey: memguard.NewBufferFromBytes(publicKey[:]), + PrivateKey: memguard.NewBufferFromBytes(privateKey[:]), + Nonce: memguard.NewBufferFromBytes(nonce), + CreatedAt: time.Now(), + LastActivity: time.Now(), } - sessionsMutex.Lock() - sessions[sessionID] = session - sessionsMutex.Unlock() + sessionManager.mutex.Lock() + sessionManager.sessions[sessionID] = session + sessionManager.mutex.Unlock() - log.Printf("Session %s started successfully", sessionID) + log.Printf("Session started: %s", sessionID) resp := StartSessionResponse{ SessionID: sessionID, - PublicKey: hex.EncodeToString(publicKey.Bytes()), Status: "success", + Version: VERSION, } json.NewEncoder(w).Encode(resp) } -// pairSessionHandler handles pairing two sessions +// Pair session handler func pairSessionHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - w.Header().Set("Content-Type", "application/json") var req PairSessionRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, "Invalid JSON", http.StatusBadRequest) + http.Error(w, "Invalid request", http.StatusBadRequest) return } if req.SessionID == "" || req.BuddySessionID == "" { - http.Error(w, "session_id and buddy_session_id are required", http.StatusBadRequest) + http.Error(w, "Missing session IDs", http.StatusBadRequest) return } - sessionsMutex.Lock() - defer sessionsMutex.Unlock() + sessionManager.mutex.RLock() + session, exists1 := sessionManager.sessions[req.SessionID] + buddySession, exists2 := sessionManager.sessions[req.BuddySessionID] + sessionManager.mutex.RUnlock() - // Check if both sessions exist - session, exists := sessions[req.SessionID] - if !exists { - resp := PairSessionResponse{ - Status: "error", - Message: "Session not found", - } - w.WriteHeader(http.StatusNotFound) - json.NewEncoder(w).Encode(resp) + if !exists1 || !exists2 { + http.Error(w, "Session not found", http.StatusNotFound) return } - buddySession, exists := sessions[req.BuddySessionID] - if !exists { - resp := PairSessionResponse{ - Status: "error", - Message: "Buddy session not found", - } - w.WriteHeader(http.StatusNotFound) - json.NewEncoder(w).Encode(resp) - return - } + // Update activity timestamps + session.mutex.Lock() + session.LastActivity = time.Now() + session.PairedWith = req.BuddySessionID + session.mutex.Unlock() - // Create shared secret using simple XOR (in production, use proper key exchange) - sharedSecretBytes := make([]byte, 32) - for i := 0; i < 32; i++ { - sharedSecretBytes[i] = session.PrivateKey.Bytes()[i] ^ buddySession.PublicKey.Bytes()[i] - } + buddySession.mutex.Lock() + buddySession.LastActivity = time.Now() + buddySession.PairedWith = req.SessionID + buddySession.mutex.Unlock() + + // Compute shared secret using NaCl box + var sharedSecret [32]byte + var myPrivate [32]byte + var theirPublic [32]byte - // Store shared secret in both sessions - session.SharedSecret = memguard.NewBufferFromBytes(sharedSecretBytes) - buddySession.SharedSecret = memguard.NewBufferFromBytes(sharedSecretBytes) + copy(myPrivate[:], session.PrivateKey.Bytes()) + copy(theirPublic[:], buddySession.PublicKey.Bytes()) - // Create pairing record - pairKey := fmt.Sprintf("%s-%s", req.SessionID, req.BuddySessionID) - pairedSessions[pairKey] = &PairedSession{ - SessionA: req.SessionID, - SessionB: req.BuddySessionID, - PairedAt: time.Now(), + box.Precompute(&sharedSecret, &theirPublic, &myPrivate) + + // Store shared secret in memguard + session.mutex.Lock() + session.SharedSecret = memguard.NewBufferFromBytes(sharedSecret[:]) + session.mutex.Unlock() + + buddySession.mutex.Lock() + buddySession.SharedSecret = memguard.NewBufferFromBytes(sharedSecret[:]) + buddySession.mutex.Unlock() + + // Zero out stack variables + for i := range myPrivate { + myPrivate[i] = 0 + } + for i := range theirPublic { + theirPublic[i] = 0 + } + for i := range sharedSecret { + sharedSecret[i] = 0 } - log.Printf("Sessions %s and %s paired successfully", req.SessionID, req.BuddySessionID) + log.Printf("Sessions paired: %s <-> %s", req.SessionID, req.BuddySessionID) resp := PairSessionResponse{ - Status: "success", - Message: "Sessions paired successfully", - BuddyPublicKey: hex.EncodeToString(buddySession.PublicKey.Bytes()), + Status: "success", + Message: "Sessions paired successfully", } json.NewEncoder(w).Encode(resp) } -// Simple XOR encryption (for demonstration - use proper encryption in production) -func xorEncrypt(data, key []byte) []byte { - result := make([]byte, len(data)) - for i := 0; i < len(data); i++ { - result[i] = data[i] ^ key[i%len(key)] - } - return result -} - -// encryptHandler handles message encryption +// Encrypt handler func encryptHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - w.Header().Set("Content-Type", "application/json") var req EncryptRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, "Invalid JSON", http.StatusBadRequest) + http.Error(w, "Invalid request", http.StatusBadRequest) return } if req.SessionID == "" || req.Message == "" { - http.Error(w, "session_id and message are required", http.StatusBadRequest) + http.Error(w, "Missing required fields", http.StatusBadRequest) return } - sessionsMutex.RLock() - session, exists := sessions[req.SessionID] - sessionsMutex.RUnlock() + sessionManager.mutex.RLock() + session, exists := sessionManager.sessions[req.SessionID] + sessionManager.mutex.RUnlock() if !exists { http.Error(w, "Session not found", http.StatusNotFound) return } + session.mutex.Lock() + defer session.mutex.Unlock() + if session.SharedSecret == nil { http.Error(w, "Session not paired", http.StatusBadRequest) return } + session.LastActivity = time.Now() + + // Store plaintext in memguard protected buffer + plaintextBuffer := memguard.NewBufferFromBytes([]byte(req.Message)) + defer plaintextBuffer.Destroy() + + // Create AEAD cipher with XChaCha20-Poly1305 + aead, err := chacha20poly1305.NewX(session.SharedSecret.Bytes()) + if err != nil { + log.Printf("Cipher creation failed: %v", err) + http.Error(w, "Encryption failed", http.StatusInternalServerError) + return + } + + // Generate random nonce + nonce := make([]byte, aead.NonceSize()) + if _, err := rand.Read(nonce); err != nil { + log.Printf("Nonce generation failed: %v", err) + http.Error(w, "Encryption failed", http.StatusInternalServerError) + return + } + // Encrypt the message - encrypted := xorEncrypt([]byte(req.Message), session.SharedSecret.Bytes()) - encryptedHex := hex.EncodeToString(encrypted) + ciphertext := aead.Seal(nonce, nonce, plaintextBuffer.Bytes(), nil) - log.Printf("Message encrypted for session %s", req.SessionID) + // Encode to hex for transmission + encryptedHex := hex.EncodeToString(ciphertext) resp := EncryptResponse{ EncryptedMessage: encryptedHex, - Status: "success", + Status: "success", } json.NewEncoder(w).Encode(resp) } -// decryptHandler handles message decryption +// Decrypt handler func decryptHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - w.Header().Set("Content-Type", "application/json") var req DecryptRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, "Invalid JSON", http.StatusBadRequest) + http.Error(w, "Invalid request", http.StatusBadRequest) return } if req.SessionID == "" || req.EncryptedMessage == "" { - http.Error(w, "session_id and encrypted_message are required", http.StatusBadRequest) + http.Error(w, "Missing required fields", http.StatusBadRequest) return } - sessionsMutex.RLock() - session, exists := sessions[req.SessionID] - sessionsMutex.RUnlock() + sessionManager.mutex.RLock() + session, exists := sessionManager.sessions[req.SessionID] + sessionManager.mutex.RUnlock() if !exists { http.Error(w, "Session not found", http.StatusNotFound) return } + session.mutex.Lock() + defer session.mutex.Unlock() + if session.SharedSecret == nil { http.Error(w, "Session not paired", http.StatusBadRequest) return } - // Decode and decrypt the message - encryptedBytes, err := hex.DecodeString(req.EncryptedMessage) + session.LastActivity = time.Now() + + // Decode from hex + ciphertext, err := hex.DecodeString(req.EncryptedMessage) + if err != nil { + http.Error(w, "Invalid ciphertext format", http.StatusBadRequest) + return + } + + // Create AEAD cipher + aead, err := chacha20poly1305.NewX(session.SharedSecret.Bytes()) if err != nil { - http.Error(w, "Invalid encrypted message format", http.StatusBadRequest) + log.Printf("Cipher creation failed: %v", err) + http.Error(w, "Decryption failed", http.StatusInternalServerError) + return + } + + // Extract nonce and ciphertext + if len(ciphertext) < aead.NonceSize() { + http.Error(w, "Invalid ciphertext", http.StatusBadRequest) return } - decrypted := xorEncrypt(encryptedBytes, session.SharedSecret.Bytes()) + nonce := ciphertext[:aead.NonceSize()] + ciphertext = ciphertext[aead.NonceSize():] - log.Printf("Message decrypted for session %s", req.SessionID) + // Decrypt the message + plaintext, err := aead.Open(nil, nonce, ciphertext, nil) + if err != nil { + log.Printf("Decryption failed: %v", err) + http.Error(w, "Decryption failed - authentication failed", http.StatusBadRequest) + return + } + + // Store plaintext in memguard protected buffer + plaintextBuffer := memguard.NewBufferFromBytes(plaintext) + defer plaintextBuffer.Destroy() + + // Zero out plaintext slice + for i := range plaintext { + plaintext[i] = 0 + } resp := DecryptResponse{ - DecryptedMessage: string(decrypted), - Status: "success", + DecryptedMessage: string(plaintextBuffer.Bytes()), + Status: "success", } json.NewEncoder(w).Encode(resp) } -// endSessionHandler handles ending a session +// End session handler func endSessionHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - w.Header().Set("Content-Type", "application/json") var req EndSessionRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, "Invalid JSON", http.StatusBadRequest) + http.Error(w, "Invalid request", http.StatusBadRequest) return } if req.SessionID == "" { - http.Error(w, "session_id is required", http.StatusBadRequest) + http.Error(w, "Missing session ID", http.StatusBadRequest) return } - sessionsMutex.Lock() - defer sessionsMutex.Unlock() + sessionManager.mutex.Lock() + defer sessionManager.mutex.Unlock() - // Check if session exists - session, exists := sessions[req.SessionID] + session, exists := sessionManager.sessions[req.SessionID] if !exists { - resp := EndSessionResponse{ - Status: "error", - Message: "Session not found", - } - w.WriteHeader(http.StatusNotFound) - json.NewEncoder(w).Encode(resp) + http.Error(w, "Session not found", http.StatusNotFound) return } - // Securely wipe session keys from memory using memguard - if session.PrivateKey != nil { - session.PrivateKey.Destroy() - } - if session.PublicKey != nil { - session.PublicKey.Destroy() - } - if session.SharedSecret != nil { - session.SharedSecret.Destroy() - } + // Destroy all protected memory + session.destroy() - // Remove session from sessions map - delete(sessions, req.SessionID) - - // Also remove from pairedSessions if it was paired - for key, pairedSession := range pairedSessions { - if pairedSession.SessionA == req.SessionID || pairedSession.SessionB == req.SessionID { - delete(pairedSessions, key) - break - } - } + // Remove from sessions map + delete(sessionManager.sessions, req.SessionID) - log.Printf("Session %s terminated successfully", req.SessionID) + log.Printf("Session terminated: %s", req.SessionID) resp := EndSessionResponse{ Status: "success", - Message: "Session terminated successfully", + Message: "Session terminated and all keys destroyed", } json.NewEncoder(w).Encode(resp) } -// cleanupAllSessions securely destroys all sessions +// Cleanup all sessions on shutdown func cleanupAllSessions() { - sessionsMutex.Lock() - defer sessionsMutex.Unlock() + sessionManager.mutex.Lock() + defer sessionManager.mutex.Unlock() log.Println("Cleaning up all sessions...") - for sessionID, session := range sessions { - // Securely destroy all memory-protected keys - if session.PrivateKey != nil { - session.PrivateKey.Destroy() - } - if session.PublicKey != nil { - session.PublicKey.Destroy() - } - if session.SharedSecret != nil { - session.SharedSecret.Destroy() - } - - log.Printf("Cleaned up session: %s", sessionID) - } - - // Clear the maps - sessions = make(map[string]*Session) - pairedSessions = make(map[string]*PairedSession) - - // Destroy demo secrets - if demoSecrets != nil { - demoSecrets.Destroy() + for id, session := range sessionManager.sessions { + session.destroy() + log.Printf("Destroyed session: %s", id) } - log.Println("All sessions cleaned up successfully") + sessionManager.sessions = make(map[string]*Session) + log.Println("All sessions cleaned up") } func main() { @@ -632,17 +572,15 @@ func main() { memguard.CatchInterrupt() defer memguard.Purge() - // Initialize security demo - initSecurityDemo() + // Start session cleanup routine + sessionManager.startCleanupRoutine() - // Setup HTTP routes - http.HandleFunc("/api/start_session", startSessionHandler) - http.HandleFunc("/api/pair_session", pairSessionHandler) - http.HandleFunc("/api/encrypt", encryptHandler) - http.HandleFunc("/api/decrypt", decryptHandler) - http.HandleFunc("/api/end_session", endSessionHandler) - http.HandleFunc("/api/security_demo", securityDemoHandler) - http.HandleFunc("/api/security_challenge", securityChallengeHandler) + // Setup HTTP routes with middleware + http.HandleFunc("/api/start_session", rateLimitMiddleware(corsMiddleware(sizeLimitMiddleware(startSessionHandler)))) + http.HandleFunc("/api/pair_session", rateLimitMiddleware(corsMiddleware(sizeLimitMiddleware(pairSessionHandler)))) + http.HandleFunc("/api/encrypt", rateLimitMiddleware(corsMiddleware(sizeLimitMiddleware(encryptHandler)))) + http.HandleFunc("/api/decrypt", rateLimitMiddleware(corsMiddleware(sizeLimitMiddleware(decryptHandler)))) + http.HandleFunc("/api/end_session", rateLimitMiddleware(corsMiddleware(sizeLimitMiddleware(endSessionHandler)))) // Serve static files http.Handle("/", http.FileServer(http.Dir("./"))) @@ -651,8 +589,9 @@ func main() { defer cleanupAllSessions() port := ":8080" - log.Printf("NoFuture server starting on port %s", port) - log.Printf("Memguard protection active - conversations are secured") + log.Printf("NoFuture-Memguard-PQ v%s starting on port %s", VERSION, port) + log.Printf("Memguard protection active - all keys and plaintext secured") + log.Println("WARNING: Use HTTPS in production (configure reverse proxy)") if err := http.ListenAndServe(port, nil); err != nil { log.Fatal("Server failed to start:", err) |
