Compare commits

...

11 commits

Author SHA1 Message Date
4f773c0d2f made the twig template look nicer 2019-08-03 21:42:01 +02:00
d386e0a520 removed commented out code and old cruft 2019-08-03 21:37:18 +02:00
9436218561 generating an embedded server config with a decrypted private key now also works 2019-08-03 20:56:59 +02:00
aa8812b837 embedded config support is now present for a locally saved private key or a remote private key, but decryption is currently a stub 2019-08-03 20:44:25 +02:00
a9c368e980 I've got file downloading working for the zip file, basically as before - now on to the meat and potatoes 2019-08-03 20:07:42 +02:00
bad2f8fa18 fixed some small formatting and logic mistakes 2019-08-03 15:29:39 +02:00
e7eb852c66 fixed all the little form popup logic and the loading of the key file, now to fix decrypting it and adding it to the embedded config 2019-08-03 15:24:37 +02:00
21c8c34d7d When the user specifies they want an embedded config they no longer get a zip file containing everything *and* an embedded config 2019-08-03 13:45:37 +02:00
e581cda081 fixed the check that sees if a ca.crt is present so it actually checks the right file 2019-08-03 13:19:53 +02:00
c5ff4af87c added a docker-compose file and an entrypoint shell script to run a test server for zer.ooo 2019-08-03 13:17:03 +02:00
cb7297eb88 added a twig template for a server config with an embedded certificate and associated code to make it work 2019-08-03 13:11:24 +02:00
6 changed files with 309 additions and 11 deletions

12
docker/docker-compose.yml Normal file
View file

@ -0,0 +1,12 @@
version: "3.6"
services:
zer.ooo:
image: d.xr.to/php
container_name: zer.ooo-test
volumes:
- ../:/sites/zer.ooo
working_dir: /sites/zer.ooo
ports:
- 127.0.0.1:80:80
command: ./docker/entrypoint.sh

10
docker/entrypoint.sh Executable file
View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
echo 'extension=iconv.so' >> /etc/php/php.ini
echo 'extension=sqlite3.so' >> /etc/php/php.ini
test -d vendor || ./bin/setup_web
test -d storage/ca || mkdir storage/ca
test -f storage/ca/ca.crt || ./bin/setup
php -S 0.0.0.0:80 -t public public/index.php

View file

@ -0,0 +1,169 @@
$(function () {
var keyLocation = $('#key-location');
var keyLocationContainer = keyLocation.parent();
var decryptKeyCheckbox = $('#decrypt-key');
var decryptKeyCheckboxContainer = decryptKeyCheckbox.parent();
var password = $('#password');
var passwordContainer = password.parent();
var embedConfiguration = $('#want-embedded');
var embedConfigurationContainer = embedConfiguration.parent();
var keyFileContent = null;
var buildConfigZip = $('#build-config-zip');
var getCertificateForm = $('#get-certificate-form');
decryptKeyCheckbox.change(function () {
if (this.checked) {
passwordContainer.show();
return;
}
passwordContainer.hide();
});
function handleKeyFile(element) {
keyFileContent = element.target.result;
}
// Retrieve zip file with post
getCertificateForm.submit(function (event) {
event.preventDefault();
});
function handleZipResult(data) {
var blobUrl = URL.createObjectURL(data);
saveBlobUrl(blobUrl, 'config.zip');
}
function handleEmbeddedResult(data) {
var fileReader = new FileReader();
fileReader.onload = function() {
var text = this.result;
if (keyFileContent === null) {
saveText(text, 'server-embedded.conf');
}
var match = /<key>/.exec(text);
matchOffset = match.index + 6;
text = text.substring(0, matchOffset) + keyFileContent + text.substring(matchOffset);
if (decryptKeyCheckbox.prop('checked') === true) {
var keyPassword = password.val();
text = decryptKey(text, keyPassword);
}
saveText(text, 'server-embedded.conf');
};
fileReader.readAsText(data);
}
function decryptKey(text, keyPassword) {
var match = /<key>/.exec(text);
var keyStartOffset = match.index + 6;
match = /<\/key>/.exec(text);
var keyEndOffset = match.index;
var keyContent = text.substring(keyStartOffset, keyEndOffset);
var decryptedPrivateKey = forge.pki.decryptRsaPrivateKey(keyContent.trim(), keyPassword);
var decryptedPem = forge.pki.privateKeyToPem(decryptedPrivateKey);
return text.substring(0, keyStartOffset) + decryptedPem + text.substring(keyEndOffset);
}
function saveText(text, fileName) {
var blob = new Blob([text], {type: 'text/plain'});
var blobUrl = URL.createObjectURL(blob);
saveBlobUrl(blobUrl, fileName);
}
var downloadElement = document.createElement("a");
document.body.appendChild(downloadElement);
downloadElement.style = "display: none";
function saveBlobUrl(url, fileName) {
downloadElement.href = url;
downloadElement.download = fileName;
downloadElement.click();
window.URL.revokeObjectURL(url);
}
function submitCertificateForm(event) {
var url = getCertificateForm.attr('action'),
method = getCertificateForm.attr('method'),
data = getCertificateForm.serialize(),
handler = handleZipResult;
if (embedConfiguration.prop('checked') === true) {
handler = handleEmbeddedResult;
}
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
//this.response is what you're looking for
handler(this.response);
}
};
xhr.open(method, url);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.responseType = 'blob';
xhr.send(data);
}
buildConfigZip.on('click', submitCertificateForm);
keyLocationContainer.on('show', function () {
decryptKeyCheckboxContainer.show();
});
keyLocationContainer.on('hide', function () {
decryptKeyCheckboxContainer.hide();
});
decryptKeyCheckboxContainer.on('show', function () {
if (decryptKeyCheckbox.prop('checked') === true) {
passwordContainer.show();
}
});
decryptKeyCheckboxContainer.on('hide', function () {
passwordContainer.hide();
passwordContainer.val('');
});
function setFileSelectVisibility() {
var selectedOption = $('#certificate option:selected');
if (selectedOption.data('hasPrivateKey') != '1') {
keyLocationContainer.show();
return;
}
keyLocationContainer.hide();
}
keyLocation.change(function () {
var file = this.files[0];
var reader = new FileReader();
reader.onload = handleKeyFile;
reader.readAsText(file);
});
embedConfiguration.change(function () {
if (this.checked) {
setFileSelectVisibility();
decryptKeyCheckboxContainer.show();
return;
}
decryptKeyCheckboxContainer.hide();
keyLocationContainer.hide();
});
});

