Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ is given two files to download: a wireguard config with his private key (and add
to load into his VPN software, and a "fragment", which is a `[Peer]` section that he
must send to the server administrator.

AT NO POINT IS ANY EXTERNAL SERVER CONTACTED. KEYS NEVER LEAVE THE USER'S COMPUTER UNTIL
AT THIS POINT NO EXTERNAL SERVER IS CONTACTED. KEYS NEVER LEAVE THE USER'S COMPUTER UNTIL
THEY EXPLICITLY SEND THE FRAGMENT TO THE SEVER ADMIN.

Compared to other online key generators out there, this one does not require you to
Expand All @@ -39,6 +39,12 @@ trust me (the author) or the person running the webserver.

Alternatively, you could host the site yourself.

## OPNsense bridge

Additionally, there's an interface to OPNsense instances. These get contacted to get back a free IP address inside the given tunnel realm, get a wireguard server list and finally push the generated config to the OPNsense. AT THIS POINT, A SERVER IS CONTACTED, but only the one You gave inside the field "OPNsense URL". THERE IS NO OTHER SERVER CONTACTED BY THIS PROJECT.
To make this work, You have to put or link the file `opnsensebridge.py` into your `cgi-bin` directory (i.e. `/usr/lib/cgi-bin`) and make it executable. It must be accessible and executable from `/cgi-bin/opnsensebridge.py`. Also You might need the apache cgi module which You can enable with `a2enmod cgid`.
This funtion is in a beta state, errors might not get caught, things can happen...

## Single-file version

A single-file version with embedded scripts is available under the "single-file"
Expand All @@ -64,7 +70,7 @@ no special knowledge required..

The VPN admin fills in the form fields and clicks "save", which generates a URL
with the parameters saved inside the query string. Upon opening this URL the form will
be pre-filled.
be pre-filled. It's strongly advised not to save with filled out OPNsense credentials - they will appear openly in the resulting URL.

The admin sends the URL to users, which generate the keys and send the "Server
Fragment" back to the admin. An email address can optionally be specified,
Expand Down
190 changes: 144 additions & 46 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,77 +5,175 @@
<meta charset=utf-8>
<title>Wireguard Key Generator (Trustless)</title>
<meta name="author" content="Juan I Carrano">
<script src="wireguard.js"
integrity="sha256-KnkrBLei0lXuE9dGbMURxP000Jg7a9Tm8Gvvc6DysrU"
<script src="wireguard.js" integrity="sha256-KnkrBLei0lXuE9dGbMURxP000Jg7a9Tm8Gvvc6DysrU="
crossorigin="anonymous"></script>
<script src="wireguard.js"></script>
<script type="text/javascript" src="jquery.min.js" integrity="sha384-XjEETw4tssUw55ox7LlBMNjrGfPkEJ+nwUh8ucgoLks4+Xbd87UqG2+ehmAMZc6G"
crossorigin="anonymous"></script>
<script type="text/javascript" src="qrcode.min.js" integrity="sha384-3zSEDfvllQohrq0PHL1fOXJuC/jSOO34H46t6UQfobFOmxE5BpjjaIJY5F2/bMnU"
crossorigin="anonymous"></script>
<style>
body{margin:40px
auto;max-width:650px;line-height:1.6;font-size:18px;color:#444;padding:0
10px}h1,h2,h3{line-height:1.2}
h2 {font-size:large;}
div {padding: 1ex; margin-top: 1ex;}
div.adm {background-color: rgb(255, 231, 231);}
div.usr {text-align: center;}
#gen {background-color: rgb(248, 206, 69);}
#results {display: none; background-color: rgb(91, 241, 136);}
body {
margin: 40px auto;
max-width: 650px;
line-height: 1.6;
font-size: 18px;
color: #444;
padding: 10px
}

h1,
h2,
h3 {
line-height: 1.2
}

h2 {
font-size: large;
}

div {
padding: 1ex;
margin-top: 1ex;
}

div.adm {
background-color: rgb(255, 231, 231);
}

div.usr {
text-align: center;
}

div.usr a {
text-decoration: none;
}

#gen {
background-color: rgb(248, 206, 69);
}

.disabled {
filter: saturate(0);
opacity: 0.5;
}

#results {
display: none;
background-color: rgb(91, 241, 136);
}

pre {
text-align: left;
font-size: small;
background-color: rgb(230, 230, 230);
display: inline-block;
padding: 5px
}
</style>
</head>

<body>
<h1>Wireguard Key Generator (Trustless)</h1>
<div class="usr"><h2><em>Use this webpage on a private window!!!</em></h2></div>
<div class="usr">
<h2><em>Use this webpage on a private window!!!</em></h2>
</div>
<div class="adm">
<details>
<summary>Parameters</summary>
<form id="params" method="get">
<label for="sa">Server Address:</label><input type="text" id="sa" name="sa" value="example.com">
<label for="sp">Port:</label><input type="number" id="sp" , name="sp" min="1" max="65635" value="51820"><br>
<label for="sk">Server Pubkey:</label><input type="text" id="sk" , name="sk" value="RANDOM-EXAMPLE"><br>
<label for="ca">Client Address:</label><input type="text" id="ca" name="ca" value="10.0.0.2"><br>
<label for="aa">Allowed Subnet:</label><input type="text" id="aa" name="aa" value="10.0.0.0"><br>
<label for="ap">Allowed Prefix:</label><input type="number" id="ap" name="ap" min="0" max="32" value="24"><br>
<label for="ka">Keepalive:</label><input type="number" id="ka" name="ka" min="0" max="1000" value="25"><br>
<label for="cn">Client name (optional):</label><input type="text" id="cn" name="cn"><br>
<label for="ae">Admin email (optional):</label><input type="text" id="ae" name="ae"><br>
<input type="submit" value="Save">
</form>
</details>
<details open>
<summary>Parameters</summary>
<form id="params" method="get">
<h2>Wireguard</h2>
<span id="relevant_for_clientconfig">
<label for="sa">Server Address</label>:<input type="text" id="sa" name="sa" value="example.com">
<label for="sp">Port</label>:<input type="number" id="sp" name="sp" min="1" max="65635"
value="51820"><br>
<label for="sk">Server Pubkey</label>:<input type="text" id="sk" id="sk" name="sk"
value="RANDOM-EXAMPLE"><br>
<label for="pk">Preshared Key</label>:<input type="text" id="pk" name="pk" value="">
<button type="button" id="pskbtn" onclick="genPsk()">Generate</button>
<label for="pka">Autogenerate</label>:<input type="checkbox" id="pka" name="pka"
onchange="pskbtn.disabled = this.checked;pk.readOnly = this.checked;"><br>
<label for="ca">Client Address</label>:<input type="text" id="ca" name="ca" value="10.0.0.2"><br>
<label for="dn">DNS</label>:<input type="text" id="dn" name="dn" value=""><br>
<label for="mt">MTU</label>:<input type="number" id="mt" name="mt" min="0" max="65635"
value="1400"><br>
<label for="aa">Allowed Subnets</label>:<input type="text" id="aa" name="aa" value="10.0.0.0"><br>
<label for="ka">Keepalive</label>:<input type="number" id="ka" name="ka" min="0" max="1000"
value="25"><br>
<label for="cn">Client name</label>:<input type="text" id="cn" name="cn">
<button type="button" id="gencnbtn" onclick="generateCN()">Generate</button>
<label for="cna">Autogenerate</label>:<input type="checkbox" id="cna" name="cna"
onchange="gencnbtn.disabled = this.checked;cn.readOnly = this.checked;"><br>
</span>
<input type="hidden" id="puk">
<h2>OPNsense interface (optional)</h2>
<label for="os">OPNsense URL</label>:<input type="text" id="os" name="os"
onfocus="this.removeAttribute('style')"><br>
<label for="osk">OPNsense API key</label>:<input type="text" id="osk" name="osk"
onfocus="this.removeAttribute('style')"><br>
<label for="oss">OPNsense API secret</label>:<input type="password" id="oss" name="oss"
onfocus="this.removeAttribute('style')"><br>
<label for="tr">Tunnel Realm</label>:<input type="text" id="tr" name="tr" value="10.0.0.2/24"
onfocus="this.removeAttribute('style')">
<button type="button" id="getipbtn" onclick="getIP()">Get unused IP from OPNsense</button><br>
<label for="owg">OPNsense WG-Server UUID</label>:<input type="text" id="owg" name="owg"><br>
or choose from <label for="owgl">list</label>:<select id="owgl" name="owgl">
</select><button type="button" id="getserversbtn" onclick="getservers()">Get Servers from
OPNsense</button><br>
<input type="submit" value="Save to URL">
</form>
</details>
</div>
<div id="gen" class="usr">
<p>Note: for maximum security, close all other browser tabs and all programs before
generating the configuration files</p>
<button id="genbtn">Generate</button>
<button id="genbtn" onclick="genCfg()">Generate</button>
</div>
<div id="results" class="usr">
<p>You must keep the <em>client configuration</em> for yourself and send the
<em>server fragment</em> to the VPN server administrator</p>
<a id="client-dl">Download Client Configuration</a><br>
<a id="server-dl">Download Server Fragment</a>
<a id="server-em" target="_blank">Email Server Fragment to Admin</a>
<em>server fragment</em> to the VPN server administrator
</p>
<h3>Client Configuration</h3>
<a id="client-dl" title="Download">&#128190;</a>
<a title="Copy to clipboard"
href="javascript:copycl(document.getElementById('client-sh').textContent);">&#128203;</a><br>
<pre id="client-sh"></pre>
<div id="qrcode" style="background-color: white; width:250px; height:250px; display: block; margin-left: auto; margin-right: auto;"></div>
<h3>Server Fragment</h3>
<a id="server-dl" title="Download">&#128190;</a>
<a title="Copy to clipboard"
href="javascript:copycl(document.getElementById('server-sh').textContent);">&#128203;</a>
<a id="server-em" title="Send by Email">&#128231;</a>
<a href="" id="server-ps" title="Push to OPNsense">&#128228;</a><br>
<pre id="server-sh"></pre>
<p>Note: for maximum security, reboot your computer after you are done</p>
</div>
<div id="err" class="usr"></div>
<div>
<details>
<summary>Additional info</summary>
<p>The parameters are usually set by the server administrator and can be saved in URL's query string. Only the
client address and name need to be changed per client. The client name is optional and serves to uniquely
label the config fragments sent to the server.</p>
<p>Filling in "Admin Email" will enable a mailto link.</p>
<p>If this webpage is not used in private mode, the contents of the client config will be stored in the
browser's history as the download location of the file.</p>
<p>There are no guarantees that javascript crypto is safe from side-channel attacks and there is no secure
wipe function, that's why it is recommended to close all other tabs before and reboot the machine after.</p>
<p>NO WARRANTY EXPRESSED OR IMPLIED!!!!</p>
<summary>Additional info</summary>
<p>The parameters are usually set by the server administrator and can be saved in URL's query string. Only
the
client address and name need to be changed per client. The client name is optional and serves to
uniquely
label the config fragments sent to the server.</p>
<p>Filling in "Admin Email" will enable a mailto link.</p>
<p>If this webpage is not used in private mode, the contents of the client config will be stored in the
browser's history as the download location of the file.</p>
<p>There are no guarantees that javascript crypto is safe from side-channel attacks and there is no secure
wipe function, that's why it is recommended to close all other tabs before and reboot the machine after.
</p>
<p>NO WARRANTY EXPRESSED OR IMPLIED!!!!</p>
</details>
</div>
<div class="usr">
<p>This tool &copy; 2022 Juan I Carrano; Keygen code &copy; 2015-2020 Jason A. Donenfeld.<br>
This project is not associated with WireGuard&reg;. "WireGuard" and the "WireGuard" logo are registered trademarks of Jason A. Donenfeld.<br>
<a href="https://github.com/jcarrano/wg-keygen-notrust">GitHub Repository</a>
This project is not associated with WireGuard&reg;. "WireGuard" and the "WireGuard" logo are registered
trademarks of Jason A. Donenfeld.<br>
<a href="https://github.com/jcarrano/wg-keygen-notrust">GitHub Repository</a>
</p>
</div>
<script src="ui.js"
integrity="sha256-XwvzfKzWUKuFO60OcErCQoX1YKuCoXD5/6oTbZkhq8g"
<script src="ui.js" integrity="sha384-QzIwycpwZ49jColvRm2Gcfi5hPQZZ7sxWzCzw+72Ombc6IX06gUMab0mVqck2iE4"
crossorigin="anonymous"></script>
</body>

