// Production-ready cryptographic functions for WireGuard key generation // Uses TweetNaCl.js for proper Curve25519 operations and Web Crypto API for HKDF class WireGuardCrypto { constructor() { this.crypto = window.crypto || window.msCrypto; // Check if TweetNaCl is available if (typeof nacl === 'undefined') { throw new Error('TweetNaCl library is required but not loaded'); } // Check if Web Crypto API is available this.hasWebCrypto = !!(this.crypto && this.crypto.subtle); if (!this.hasWebCrypto) { console.warn('Web Crypto API not available, falling back to less secure methods'); } // Key counter for deterministic key generation this.keyCounter = 0; } // Generate a cryptographically secure random seed generateSeed() { const array = new Uint8Array(32); this.crypto.getRandomValues(array); return array; } // HKDF implementation using Web Crypto API when available async hkdf(seed, salt, info, length = 32) { if (this.hasWebCrypto) { try { // Import the seed as key material const keyMaterial = await this.crypto.subtle.importKey( 'raw', seed, 'HKDF', false, ['deriveBits'] ); // Derive key using HKDF const derivedBits = await this.crypto.subtle.deriveBits( { name: 'HKDF', hash: 'SHA-256', salt: salt, info: info }, keyMaterial, length * 8 // length in bits ); return new Uint8Array(derivedBits); } catch (error) { console.warn('Web Crypto HKDF failed, falling back to HMAC-based implementation:', error); return this.hkdfFallback(seed, salt, info, length); } } else { return this.hkdfFallback(seed, salt, info, length); } } // Fallback HKDF implementation using TweetNaCl's hash function hkdfFallback(seed, salt, info, length = 32) { // HKDF-Extract const prk = this.hmacSha256(salt.length > 0 ? salt : new Uint8Array(32), seed); // HKDF-Expand const n = Math.ceil(length / 32); const okm = new Uint8Array(length); let t = new Uint8Array(0); for (let i = 1; i <= n; i++) { const concat = new Uint8Array(t.length + info.length + 1); concat.set(t); concat.set(info, t.length); concat[concat.length - 1] = i; t = this.hmacSha256(prk, concat); const copyLength = Math.min(32, length - (i - 1) * 32); okm.set(t.subarray(0, copyLength), (i - 1) * 32); } return okm; } // HMAC-SHA256 implementation using TweetNaCl hmacSha256(key, data) { const blockSize = 64; const hashSize = 32; // If key is longer than block size, hash it if (key.length > blockSize) { key = nacl.hash(key).subarray(0, hashSize); } // Pad key to block size const paddedKey = new Uint8Array(blockSize); paddedKey.set(key); // Create inner and outer padding const ipad = new Uint8Array(blockSize); const opad = new Uint8Array(blockSize); for (let i = 0; i < blockSize; i++) { ipad[i] = paddedKey[i] ^ 0x36; opad[i] = paddedKey[i] ^ 0x5c; } // Inner hash const innerData = new Uint8Array(blockSize + data.length); innerData.set(ipad); innerData.set(data, blockSize); const innerHash = nacl.hash(innerData); // Outer hash const outerData = new Uint8Array(blockSize + hashSize); outerData.set(opad); outerData.set(innerHash); return nacl.hash(outerData); } // Generate a WireGuard private key using proper key derivation async generatePrivateKey(seed) { const salt = new TextEncoder().encode('WireGuard v1 private key'); const info = new Uint8Array(4); // Convert key counter to bytes (little endian) const keyIndex = this.keyCounter++; info[0] = keyIndex & 0xff; info[1] = (keyIndex >> 8) & 0xff; info[2] = (keyIndex >> 16) & 0xff; info[3] = (keyIndex >> 24) & 0xff; const keyMaterial = await this.hkdf(seed, salt, info, 32); // Apply Curve25519 key clamping as per RFC 7748 keyMaterial[0] &= 248; // Clear bottom 3 bits keyMaterial[31] &= 127; // Clear top bit keyMaterial[31] |= 64; // Set second-highest bit return this.arrayToBase64(keyMaterial); } // Generate public key from private key using real Curve25519 scalar multiplication generatePublicKey(privateKeyBase64) { try { const privateKeyBytes = this.base64ToArray(privateKeyBase64); // Use TweetNaCl's scalar multiplication with base point const publicKeyBytes = nacl.scalarMult.base(privateKeyBytes); return this.arrayToBase64(publicKeyBytes); } catch (error) { throw new Error('Failed to generate public key: ' + error.message); } } // Generate preshared key using proper key derivation async generatePresharedKey(seed) { const salt = new TextEncoder().encode('WireGuard v1 preshared key'); const info = new Uint8Array(4); const keyIndex = this.keyCounter++; info[0] = keyIndex & 0xff; info[1] = (keyIndex >> 8) & 0xff; info[2] = (keyIndex >> 16) & 0xff; info[3] = (keyIndex >> 24) & 0xff; const keyMaterial = await this.hkdf(seed, salt, info, 32); return this.arrayToBase64(keyMaterial); } // Reset key counter for deterministic generation resetKeyCounter() { this.keyCounter = 0; } // Utility functions arrayToBase64(array) { return btoa(String.fromCharCode.apply(null, array)); } base64ToArray(base64) { const binary = atob(base64); const array = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { array[i] = binary.charCodeAt(i); } return array; } arrayToHex(array) { return Array.from(array).map(b => b.toString(16).padStart(2, '0')).join(''); } hexToArray(hex) { if (hex.length !== 64) { throw new Error('Hex string must be exactly 64 characters (32 bytes)'); } const array = new Uint8Array(32); for (let i = 0; i < 32; i++) { array[i] = parseInt(hex.substr(i * 2, 2), 16); } return array; } // Parse seed from input (hex string or generate new) parseSeed(customSeedHex) { if (customSeedHex && customSeedHex.trim()) { return this.hexToArray(customSeedHex.trim()); } else { return this.generateSeed(); } } // Validate that generated keys are valid validateKeyPair(privateKeyBase64, publicKeyBase64) { try { const privateKey = this.base64ToArray(privateKeyBase64); const publicKey = this.base64ToArray(publicKeyBase64); // Check lengths if (privateKey.length !== 32 || publicKey.length !== 32) { console.warn('Invalid key length'); return false; } // Check key clamping on private key if ((privateKey[0] & 7) !== 0 || (privateKey[31] & 128) !== 0 || (privateKey[31] & 64) === 0) { console.warn('Invalid key clamping'); return false; } // Verify public key matches private key const derivedPublic = nacl.scalarMult.base(privateKey); for (let i = 0; i < 32; i++) { if (derivedPublic[i] !== publicKey[i]) { console.warn('Public key does not match private key'); return false; } } return true; } catch (error) { console.error('Key validation error:', error); return false; } } // Get info about the crypto implementation getCryptoInfo() { return { library: 'TweetNaCl.js v1.0.3', hasWebCrypto: this.hasWebCrypto, curve: 'Curve25519 (X25519)', hkdf: this.hasWebCrypto ? 'Web Crypto API' : 'HMAC-SHA256 fallback', secure: true }; } } let currentConfigs = null; let currentSeed = null; let currentMeshConfigs = null; // Initialize crypto with error handling let crypto; try { crypto = new WireGuardCrypto(); console.log('Crypto initialized:', crypto.getCryptoInfo()); } catch (error) { console.error('Failed to initialize crypto:', error); alert('Cryptographic library failed to initialize. Please refresh the page.'); } async function generateConfigs() { try { const serverName = document.getElementById('serverName').value; const serverPort = parseInt(document.getElementById('serverPort').value); const serverNetwork = document.getElementById('serverNetwork').value; const serverEndpoint = document.getElementById('serverEndpoint').value; const clientCount = parseInt(document.getElementById('clientCount').value); const dns = document.getElementById('dns').value; // Validate inputs if (!validateInputs(serverNetwork, serverEndpoint, clientCount)) { return; } // Show loading state const generateBtn = document.querySelector('button[onclick="generateConfigs()"]'); const originalText = generateBtn.textContent; generateBtn.textContent = 'Generating...'; generateBtn.disabled = true; // Generate or use existing seed const customSeedHex = document.getElementById('customSeed').value; let seed; if (customSeedHex && customSeedHex.trim()) { // Use custom seed from input seed = crypto.parseSeed(customSeedHex); } else if (currentSeed) { // Use existing seed if available seed = currentSeed; } else { // Generate new seed only if none exists seed = crypto.generateSeed(); } currentSeed = seed; // Update seed display and input field const seedHex = crypto.arrayToHex(seed); document.getElementById('seedDisplay').textContent = seedHex; if (!customSeedHex || !customSeedHex.trim()) { document.getElementById('customSeed').value = seedHex; } // Reset key counter for deterministic generation crypto.resetKeyCounter(); // Generate server keys const serverPrivateKey = await crypto.generatePrivateKey(seed); const serverPublicKey = crypto.generatePublicKey(serverPrivateKey); // Parse network const [networkBase, cidr] = serverNetwork.split('/'); const networkParts = networkBase.split('.').map(n => parseInt(n)); // Generate client configurations const clients = []; for (let i = 1; i <= clientCount; i++) { const clientPrivateKey = await crypto.generatePrivateKey(seed); const clientPublicKey = crypto.generatePublicKey(clientPrivateKey); const presharedKey = await crypto.generatePresharedKey(seed); // Calculate client IP (server gets .1, clients get .2, .3, etc.) const clientIP = `${networkParts[0]}.${networkParts[1]}.${networkParts[2]}.${networkParts[3] + i}`; clients.push({ name: `client-${i}`, privateKey: clientPrivateKey, publicKey: clientPublicKey, presharedKey: presharedKey, ip: clientIP }); } // Generate configurations const serverConfig = generateServerConfig(serverName, serverPrivateKey, serverPort, `${networkParts[0]}.${networkParts[1]}.${networkParts[2]}.${networkParts[3] + 1}`, cidr, clients); const clientConfigs = clients.map(client => generateClientConfig(client, serverPublicKey, serverEndpoint, serverPort, `${networkParts[0]}.${networkParts[1]}.${networkParts[2]}.${networkParts[3] + 1}`, cidr, dns) ); // Store configurations currentConfigs = { server: serverConfig, clients: clientConfigs.map((config, index) => ({ name: clients[index].name, config: config })) }; // Display configurations displayConfigurations(); // Restore button generateBtn.textContent = originalText; generateBtn.disabled = false; } catch (error) { // Restore button on error const generateBtn = document.querySelector('button[onclick="generateConfigs()"]'); if (generateBtn) { generateBtn.textContent = 'Generate Configurations'; generateBtn.disabled = false; } alert('Error generating configurations: ' + error.message); } } function validateInputs(serverNetwork, serverEndpoint, clientCount) { // Validate network CIDR const cidrRegex = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}\/([1-2]?[0-9]|3[0-2])$/; if (!cidrRegex.test(serverNetwork)) { alert('Invalid network CIDR format'); return false; } // Validate endpoint if (!serverEndpoint.trim()) { alert('Server endpoint is required'); return false; } // Validate client count if (clientCount < 1 || clientCount > 50) { alert('Client count must be between 1 and 50'); return false; } return true; } function generateServerConfig(name, privateKey, port, serverIP, cidr, clients) { let config = `# ${name} Configuration [Interface] PrivateKey = ${privateKey} Address = ${serverIP}/${cidr} ListenPort = ${port} SaveConfig = true # PostUp and PostDown rules for NAT PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE `; clients.forEach(client => { config += `# ${client.name} [Peer] PublicKey = ${client.publicKey} PresharedKey = ${client.presharedKey} AllowedIPs = ${client.ip}/32 `; }); return config; } function generateClientConfig(client, serverPublicKey, serverEndpoint, serverPort, serverIP, cidr, dns) { let config = `# ${client.name} Configuration [Interface] PrivateKey = ${client.privateKey} Address = ${client.ip}/${cidr}`; // Only add DNS if it's not empty if (dns && dns.trim()) { config += ` DNS = ${dns.trim()}`; } config += ` [Peer] PublicKey = ${serverPublicKey} PresharedKey = ${client.presharedKey} AllowedIPs = 0.0.0.0/0, ::/0 Endpoint = ${serverEndpoint}:${serverPort} PersistentKeepalive = 25 `; return config; } function displayConfigurations() { // Show server config document.getElementById('serverConfig').textContent = currentConfigs.server; // Generate client configs in row layout const clientConfigs = document.getElementById('clientConfigs'); clientConfigs.innerHTML = ''; currentConfigs.clients.forEach((client, index) => { // Create client config item const configItem = document.createElement('div'); configItem.className = 'client-config-item'; configItem.innerHTML = `