View file

@ -16,20 +16,38 @@ class Action extends Session
$zipFile = tempnam(sys_get_temp_dir(), '0zip');
$zip = new \ZipArchive();
$zip->open($zipFile, \ZipArchive::CREATE);
$cert = $this->post('cert');
if ($cert === null) {
$this->getResponse()
->withStatus(500)
->write('Stop messing with the form');
}
$wantEmbedded = $this->post('want-embedded');
$server = ServerQuery::create()->findOneByFingerprint($this->post('fingerprint'));
$name = $server->getFqdn();
$this->fillZipWithCaAndConfig($zip, $server);
$cert = $this->post('cert');
if ($cert !== null) {
if ($wantEmbedded !== null) {
$certModel = CertificateQuery::create()->findOneByUserAndName($this->getUser(), $cert);
$this->addClientCertificateData($zip, $certModel);
$config = $this->getEmbeddedConfig($server, $certModel);
$name .= '-' . $certModel->getName() . '.' . $certModel->getSerial();
return $this->getResponse()
->withHeader('Content-Type', 'text/plain')
->withHeader('Content-Disposition', 'attachment; filename="' . $name . '-embedded.conf"')
->write($config);
}
$certModel = CertificateQuery::create()->findOneByUserAndName($this->getUser(), $cert);
$this->addClientCertificateData($zip, $certModel);
$name .= '-' . $certModel->getName() . '.' . $certModel->getSerial();
$this->fillZipWithCaAndConfig($zip, $server);
$zip->close();
$zipContents = file_get_contents($zipFile);
@ -52,6 +70,33 @@ class Action extends Session
$zip->addFromString('ca.crt', file_get_contents($this->getCore()->getBaseDir() . '/storage/ca/ca.crt'));
}
/**
* @param Server $server
* @param Certificate $cert
* @throws \Twig_Error_Loader
* @throws \Twig_Error_Runtime
* @throws \Twig_Error_Syntax
* @return string
*/
public function getEmbeddedConfig($server, $cert)
{
/** @var \Twig_Environment $twig */
$twig = $this->get('twig');
$parameters = [
'server' => $server,
'ca' => file_get_contents($this->getCore()->getBaseDir() . '/storage/ca/ca.crt'),
'cert' => $cert->getCertificate(),
'key' => '',
];
if ($cert->hasPrivateKey()) {
$parameters['key'] = $cert->getPrivateKey();
}
return $twig->render('etc/openvpn-client-embedded.conf.twig', $parameters);
}
/**
* @param Server $server
* @return string

View file

@ -0,0 +1,34 @@
client
dev zerooo
dev-type tun
proto {{ server.getProtocol() }}
remote {{ server.getExternalIp() }} {{ server.getPort() }}
resolv-retry infinite
nobind
user nobody
group nogroup
persist-key
persist-tun
remote-cert-tls server
cipher AES-256-CBC
comp-lzo
<ca>
{{ ca|trim }}
</ca>
<cert>
{{ cert|trim }}
</cert>
<key>
{{ key|trim }}
</key>

View file

@ -1,8 +1,19 @@
{% extends "panel.html.twig" %}
{% block head %}
{{ parent() }}
<script src="/js/jquery.min.js"></script>
<script src="/js/forge.min.js"></script>
<script src="/js/jszip.min.js"></script>
<script src="/js/FileSaver.min.js"></script>
<script src="/js/pages/configBuilder.js"></script>
{% endblock %}
{% block panel_contents %}
<h1 class="title">Config builder</h1>
<form target="_blank" action="/panel/config-builder" method="post" class="form">
<form target="_blank" action="/panel/config-builder" method="post" class="form" id="get-certificate-form">
<div class="row">
<label for="server">Server</label>
<select name="fingerprint" id="server">
@ -15,12 +26,29 @@
<label for="certificate">Certificate</label>
<select name="cert" id="certificate">
{% for certificate in user.getActiveCertificates() %}
<option value="{{ certificate.getName() }}">{{ certificate.getName() }}</option>
<option value="{{ certificate.getName() }}" data-has-private-key="{{ certificate.hasPrivateKey() ? '1' : '0' }}">{{ certificate.getName() }}</option>
{% endfor %}
</select>
</div>
<div class="buttons">
<button type="submit">Build</button>
<div class="row">
<label for="want-embedded">Generate embedded server config</label>
<input type="checkbox" id="want-embedded" name="want-embedded">
</div>
</form>
<div class="row" style="display: none">
<label for="key-location">Select key file</label>
<input type="file" id="key-location" name="key-location">
</div>
<div class="row" style="display: none">
<label for="decrypt-key">Decrypt embedded private key?</label>
<input type="checkbox" id="decrypt-key" name="decrypt-key">
</div>
<div class="row" style="display: none">
<label for="password">Password</label>
<input type="password" id="password" name="password">
</div>
<div class="buttons">
<button id="build-config-zip" type="submit">Build</button>
</div>
{% endblock %}