You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

211 lines
11 KiB
HTML

<!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 -->
<link rel="stylesheet" href="../public/css/styles.min.css">
<meta name="theme-color" content="#ffffff">
<title>CSR generation in your browser</title>
</head>
<body>
<div class="container">
<h1>CSR generation in your browser</h1>
<div class="row">
<div class="col-12">
<form id="csr-form">
<div class="form-group">
<label for="nameInput">Your name</label>
<input type="text" class="form-control" id="nameInput" aria-describedby="nameHelp" required
minlength="3">
<small id="nameHelp" class="form-text text-muted">Please input your name as it should be added to
your certificate</small>
</div>
<div class="form-group">
<label for="passwordInput">Password for your client certificate</label>
<input type="password" class="form-control" id="passwordInput" aria-describedby="nameHelp" required
minlength="8">
</div>
<fieldset class="form-group">
<legend>RSA Key Size</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">3072 Bit</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">2048 Bit (most compatible, least secure)</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">4096 Bit</label>
</div>
<small id="keySizeHelp" class="form-text text-muted">An RSA key pair will be generated in your
browser. Longer key sizes provide better security but take longer to generate.</small>
</fieldset>
<button type="submit" id="gen-csr-button" class="btn btn-primary">Generate signing request</button>
</form>
</div>
</div>
<div id="status-block" class="d-none row">
<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">Loading ...
</div>
</div>
</div>
<div class="col-12 d-none" id="download-wrapper">
<p class="text-info">Your key material is ready for download. The downloadable file contains your private
key and your certificate encrypted with your password. You can now use the file to install your
certificate in your browser or other applications.</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>
Download</a>
</div>
</div>
<pre id="key"></pre>
<pre id="csr"></pre>
<pre id="crt"></pre>
</div>
<script src="../public/js/forge.min.js"></script>
<script src="../public/js/bootstrap.bundle.min.js"></script>
<script>
async function postData(url = '', data = {}) {
const response = await fetch(url, {
method: 'POST',
mode: 'cors',
cache: 'no-cache',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
},
redirect: "error",
referrerPolicy: "no-referrer",
body: JSON.stringify(data),
});
return response.json()
}
document.addEventListener("DOMContentLoaded", function () {
const keyElement = document.getElementById('key');
document.getElementById('csr-form').onsubmit = function (event) {
const subject = event.target["nameInput"].value;
const password = event.target["passwordInput"].value;
const keySize = parseInt(event.target["keySize"].value);
if (isNaN(keySize)) {
return false;
}
const statusBlock = document.getElementById('status-block');
const progressBar = document.getElementById('progress-bar');
statusBlock.classList.remove('d-none');
progressBar.classList.add('progress-bar-striped', 'progress-bar-animated');
progressBar.style.width = "25%";
progressBar.setAttribute("aria-valuenow", "1");
const state = forge.pki.rsa.createKeyPairGenerationState(keySize, 0x10001);
progressBar.innerHTML = 'started key generation';
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);
progressBar.innerHTML = "key generation running";
} else {
progressBar.classList.remove("progress-bar-animated", 'progress-bar-striped');
progressBar.style.width = "50%";
progressBar.setAttribute("aria-valuenow", "2");
progressBar.innerHTML = "key generated";
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;
progressBar.style.width = "75%";
progressBar.setAttribute("aria-valuenow", "3");
progressBar.classList.add('progress-bar-striped', 'progress-bar-animated');
progressBar.innerHTML = "key generated, certificate waiting";
const sendButton =
document.getElementById("send-button");
sendButton.addEventListener("click", function () {
postData("/sign/", {"csr": csrPem, "commonName": subject})
.then(data => {
console.log(data);
document.getElementById("crt").innerHTML = data["certificate"];
const certificate = forge.pki.certificateFromPem(data["certificate"]);
// browsers have trouble importing anything but 3des encrypted PKCS#12
const p12asn1 = forge.pkcs12.toPkcs12Asn1(
keys.privateKey, certificate, password,
{algorithm: '3des'}
);
const p12Der = forge.asn1.toDer(p12asn1).getBytes();
const p12B64 = forge.util.encode64(p12Der);
const a = document.createElement('a');
a.download = 'client_certificate.p12';
a.setAttribute('href', 'data:application/x-pkcs12;base64,' + p12B64);
a.appendChild(document.createTextNode("Download"));
document.getElementById('result').appendChild(a);
});
});
sendButton.removeAttribute("disabled");
sendButton.classList.remove("disabled");
function handleCertificateResponse(data) {
document.getElementById("crt").innerHTML = data["certificate"];
let certificates = []
certificates.push(forge.pki.certificateFromPem(data["certificate"]));
for (let certificatePemData of data["ca_chain"]) {
certificates.push(forge.pki.certificateFromPem(certificatePemData));
}
// 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");
}
}
}
}
setTimeout(step);
return false;
};
});
</script>
</body>
</html>