Expand Down
2 changes: 2 additions & 0 deletions jquery.min.js

Large diffs are not rendered by default.

134 changes: 134 additions & 0 deletions opnsensebridge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#!/usr/bin/env python3
import requests
import json
import ipaddress
import sys
import os
import urllib

def getservers(opnsenseURL, APIkey, APIsecret):
r = requests.get(f'{opnsenseURL}/api/wireguard/server/searchServer/',
auth=(APIkey, APIsecret), verify=True)
if r.status_code != 200:
print(r.text)
sys.exit()

list=[{'uuid':item['uuid'],'interface':item['interface'],'name':item['name']} for item in r.json()['rows']]
print('{"arr":'+json.dumps(list)+'}')

def getclients(opnsenseURL, APIkey, APIsecret):
r = requests.get(f'{opnsenseURL}/api/wireguard/client/searchClient/',
auth=(APIkey, APIsecret), verify=True)
if r.status_code != 200:
print(r.text)
sys.exit()
return r.json()['rows']

def getoverlappingnetworks(Realms, Realm):
overlapping = [ipaddress.ip_network(entry) for entry in sum([item.split(",") for item in Realms], []) if ipaddress.ip_network(entry).overlaps(Realm)]
return overlapping

def getip(opnsenseURL, APIkey, APIsecret, tunnelRealm):

