Move application code to external file
This commit moves the application code to a separate JavaScript file. Error handling for missing CA certificates has been added to avoid endless loops.pull/1/head
parent
90cca97ce3
commit
4bb13ff982
@ -0,0 +1,285 @@
|
|||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const csrForm = document.getElementById("csr-form");
|
||||||
|
const crtInput = document.getElementById("crt-input");
|
||||||
|
|
||||||
|
const app = {
|
||||||
|
keyPair: undefined,
|
||||||
|
certificate: undefined,
|
||||||
|
|
||||||
|
progressBar: document.getElementById("progress-bar"),
|
||||||
|
keyOutput: document.getElementById("key-output"),
|
||||||
|
csrOutput: document.getElementById("csr-output"),
|
||||||
|
prepareButton: document.getElementById("generate-p12"),
|
||||||
|
downloadButton: document.getElementById("download-link"),
|
||||||
|
|
||||||
|
updateProgress(percent, valuenow, title, ...tokens) {
|
||||||
|
this.progressBar.classList.add(...tokens)
|
||||||
|
this.progressBar.style.width = `${percent}%`;
|
||||||
|
this.progressBar.setAttribute("aria-valuenow", `${valuenow}`)
|
||||||
|
this.progressBar.innerHTML = title;
|
||||||
|
},
|
||||||
|
generateKeyPair(keySize) {
|
||||||
|
this.updateProgress(40, 2, "Started key generation", 'progress-bar-striped', 'progress-bar-animated');
|
||||||
|
|
||||||
|
const state = forge.pki.rsa.createKeyPairGenerationState(keySize, 0x10001);
|
||||||
|
const startDate = new Date();
|
||||||
|
|
||||||
|
return new Promise(done => {
|
||||||
|
const step = () => {
|
||||||
|
let duration = (new Date()).getTime() - startDate.getTime();
|
||||||
|
let seconds = Math.floor(duration / 100) / 10;
|
||||||
|
|
||||||
|
if (!forge.pki.rsa.stepKeyPairGenerationState(state, 100)) {
|
||||||
|
setTimeout(step, 1);
|
||||||
|
this.progressBar.innerHTML = `Key pair generation running for ${seconds} seconds`;
|
||||||
|
} else {
|
||||||
|
this.progressBar.classList.remove("progress-bar-animated", 'progress-bar-striped');
|
||||||
|
this.progressBar.innerHTML = "Key pair generated";
|
||||||
|
|
||||||
|
const keys = state.keys;
|
||||||
|
|
||||||
|
document.getElementById("key-wrapper").classList.remove("d-none");
|
||||||
|
|
||||||
|
this.keyOutput.innerHTML = forge.pki.privateKeyToPem(keys.privateKey);
|
||||||
|
|
||||||
|
this.keyPair = state.keys;
|
||||||
|
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
step();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
generateCSR(subjectName) {
|
||||||
|
const csr = forge.pki.createCertificationRequest();
|
||||||
|
|
||||||
|
csr.publicKey = this.keyPair.publicKey;
|
||||||
|
csr.setSubject([{
|
||||||
|
name: 'commonName',
|
||||||
|
value: subjectName,
|
||||||
|
valueTagClass: forge.asn1.Type.UTF8,
|
||||||
|
}]);
|
||||||
|
csr.sign(this.keyPair.privateKey, forge.md.sha256.create());
|
||||||
|
|
||||||
|
const verified = csr.verify();
|
||||||
|
if (verified) {
|
||||||
|
let csrPem = forge.pki.certificationRequestToPem(csr);
|
||||||
|
|
||||||
|
this.updateProgress(60, 3, "CSR generated");
|
||||||
|
|
||||||
|
document.getElementById("csr-output").innerHTML = csrPem;
|
||||||
|
|
||||||
|
const csrWrapper = document.getElementById("csr-wrapper");
|
||||||
|
csrWrapper.classList.remove("d-none");
|
||||||
|
csrWrapper.scrollIntoView();
|
||||||
|
|
||||||
|
document.getElementById("copy-csr-to-clipboard").addEventListener("click", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(csrPem)
|
||||||
|
|
||||||
|
const crtInputWrapper = document.getElementById("crt-input-wrapper");
|
||||||
|
crtInputWrapper.classList.remove("d-none");
|
||||||
|
crtInputWrapper.scrollIntoView();
|
||||||
|
|
||||||
|
this.updateProgress(80, 4, "CSR copied to clipboard, waiting for certificate");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleCertificate(certificate) {
|
||||||
|
this.certificate = certificate;
|
||||||
|
|
||||||
|
const prepareDownloadWrapper = document.getElementById("prepare-download-wrapper");
|
||||||
|
prepareDownloadWrapper.classList.remove("d-none");
|
||||||
|
prepareDownloadWrapper.scrollIntoView();
|
||||||
|
|
||||||
|
this.progressBar.innerHTML = "Certificate pasted";
|
||||||
|
|
||||||
|
this.prepareButton.addEventListener("click", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const passwordInput = document.getElementById("passwordInput");
|
||||||
|
let password = passwordInput.value;
|
||||||
|
|
||||||
|
if (password === "" || password.length < 8) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.buildKeyStore(password);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
buildKeyStore(password) {
|
||||||
|
this.updateProgress(100, 5, "Building keystore");
|
||||||
|
|
||||||
|
let certificates = [];
|
||||||
|
|
||||||
|
certificates.push(this.certificate);
|
||||||
|
|
||||||
|
let rootCerts = [];
|
||||||
|
// add CAcert class 3 certificate from http://www.cacert.org/certs/CAcert_Class3Root_x14E228.crt
|
||||||
|
rootCerts.push(forge.pki.certificateFromPem("-----BEGIN CERTIFICATE-----\n" +
|
||||||
|
"MIIGPTCCBCWgAwIBAgIDFOIoMA0GCSqGSIb3DQEBDQUAMHkxEDAOBgNVBAoTB1Jv\n" +
|
||||||
|
"b3QgQ0ExHjAcBgNVBAsTFWh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzEiMCAGA1UEAxMZ\n" +
|
||||||
|
"Q0EgQ2VydCBTaWduaW5nIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJARYSc3VwcG9y\n" +
|
||||||
|
"dEBjYWNlcnQub3JnMB4XDTIxMDQxOTEyMTgzMFoXDTMxMDQxNzEyMTgzMFowVDEU\n" +
|
||||||
|
"MBIGA1UEChMLQ0FjZXJ0IEluYy4xHjAcBgNVBAsTFWh0dHA6Ly93d3cuQ0FjZXJ0\n" +
|
||||||
|
"Lm9yZzEcMBoGA1UEAxMTQ0FjZXJ0IENsYXNzIDMgUm9vdDCCAiIwDQYJKoZIhvcN\n" +
|
||||||
|
"AQEBBQADggIPADCCAgoCggIBAKtJNRFIfNImflOUz0Op3SjXQiqL84d4GVh8D57a\n" +
|
||||||
|
"iX3h++tykA10oZZkq5+gJJlz2uJVdscXe/UErEa4w75/ZI0QbCTzYZzA8pD6Ueb1\n" +
|
||||||
|
"aQFjww9W4kpCz+JEjCUoqMV5CX1GuYrz6fM0KQhF5Byfy5QEHIGoFLOYZcRD7E6C\n" +
|
||||||
|
"jQnRvapbjZLQ7N6QxX8KwuPr5jFaXnQ+lzNZ6MMDPWAzv/fRb0fEze5ig1JuLgia\n" +
|
||||||
|
"pNkVGJGmhZJHsK5I6223IeyFGmhyNav/8BBdwPSUp2rVO5J+TJAFfpPBLIukjmJ0\n" +
|
||||||
|
"FXFuC3ED6q8VOJrU0gVyb4z5K+taciX5OUbjchs+BMNkJyIQKopPWKcDrb60LhPt\n" +
|
||||||
|
"XapI19V91Cp7XPpGBFDkzA5CW4zt2/LP/JaT4NsRNlRiNDiPDGCbO5dWOK3z0luL\n" +
|
||||||
|
"oFvqTpa4fNfVoIZwQNORKbeiPK31jLvPGpKK5DR7wNhsX+kKwsOnIJpa3yxdUly6\n" +
|
||||||
|
"R9Wb7yQocDggL9V/KcCyQQNokszgnMyXS0XvOhAKq3A6mJVwrTWx6oUrpByAITGp\n" +
|
||||||
|
"rmB6gCZIALgBwJNjVSKRPFbnr9s6JfOPMVTqJouBWfmh0VMRxXudA/Z0EeBtsSw/\n" +
|
||||||
|
"LIaRmXGapneLNGDRFLQsrJ2vjBDTn8Rq+G8T/HNZ92ZCdB6K4/jc0m+YnMtHmJVA\n" +
|
||||||
|
"BfvpAgMBAAGjgfIwge8wDwYDVR0TAQH/BAUwAwEB/zBhBggrBgEFBQcBAQRVMFMw\n" +
|
||||||
|
"IwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLkNBY2VydC5vcmcvMCwGCCsGAQUFBzAC\n" +
|
||||||
|
"hiBodHRwOi8vd3d3LkNBY2VydC5vcmcvY2xhc3MzLmNydDBFBgNVHSAEPjA8MDoG\n" +
|
||||||
|
"CysGAQQBgZBKAgMBMCswKQYIKwYBBQUHAgEWHWh0dHA6Ly93d3cuQ0FjZXJ0Lm9y\n" +
|
||||||
|
"Zy9jcHMucGhwMDIGA1UdHwQrMCkwJ6AloCOGIWh0dHBzOi8vd3d3LmNhY2VydC5v\n" +
|
||||||
|
"cmcvY2xhc3MzLmNybDANBgkqhkiG9w0BAQ0FAAOCAgEAxh6td1y0KJvRyI1EEsC9\n" +
|
||||||
|
"dnYEgyEH+BGCf2vBlULAOBG1JXCNiwzB1Wz9HBoDfIv4BjGlnd5BKdSLm4TXPcE3\n" +
|
||||||
|
"hnGjH1thKR5dd3278K25FRkTFOY1gP+mGbQ3hZRB6IjDX+CyBqS7+ECpHTms7eo/\n" +
|
||||||
|
"mARN+Yz5R3lzUvXs3zSX+z534NzRg4i6iHNHWqakFcQNcA0PnksTB37vGD75pQGq\n" +
|
||||||
|
"eSmx51L6UzrIpn+274mhsaFNL85jhX+lKuk71MGjzwoThbuZ15xmkITnZtRQs6Hh\n" +
|
||||||
|
"LSIqJWjDILIrxLqYHehK71xYwrRNhFb3TrsWaEJskrhveM0Os/vvoLNkh/L3iEQ5\n" +
|
||||||
|
"/LnmLMCYJNRALF7I7gsduAJNJrgKGMYvHkt1bo8uIXO8wgNV7qoU4JoaB1ML30QU\n" +
|
||||||
|
"qGcFr0TI06FFdgK2fwy5hulPxm6wuxW0v+iAtXYx/mRkwQpYbcVQtrIDvx1CT1k5\n" +
|
||||||
|
"0cQxi+jIKjkcFWHw3kBoDnCos0/ukegPT7aQnk2AbL4c7nCkuAcEKw1BAlSETkfq\n" +
|
||||||
|
"i5btdlhh58MhewZv1LcL5zQyg8w1puclT3wXQvy8VwPGn0J/mGD4gLLZ9rGcHDUE\n" +
|
||||||
|
"CokxFoWk+u5MCcVqmGbsyG4q5suS3CNslsHURfM8bQK4oLvHR8LCHEBMRcdFBn87\n" +
|
||||||
|
"cSvOK6eB1kdGKLA8ymXxZp8=\n" +
|
||||||
|
"-----END CERTIFICATE-----"));
|
||||||
|
|
||||||
|
// add CAcert root certificate from http://www.cacert.org/certs/root_X0F.crt
|
||||||
|
rootCerts.push(forge.pki.certificateFromPem("-----BEGIN CERTIFICATE-----\n" +
|
||||||
|
"MIIG7jCCBNagAwIBAgIBDzANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQKEwdSb290\n" +
|
||||||
|
"IENBMR4wHAYDVQQLExVodHRwOi8vd3d3LmNhY2VydC5vcmcxIjAgBgNVBAMTGUNB\n" +
|
||||||
|
"IENlcnQgU2lnbmluZyBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEWEnN1cHBvcnRA\n" +
|
||||||
|
"Y2FjZXJ0Lm9yZzAeFw0wMzAzMzAxMjI5NDlaFw0zMzAzMjkxMjI5NDlaMHkxEDAO\n" +
|
||||||
|
"BgNVBAoTB1Jvb3QgQ0ExHjAcBgNVBAsTFWh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzEi\n" +
|
||||||
|
"MCAGA1UEAxMZQ0EgQ2VydCBTaWduaW5nIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJ\n" +
|
||||||
|
"ARYSc3VwcG9ydEBjYWNlcnQub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC\n" +
|
||||||
|
"CgKCAgEAziLA4kZ97DYoB1CW8qAzQIxL8TtmPzHlawI229Z89vGIj053NgVBlfkJ\n" +
|
||||||
|
"8BLPRoZzYLdufujAWGSuzbCtRRcMY/pnCujW0r8+55jE8Ez64AO7NV1sId6eINm6\n" +
|
||||||
|
"zWYyN3L69wj1x81YyY7nDl7qPv4coRQKFWyGhFtkZip6qUtTefWIonvuLwphK42y\n" +
|
||||||
|
"fk1WpRPs6tqSnqxEQR5YYGUFZvjARL3LlPdCfgv3ZWiYUQXw8wWRBB0bF4LsyFe7\n" +
|
||||||
|
"w2t6iPGwcswlWyCR7BYCEo8y6RcYSNDHBS4CMEK4JZwFaz+qOqfrU0j36NK2B5jc\n" +
|
||||||
|
"G8Y0f3/JHIJ6BVgrCFvzOKKrF11myZjXnhCLotLddJr3cQxyYN/Nb5gznZY0dj4k\n" +
|
||||||
|
"epKwDpUeb+agRThHqtdB7Uq3EvbXG4OKDy7YCbZZ16oE/9KTfWgu3YtLq1i6L43q\n" +
|
||||||
|
"laegw1SJpfvbi1EinbLDvhG+LJGGi5Z4rSDTii8aP8bQUWWHIbEZAWV/RRyH9XzQ\n" +
|
||||||
|
"QUxPKZgh/TMfdQwEUfoZd9vUFBzugcMd9Zi3aQaRIt0AUMyBMawSB3s42mhb5ivU\n" +
|
||||||
|
"fslfrejrckzzAeVLIL+aplfKkQABi6F1ITe1Yw1nPkZPcCBnzsXWWdsC4PDSy826\n" +
|
||||||
|
"YreQQejdIOQpvGQpQsgi3Hia/0PsmBsJUUtaWsJx8cTLc6nloQsCAwEAAaOCAX8w\n" +
|
||||||
|
"ggF7MB0GA1UdDgQWBBQWtTIb1Mfz4OaO873SsDrusjkY0TAPBgNVHRMBAf8EBTAD\n" +
|
||||||
|
"AQH/MDQGCWCGSAGG+EIBCAQnFiVodHRwOi8vd3d3LmNhY2VydC5vcmcvaW5kZXgu\n" +
|
||||||
|
"cGhwP2lkPTEwMFYGCWCGSAGG+EIBDQRJFkdUbyBnZXQgeW91ciBvd24gY2VydGlm\n" +
|
||||||
|
"aWNhdGUgZm9yIEZSRUUgaGVhZCBvdmVyIHRvIGh0dHA6Ly93d3cuY2FjZXJ0Lm9y\n" +
|
||||||
|
"ZzAxBgNVHR8EKjAoMCagJKAihiBodHRwOi8vY3JsLmNhY2VydC5vcmcvcmV2b2tl\n" +
|
||||||
|
"LmNybDAzBglghkgBhvhCAQQEJhYkVVJJOmh0dHA6Ly9jcmwuY2FjZXJ0Lm9yZy9y\n" +
|
||||||
|
"ZXZva2UuY3JsMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcwAYYWaHR0cDovL29j\n" +
|
||||||
|
"c3AuY2FjZXJ0Lm9yZzAfBgNVHSMEGDAWgBQWtTIb1Mfz4OaO873SsDrusjkY0TAN\n" +
|
||||||
|
"BgkqhkiG9w0BAQsFAAOCAgEAR5zXs6IX01JTt7Rq3b+bNRUhbO9vGBMggczo7R0q\n" +
|
||||||
|
"Ih1kdhS6WzcrDoO6PkpuRg0L3qM7YQB6pw2V+ubzF7xl4C0HWltfzPTbzAHdJtja\n" +
|
||||||
|
"JQw7QaBlmAYpN2CLB6Jeg8q/1Xpgdw/+IP1GRwdg7xUpReUA482l4MH1kf0W0ad9\n" +
|
||||||
|
"4SuIfNWQHcdLApmno/SUh1bpZyeWrMnlhkGNDKMxCCQXQ360TwFHc8dfEAaq5ry6\n" +
|
||||||
|
"cZzm1oetrkSviE2qofxvv1VFiQ+9TX3/zkECCsUB/EjPM0lxFBmu9T5Ih+Eqns9i\n" +
|
||||||
|
"vmrEIQDv9tNyJHuLsDNqbUBal7OoiPZnXk9LH+qb+pLf1ofv5noy5vX2a5OKebHe\n" +
|
||||||
|
"+0Ex/A7e+G/HuOjVNqhZ9j5Nispfq9zNyOHGWD8ofj8DHwB50L1Xh5H+EbIoga/h\n" +
|
||||||
|
"JCQnRtxWkHP699T1JpLFYwapgplivF4TFv4fqp0nHTKC1x9gGrIgvuYJl1txIKmx\n" +
|
||||||
|
"XdfJzgscMzqpabhtHOMXOiwQBpWzyJkofF/w55e0LttZDBkEsilV/vW0CJsPs3eN\n" +
|
||||||
|
"aQF+iMWscGOkgLFlWsAS3HwyiYLNJo26aqyWPaIdc8E4ck7Sk08WrFrHIK3EHr4n\n" +
|
||||||
|
"1FZwmLpFAvucKqgl0hr+2jypyh5puA3KksHF3CsUzjMUvzxMhykh9zrMxQAHLBVr\n" +
|
||||||
|
"Gwc=\n" +
|
||||||
|
"-----END CERTIFICATE-----"));
|
||||||
|
|
||||||
|
let lastCert = certificates[certificates.length - 1];
|
||||||
|
let foundNext = true;
|
||||||
|
|
||||||
|
while (foundNext && !lastCert.isIssuer(lastCert)) {
|
||||||
|
foundNext = false;
|
||||||
|
|
||||||
|
for (let certIndex in rootCerts) {
|
||||||
|
let rootCert = rootCerts[certIndex];
|
||||||
|
|
||||||
|
if (lastCert.isIssuer(rootCert)) {
|
||||||
|
certificates.push(rootCert);
|
||||||
|
|
||||||
|
foundNext = true;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastCert = certificates[certificates.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundNext) {
|
||||||
|
console.warn(`chain construction may be incomplete, could not find certificate for issuer`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// browsers have trouble importing anything but 3des encrypted PKCS#12
|
||||||
|
const p12asn1 = forge.pkcs12.toPkcs12Asn1(
|
||||||
|
this.keyPair.privateKey, certificates, password,
|
||||||
|
{algorithm: '3des'}
|
||||||
|
);
|
||||||
|
const p12Der = forge.asn1.toDer(p12asn1).getBytes();
|
||||||
|
const p12B64 = forge.util.encode64(p12Der);
|
||||||
|
|
||||||
|
this.downloadButton.download = 'client_certificate.p12';
|
||||||
|
this.downloadButton.setAttribute('href', 'data:application/x-pkcs12;base64,' + p12B64);
|
||||||
|
|
||||||
|
const downloadWrapper = document.getElementById("download-wrapper");
|
||||||
|
downloadWrapper.classList.remove("d-none");
|
||||||
|
downloadWrapper.scrollIntoView();
|
||||||
|
|
||||||
|
this.progressBar.innerHTML = "Client certificate ready";
|
||||||
|
this.progressBar.classList.add("bg-success")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
csrForm.addEventListener("submit", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
let valid = csrForm.checkValidity();
|
||||||
|
|
||||||
|
if (!valid) {
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
csrForm.classList.add("was-validated");
|
||||||
|
if (!valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let subjectName = event.target["nameInput"].value;
|
||||||
|
let keySize = parseInt(event.target["keySize"].value);
|
||||||
|
|
||||||
|
if (isNaN(keySize)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
app.generateKeyPair(keySize).then(function () {
|
||||||
|
app.generateCSR(subjectName);
|
||||||
|
});
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
crtInput.addEventListener("paste", (event) => {
|
||||||
|
let crtData = (event.clipboardData || window.clipboardData).getData("text");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const certificate = forge.pki.certificateFromPem(crtData);
|
||||||
|
|
||||||
|
app.handleCertificate(certificate);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(e);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue