Add CA chain to download, improve UI
This commit is contained in:
		
							parent
							
								
									a960a60ecd
								
							
						
					
					
						commit
						b748050de3
					
				
					 6 changed files with 222 additions and 97 deletions
				
			
		|  | @ -5,8 +5,7 @@ | |||
|     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | ||||
| 
 | ||||
|     <!-- Bootstrap CSS --> | ||||
|     <link rel="stylesheet" href="css/styles.min.css" | ||||
|           integrity="sha384-vKuz4xd0kXa+x9wRdibDAVE8gXC/1up2T9QVSas8Rk07AZhzOzbwFdj00XUjOO4i" crossorigin="anonymous"> | ||||
|     <link rel="stylesheet" href="css/styles.min.css"> | ||||
|     <meta name="theme-color" content="#ffffff"> | ||||
| 
 | ||||
|     <title>{{ .Title }}</title> | ||||
|  | @ -45,40 +44,39 @@ | |||
|                     </div> | ||||
|                     <small id="keySizeHelp" class="form-text text-muted">{{ .RSAHelpText }}</small> | ||||
|                 </fieldset> | ||||
|                 <button type="submit" id="gen-csr-button" class="btn btn-primary">{{ .CSRButtonLabel }}</button> | ||||
|                 <button type="submit" id="action-button" class="btn btn-primary">{{ .CSRButtonLabel }}</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">{{ .StatusLoading }}</strong> | ||||
|                 <div class="spinner-border ml-auto" id="status-spinner" role="status" aria-hidden="true"></div> | ||||
|     <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> | ||||
|             </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"> | ||||
|                     {{ .SendCSRButtonLabel }} | ||||
|                 </button> | ||||
|             </div> | ||||
|         <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> | ||||
|         </div> | ||||
|     </div> | ||||
|     <pre id="key"></pre> | ||||
|     <pre id="csr"></pre> | ||||
|     <pre id="crt"></pre> | ||||
|     <pre id="key" class="d-none"></pre> | ||||
|     <pre id="csr" class="d-none"></pre> | ||||
|     <pre id="crt" class="d-none"></pre> | ||||
| </div> | ||||
| <script src="js/jquery.min.js" integrity="sha384-ZvpUoO/+PpLXR1lu4jmpXWu80pZlYUAfxl5NsBMWOEPSjUn/6Z/hRTt8+pR6L4N2" | ||||
|         crossorigin="anonymous"></script> | ||||
| <script src="js/forge.all.min.js" integrity="sha384-VfWVy4csHnuL0Tq/vQkZtIpDf4yhSLNf3aBffGj3wKUmyn1UPNx4v0Pzo9chiHu1" | ||||
|         crossorigin="anonymous"></script> | ||||
| <script src="js/bootstrap.bundle.min.js" | ||||
|         integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx" | ||||
|         crossorigin="anonymous"></script> | ||||
| <script src="js/i18next.min.js" integrity="sha384-Juj1kpjwKBUTV6Yp9WHG4GdeoMxCmx0zBN9SkwlyrAh5QYWb3l4WrfG7oTv/b00a" | ||||
|         crossorigin="anonymous"></script> | ||||
| <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> | ||||
| <script> | ||||
|     async function postData(url = '', data = {}, csrfToken) { | ||||
|         const response = await fetch(url, { | ||||
|  | @ -98,7 +96,7 @@ | |||
|     } | ||||
| 
 | ||||
|     document.addEventListener("DOMContentLoaded", function () { | ||||
|         i18n.init({fallbackLng: 'en', debug: true}, (err) => { | ||||
|         i18n.init({fallbackLng: 'en', debug: true, useCookie: false}, (err) => { | ||||
|             if (err) return console.log('something went wrong loading', err); | ||||
|         }); | ||||
| 
 | ||||
|  | @ -111,24 +109,27 @@ | |||
|             if (isNaN(keySize)) { | ||||
|                 return false; | ||||
|             } | ||||
|             const spinner = document.getElementById('status-spinner'); | ||||
|             const statusText = document.getElementById('status-text'); | ||||
|             const statusBlock = document.getElementById('status-block'); | ||||
|             const progressBar = document.getElementById('progress-bar'); | ||||
|             statusBlock.classList.remove('d-none'); | ||||
|             spinner.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); | ||||
|             statusText.innerHTML = i18n.t('keygen.started'); | ||||
|             progressBar.innerHTML = i18n.t('keygen.started'); | ||||
|             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 = i18n.t('keygen.running', {seconds: seconds}); // `key generation running for ${seconds} seconds`; | ||||
|                     progressBar.innerHTML = i18n.t('keygen.running', {seconds: seconds}); | ||||
|                 } else { | ||||
|                     statusText.innerHTML = i18n.t('keygen.generated', {seconds: seconds}); // `` | ||||
|                     spinner.classList.add('d-none'); | ||||
|                     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}); | ||||
|                     const keys = state.keys; | ||||
|                     keyElement.innerHTML = forge.pki.privateKeyToPem(keys.privateKey); | ||||
|                     const csr = forge.pki.createCertificationRequest(); | ||||
|  | @ -145,31 +146,34 @@ | |||
|                     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}, csrfToken) | ||||
|                                 .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); | ||||
|                         progressBar.style.width = "75%"; | ||||
|                         progressBar.setAttribute("aria-valuenow", "3"); | ||||
|                         postData("/sign/", {"csr": csrPem, "commonName": subject}, csrfToken) | ||||
|                             .then(data => { | ||||
|                                 document.getElementById("crt").innerHTML = data["certificate"]; | ||||
|                                 let certificates = [] | ||||
|                                 certificates.push(forge.pki.certificateFromPem(data["certificate"])); | ||||
| 
 | ||||
|                                     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"); | ||||
|                                 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.style.width = "100%"; | ||||
|                                 progressBar.setAttribute("aria-valuenow", "4"); | ||||
|                             }); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  |  | |||
		Reference in a new issue