(() => { "use strict"; // ---------- base64 helpers ---------- function toBase64(bytes) { let bin = ""; for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]); return btoa(bin); } // ---------- WireGuard key generation (X25519 via TweetNaCl) ---------- function genKeyPair() { const kp = nacl.box.keyPair(); return { privateKey: toBase64(kp.secretKey), publicKey: toBase64(kp.publicKey), }; } function genPresharedKey() { return toBase64(nacl.randomBytes(32)); } // ---------- IP helpers (IPv4 only) ---------- function parseCidr(cidr) { const [ip, prefixStr] = cidr.trim().split("/"); const prefix = parseInt(prefixStr, 10); const parts = ip.split(".").map(Number); if (parts.length !== 4 || parts.some((p) => isNaN(p) || p < 0 || p > 255) || isNaN(prefix)) { throw new Error("Réseau CIDR invalide: " + cidr); } const base = ((parts[0] << 24) >>> 0) + (parts[1] << 16) + (parts[2] << 8) + parts[3]; return { base: base >>> 0, prefix }; } function intToIp(int) { return [ (int >>> 24) & 255, (int >>> 16) & 255, (int >>> 8) & 255, int & 255, ].join("."); } function nthAddress(cidr, n) { const { base, prefix } = parseCidr(cidr); const hostBits = 32 - prefix; const maxHosts = Math.pow(2, hostBits); if (n >= maxHosts) throw new Error("Le réseau " + cidr + " est trop petit pour ce nombre de pairs."); const network = (base >>> hostBits) << hostBits; return intToIp((network >>> 0) + n); } // ---------- DOM ---------- const clientsList = document.getElementById("clients-list"); const addClientBtn = document.getElementById("add-client"); const generateBtn = document.getElementById("generate-btn"); const resetBtn = document.getElementById("reset-btn"); const resultsEl = document.getElementById("results"); const serverSummary = document.getElementById("server-summary"); const serverConfText = document.getElementById("server-conf-text"); let clientCounter = 0; function addClientRow(name) { clientCounter++; const row = document.createElement("div"); row.className = "client-row"; row.innerHTML = ` `; row.querySelector(".remove-client").addEventListener("click", () => { row.remove(); }); clientsList.appendChild(row); } addClientBtn.addEventListener("click", () => addClientRow()); addClientRow("client1"); resetBtn.addEventListener("click", () => { clientsList.innerHTML = ""; clientCounter = 0; addClientRow("client1"); resultsEl.innerHTML = ""; serverSummary.style.display = "none"; }); document.addEventListener("click", (e) => { if (e.target.matches("[data-copy-target]")) { const targetId = e.target.getAttribute("data-copy-target"); const el = document.getElementById(targetId); const text = el.tagName === "TEXTAREA" || el.tagName === "INPUT" ? el.value : el.textContent; navigator.clipboard.writeText(text).then(() => { const original = e.target.textContent; e.target.textContent = "Copié !"; setTimeout(() => (e.target.textContent = original), 1200); }); } }); function buildClientConfig({ clientPrivateKey, clientIp, network, dns, serverPubKey, endpoint, port, allowedIps, psk }) { const prefixLen = network.split("/")[1]; let conf = `[Interface]\n`; conf += `PrivateKey = ${clientPrivateKey}\n`; conf += `Address = ${clientIp}/${prefixLen}\n`; if (dns) conf += `DNS = ${dns}\n`; conf += `\n[Peer]\n`; conf += `PublicKey = ${serverPubKey}\n`; if (psk) conf += `PresharedKey = ${psk}\n`; conf += `Endpoint = ${endpoint}:${port}\n`; conf += `AllowedIPs = ${allowedIps}\n`; conf += `PersistentKeepalive = 25\n`; return conf; } function buildServerPeerBlock({ name, clientPubKey, clientIp, psk }) { let block = `# ${name}\n[Peer]\n`; block += `PublicKey = ${clientPubKey}\n`; if (psk) block += `PresharedKey = ${psk}\n`; block += `AllowedIPs = ${clientIp}/32\n`; return block; } function renderQr(container, text) { try { const qr = qrcode(0, "M"); qr.addData(text); qr.make(); container.innerHTML = qr.createSvgTag(4, 8); } catch (err) { container.textContent = "QR indisponible (config trop longue)"; } } function downloadFile(filename, content) { const blob = new Blob([content], { type: "text/plain;charset=utf-8" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } generateBtn.addEventListener("click", () => { resultsEl.innerHTML = ""; serverSummary.style.display = "none"; try { const endpoint = document.getElementById("srv-endpoint").value.trim(); const port = document.getElementById("srv-port").value.trim() || "51820"; const network = document.getElementById("srv-network").value.trim(); const serverIp = document.getElementById("srv-server-ip").value.trim(); const dns = document.getElementById("srv-dns").value.trim(); const allowedIps = document.getElementById("srv-allowed-ips").value.trim() || "0.0.0.0/0, ::/0"; const usePsk = document.getElementById("use-psk").checked; let serverPubKeyInput = document.getElementById("srv-pubkey").value.trim(); if (!endpoint) { alert("Merci de renseigner l'endpoint du serveur."); return; } parseCidr(network); // validate const clientNames = Array.from(document.querySelectorAll(".client-name")) .map((i) => i.value.trim()) .filter((v) => v.length > 0); if (clientNames.length === 0) { alert("Ajoutez au moins un client."); return; } let serverKeyPair = null; let serverPubKey = serverPubKeyInput; if (!serverPubKey) { serverKeyPair = genKeyPair(); serverPubKey = serverKeyPair.publicKey; } const serverPeerBlocks = []; const resultCards = []; clientNames.forEach((name, idx) => { const clientIp = nthAddress(network, idx + 2); // .1 reserved for server const clientKeys = genKeyPair(); const psk = usePsk ? genPresharedKey() : null; const clientConf = buildClientConfig({ clientPrivateKey: clientKeys.privateKey, clientIp, network, dns, serverPubKey, endpoint, port, allowedIps, psk, }); serverPeerBlocks.push( buildServerPeerBlock({ name, clientPubKey: clientKeys.publicKey, clientIp, psk }) ); resultCards.push({ name, clientIp, clientConf }); }); // Server config summary let serverConf = `[Interface]\n`; serverConf += `Address = ${serverIp}/${network.split("/")[1]}\n`; serverConf += `ListenPort = ${port}\n`; if (serverKeyPair) { serverConf += `PrivateKey = ${serverKeyPair.privateKey}\n`; } else { serverConf += `PrivateKey = \n`; } serverConf += `\n` + serverPeerBlocks.join("\n"); serverConfText.textContent = serverConf; serverSummary.style.display = "block"; resultCards.forEach((card, i) => { const wrapper = document.createElement("div"); wrapper.className = "panel result-card"; const preId = `client-conf-${i}`; wrapper.innerHTML = `

${card.name} — ${card.clientIp}

${card.clientConf}
Scanner avec l'app WireGuard mobile
`; wrapper.querySelector(".download-btn").addEventListener("click", () => { downloadFile(`${card.name}.conf`, card.clientConf); }); renderQr(wrapper.querySelector(".qr-canvas"), card.clientConf); resultsEl.appendChild(wrapper); }); resultsEl.scrollIntoView({ behavior: "smooth", block: "start" }); } catch (err) { alert(err.message || String(err)); } }); })();