// Package session manages secure communication sessions with Forward Secrecy package session import ( "crypto/rand" "encoding/hex" "fmt" "sync" "time" "github.com/flynn/noise" ) // Session represents a secure communication session type Session struct { ID string RemotePubKey string // Remote peer's public key (hex) HandshakeState *noise.HandshakeState CipherState *noise.CipherState CreatedAt time.Time LastActivity time.Time IsInitiator bool } // SessionManager manages multiple sessions type SessionManager struct { sessions map[string]*Session // key = session ID mu sync.RWMutex } // NewSessionManager creates a new session manager func NewSessionManager() *SessionManager { return &SessionManager{ sessions: make(map[string]*Session), } } // CreateSession creates a new Noise Protocol session func (sm *SessionManager) CreateSession(remotePubKey string, isInitiator bool) (*Session, error) { sm.mu.Lock() defer sm.mu.Unlock() // Generate session ID sessionID := generateSessionID() // Create Noise handshake configuration // Using Noise_XX_25519_ChaChaPoly_BLAKE2b pattern for mutual authentication var hs *noise.HandshakeState var err error config := noise.Config{ CipherSuite: noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashBLAKE2b), Pattern: noise.HandshakeXX, Initiator: isInitiator, } // Generate ephemeral keypair keypair, err := noise.DH25519.GenerateKeypair(rand.Reader) if err != nil { return nil, fmt.Errorf("failed to generate keypair: %w", err) } config.StaticKeypair = keypair hs, err = noise.NewHandshakeState(config) if err != nil { return nil, fmt.Errorf("failed to create handshake state: %w", err) } session := &Session{ ID: sessionID, RemotePubKey: remotePubKey, HandshakeState: hs, CreatedAt: time.Now(), LastActivity: time.Now(), IsInitiator: isInitiator, } sm.sessions[sessionID] = session return session, nil } // GetSession retrieves a session by ID func (sm *SessionManager) GetSession(sessionID string) (*Session, bool) { sm.mu.RLock() defer sm.mu.RUnlock() session, exists := sm.sessions[sessionID] return session, exists } // RemoveSession removes a session and securely wipes it func (sm *SessionManager) RemoveSession(sessionID string) { sm.mu.Lock() defer sm.mu.Unlock() if session, exists := sm.sessions[sessionID]; exists { session.SecureWipe() delete(sm.sessions, sessionID) } } // SecureWipeAll securely wipes all sessions func (sm *SessionManager) SecureWipeAll() { sm.mu.Lock() defer sm.mu.Unlock() for _, session := range sm.sessions { session.SecureWipe() } sm.sessions = make(map[string]*Session) } // ListSessions returns all active sessions func (sm *SessionManager) ListSessions() []*Session { sm.mu.RLock() defer sm.mu.RUnlock() sessions := make([]*Session, 0, len(sm.sessions)) for _, session := range sm.sessions { sessions = append(sessions, session) } return sessions } // CleanupExpiredSessions removes sessions older than the specified duration func (sm *SessionManager) CleanupExpiredSessions(maxAge time.Duration) int { sm.mu.Lock() defer sm.mu.Unlock() now := time.Now() removed := 0 for id, session := range sm.sessions { if now.Sub(session.LastActivity) > maxAge { session.SecureWipe() delete(sm.sessions, id) removed++ } } return removed } // UpdateActivity updates the last activity timestamp func (sm *SessionManager) UpdateActivity(sessionID string) { sm.mu.Lock() defer sm.mu.Unlock() if session, exists := sm.sessions[sessionID]; exists { session.LastActivity = time.Now() } } // Count returns the number of active sessions func (sm *SessionManager) Count() int { sm.mu.RLock() defer sm.mu.RUnlock() return len(sm.sessions) } // SecureWipe zeros out sensitive session data func (s *Session) SecureWipe() { // Wipe cipher state if exists if s.CipherState != nil { // Noise library doesn't expose key wiping, but we can nil the reference s.CipherState = nil } // Wipe handshake state if s.HandshakeState != nil { s.HandshakeState = nil } // Zero out session ID s.ID = "" s.RemotePubKey = "" } // generateSessionID generates a random session ID func generateSessionID() string { id := make([]byte, 16) rand.Read(id) return hex.EncodeToString(id) } // String returns a safe string representation func (s *Session) String() string { role := "responder" if s.IsInitiator { role = "initiator" } return fmt.Sprintf("Session(id=%s, role=%s, remote=%s...)", s.ID[:8], role, s.RemotePubKey[:16]) } // String returns session manager info func (sm *SessionManager) String() string { sm.mu.RLock() defer sm.mu.RUnlock() return fmt.Sprintf("SessionManager(active=%d)", len(sm.sessions)) }