package main import ( "crypto/rand" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "io" "log" "net/http" "sync" "time" "github.com/awnumar/memguard" "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/nacl/box" ) 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 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 } // SessionManager handles all sessions with rate limiting type SessionManager struct { sessions map[string]*Session mutex sync.RWMutex rateLimiter map[string]*RateLimit limiterMutex sync.RWMutex } type RateLimit struct { requests int resetTime time.Time } // Request/Response types type StartSessionResponse struct { SessionID string `json:"session_id"` Status string `json:"status"` Version string `json:"version"` } type PairSessionRequest struct { SessionID string `json:"session_id"` BuddySessionID string `json:"buddy_session_id"` } type PairSessionResponse struct { Status string `json:"status"` Message string `json:"message"` } type EncryptRequest struct { SessionID string `json:"session_id"` Message string `json:"message"` } type EncryptResponse struct { EncryptedMessage string `json:"encrypted_message"` Status string `json:"status"` } type DecryptRequest struct { SessionID string `json:"session_id"` EncryptedMessage string `json:"encrypted_message"` } type DecryptResponse struct { DecryptedMessage string `json:"decrypted_message"` Status string `json:"status"` } type EndSessionRequest struct { SessionID string `json:"session_id"` } type EndSessionResponse struct { Status string `json:"status"` Message string `json:"message"` } var sessionManager *SessionManager func init() { sessionManager = &SessionManager{ sessions: make(map[string]*Session), rateLimiter: make(map[string]*RateLimit), } } // Rate limiting func (sm *SessionManager) checkRateLimit(ip string) bool { sm.limiterMutex.Lock() defer sm.limiterMutex.Unlock() now := time.Now() limit, exists := sm.rateLimiter[ip] if !exists || now.After(limit.resetTime) { sm.rateLimiter[ip] = &RateLimit{ requests: 1, resetTime: now.Add(RATE_LIMIT_WINDOW), } return true } if limit.requests >= MAX_REQUESTS { return false } limit.requests++ return true } // 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)) } // Session cleanup goroutine func (sm *SessionManager) startCleanupRoutine() { ticker := time.NewTicker(1 * time.Hour) go func() { for range ticker.C { sm.cleanupExpiredSessions() } }() } func (sm *SessionManager) cleanupExpiredSessions() { sm.mutex.Lock() defer sm.mutex.Unlock() 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) } } } // Destroy session - securely wipe all sensitive data func (s *Session) destroy() { s.mutex.Lock() defer s.mutex.Unlock() 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() } } // 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) } 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 } next(w, r) } } // Middleware for rate limiting func rateLimitMiddleware(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ip := r.RemoteAddr if !sessionManager.checkRateLimit(ip) { http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests) return } next(w, r) } } // 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) } } // Start session handler func startSessionHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") 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 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 } // 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 } // Generate session ID from public key and nonce sessionID := generateSessionID(publicKey[:], nonce) // Store keys in memguard protected memory session := &Session{ ID: sessionID, PublicKey: memguard.NewBufferFromBytes(publicKey[:]), PrivateKey: memguard.NewBufferFromBytes(privateKey[:]), Nonce: memguard.NewBufferFromBytes(nonce), CreatedAt: time.Now(), LastActivity: time.Now(), } sessionManager.mutex.Lock() sessionManager.sessions[sessionID] = session sessionManager.mutex.Unlock() log.Printf("Session started: %s", sessionID) resp := StartSessionResponse{ SessionID: sessionID, Status: "success", Version: VERSION, } json.NewEncoder(w).Encode(resp) } // Pair session handler func pairSessionHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") var req PairSessionRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request", http.StatusBadRequest) return } if req.SessionID == "" || req.BuddySessionID == "" { http.Error(w, "Missing session IDs", http.StatusBadRequest) return } sessionManager.mutex.RLock() session, exists1 := sessionManager.sessions[req.SessionID] buddySession, exists2 := sessionManager.sessions[req.BuddySessionID] sessionManager.mutex.RUnlock() if !exists1 || !exists2 { http.Error(w, "Session not found", http.StatusNotFound) return } // Update activity timestamps session.mutex.Lock() session.LastActivity = time.Now() session.PairedWith = req.BuddySessionID session.mutex.Unlock() 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 copy(myPrivate[:], session.PrivateKey.Bytes()) copy(theirPublic[:], buddySession.PublicKey.Bytes()) 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 paired: %s <-> %s", req.SessionID, req.BuddySessionID) resp := PairSessionResponse{ Status: "success", Message: "Sessions paired successfully", } json.NewEncoder(w).Encode(resp) } // Encrypt handler func encryptHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") var req EncryptRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request", http.StatusBadRequest) return } if req.SessionID == "" || req.Message == "" { http.Error(w, "Missing required fields", http.StatusBadRequest) return } 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 ciphertext := aead.Seal(nonce, nonce, plaintextBuffer.Bytes(), nil) // Encode to hex for transmission encryptedHex := hex.EncodeToString(ciphertext) resp := EncryptResponse{ EncryptedMessage: encryptedHex, Status: "success", } json.NewEncoder(w).Encode(resp) } // Decrypt handler func decryptHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") var req DecryptRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request", http.StatusBadRequest) return } if req.SessionID == "" || req.EncryptedMessage == "" { http.Error(w, "Missing required fields", http.StatusBadRequest) return } 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() // 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 { 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 } nonce := ciphertext[:aead.NonceSize()] ciphertext = ciphertext[aead.NonceSize():] // 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(plaintextBuffer.Bytes()), Status: "success", } json.NewEncoder(w).Encode(resp) } // End session handler func endSessionHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") var req EndSessionRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request", http.StatusBadRequest) return } if req.SessionID == "" { http.Error(w, "Missing session ID", http.StatusBadRequest) return } sessionManager.mutex.Lock() defer sessionManager.mutex.Unlock() session, exists := sessionManager.sessions[req.SessionID] if !exists { http.Error(w, "Session not found", http.StatusNotFound) return } // Destroy all protected memory session.destroy() // Remove from sessions map delete(sessionManager.sessions, req.SessionID) log.Printf("Session terminated: %s", req.SessionID) resp := EndSessionResponse{ Status: "success", Message: "Session terminated and all keys destroyed", } json.NewEncoder(w).Encode(resp) } // Cleanup all sessions on shutdown func cleanupAllSessions() { sessionManager.mutex.Lock() defer sessionManager.mutex.Unlock() log.Println("Cleaning up all sessions...") for id, session := range sessionManager.sessions { session.destroy() log.Printf("Destroyed session: %s", id) } sessionManager.sessions = make(map[string]*Session) log.Println("All sessions cleaned up") } func main() { // Initialize memguard memguard.CatchInterrupt() defer memguard.Purge() // Start session cleanup routine sessionManager.startCleanupRoutine() // 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("./"))) // Cleanup on exit defer cleanupAllSessions() port := ":8080" 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) } }