/** * SMB2/3 Authentication Manager * Handles user authentication, session security, and access control */ import type { SMB2ServerConfig, SMB2Session, SMB2Connection, SMB2UserConfig, } from '../protocol/types.js'; import { Logger } from '../utils/logger.js'; import { generateRandomBytes, calculateSha256, constantTimeCompare } from '../utils/helpers.js'; export interface AuthenticationResult { readonly success: boolean; readonly userId?: string; readonly isGuest: boolean; readonly isAnonymous: boolean; readonly sessionKey?: Buffer; readonly error?: string; } export interface SecurityContext { readonly userId: string; readonly isAuthenticated: boolean; readonly isGuest: boolean; readonly isAnonymous: boolean; readonly groups: string[]; readonly permissions: string[]; readonly sessionKey: Buffer; readonly signKey: Buffer; readonly applicationKey: Buffer; } export class SMB2AuthenticationManager { private readonly config: SMB2ServerConfig; private readonly logger: Logger; private readonly users: Map; private readonly activeSessions: Map; constructor(config: SMB2ServerConfig) { this.config = config; this.logger = new Logger('SMB2AuthManager'); this.users = new Map(); this.activeSessions = new Map(); // Load users from configuration this.loadUsers(); } /** * Authenticate a user with username and password */ public async authenticateUser( username: string, password: string, connection: SMB2Connection ): Promise { try { this.logger.debug(`Authentication attempt for user: ${username}`); // Handle anonymous authentication if (!username || username.toLowerCase() === 'anonymous') { return this.handleAnonymousAuth(); } // Handle guest authentication if (username.toLowerCase() === 'guest') { return this.handleGuestAuth(); } // Handle regular user authentication const user = this.users.get(username.toLowerCase()); if (!user) { this.logger.warn(`Authentication failed: user not found - ${username}`); return { success: false, isGuest: false, isAnonymous: false, error: 'User not found', }; } // Verify password const isValidPassword = await this.verifyPassword(password, user.passwordHash); if (!isValidPassword) { this.logger.warn(`Authentication failed: invalid password for user - ${username}`); return { success: false, isGuest: false, isAnonymous: false, error: 'Invalid password', }; } // Generate session key const sessionKey = this.generateSessionKey(username, password); this.logger.info(`Authentication successful for user: ${username}`); return { success: true, userId: username, isGuest: user.isGuest, isAnonymous: false, sessionKey, }; } catch (error) { this.logger.error('Authentication error:', error); return { success: false, isGuest: false, isAnonymous: false, error: 'Authentication failed', }; } } /** * Authenticate using NTLM challenge/response */ public async authenticateNTLM( ntlmToken: Buffer, connection: SMB2Connection ): Promise { try { this.logger.debug('NTLM authentication attempt'); // Parse NTLM token (stub implementation) const ntlmData = this.parseNTLMToken(ntlmToken); if (!ntlmData) { return { success: false, isGuest: false, isAnonymous: false, error: 'Invalid NTLM token', }; } // For now, just accept any NTLM token and create a guest session // In a real implementation, this would verify the NTLM response this.logger.warn('NTLM authentication is stubbed - allowing guest access'); return this.handleGuestAuth(); } catch (error) { this.logger.error('NTLM authentication error:', error); return { success: false, isGuest: false, isAnonymous: false, error: 'NTLM authentication failed', }; } } /** * Create security context for authenticated session */ public createSecurityContext( sessionId: bigint, authResult: AuthenticationResult ): SecurityContext { const context: SecurityContext = { userId: authResult.userId || 'anonymous', isAuthenticated: authResult.success, isGuest: authResult.isGuest, isAnonymous: authResult.isAnonymous, groups: this.getUserGroups(authResult.userId), permissions: this.getUserPermissions(authResult.userId), sessionKey: authResult.sessionKey || Buffer.alloc(32), signKey: this.deriveSigningKey(authResult.sessionKey || Buffer.alloc(32)), applicationKey: this.deriveApplicationKey(authResult.sessionKey || Buffer.alloc(32)), }; this.activeSessions.set(sessionId, context); this.logger.debug(`Security context created for session ${sessionId}`); return context; } /** * Get security context for session */ public getSecurityContext(sessionId: bigint): SecurityContext | undefined { return this.activeSessions.get(sessionId); } /** * Remove security context for session */ public removeSecurityContext(sessionId: bigint): void { this.activeSessions.delete(sessionId); this.logger.debug(`Security context removed for session ${sessionId}`); } /** * Check if user has access to share */ public checkShareAccess(userId: string, shareName: string): boolean { if (!userId || userId === 'anonymous') { // Check if anonymous access is allowed for this share return true; // For now, allow anonymous access } const user = this.users.get(userId.toLowerCase()); if (!user) { return false; } // Check if user has access to this share return user.allowedShares.length === 0 || user.allowedShares.includes(shareName); } /** * Generate authentication challenge */ public generateChallenge(): Buffer { return generateRandomBytes(8); } /** * Load users from configuration */ private loadUsers(): void { if (!this.config.users) { this.logger.info('No users configured - anonymous access only'); return; } for (const user of this.config.users) { this.users.set(user.username.toLowerCase(), user); this.logger.debug(`Loaded user: ${user.username} (guest: ${user.isGuest})`); } this.logger.info(`Loaded ${this.users.size} users`); } /** * Handle anonymous authentication */ private handleAnonymousAuth(): AuthenticationResult { this.logger.debug('Anonymous authentication'); return { success: true, userId: 'anonymous', isGuest: false, isAnonymous: true, sessionKey: Buffer.alloc(32), // Empty session key for anonymous }; } /** * Handle guest authentication */ private handleGuestAuth(): AuthenticationResult { this.logger.debug('Guest authentication'); const sessionKey = this.generateSessionKey('guest', ''); return { success: true, userId: 'guest', isGuest: true, isAnonymous: false, sessionKey, }; } /** * Verify password against hash */ private async verifyPassword(password: string, passwordHash: string): Promise { // Simple SHA-256 hash verification (in production, use proper password hashing like bcrypt) const hash = calculateSha256(password); return constantTimeCompare(Buffer.from(hash, 'hex'), Buffer.from(passwordHash, 'hex')); } /** * Generate session key from username and password */ private generateSessionKey(username: string, password: string): Buffer { const combined = `${username}:${password}:${Date.now()}`; const hash = calculateSha256(combined); return Buffer.from(hash, 'hex'); } /** * Derive signing key from session key */ private deriveSigningKey(sessionKey: Buffer): Buffer { const signKeyMaterial = Buffer.concat([sessionKey, Buffer.from('SMB2AESCMAC', 'ascii')]); return Buffer.from(calculateSha256(signKeyMaterial), 'hex').subarray(0, 16); } /** * Derive application key from session key */ private deriveApplicationKey(sessionKey: Buffer): Buffer { const appKeyMaterial = Buffer.concat([sessionKey, Buffer.from('SMB2APP', 'ascii')]); return Buffer.from(calculateSha256(appKeyMaterial), 'hex').subarray(0, 16); } /** * Get user groups */ private getUserGroups(userId?: string): string[] { if (!userId || userId === 'anonymous') { return ['anonymous']; } const user = this.users.get(userId.toLowerCase()); if (!user) { return []; } const groups = ['users']; if (user.isGuest) { groups.push('guests'); } if (user.isAdmin) { groups.push('administrators'); } return groups; } /** * Get user permissions */ private getUserPermissions(userId?: string): string[] { if (!userId || userId === 'anonymous') { return ['read']; } const user = this.users.get(userId.toLowerCase()); if (!user) { return []; } const permissions = ['read']; if (!user.isGuest) { permissions.push('write'); } if (user.isAdmin) { permissions.push('admin'); } return permissions; } /** * Parse NTLM token (stub implementation) */ private parseNTLMToken(token: Buffer): any | null { try { // This is a stub - a real implementation would parse the NTLM token structure if (token.length < 8) { return null; } // Return dummy parsed data return { messageType: token.readUInt32LE(0), flags: token.readUInt32LE(4), }; } catch { return null; } } /** * Add user at runtime */ public addUser(user: SMB2UserConfig): void { this.users.set(user.username.toLowerCase(), user); this.logger.info(`User added: ${user.username}`); } /** * Remove user at runtime */ public removeUser(username: string): boolean { const removed = this.users.delete(username.toLowerCase()); if (removed) { this.logger.info(`User removed: ${username}`); } return removed; } /** * Get user list */ public getUsers(): string[] { return Array.from(this.users.keys()); } /** * Get active session count */ public getActiveSessionCount(): number { return this.activeSessions.size; } /** * Cleanup expired sessions */ public cleanupExpiredSessions(): void { // This would be implemented to remove expired sessions // For now, it's a placeholder this.logger.debug('Session cleanup completed'); } }