2020-12-05 00:21:18 +01:00
|
|
|
<!DOCTYPE html>
|
|
|
|
<html lang="en">
|
|
|
|
<head>
|
|
|
|
<meta charset="utf-8">
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
|
|
|
|
|
|
<!-- Bootstrap CSS -->
|
2020-12-11 22:05:27 +01:00
|
|
|
<link rel="stylesheet" href="css/styles.min.css">
|
2020-12-05 00:21:18 +01:00
|
|
|
<meta name="theme-color" content="#ffffff">
|
|
|
|
|
|
|
|
<title>{{ .Title }}</title>
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div class="container">
|
|
|
|
<h1>{{ .Title }}</h1>
|
|
|
|
<div class="row">
|
|
|
|
<div class="col-12">
|
|
|
|
<form id="csr-form">
|
2020-12-05 19:46:15 +01:00
|
|
|
{{ .csrfField }}
|
2020-12-05 00:21:18 +01:00
|
|
|
<div class="form-group">
|
|
|
|
<label for="nameInput">{{ .NameLabel }}</label>
|
|
|
|
<input type="text" class="form-control" id="nameInput" aria-describedby="nameHelp" required
|
|
|
|
minlength="3">
|
|
|
|
<small id="nameHelp" class="form-text text-muted">{{ .NameHelpText }}</small>
|
|
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
|
|
<label for="passwordInput">{{ .PasswordLabel }}</label>
|
|
|
|
<input type="password" class="form-control" id="passwordInput" aria-describedby="nameHelp" required
|
|
|
|
minlength="8">
|
|
|
|
</div>
|
|
|
|
<fieldset class="form-group">
|
|
|
|
<legend>{{ .RSAKeySizeLegend }}</legend>
|
|
|
|
<div class="form-check">
|
|
|
|
<input class="form-check-input" type="radio" name="keySize" id="size3072" value="3072" checked>
|
|
|
|
<label class="form-check-label" for="size3072">{{ .RSA3072Label }}</label>
|
|
|
|
</div>
|
|
|
|
<div class="form-check">
|
|
|
|
<input class="form-check-input" type="radio" name="keySize" id="size2048" value="2048">
|
|
|
|
<label class="form-check-label" for="size2048">{{ .RSA2048Label }}</label>
|
|
|
|
</div>
|
|
|
|
<div class="form-check">
|
|
|
|
<input class="form-check-input" type="radio" name="keySize" id="size4096" value="4096">
|
|
|
|
<label class="form-check-label" for="size4096">{{ .RSA4096Label }}</label>
|
|
|
|
</div>
|
|
|
|
<small id="keySizeHelp" class="form-text text-muted">{{ .RSAHelpText }}</small>
|
|
|
|
</fieldset>
|
2020-12-11 22:05:27 +01:00
|
|
|
<button type="submit" id="action-button" class="btn btn-primary">{{ .CSRButtonLabel }}</button>
|
2020-12-05 00:21:18 +01:00
|
|
|
</form>
|
|
|
|
</div>
|
|
|
|
</div>
|
2020-12-11 22:05:27 +01:00
|
|
|
<div class="row d-none" id="status-block">
|
|
|
|
<div class="col-12 py-3">
|
|
|
|
<div class="progress" style="height: 2rem">
|
|
|
|
<div id="progress-bar" class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0"
|
|
|
|
aria-valuemax="4">{{ .StatusLoading }}
|
|
|
|
</div>
|
2020-12-05 00:21:18 +01:00
|
|
|
</div>
|
|
|
|
</div>
|
2020-12-11 22:05:27 +01:00
|
|
|
<div class="col-12 d-none" id="download-wrapper">
|
|
|
|
<p class="text-info">{{ .DownloadDescription }}</p>
|
|
|
|
<a href="#" class="btn btn-success" id="download-link">
|
|
|
|
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-download" fill="currentColor"
|
|
|
|
xmlns="http://www.w3.org/2000/svg">
|
|
|
|
<path fill-rule="evenodd"
|
|
|
|
d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/>
|
|
|
|
<path fill-rule="evenodd"
|
|
|
|
d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/>
|
|
|
|
</svg>
|
|
|
|
{{ .DownloadLabel }}</a>
|
2020-12-05 00:21:18 +01:00
|
|
|
</div>
|
|
|
|
</div>
|
2020-12-11 22:05:27 +01:00
|
|
|
<pre id="key" class="d-none"></pre>
|
|
|
|
<pre id="csr" class="d-none"></pre>
|
|
|
|
<pre id="crt" class="d-none"></pre>
|
2020-12-05 00:21:18 +01:00
|
|
|
</div>
|
2020-12-11 22:05:27 +01:00
|
|
|
<script src="js/jquery.min.js"></script>
|
|
|
|
<script src="js/forge.all.min.js"></script>
|
|
|
|
<script src="js/bootstrap.bundle.min.js"></script>
|
|
|
|
<script src="js/i18next.min.js"></script>
|
2020-12-05 00:21:18 +01:00
|
|
|
<script>
|
2020-12-05 19:46:15 +01:00
|
|
|
async function postData(url = '', data = {}, csrfToken) {
|
2020-12-05 00:21:18 +01:00
|
|
|
const response = await fetch(url, {
|
|
|
|
method: 'POST',
|
|
|
|
mode: 'cors',
|
|
|
|
cache: 'no-cache',
|
|
|
|
credentials: 'same-origin',
|
|
|
|
headers: {
|
|
|
|
'Content-Type': 'application/json',
|
2020-12-05 19:46:15 +01:00
|
|
|
'X-CSRF-Token': csrfToken,
|
2020-12-05 00:21:18 +01:00
|
|
|
},
|
|
|
|
redirect: "error",
|
|
|
|
referrerPolicy: "no-referrer",
|
|
|
|
body: JSON.stringify(data),
|
|
|
|
});
|
|
|
|
return response.json()
|
|
|
|
}
|
|
|
|
|
|
|
|
document.addEventListener("DOMContentLoaded", function () {
|
2020-12-11 22:05:27 +01:00
|
|
|
i18n.init({fallbackLng: 'en', debug: true, useCookie: false}, (err) => {
|
2020-12-05 00:21:18 +01:00
|
|
|
if (err) return console.log('something went wrong loading', err);
|
|
|
|
});
|
|
|
|
|
|
|
|
const keyElement = document.getElementById('key');
|
|
|
|
document.getElementById('csr-form').onsubmit = function (event) {
|
|
|
|
const subject = event.target["nameInput"].value;
|
|
|
|
const password = event.target["passwordInput"].value;
|
2020-12-05 19:46:15 +01:00
|
|
|
const csrfToken = event.target["csrfToken"].value;
|
2020-12-05 00:21:18 +01:00
|
|
|
const keySize = parseInt(event.target["keySize"].value);
|
|
|
|
if (isNaN(keySize)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const statusBlock = document.getElementById('status-block');
|
2020-12-11 22:05:27 +01:00
|
|
|
const progressBar = document.getElementById('progress-bar');
|
2020-12-05 00:21:18 +01:00
|
|
|
statusBlock.classList.remove('d-none');
|
|
|
|
|
2020-12-11 22:05:27 +01:00
|
|
|
progressBar.classList.add('progress-bar-striped', 'progress-bar-animated');
|
|
|
|
progressBar.style.width = "25%";
|
|
|
|
progressBar.setAttribute("aria-valuenow", "1");
|
2020-12-05 00:21:18 +01:00
|
|
|
const state = forge.pki.rsa.createKeyPairGenerationState(keySize, 0x10001);
|
2020-12-11 22:05:27 +01:00
|
|
|
progressBar.innerHTML = i18n.t('keygen.started');
|
2020-12-05 00:21:18 +01:00
|
|
|
const startDate = new Date();
|
|
|
|
const step = function () {
|
|
|
|
let duration = (new Date()).getTime() - startDate.getTime();
|
|
|
|
let seconds = Math.floor(duration / 100) / 10;
|
|
|
|
if (!forge.pki.rsa.stepKeyPairGenerationState(state, 100)) {
|
|
|
|
setTimeout(step, 1);
|
2020-12-11 22:05:27 +01:00
|
|
|
progressBar.innerHTML = i18n.t('keygen.running', {seconds: seconds});
|
2020-12-05 00:21:18 +01:00
|
|
|
} else {
|
2020-12-11 22:05:27 +01:00
|
|
|
progressBar.classList.remove("progress-bar-animated", 'progress-bar-striped');
|
|
|
|
progressBar.style.width = "50%";
|
|
|
|
progressBar.setAttribute("aria-valuenow", "2");
|
|
|
|
progressBar.innerHTML = i18n.t('keygen.generated', {seconds: seconds});
|
2020-12-05 00:21:18 +01:00
|
|
|
const keys = state.keys;
|
|
|
|
keyElement.innerHTML = forge.pki.privateKeyToPem(keys.privateKey);
|
|
|
|
const csr = forge.pki.createCertificationRequest();
|
|
|
|
|
|
|
|
csr.publicKey = keys.publicKey;
|
|
|
|
csr.setSubject([{
|
|
|
|
name: 'commonName',
|
|
|
|
value: subject,
|
|
|
|
valueTagClass: forge.asn1.Type.UTF8,
|
|
|
|
}]);
|
|
|
|
csr.sign(keys.privateKey, forge.md.sha256.create());
|
|
|
|
|
|
|
|
const verified = csr.verify();
|
|
|
|
if (verified) {
|
|
|
|
let csrPem = forge.pki.certificationRequestToPem(csr);
|
|
|
|
document.getElementById("csr").innerHTML = csrPem;
|
2020-12-11 22:05:27 +01:00
|
|
|
progressBar.style.width = "75%";
|
|
|
|
progressBar.setAttribute("aria-valuenow", "3");
|
2020-12-12 09:59:06 +01:00
|
|
|
progressBar.classList.add('progress-bar-striped', 'progress-bar-animated');
|
|
|
|
progressBar.innerHTML = i18n.t('keygen.generated', {seconds: seconds}) + ', ' + i18n.t('certificate.waiting');
|
2020-12-14 06:59:30 +01:00
|
|
|
postData("/sign/", {"csr": csrPem, "common_name": subject}, csrfToken)
|
2020-12-11 22:05:27 +01:00
|
|
|
.then(data => {
|
2020-12-12 09:59:06 +01:00
|
|
|
const request_id = data["request_id"]
|
|
|
|
const webSocket = new WebSocket(
|
|
|
|
"wss://" + window.location.toString().substring(
|
|
|
|
"https://".length
|
|
|
|
).split("/")[0] + "/ws/")
|
|
|
|
webSocket.onopen = function () {
|
|
|
|
webSocket.send(JSON.stringify({"request_id": request_id}))
|
|
|
|
}
|
|
|
|
webSocket.onmessage = function (event) {
|
|
|
|
handleCertificateResponse(JSON.parse(event.data));
|
|
|
|
}
|
|
|
|
webSocket.onclose = function (event) {
|
|
|
|
if (event.wasClean) {
|
|
|
|
console.debug("websocket closed cleanly");
|
|
|
|
} else {
|
|
|
|
console.error("websocket connection died");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
webSocket.onerror = function (error) {
|
|
|
|
console.error(error.message);
|
2020-12-11 22:05:27 +01:00
|
|
|
}
|
2020-12-12 09:59:06 +01:00
|
|
|
});
|
2020-12-11 22:05:27 +01:00
|
|
|
|
2020-12-12 09:59:06 +01:00
|
|
|
function handleCertificateResponse(data) {
|
|
|
|
document.getElementById("crt").innerHTML = data["certificate"];
|
|
|
|
let certificates = []
|
|
|
|
certificates.push(forge.pki.certificateFromPem(data["certificate"]));
|
2020-12-11 22:05:27 +01:00
|
|
|
|
2020-12-12 09:59:06 +01:00
|
|
|
for (let certificatePemData of data["ca_chain"]) {
|
|
|
|
certificates.push(forge.pki.certificateFromPem(certificatePemData));
|
|
|
|
}
|
2020-12-05 00:21:18 +01:00
|
|
|
|
2020-12-12 09:59:06 +01:00
|
|
|
// browsers have trouble importing anything but 3des encrypted PKCS#12
|
|
|
|
const p12asn1 = forge.pkcs12.toPkcs12Asn1(
|
|
|
|
keys.privateKey, certificates, password,
|
|
|
|
{algorithm: '3des'}
|
|
|
|
);
|
|
|
|
const p12Der = forge.asn1.toDer(p12asn1).getBytes();
|
|
|
|
const p12B64 = forge.util.encode64(p12Der);
|
|
|
|
|
|
|
|
const downloadLink = document.getElementById('download-link');
|
|
|
|
downloadLink.download = 'client_certificate.p12';
|
|
|
|
downloadLink.setAttribute('href', 'data:application/x-pkcs12;base64,' + p12B64);
|
|
|
|
|
|
|
|
document.getElementById('download-wrapper').classList.remove("d-none");
|
|
|
|
progressBar.classList.remove("progress-bar-animated", 'progress-bar-striped');
|
|
|
|
progressBar.style.width = "100%";
|
|
|
|
progressBar.innerHTML = i18n.t('keygen.generated', {seconds: seconds}) + ', ' + i18n.t('certificate.received');
|
|
|
|
progressBar.setAttribute("aria-valuenow", "4");
|
|
|
|
}
|
2020-12-05 00:21:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setTimeout(step);
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
});
|
|
|
|
</script>
|
|
|
|
</body>
|
|
|
|
</html>
|