import * as crypto from 'crypto'; import type { SnmpConfig, SnmpV3SecurityParams } from './types.js'; import { SnmpEncoder } from './encoder.js'; /** * SNMP packet creation utilities * Creates SNMP request packets for different SNMP versions */ export class SnmpPacketCreator { /** * Create an SNMPv1 GET request * @param oid OID to query * @param community Community string * @param debug Whether to enable debug output * @returns Buffer containing the SNMP request */ public static createSnmpGetRequest(oid: string, community: string, debug: boolean = false): Buffer { const oidArray = SnmpEncoder.oidToArray(oid); const encodedOid = SnmpEncoder.encodeOID(oidArray); if (debug) { console.log('OID array length:', oidArray.length); console.log('OID array:', oidArray); } // SNMP message structure // Sequence // Version (Integer) // Community (String) // PDU (GetRequest) // Request ID (Integer) // Error Status (Integer) // Error Index (Integer) // Variable Bindings (Sequence) // Variable (Sequence) // OID (ObjectIdentifier) // Value (Null) // Use the standard method from our test that is known to work // Create a fixed request ID (0x00000001) to ensure deterministic behavior const requestId = Buffer.from([0x00, 0x00, 0x00, 0x01]); // Encode values const versionBuf = Buffer.concat([ Buffer.from([0x02, 0x01]), // ASN.1 Integer, length 1 Buffer.from([0x00]) // SNMP version 1 (0) ]); const communityBuf = Buffer.concat([ Buffer.from([0x04, community.length]), // ASN.1 Octet String, length Buffer.from(community) // Community string ]); const requestIdBuf = Buffer.concat([ Buffer.from([0x02, 0x04]), // ASN.1 Integer, length 4 requestId // Fixed Request ID ]); const errorStatusBuf = Buffer.concat([ Buffer.from([0x02, 0x01]), // ASN.1 Integer, length 1 Buffer.from([0x00]) // Error Status (0 = no error) ]); const errorIndexBuf = Buffer.concat([ Buffer.from([0x02, 0x01]), // ASN.1 Integer, length 1 Buffer.from([0x00]) // Error Index (0) ]); const oidValueBuf = Buffer.concat([ Buffer.from([0x30]), // ASN.1 Sequence Buffer.from([encodedOid.length + 2]), // Length Buffer.from([0x06]), // ASN.1 Object Identifier Buffer.from([encodedOid.length]), // Length encodedOid, // OID Buffer.from([0x05, 0x00]) // Null value ]); const varBindingsBuf = Buffer.concat([ Buffer.from([0x30]), // ASN.1 Sequence Buffer.from([oidValueBuf.length]), // Length oidValueBuf // Variable binding ]); const pduBuf = Buffer.concat([ Buffer.from([0xa0]), // ASN.1 Context-specific Constructed 0 (GetRequest) Buffer.from([requestIdBuf.length + errorStatusBuf.length + errorIndexBuf.length + varBindingsBuf.length]), // Length requestIdBuf, // Request ID errorStatusBuf, // Error Status errorIndexBuf, // Error Index varBindingsBuf // Variable Bindings ]); const messageBuf = Buffer.concat([ Buffer.from([0x30]), // ASN.1 Sequence Buffer.from([versionBuf.length + communityBuf.length + pduBuf.length]), // Length versionBuf, // Version communityBuf, // Community pduBuf // PDU ]); if (debug) { console.log('SNMP Request buffer:', messageBuf.toString('hex')); } return messageBuf; } /** * Create an SNMPv3 GET request * @param oid OID to query * @param config SNMP configuration * @param engineID Engine ID * @param engineBoots Engine boots counter * @param engineTime Engine time counter * @param requestID Request ID * @param debug Whether to enable debug output * @returns Buffer containing the SNMP request */ public static createSnmpV3GetRequest( oid: string, config: SnmpConfig, engineID: Buffer, engineBoots: number, engineTime: number, requestID: number, debug: boolean = false ): Buffer { if (debug) { console.log('Creating SNMPv3 GET request for OID:', oid); console.log('With config:', { ...config, authKey: config.authKey ? '***' : undefined, privKey: config.privKey ? '***' : undefined }); } const oidArray = SnmpEncoder.oidToArray(oid); const encodedOid = SnmpEncoder.encodeOID(oidArray); if (debug) { console.log('Using engine ID:', engineID.toString('hex')); console.log('Engine boots:', engineBoots); console.log('Engine time:', engineTime); console.log('Request ID:', requestID); } // Create security parameters const securityParams: SnmpV3SecurityParams = { msgAuthoritativeEngineID: engineID, msgAuthoritativeEngineBoots: engineBoots, msgAuthoritativeEngineTime: engineTime, msgUserName: config.username || '', msgAuthenticationParameters: Buffer.alloc(12, 0), // Will be filled in later for auth msgPrivacyParameters: Buffer.alloc(8, 0), // For privacy }; // Create the PDU (Protocol Data Unit) // This is wrapped within the security parameters const requestIdBuf = Buffer.concat([ Buffer.from([0x02, 0x04]), // ASN.1 Integer, length 4 SnmpEncoder.encodeInteger(requestID) // Request ID ]); const errorStatusBuf = Buffer.concat([ Buffer.from([0x02, 0x01]), // ASN.1 Integer, length 1 Buffer.from([0x00]) // Error Status (0 = no error) ]); const errorIndexBuf = Buffer.concat([ Buffer.from([0x02, 0x01]), // ASN.1 Integer, length 1 Buffer.from([0x00]) // Error Index (0) ]); const oidValueBuf = Buffer.concat([ Buffer.from([0x30]), // ASN.1 Sequence Buffer.from([encodedOid.length + 2]), // Length Buffer.from([0x06]), // ASN.1 Object Identifier Buffer.from([encodedOid.length]), // Length encodedOid, // OID Buffer.from([0x05, 0x00]) // Null value ]); const varBindingsBuf = Buffer.concat([ Buffer.from([0x30]), // ASN.1 Sequence Buffer.from([oidValueBuf.length]), // Length oidValueBuf // Variable binding ]); const pduBuf = Buffer.concat([ Buffer.from([0xa0]), // ASN.1 Context-specific Constructed 0 (GetRequest) Buffer.from([requestIdBuf.length + errorStatusBuf.length + errorIndexBuf.length + varBindingsBuf.length]), // Length requestIdBuf, // Request ID errorStatusBuf, // Error Status errorIndexBuf, // Error Index varBindingsBuf // Variable Bindings ]); // Create the security parameters const engineIdBuf = Buffer.concat([ Buffer.from([0x04, securityParams.msgAuthoritativeEngineID.length]), // ASN.1 Octet String securityParams.msgAuthoritativeEngineID ]); const engineBootsBuf = Buffer.concat([ Buffer.from([0x02, 0x04]), // ASN.1 Integer, length 4 SnmpEncoder.encodeInteger(securityParams.msgAuthoritativeEngineBoots) ]); const engineTimeBuf = Buffer.concat([ Buffer.from([0x02, 0x04]), // ASN.1 Integer, length 4 SnmpEncoder.encodeInteger(securityParams.msgAuthoritativeEngineTime) ]); const userNameBuf = Buffer.concat([ Buffer.from([0x04, securityParams.msgUserName.length]), // ASN.1 Octet String Buffer.from(securityParams.msgUserName) ]); const authParamsBuf = Buffer.concat([ Buffer.from([0x04, securityParams.msgAuthenticationParameters.length]), // ASN.1 Octet String securityParams.msgAuthenticationParameters ]); const privParamsBuf = Buffer.concat([ Buffer.from([0x04, securityParams.msgPrivacyParameters.length]), // ASN.1 Octet String securityParams.msgPrivacyParameters ]); // Security parameters sequence const securityParamsBuf = Buffer.concat([ Buffer.from([0x30]), // ASN.1 Sequence Buffer.from([engineIdBuf.length + engineBootsBuf.length + engineTimeBuf.length + userNameBuf.length + authParamsBuf.length + privParamsBuf.length]), // Length engineIdBuf, engineBootsBuf, engineTimeBuf, userNameBuf, authParamsBuf, privParamsBuf ]); // Determine security level flags let securityFlags = 0; if (config.securityLevel === 'authNoPriv' || config.securityLevel === 'authPriv') { securityFlags |= 0x01; // Authentication flag } if (config.securityLevel === 'authPriv') { securityFlags |= 0x02; // Privacy flag } // Set reportable flag - required for SNMPv3 securityFlags |= 0x04; // Reportable flag // Create SNMPv3 header const msgIdBuf = Buffer.concat([ Buffer.from([0x02, 0x04]), // ASN.1 Integer, length 4 SnmpEncoder.encodeInteger(requestID) // Message ID (same as request ID for simplicity) ]); const msgMaxSizeBuf = Buffer.concat([ Buffer.from([0x02, 0x04]), // ASN.1 Integer, length 4 SnmpEncoder.encodeInteger(65507) // Max message size ]); const msgFlagsBuf = Buffer.concat([ Buffer.from([0x04, 0x01]), // ASN.1 Octet String, length 1 Buffer.from([securityFlags]) ]); const msgSecModelBuf = Buffer.concat([ Buffer.from([0x02, 0x01]), // ASN.1 Integer, length 1 Buffer.from([0x03]) // Security model (3 = USM) ]); // SNMPv3 header const msgHeaderBuf = Buffer.concat([ Buffer.from([0x30]), // ASN.1 Sequence Buffer.from([msgIdBuf.length + msgMaxSizeBuf.length + msgFlagsBuf.length + msgSecModelBuf.length]), // Length msgIdBuf, msgMaxSizeBuf, msgFlagsBuf, msgSecModelBuf ]); // SNMPv3 security parameters const msgSecurityBuf = Buffer.concat([ Buffer.from([0x04]), // ASN.1 Octet String Buffer.from([securityParamsBuf.length]), // Length securityParamsBuf ]); // Create scopedPDU // In SNMPv3, the PDU is wrapped in a "scoped PDU" structure const contextEngineBuf = Buffer.concat([ Buffer.from([0x04, engineID.length]), // ASN.1 Octet String engineID ]); const contextNameBuf = Buffer.concat([ Buffer.from([0x04, 0x00]), // ASN.1 Octet String, length 0 (empty context name) ]); const scopedPduBuf = Buffer.concat([ Buffer.from([0x30]), // ASN.1 Sequence Buffer.from([contextEngineBuf.length + contextNameBuf.length + pduBuf.length]), // Length contextEngineBuf, contextNameBuf, pduBuf ]); // For authPriv, we need to encrypt the scopedPDU let encryptedPdu = scopedPduBuf; if (config.securityLevel === 'authPriv' && config.privKey) { // In a real implementation, encryption would be applied here // For this example, we'll just simulate it encryptedPdu = this.simulateEncryption(scopedPduBuf, config); } // Final scopedPDU (encrypted or not) const finalScopedPduBuf = Buffer.concat([ Buffer.from([0x04]), // ASN.1 Octet String Buffer.from([encryptedPdu.length]), // Length encryptedPdu ]); // Combine everything for the final message const versionBuf = Buffer.concat([ Buffer.from([0x02, 0x01]), // ASN.1 Integer, length 1 Buffer.from([0x03]) // SNMP version 3 (3) ]); const messageBuf = Buffer.concat([ Buffer.from([0x30]), // ASN.1 Sequence Buffer.from([versionBuf.length + msgHeaderBuf.length + msgSecurityBuf.length + finalScopedPduBuf.length]), // Length versionBuf, msgHeaderBuf, msgSecurityBuf, finalScopedPduBuf ]); // If using authentication, calculate and insert the authentication parameters if ((config.securityLevel === 'authNoPriv' || config.securityLevel === 'authPriv') && config.authKey && config.authProtocol) { const authenticatedMsg = this.addAuthentication(messageBuf, config, authParamsBuf); if (debug) { console.log('Created authenticated SNMPv3 message'); console.log('Final message length:', authenticatedMsg.length); } return authenticatedMsg; } if (debug) { console.log('Created SNMPv3 message without authentication'); console.log('Final message length:', messageBuf.length); } return messageBuf; } /** * Simulate encryption for authPriv security level * In a real implementation, this would use the specified privacy protocol (DES/AES) * @param data Data to encrypt * @param config SNMP configuration * @returns Encrypted data */ private static simulateEncryption(data: Buffer, config: SnmpConfig): Buffer { // This is a placeholder - in a real implementation, you would: // 1. Generate an initialization vector (IV) // 2. Use the privacy key derived from the privKey // 3. Apply the appropriate encryption algorithm (DES/AES) // For demonstration purposes only if (config.privProtocol === 'AES' && config.privKey) { try { // Create a deterministic IV for demo purposes (not secure for production) const iv = Buffer.alloc(16, 0); const engineID = Buffer.from([0x80, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06]); for (let i = 0; i < 8; i++) { iv[i] = engineID[i % engineID.length]; } // Create a key from the privKey (proper key localization should be used in production) const key = crypto.createHash('md5').update(config.privKey).digest(); // Create cipher and encrypt const cipher = crypto.createCipheriv('aes-128-cfb', key, iv); const encrypted = Buffer.concat([cipher.update(data), cipher.final()]); return encrypted; } catch (error) { console.warn('AES encryption failed, falling back to plaintext:', error); return data; } } else if (config.privProtocol === 'DES' && config.privKey) { try { // Create a deterministic IV for demo purposes (not secure for production) const iv = Buffer.alloc(8, 0); const engineID = Buffer.from([0x80, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06]); for (let i = 0; i < 8; i++) { iv[i] = engineID[i % engineID.length]; } // Create a key from the privKey (proper key localization should be used in production) const key = crypto.createHash('md5').update(config.privKey).digest().slice(0, 8); // Create cipher and encrypt const cipher = crypto.createCipheriv('des-cbc', key, iv); const encrypted = Buffer.concat([cipher.update(data), cipher.final()]); return encrypted; } catch (error) { console.warn('DES encryption failed, falling back to plaintext:', error); return data; } } return data; // Return unencrypted data as fallback } /** * Add authentication to SNMPv3 message * @param message Message to authenticate * @param config SNMP configuration * @param authParamsBuf Authentication parameters buffer * @returns Authenticated message */ private static addAuthentication(message: Buffer, config: SnmpConfig, authParamsBuf: Buffer): Buffer { // In a real implementation, this would: // 1. Zero out the authentication parameters field // 2. Calculate HMAC-MD5 or HMAC-SHA1 over the entire message // 3. Insert the HMAC into the authentication parameters field if (!config.authKey) { return message; } try { // Find position of auth parameters in the message // This is a more reliable way to find the exact position let authParamsPos = -1; for (let i = 0; i < message.length - 16; i++) { // Look for the auth params pattern: 0x04 0x0C 0x00 0x00... if (message[i] === 0x04 && message[i + 1] === 0x0C) { // Check if next 12 bytes are all zeros let allZeros = true; for (let j = 0; j < 12; j++) { if (message[i + 2 + j] !== 0) { allZeros = false; break; } } if (allZeros) { authParamsPos = i; break; } } } if (authParamsPos === -1) { return message; } // Create a copy of the message with zeroed auth parameters const msgCopy = Buffer.from(message); // Prepare the authentication key according to RFC3414 // We should use the standard key localization process const localizedKey = this.localizeAuthKey(config.authKey, Buffer.from([0x80, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06]), config.authProtocol); // Calculate HMAC let hmac; if (config.authProtocol === 'SHA') { hmac = crypto.createHmac('sha1', localizedKey).update(msgCopy).digest().slice(0, 12); } else { // Default to MD5 hmac = crypto.createHmac('md5', localizedKey).update(msgCopy).digest().slice(0, 12); } // Copy HMAC into original message hmac.copy(message, authParamsPos + 2); return message; } catch (error) { console.warn('Authentication failed:', error); return message; } } /** * Localize authentication key according to RFC3414 * @param key Authentication key * @param engineId Engine ID * @param authProtocol Authentication protocol * @returns Localized key */ private static localizeAuthKey(key: string, engineId: Buffer, authProtocol: string = 'MD5'): Buffer { try { // Convert password to key using hash let initialHash; if (authProtocol === 'SHA') { initialHash = crypto.createHash('sha1'); } else { initialHash = crypto.createHash('md5'); } // Generate the initial key - repeated hashing of password + padding const password = Buffer.from(key); let passwordIndex = 0; // Create a buffer of 1MB (1048576 bytes) filled with the password const buffer = Buffer.alloc(1048576); for (let i = 0; i < 1048576; i++) { buffer[i] = password[passwordIndex]; passwordIndex = (passwordIndex + 1) % password.length; } initialHash.update(buffer); let initialKey = initialHash.digest(); // Localize the key with engine ID let localHash; if (authProtocol === 'SHA') { localHash = crypto.createHash('sha1'); } else { localHash = crypto.createHash('md5'); } localHash.update(initialKey); localHash.update(engineId); localHash.update(initialKey); return localHash.digest(); } catch (error) { console.error('Error localizing auth key:', error); // Return a fallback key return Buffer.from(key); } } /** * Create a discovery message for SNMPv3 engine ID discovery * @param config SNMP configuration * @param requestID Request ID * @returns Discovery message */ public static createDiscoveryMessage(config: SnmpConfig, requestID: number): Buffer { // Basic SNMPv3 header for discovery const msgIdBuf = Buffer.concat([ Buffer.from([0x02, 0x04]), // ASN.1 Integer, length 4 SnmpEncoder.encodeInteger(requestID) ]); const msgMaxSizeBuf = Buffer.concat([ Buffer.from([0x02, 0x04]), // ASN.1 Integer, length 4 SnmpEncoder.encodeInteger(65507) // Max message size ]); const msgFlagsBuf = Buffer.concat([ Buffer.from([0x04, 0x01]), // ASN.1 Octet String, length 1 Buffer.from([0x00]) // No authentication or privacy ]); const msgSecModelBuf = Buffer.concat([ Buffer.from([0x02, 0x01]), // ASN.1 Integer, length 1 Buffer.from([0x03]) // Security model (3 = USM) ]); // SNMPv3 header const msgHeaderBuf = Buffer.concat([ Buffer.from([0x30]), // ASN.1 Sequence Buffer.from([msgIdBuf.length + msgMaxSizeBuf.length + msgFlagsBuf.length + msgSecModelBuf.length]), // Length msgIdBuf, msgMaxSizeBuf, msgFlagsBuf, msgSecModelBuf ]); // Simple security parameters for discovery const securityBuf = Buffer.concat([ Buffer.from([0x04, 0x00]), // Empty octet string ]); // Simple Get request for discovery const requestIdBuf = Buffer.concat([ Buffer.from([0x02, 0x04]), // ASN.1 Integer, length 4 SnmpEncoder.encodeInteger(requestID + 1) ]); const errorStatusBuf = Buffer.concat([ Buffer.from([0x02, 0x01]), // ASN.1 Integer, length 1 Buffer.from([0x00]) // Error Status (0 = no error) ]); const errorIndexBuf = Buffer.concat([ Buffer.from([0x02, 0x01]), // ASN.1 Integer, length 1 Buffer.from([0x00]) // Error Index (0) ]); // Empty varbinds for discovery const varBindingsBuf = Buffer.concat([ Buffer.from([0x30, 0x00]), // Empty sequence ]); const pduBuf = Buffer.concat([ Buffer.from([0xa0]), // GetRequest Buffer.from([requestIdBuf.length + errorStatusBuf.length + errorIndexBuf.length + varBindingsBuf.length]), requestIdBuf, errorStatusBuf, errorIndexBuf, varBindingsBuf ]); // Context data const contextEngineBuf = Buffer.concat([ Buffer.from([0x04, 0x00]), // Empty octet string ]); const contextNameBuf = Buffer.concat([ Buffer.from([0x04, 0x00]), // Empty octet string ]); const scopedPduBuf = Buffer.concat([ Buffer.from([0x30]), Buffer.from([contextEngineBuf.length + contextNameBuf.length + pduBuf.length]), contextEngineBuf, contextNameBuf, pduBuf ]); // Version const versionBuf = Buffer.concat([ Buffer.from([0x02, 0x01]), // ASN.1 Integer, length 1 Buffer.from([0x03]) // SNMP version 3 (3) ]); // Complete message return Buffer.concat([ Buffer.from([0x30]), Buffer.from([versionBuf.length + msgHeaderBuf.length + securityBuf.length + scopedPduBuf.length]), versionBuf, msgHeaderBuf, securityBuf, scopedPduBuf ]); } }