// 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 = `
${client.name}
${client.config}
`; clientConfigs.appendChild(configItem); }); document.getElementById('configOutput').style.display = 'block'; } function downloadConfig(name, content) { const blob = new Blob([content], { type: 'text/plain' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${name}.conf`; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); } // Seed management functions function generateNewSeed() { const seed = crypto.generateSeed(); currentSeed = seed; const hexSeed = crypto.arrayToHex(seed); document.getElementById('seedDisplay').textContent = hexSeed; document.getElementById('customSeed').value = hexSeed; } function copySeed() { if (currentSeed) { const hexSeed = crypto.arrayToHex(currentSeed); navigator.clipboard.writeText(hexSeed).then(() => { alert('Seed copied to clipboard!'); }).catch(() => { // Fallback for older browsers const textArea = document.createElement('textarea'); textArea.value = hexSeed; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); alert('Seed copied to clipboard!'); }); } else { alert('No seed to copy. Generate configurations first.'); } } function pasteSeed() { navigator.clipboard.readText().then(text => { if (text && text.length === 64 && /^[0-9a-fA-F]+$/.test(text)) { document.getElementById('customSeed').value = text; document.getElementById('seedDisplay').textContent = text; // Update current seed try { currentSeed = crypto.hexToArray(text); alert('Seed pasted successfully!'); } catch (error) { alert('Invalid seed format.'); } } else { alert('Invalid seed in clipboard. Must be 64-character hex string.'); } }).catch(() => { alert('Cannot read from clipboard. Please paste manually into the Custom Seed field.'); }); } // Initialize form validation document.addEventListener('DOMContentLoaded', function() { const form = document.getElementById('configForm'); if (form) { form.addEventListener('submit', function(e) { e.preventDefault(); generateConfigs(); }); } const meshForm = document.getElementById('meshConfigForm'); if (meshForm) { meshForm.addEventListener('submit', function(e) { e.preventDefault(); generateMeshConfigs(); }); // Initialize with default peer fields for mesh updatePeerFields(); } // Validate custom seed input const customSeedInput = document.getElementById('customSeed'); if (customSeedInput) { customSeedInput.addEventListener('input', function(e) { const value = e.target.value.trim(); if (value && (value.length !== 64 || !/^[0-9a-fA-F]*$/.test(value))) { e.target.style.borderColor = '#dc3545'; } else { e.target.style.borderColor = '#ddd'; // Update current seed and display when valid seed is entered if (value && value.length === 64 && /^[0-9a-fA-F]+$/.test(value)) { try { currentSeed = crypto.hexToArray(value); document.getElementById('seedDisplay').textContent = value; } catch (error) { console.warn('Invalid hex seed:', error); } } } }); // Handle clearing the custom seed input customSeedInput.addEventListener('keydown', function(e) { if (e.key === 'Delete' || e.key === 'Backspace') { // If user is clearing the field, don't update currentSeed immediately // Let them finish editing first } }); } }); // === MESH NETWORK FUNCTIONS === function updatePeerFields() { const peerCount = parseInt(document.getElementById('peerCount').value); const peerInputs = document.getElementById('peerInputs'); if (peerCount < 2 || peerCount > 20) { alert('Number of peers must be between 2 and 20'); return; } peerInputs.innerHTML = ''; for (let i = 1; i <= peerCount; i++) { const peerGroup = document.createElement('div'); peerGroup.className = 'peer-input-group'; peerGroup.innerHTML = `

Peer ${i}

`; peerInputs.appendChild(peerGroup); } } async function generateMeshConfigs() { try { const networkCIDR = document.getElementById('networkCIDR').value; const dns = document.getElementById('dns').value; const peerCount = parseInt(document.getElementById('peerCount').value); // Validate inputs if (!validateMeshInputs(networkCIDR, peerCount)) { return; } // Show loading state const generateBtn = document.querySelector('button[onclick="generateMeshConfigs()"]'); 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(); // Parse network const [networkBase, cidr] = networkCIDR.split('/'); const networkParts = networkBase.split('.').map(n => parseInt(n)); // Collect peer information const peers = []; for (let i = 1; i <= peerCount; i++) { const name = document.getElementById(`peerName${i}`).value.trim(); const endpoint = document.getElementById(`peerEndpoint${i}`).value.trim(); const port = parseInt(document.getElementById(`peerPort${i}`).value); if (!name) { alert(`Peer ${i} name is required`); // Restore button generateBtn.textContent = originalText; generateBtn.disabled = false; return; } const privateKey = await crypto.generatePrivateKey(seed); const publicKey = crypto.generatePublicKey(privateKey); const ip = `${networkParts[0]}.${networkParts[1]}.${networkParts[2]}.${networkParts[3] + i}`; peers.push({ name: name, privateKey: privateKey, publicKey: publicKey, endpoint: endpoint, port: port, ip: ip }); } // Generate preshared keys for each pair of peers const presharedKeys = {}; for (let i = 0; i < peers.length; i++) { for (let j = i + 1; j < peers.length; j++) { const key = `${i}-${j}`; presharedKeys[key] = await crypto.generatePresharedKey(seed); } } // Generate configurations for each peer const meshConfigs = peers.map((peer, index) => ({ name: peer.name, config: generateMeshPeerConfig(peer, peers, index, cidr, dns, presharedKeys) })); // Store configurations currentMeshConfigs = meshConfigs; // Display configurations displayMeshConfigurations(); // Restore button generateBtn.textContent = originalText; generateBtn.disabled = false; } catch (error) { // Restore button on error const generateBtn = document.querySelector('button[onclick="generateMeshConfigs()"]'); if (generateBtn) { generateBtn.textContent = 'Generate Mesh Configurations'; generateBtn.disabled = false; } alert('Error generating mesh configurations: ' + error.message); } } function validateMeshInputs(networkCIDR, peerCount) { // 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(networkCIDR)) { alert('Invalid network CIDR format'); return false; } // Validate peer count if (peerCount < 2 || peerCount > 20) { alert('Number of peers must be between 2 and 20'); return false; } return true; } function generateMeshPeerConfig(currentPeer, allPeers, currentIndex, cidr, dns, presharedKeys) { let config = `# ${currentPeer.name} Configuration (Mesh Network) [Interface] PrivateKey = ${currentPeer.privateKey} Address = ${currentPeer.ip}/${cidr}`; // Only add DNS if it's not empty if (dns && dns.trim()) { config += ` DNS = ${dns.trim()}`; } config += ` ListenPort = ${currentPeer.port} `; // Add all other peers as peers allPeers.forEach((peer, peerIndex) => { if (peerIndex !== currentIndex) { // Get preshared key for this pair const minIndex = Math.min(currentIndex, peerIndex); const maxIndex = Math.max(currentIndex, peerIndex); const presharedKey = presharedKeys[`${minIndex}-${maxIndex}`]; config += `# ${peer.name} [Peer] PublicKey = ${peer.publicKey} PresharedKey = ${presharedKey} AllowedIPs = ${peer.ip}/32`; // Add endpoint if available if (peer.endpoint) { config += ` Endpoint = ${peer.endpoint}:${peer.port}`; } config += ` PersistentKeepalive = 25 `; } }); return config; } function displayMeshConfigurations() { const meshConfigs = document.getElementById('meshConfigs'); meshConfigs.innerHTML = ''; currentMeshConfigs.forEach((peerConfig, index) => { const configItem = document.createElement('div'); configItem.className = 'client-config-item'; configItem.innerHTML = `
${peerConfig.name}
${peerConfig.config}
`; meshConfigs.appendChild(configItem); }); document.getElementById('configOutput').style.display = 'block'; }