try:
tunnelNetwork = ipaddress.ip_network(tunnelRealm)
except:
print('Tunnel Address "' + tunnelRealm + '" is not valid.')
sys.exit()
if not tunnelNetwork.num_addresses > 1:
print('Only 1 network address given as Tunnel Realm.')
sys.exit()

occupiedNetworks = getoverlappingnetworks([client['tunneladdress'] for client in getclients(opnsenseURL, APIkey, APIsecret)],tunnelNetwork)

occupiedHosts = []
for occupiedNetwork in occupiedNetworks:
occupiedHosts += set(occupiedNetwork.hosts())
usableHosts = sorted(set(tunnelNetwork.hosts()) -
set(occupiedHosts))
usableHost = next(item for item in usableHosts if item > list(tunnelNetwork.hosts())[0]+9)

print('{"ip":"'+usableHost.exploded+'/32"}')

def createclient(opnsenseURL, APIkey, APIsecret, PeerName, pubkey, pskey, tunnelAddress):
wireguardsClients = getclients(opnsenseURL, APIkey, APIsecret)

if PeerName in [item["name"] for item in wireguardsClients]:
print('Client name exists. This is OPNsense-wise valid, but not recommended. Make manual configuration in WebGUI if wanted.')
sys.exit()

try:
tunnelNetwork=ipaddress.ip_network(tunnelAddress)
except:
print('Tunnel Address "' + tunnelAddress + '" is not valid.')
sys.exit()

overlapping = getoverlappingnetworks([client['tunneladdress'] for client in wireguardsClients],tunnelNetwork)

if overlapping:
print('Client Adress overlaps with existing tunnels. This is OPNsense-wise valid, but not recommended. Make manual configuration in WebGUI if wanted.')
sys.exit()

createObject = {
"client": {
"enabled": '1',
"name": PeerName,
"pubkey": pubkey,
"psk": pskey,
"tunneladdress": tunnelAddress,
"keepalive ": '25'
}
}

r = requests.post(f'{opnsenseURL}/api/wireguard/client/addClient/', data=json.dumps(createObject),
headers={'content-type': 'application/json'}, auth=(APIkey, APIsecret), verify=True)
if r.status_code != 200:
print(r.text)
sys.exit()

if r.json()["uuid"]:
print('{"uuid":"'+r.json()["uuid"]+'"}')
else:
print(r.text)
sys.exit()

def enableclient(opnsenseURL, APIkey, APIsecret, ServerUUID, PeerUUID):

# get currently selected peers from server
r = requests.get(f'{opnsenseURL}/api/wireguard/server/getServer/{ServerUUID}',
auth=(APIkey, APIsecret), verify=True)
if r.status_code != 200:
print(r.text)
sys.exit()

ServerPeers = r.json()['server']['peers']
selectedPeers = [peer for peer in ServerPeers if ServerPeers[peer]['selected']]

peersToSelect = ','.join(selectedPeers + [PeerUUID])

wireguardInstanceInfo = {'server': {'peers': peersToSelect}}
r = requests.post(f'{opnsenseURL}/api/wireguard/server/setServer/{ServerUUID}', data=json.dumps(
wireguardInstanceInfo), headers={'content-type': 'application/json'}, auth=(APIkey, APIsecret), verify=True)
print(r.text)

def reconfigure(opnsenseURL, APIkey, APIsecret):
r = requests.post(f'{opnsenseURL}/api/wireguard/service/reconfigure', data='{}',
headers={'content-type': 'application/json'}, auth=(APIkey, APIsecret), verify=True)
print(r.text)


print("Content-type: text/html\n\n")

form=urllib.parse.parse_qs(os.environ['QUERY_STRING'])
match form['task'][0]:
case "getservers":
getservers(form['opnsenseURL'][0],form['key'][0],form['secret'][0])
case "getip":
getip(form['opnsenseURL'][0],form['key'][0],form['secret'][0],form['tunnelRealm'][0])
case "createclient":
uuid = createclient(form['opnsenseURL'][0],form['key'][0],form['secret'][0],form['PeerName'][0],form['pubkey'][0],form['pskey'][0],form['tunnelAddress'][0])
case "enableclient":
enableclient(form['opnsenseURL'][0],form['key'][0],form['secret'][0],form['ServerUUID'][0],form['PeerUUID'][0])
case "reconfigure":
reconfigure(form['opnsenseURL'][0],form['key'][0],form['secret'][0])

Loading