UNPKG

cleansend

Version:

A TypeScript implementation of the OpenMsg Protocol - secure, decentralized messaging system with end-to-end encryption for cross-domain communication

276 lines 12 kB
"use strict"; /** * OpenMsg Message Routes * * This module handles encrypted message receiving and verification for the * OpenMsg protocol. It implements secure message delivery with integrity * checking, replay attack prevention, and cross-domain authentication. * * Message Flow: * 1. Sender encrypts message with connection's encryption key * 2. Sender creates verification hash with auth code and timestamp * 3. Sender posts encrypted package to recipient's server * 4. Recipient's server validates hash and decrypts message * 5. Recipient's server confirms authenticity with sender's server * 6. Message is stored in recipient's inbox * * Security Features: * - AES-256-GCM authenticated encryption * - SHA-256 hash verification with auth codes * - Timestamp-based replay attack prevention * - Cross-domain message confirmation * - Connection-based authorization */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const express_1 = __importDefault(require("express")); const axios_1 = __importDefault(require("axios")); const database_1 = require("../config/database"); const crypto_1 = require("../utils/crypto"); const settings_1 = __importDefault(require("../config/settings")); const router = express_1.default.Router(); /** * POST /message/receive * * Main endpoint for receiving encrypted messages from other OpenMsg servers. * This endpoint is called when another server wants to deliver a message * to a user on this server. * * Security checks performed: * - Validates sender authorization (connection must exist) * - Verifies message hash to prevent tampering * - Checks timestamp to prevent replay attacks * - Confirms message authenticity with sending server * - Decrypts message with connection-specific key */ router.post('/receive', async (req, res) => { try { const { receiving_openmsg_address_id, ident_code, message_package, message_hash, message_salt, message_timestamp } = req.body; // Process the message receive request const result = await messageCheck(receiving_openmsg_address_id, ident_code, message_package, message_hash, message_salt, message_timestamp); res.json(result); } catch (error) { console.error('Message receive error:', error); res.json({ error: true, response_code: 'SM_E000', error_message: 'Internal server error' }); } }); /** * POST /message/confirm * * Message confirmation endpoint used to verify message authenticity. * Other OpenMsg servers call this to confirm that a message they received * actually originated from this server. * * This prevents message spoofing attacks where malicious users claim * their messages came from a different domain. */ router.post('/confirm', async (req, res) => { try { const { message_hash, message_nonce } = req.body; const result = await messageConfirm(message_hash, message_nonce); res.json(result); } catch (error) { console.error('Message confirm error:', error); res.json({ error: true, error_message: 'Internal server error' }); } }); /** * Process incoming encrypted message * * This function implements the core message receiving logic with multiple * security layers: * * 1. Data validation - ensures all required fields are present * 2. Connection verification - checks if sender is authorized * 3. Hash verification - prevents message tampering * 4. Timestamp checking - prevents replay attacks * 5. Cross-domain confirmation - verifies message origin * 6. Message decryption - decrypts with connection-specific key * 7. Database storage - saves decrypted message to inbox * * @param receivingOpenmsgAddressId - Recipient's user ID on this server * @param identCode - Connection identity code for sender * @param messagePackage - Base64-encoded encrypted message package * @param messageHash - SHA-256 verification hash * @param messageSalt - Random salt used in hash calculation * @param messageTimestamp - Unix timestamp when message was created * @returns MessageReceiveResponse indicating success or specific error */ async function messageCheck(receivingOpenmsgAddressId, identCode, messagePackage, messageHash, messageSalt, messageTimestamp) { // Validate all required fields are present if (!receivingOpenmsgAddressId || !identCode || !messagePackage || !messageHash || !messageSalt || !messageTimestamp) { return { error: true, response_code: 'SM_E000', error_message: 'Missing data (wMv4J)' }; } // Construct full OpenMsg address for the recipient const receivingOpenmsgAddress = `${receivingOpenmsgAddressId}*${settings_1.default.openmsgDomain}`; try { // Look up the connection between sender and recipient // This verifies that the sender is authorized to send messages const [connectionRows] = await database_1.pool.execute('SELECT auth_code, message_crypt_key, other_openmsg_address FROM openmsg_user_connections WHERE self_openmsg_address = ? AND ident_code = ?', [receivingOpenmsgAddress, identCode]); if (connectionRows.length === 0) { return { error: true, response_code: 'SM_E001', error_message: `Could not find user: ${receivingOpenmsgAddress} (rB6Xl)` }; } const { auth_code, message_crypt_key, other_openmsg_address } = connectionRows[0]; if (!auth_code || !message_crypt_key || !other_openmsg_address) { return { error: true, response_code: 'SM_E001', error_message: `Missing connection data for: ${receivingOpenmsgAddress} (rB6Xl)` }; } // Parse sender's domain from their address const addressParts = other_openmsg_address.split('*'); if (addressParts.length !== 2) { return { error: true, response_code: 'SM_E003', error_message: 'Invalid sending address format' }; } const sendingDomain = addressParts[1]; // Verify message hash to ensure message hasn't been tampered with // Hash combines: message package + auth code + salt + timestamp const expectedHash = (0, crypto_1.createMessageHash)(messagePackage, auth_code, messageSalt, messageTimestamp); if (messageHash !== expectedHash) { return { error: true, response_code: 'SM_E004', error_message: 'Authorization hash mismatch (4NxWV)' }; } // Check timestamp to prevent replay attacks // Messages expire after 60 seconds const now = Math.floor(Date.now() / 1000); const expiry = 60; // seconds if ((messageTimestamp + expiry) < now) { return { error: true, response_code: 'SM_E005', error_message: 'Hash is too old (kmqVE)' }; } // Extract nonce from the message package for confirmation // First 16 bytes of the decoded package contain the nonce const decodedPackage = Buffer.from(messagePackage, 'base64'); const messageNonce = decodedPackage.slice(0, 16).toString('base64'); // Confirm message authenticity with the sending server // This prevents spoofing attacks where someone claims a message came from another domain const confirmUrl = `https://${sendingDomain}/openmsg${settings_1.default.sandboxDir}/message/confirm`; const confirmPayload = { message_hash: messageHash, message_nonce: messageNonce }; let confirmResponse; try { confirmResponse = await axios_1.default.post(confirmUrl, confirmPayload, { headers: { 'Content-Type': 'application/json' }, timeout: 10000 // 10 second timeout for cross-domain requests }); } catch (err) { return { error: true, response_code: 'SM_E000', error_message: `Request failed: ${err.message} (vSiFb)` }; } // Verify the confirmation response const response = confirmResponse.data; if (confirmResponse.status !== 200 || response.error || response.success !== true) { return { error: true, response_code: 'SM_E000', error_message: response.error_message || `Error confirming message from ${sendingDomain}` }; } // Decrypt the message using the connection's encryption key const decryptedMessage = (0, crypto_1.decryptMessagePackage)(messagePackage, message_crypt_key); if (!decryptedMessage) { return { error: true, response_code: 'SM_E005', error_message: 'Invalid key or corrupt message (QctWn)' }; } // Store the decrypted message in the recipient's inbox await database_1.pool.execute('INSERT INTO openmsg_messages_inbox (self_openmsg_address, ident_code, message_hash, message_text) VALUES (?, ?, ?, ?)', [receivingOpenmsgAddress, identCode, messageHash, decryptedMessage]); // Return success response return { success: true, response_code: 'SM_S888' }; } catch (err) { console.error('messageCheck error:', err); return { error: true, response_code: 'SM_E000', error_message: 'Database error' }; } } /** * Confirm message authenticity * * This function verifies that a message with the given hash and nonce * was actually sent from this server. It's called by other OpenMsg servers * to prevent message spoofing attacks. * * Verification process: * 1. Look for the message in our outbox with matching hash and nonce * 2. If found, the message is confirmed as authentic * 3. Move the message from outbox to sent archive * * @param messageHash - SHA-256 hash of the message being confirmed * @param messageNonce - Nonce from the message encryption * @returns MessageConfirmResponse indicating if message is authentic */ async function messageConfirm(messageHash, messageNonce) { try { // Look for the message in our outbox // Only messages we actually sent will be in the outbox const [rows] = await database_1.pool.execute('SELECT * FROM openmsg_messages_outbox WHERE message_hash = ? AND message_nonce = ?', [messageHash, messageNonce]); if (rows.length === 0) { // Message not found in our outbox - not authentic return { error: true, error_message: `Message not found in outbox: ${messageHash}` }; } const message = rows[0]; // Move the message from outbox to sent archive // This confirms delivery and prevents duplicate confirmations await database_1.pool.execute('INSERT INTO openmsg_messages_sent (self_openmsg_address, ident_code, message_hash, message_text) VALUES (?, ?, ?, ?)', [message.self_openmsg_address, message.ident_code, message.message_hash, message.message_text]); // Remove from outbox await database_1.pool.execute('DELETE FROM openmsg_messages_outbox WHERE message_hash = ? AND message_nonce = ? LIMIT 1', [messageHash, messageNonce]); return { success: true }; } catch (error) { console.error('messageConfirm error:', error); return { error: true, error_message: 'Database error during confirmation' }; } } exports.default = router; //# sourceMappingURL=messages.js.map