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.

175 lines
8.2 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 browser</title>
</head>
<body>
<div class="container">
<h1>CSR generation in 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 (not recommended)</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">
<div class="d-flex align-items-center">
<strong id="status-text">Loading ...</strong>
<div class="spinner-border ml-auto" id="status-spinner" role="status" aria-hidden="true"></div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div id="result">
<button type="button" disabled id="send-button" class="btn btn-default disabled">Send signing request
</button>
</div>
</div>
</div>
<pre id="key"></pre>
<pre id="csr"></pre>
<pre id="crt"></pre>
</div>
<script src="../public/js/jquery.min.js"></script>
<script src="../public/js/forge.all.min.js"></script>
<script src="../public/js/bootstrap.bundle.min.js"></script>
<script src="../public/js/i18next.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 () {
i18n.init({fallbackLng: 'en', debug: true}, (err) => {
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;
const keySize = parseInt(event.target["keySize"].value);
if (isNaN(keySize)) {
return false;
}
const spinner = document.getElementById('status-spinner');
const statusText = document.getElementById('status-text');
const statusBlock = document.getElementById('status-block');
statusBlock.classList.remove('d-none');
spinner.classList.remove('d-none');
const state = forge.pki.rsa.createKeyPairGenerationState(keySize, 0x10001);
statusText.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);
statusText.innerHTML = `key generation running for ${seconds} seconds`;
} else {
statusText.innerHTML = `key generated in ${seconds} seconds`
spinner.classList.add('d-none');
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;
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");
}
}
}
setTimeout(step);
return false;
};
});
</script>
</body>
</html>