From 9227d94e790f4558e46a4e9fbbf8b3ced8743ecb Mon Sep 17 00:00:00 2001 From: shuaiplus <2327005759@qq.com> Date: Sat, 24 May 2025 16:26:09 +0800 Subject: [PATCH] =?UTF-8?q?server=E5=8E=BB=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/server.js | 864 ++++++++++++++++++++++------------------------- 1 file changed, 407 insertions(+), 457 deletions(-) diff --git a/server/server.js b/server/server.js index f098038..b5196d2 100644 --- a/server/server.js +++ b/server/server.js @@ -5,617 +5,567 @@ const crypto = require('crypto'); const ws = require('ws'); -// 生成RSA密钥对函数,整合自key-generate.js const generateRSAKeyPair = () => { - try { - console.log('Generating new RSA keypair...'); - const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', { - modulusLength: 2048, - publicKeyEncoding: { - type: 'spki', - format: 'der' - }, - privateKeyEncoding: { - type: 'pkcs8', - format: 'der' - } - }); + try { + console.log('Generating new RSA keypair...'); + const { + publicKey, + privateKey + } = crypto.generateKeyPairSync('rsa', { + modulusLength: 2048, + publicKeyEncoding: { + type: 'spki', + format: 'der' + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'der' + } + }); - return { - rsaPublic: Buffer.from(publicKey).toString('base64'), - rsaPrivate: crypto.createPrivateKey({ - key: privateKey, - format: 'der', - type: 'pkcs8' - }) - }; - } catch (error) { - console.error('Error generating RSA key pair:', error); - process.exit(1); - } + return { + rsaPublic: Buffer.from(publicKey).toString('base64'), + rsaPrivate: crypto.createPrivateKey({ + key: privateKey, + format: 'der', + type: 'pkcs8' + }) + }; + } catch (error) { + console.error('Error generating RSA key pair:', error); + process.exit(1); + } }; -// 生成密钥并配置服务器 const keyPair = generateRSAKeyPair(); console.log('RSA key pair generated successfully'); const config = { - rsaPrivate: keyPair.rsaPrivate, - rsaPublic: keyPair.rsaPublic, - wsHost: '127.0.0.1', - wsPort: 8088, - seenTimeout: 60000, - debug: false + rsaPrivate: keyPair.rsaPrivate, + rsaPublic: keyPair.rsaPublic, + wsHost: '127.0.0.1', + wsPort: 8088, + seenTimeout: 60000, + debug: false }; -// Create websocket server const wss = new ws.Server({ - host: config.wsHost, - port: config.wsPort, - perMessageDeflate: false + host: config.wsHost, + port: config.wsPort, + perMessageDeflate: false }); console.log('server started', config.wsHost, config.wsPort); -// Listen for client connections var clients = {}; var channels = {}; -wss.on('connection', ( connection ) => { +wss.on('connection', (connection) => { - if ( - ! connection - ) { - return; - } + if ( + !connection + ) { + return; + } - // Terminate clients have not seen for a while - const seenThreshold = ( getTime() - config.seenTimeout ); + const seenThreshold = (getTime() - config.seenTimeout); - for ( - const clientId in clients - ) { - if ( - clients[clientId].seen < seenThreshold - ) { - try { - logEvent('connection-seen', clientId, 'debug'); - clients[clientId].connection.terminate(); - } catch ( error ) { - logEvent('connection-seen', error, 'error'); - } - } - } + for ( + const clientId in clients + ) { + if ( + clients[clientId].seen < seenThreshold + ) { + try { + logEvent('connection-seen', clientId, 'debug'); + clients[clientId].connection.terminate(); + } catch (error) { + logEvent('connection-seen', error, 'error'); + } + } + } - // Generate client id - // Ultimately make sure that it does not collide - const clientId = generateClientId(); + const clientId = generateClientId(); - if ( - ! clientId || - clients[clientId] - ) { - closeConnection(connection); - return; - } + if ( + !clientId || + clients[clientId] + ) { + closeConnection(connection); + return; + } - logEvent('connection', clientId, 'debug'); + logEvent('connection', clientId, 'debug'); - // Add client to client list - clients[clientId] = { - connection: connection, - seen: getTime(), - key: null, - channel: null - }; + clients[clientId] = { + connection: connection, + seen: getTime(), + key: null, + channel: null + }; - // Send the server's public key to the client first - try { - logEvent('sending-public-key', clientId, 'debug'); - sendMessage(connection, JSON.stringify({ - type: 'server-key', - key: config.rsaPublic - })); - } catch (error) { - logEvent('sending-public-key', error, 'error'); - } + try { + logEvent('sending-public-key', clientId, 'debug'); + sendMessage(connection, JSON.stringify({ + type: 'server-key', + key: config.rsaPublic + })); + } catch (error) { + logEvent('sending-public-key', error, 'error'); + } - /* ============ - ON MESSAGE - ============ */ - connection.on('message', ( message ) => { - if ( - ! isString(message) || - ! clients[clientId] - ) { - return; - } + connection.on('message', (message) => { - // Update seen - clients[clientId].seen = getTime(); + if ( + !isString(message) || + !clients[clientId] + ) { + return; + } - // Respond to ping requests - if ( - message === 'ping' - ) { - sendMessage(connection, 'pong'); - return; - } + clients[clientId].seen = getTime(); - logEvent('message', [ clientId, message ], 'debug'); + if ( + message === 'ping' + ) { + sendMessage(connection, 'pong'); + return; + } - // Receive client ecdh public key - if ( - ! clients[clientId].shared && - message.length < 2048 - ) { + logEvent('message', [clientId, message], 'debug'); - // Generate client ecdh keys - // Set client ecdh shared key (just 256 bits required from the possible 384) - // Send rsa signed ecdh public key to client + if ( + !clients[clientId].shared && + message.length < 2048 + ) { - try { - const keys = crypto.createECDH('secp384r1'); + try { - keys.generateKeys(); + const keys = crypto.createECDH('secp384r1'); - const publicKey = keys.getPublicKey(); - const signature = crypto.sign('sha256', publicKey, { - key: config.rsaPrivate, - padding: crypto.constants.RSA_PKCS1_PADDING, - dsaEncoding: 'ieee-p1363' - }); + keys.generateKeys(); - clients[clientId].shared = keys.computeSecret(message, 'hex', null).slice(8, 40); + const publicKey = keys.getPublicKey(); + const signature = crypto.sign('sha256', publicKey, { + key: config.rsaPrivate, + padding: crypto.constants.RSA_PKCS1_PADDING, + dsaEncoding: 'ieee-p1363' + }); - sendMessage(connection, publicKey.toString('hex') + '|' + signature.toString('base64')); + clients[clientId].shared = keys.computeSecret(message, 'hex', null).slice(8, 40); - } catch ( error ) { - logEvent('message-key', [ clientId, error ], 'error'); - closeConnection(connection); - } + sendMessage(connection, publicKey.toString('hex') + '|' + signature.toString('base64')); - return; + } catch (error) { + logEvent('message-key', [clientId, error], 'error'); + closeConnection(connection); + } - } + return; - // Receive encrypted message from client - if ( - clients[clientId].shared && - message.length <= ( 8 * 1024 * 1024 ) - ) { + } - // 立即处理并清理,避免在回调中持有大对象引用 - processEncryptedMessage(clientId, message); + if ( + clients[clientId].shared && + message.length <= (8 * 1024 * 1024) + ) { - } + processEncryptedMessage(clientId, message); - }); + } - /* ========== - ON CLOSE - ========== */ + }); - connection.on('close', ( event ) => { - logEvent('close', [ clientId, event ], 'debug'); - // Remove client from channel + connection.on('close', (event) => { - const channel = clients[clientId].channel; + logEvent('close', [clientId, event], 'debug'); - if ( - channel && - channels[channel] - ) { - channels[channel].splice(channels[channel].indexOf(clientId), 1); + const channel = clients[clientId].channel; - if ( - channels[channel].length === 0 - ) { + if ( + channel && + channels[channel] + ) { - // Remove empty channel + channels[channel].splice(channels[channel].indexOf(clientId), 1); - delete(channels[channel]); + if ( + channels[channel].length === 0 + ) { - } else { - // Send channel client list (except themselves) to all clients in the channel + delete(channels[channel]); - try { + } else { - const members = channels[channel]; - for ( - const member of members - ) { + try { - const client = clients[member]; + const members = channels[channel]; - if ( - isClientInChannel(client, channel) - ) { - sendMessage(client.connection, encryptMessage({ - a: 'l', - p: members.filter(( value ) => { - return( - value !== member - ? true - : false - ); - }) - }, client.shared)); - } + for ( + const member of members + ) { - } + const client = clients[member]; - } catch ( error ) { - logEvent('close-list', [ clientId, error ], 'error'); - } + if ( + isClientInChannel(client, channel) + ) { + sendMessage(client.connection, encryptMessage({ + a: 'l', + p: members.filter((value) => { + return ( + value !== member ? + true : + false + ); + }) + }, client.shared)); + } - } + } - } + } catch (error) { + logEvent('close-list', [clientId, error], 'error'); + } - // Remove client from client list + } - if ( - clients[clientId] - ) { - delete(clients[clientId]); - } + } - }); + + if ( + clients[clientId] + ) { + delete(clients[clientId]); + } + + }); }); -// 将加密消息处理提取为独立函数,便于内存管理 const processEncryptedMessage = (clientId, message) => { - let decrypted = null; - - try { - decrypted = decryptMessage(message, clients[clientId].shared); - - logEvent('message-decrypted', [ clientId, decrypted ], 'debug'); + let decrypted = null; - if ( - ! isObject(decrypted) || - ! isString(decrypted.a) - ) { - return; - } + try { + decrypted = decryptMessage(message, clients[clientId].shared); - // 根据消息类型分发处理 - const action = decrypted.a; - - if (action === 'j') { - handleJoinChannel(clientId, decrypted); - } else if (action === 'c') { - handleClientMessage(clientId, decrypted); - } else if (action === 'w') { - handleChannelMessage(clientId, decrypted); - } + logEvent('message-decrypted', [clientId, decrypted], 'debug'); - } catch (error) { - logEvent('process-encrypted-message', [clientId, error], 'error'); - } finally { - // 确保解密对象被立即清理 - decrypted = null; - } + if ( + !isObject(decrypted) || + !isString(decrypted.a) + ) { + return; + } + + const action = decrypted.a; + + if (action === 'j') { + handleJoinChannel(clientId, decrypted); + } else if (action === 'c') { + handleClientMessage(clientId, decrypted); + } else if (action === 'w') { + handleChannelMessage(clientId, decrypted); + } + + } catch (error) { + logEvent('process-encrypted-message', [clientId, error], 'error'); + } finally { + decrypted = null; + } }; -// 处理加入频道请求 const handleJoinChannel = (clientId, decrypted) => { - if ( - ! isString(decrypted.p) || - clients[clientId].channel - ) { - return; - } + if ( + !isString(decrypted.p) || + clients[clientId].channel + ) { + return; + } - try { - const channel = decrypted.p; + try { + const channel = decrypted.p; - // Set client channel - clients[clientId].channel = channel; + clients[clientId].channel = channel; - // Add client to channel - if (! channels[channel]) { - channels[channel] = [ clientId ]; - } else { - channels[channel].push(clientId); - } + if (!channels[channel]) { + channels[channel] = [clientId]; + } else { + channels[channel].push(clientId); + } - // Send channel client list - broadcastMemberList(channel); + broadcastMemberList(channel); - } catch (error) { - logEvent('message-join', [ clientId, error ], 'error'); - } + } catch (error) { + logEvent('message-join', [clientId, error], 'error'); + } }; -// 处理客户端私聊消息 const handleClientMessage = (clientId, decrypted) => { - if ( - ! isString(decrypted.p) || - ! isString(decrypted.c) || - ! clients[clientId].channel - ) { - return; - } + if ( + !isString(decrypted.p) || + !isString(decrypted.c) || + !clients[clientId].channel + ) { + return; + } - try { - const channel = clients[clientId].channel; - const targetClient = clients[decrypted.c]; + try { + const channel = clients[clientId].channel; + const targetClient = clients[decrypted.c]; - if (isClientInChannel(targetClient, channel)) { - // 创建临时消息对象,发送后立即清理 - const messageObj = { - a: 'c', - p: decrypted.p, - c: clientId - }; - - const encrypted = encryptMessage(messageObj, targetClient.shared); - sendMessage(targetClient.connection, encrypted); - - // 清理临时对象 - messageObj.p = null; - } + if (isClientInChannel(targetClient, channel)) { + const messageObj = { + a: 'c', + p: decrypted.p, + c: clientId + }; - } catch (error) { - logEvent('message-client', [ clientId, error ], 'error'); - } + const encrypted = encryptMessage(messageObj, targetClient.shared); + sendMessage(targetClient.connection, encrypted); + + messageObj.p = null; + } + + } catch (error) { + logEvent('message-client', [clientId, error], 'error'); + } }; -// 处理频道广播消息 const handleChannelMessage = (clientId, decrypted) => { - if ( - ! isObject(decrypted.p) || - ! clients[clientId].channel - ) { - return; - } + if ( + !isObject(decrypted.p) || + !clients[clientId].channel + ) { + return; + } - try { - const channel = clients[clientId].channel; + try { + const channel = clients[clientId].channel; - for (const member in decrypted.p) { - const targetClient = clients[member]; - - if ( - isString(decrypted.p[member]) && - isClientInChannel(targetClient, channel) - ) { - // 创建临时消息对象 - const messageObj = { - a: 'c', - p: decrypted.p[member], - c: clientId - }; - - const encrypted = encryptMessage(messageObj, targetClient.shared); - sendMessage(targetClient.connection, encrypted); - - // 清理临时对象 - messageObj.p = null; - } - } + for (const member in decrypted.p) { + const targetClient = clients[member]; - } catch (error) { - logEvent('message-channel', [ clientId, error ], 'error'); - } + if ( + isString(decrypted.p[member]) && + isClientInChannel(targetClient, channel) + ) { + const messageObj = { + a: 'c', + p: decrypted.p[member], + c: clientId + }; + + const encrypted = encryptMessage(messageObj, targetClient.shared); + sendMessage(targetClient.connection, encrypted); + + messageObj.p = null; + } + } + + } catch (error) { + logEvent('message-channel', [clientId, error], 'error'); + } }; -// 广播成员列表 const broadcastMemberList = (channel) => { - try { - const members = channels[channel]; - - for (const member of members) { - const client = clients[member]; - - if (isClientInChannel(client, channel)) { - // 创建过滤后的成员列表 - const filteredMembers = members.filter(value => value !== member); - - const listObj = { - a: 'l', - p: filteredMembers - }; - - const encrypted = encryptMessage(listObj, client.shared); - sendMessage(client.connection, encrypted); - - // 清理临时对象 - listObj.p = null; - } - } - } catch (error) { - logEvent('broadcast-member-list', error, 'error'); - } + try { + const members = channels[channel]; + + for (const member of members) { + const client = clients[member]; + + if (isClientInChannel(client, channel)) { + const filteredMembers = members.filter(value => value !== member); + + const listObj = { + a: 'l', + p: filteredMembers + }; + + const encrypted = encryptMessage(listObj, client.shared); + sendMessage(client.connection, encrypted); + + listObj.p = null; + } + } + } catch (error) { + logEvent('broadcast-member-list', error, 'error'); + } }; -/* ================== - HELPER FUNCTIONS - ================== */ -// Log event -const logEvent = ( source, message, level ) => { - if ( - level !== 'debug' || - config.debug - ) { - const date = new Date(), - dateString = date.getFullYear() + '-' + - ( '0' + (date.getMonth() + 1) ).slice(-2) + '-' + - ( '0' + date.getDate() ).slice(-2) + ' ' + - ( '0' + date.getHours() ).slice(-2) + ':' + - ( '0' + date.getMinutes() ).slice(-2) + ':' + - ( '0' + date.getSeconds() ).slice(-2); +const logEvent = (source, message, level) => { + if ( + level !== 'debug' || + config.debug + ) { - console.log('[' + dateString + ']', ( level ? level.toUpperCase() : 'INFO' ), source + ( message ? ':' : '' ), ( message ? message : '' )); + const date = new Date(), + dateString = date.getFullYear() + '-' + + ('0' + (date.getMonth() + 1)).slice(-2) + '-' + + ('0' + date.getDate()).slice(-2) + ' ' + + ('0' + date.getHours()).slice(-2) + ':' + + ('0' + date.getMinutes()).slice(-2) + ':' + + ('0' + date.getSeconds()).slice(-2); - } + console.log('[' + dateString + ']', (level ? level.toUpperCase() : 'INFO'), source + (message ? ':' : ''), (message ? message : '')); + + } }; -// Generate client id const generateClientId = () => { - try { - return(crypto.randomBytes(8).toString('hex')); - } catch ( error ) { - logEvent('generateClientId', error, 'error'); - return(null); - } -}; - -// Close connection - -const closeConnection = ( connection ) => { - try { - connection.close(); - } catch ( error ) { - logEvent('closeConnection', error, 'error'); - } -}; - -// Is client in channel - -const isClientInChannel = ( client, channel ) => { - return( - client && - client.connection && - client.shared && - client.channel && - client.channel === channel - ? true - : false - ); -}; - -// Send message - -const sendMessage = ( connection, message ) => { - try { - if ( - connection.readyState && - connection.readyState === ws.OPEN - ) { - connection.send(message); - } - } catch ( error ) { - logEvent('sendMessage', error, 'error'); - } + try { + return (crypto.randomBytes(8).toString('hex')); + } catch (error) { + logEvent('generateClientId', error, 'error'); + return (null); + } }; -const encryptMessage = ( message, key ) => { +const closeConnection = (connection) => { + try { + connection.close(); + } catch (error) { + logEvent('closeConnection', error, 'error'); + } +}; - let encrypted = ''; - try { +const isClientInChannel = (client, channel) => { + return ( + client && + client.connection && + client.shared && + client.channel && + client.channel === channel ? + true : + false + ); +}; - const messageBuffer = Buffer.from(JSON.stringify(message), 'utf8'); - const paddedBuffer = ( messageBuffer.length % 16 ) !== 0 - ? Buffer.concat([messageBuffer, Buffer.alloc(16 - ( messageBuffer.length % 16 ))]) - : messageBuffer; +const sendMessage = (connection, message) => { + try { + if ( + connection.readyState && + connection.readyState === ws.OPEN + ) { + connection.send(message); + } + } catch (error) { + logEvent('sendMessage', error, 'error'); + } +}; - const iv = crypto.randomBytes(16); - const cipher = crypto.createCipheriv('aes-256-cbc', key, iv); - cipher.setAutoPadding(false); - encrypted = iv.toString('base64') + '|' + cipher.update(paddedBuffer, '', 'base64') + cipher.final('base64'); +const encryptMessage = (message, key) => { - } catch ( error ) { - logEvent('encryptMessage', error, 'error'); - } + let encrypted = ''; - return(encrypted); + try { + + const messageBuffer = Buffer.from(JSON.stringify(message), 'utf8'); + + const paddedBuffer = (messageBuffer.length % 16) !== 0 ? + Buffer.concat([messageBuffer, Buffer.alloc(16 - (messageBuffer.length % 16))]) : + messageBuffer; + + const iv = crypto.randomBytes(16); + const cipher = crypto.createCipheriv('aes-256-cbc', key, iv); + cipher.setAutoPadding(false); + + encrypted = iv.toString('base64') + '|' + cipher.update(paddedBuffer, '', 'base64') + cipher.final('base64'); + + } catch (error) { + logEvent('encryptMessage', error, 'error'); + } + + return (encrypted); }; -const decryptMessage = ( message, key ) => { +const decryptMessage = (message, key) => { - let decrypted = {}; + let decrypted = {}; - try { + try { - const parts = message.split('|'); - const decipher = crypto.createDecipheriv( - 'aes-256-cbc', - key, - Buffer.from(parts[0], 'base64') - ); + const parts = message.split('|'); + const decipher = crypto.createDecipheriv( + 'aes-256-cbc', + key, + Buffer.from(parts[0], 'base64') + ); - decipher.setAutoPadding(false); + decipher.setAutoPadding(false); - const decryptedText = decipher.update(parts[1], 'base64', 'utf8') + decipher.final('utf8'); - decrypted = JSON.parse(decryptedText.replace(/\0+$/, '')); + const decryptedText = decipher.update(parts[1], 'base64', 'utf8') + decipher.final('utf8'); + decrypted = JSON.parse(decryptedText.replace(/\0+$/, '')); - } catch ( error ) { - logEvent('decryptMessage', error, 'error'); - } + } catch (error) { + logEvent('decryptMessage', error, 'error'); + } - return(decrypted); + return (decrypted); }; -// Get time const getTime = () => { - return(new Date().getTime()); + return (new Date().getTime()); }; -// Is string -const isString = ( value ) => { - return( - value && - Object.prototype.toString.call(value) === '[object String]' - ? true - : false - ); +const isString = (value) => { + return ( + value && + Object.prototype.toString.call(value) === '[object String]' ? + true : + false + ); }; -// Is array -const isArray = ( value ) => { - return( - value && - Object.prototype.toString.call(value) === '[object Array]' - ? true - : false - ); +const isArray = (value) => { + return ( + value && + Object.prototype.toString.call(value) === '[object Array]' ? + true : + false + ); }; -// Is object -const isObject = ( value ) => { - return( - value && - Object.prototype.toString.call(value) === '[object Object]' - ? true - : false - ); +const isObject = (value) => { + return ( + value && + Object.prototype.toString.call(value) === '[object Object]' ? + true : + false + ); }; -// 每30秒强制触发一次垃圾回收 setInterval(() => { - if (global.gc) { - global.gc(); - } + if (global.gc) { + global.gc(); + } }, 30000); \ No newline at end of file