Some styling updates and created a separate front page.
This commit is contained in:
		
							parent
							
								
									430b589038
								
							
						
					
					
						commit
						62f3d28a71
					
				
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,2 +1,2 @@ | |||||||
| js/pkijs.org | app/js/pkijs.org | ||||||
| js/browser-csr | app/js/browser-csr | ||||||
|  | |||||||
							
								
								
									
										237
									
								
								app/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								app/index.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,237 @@ | |||||||
|  | <html> | ||||||
|  |   <head> | ||||||
|  |     <title>Greenlock™</title> | ||||||
|  |     <meta property="og:image" content="https://greenlock.ppl.family/img/greenlock-mark-400x400.png" /> | ||||||
|  |     <link href="style/main.css" rel="stylesheet"> | ||||||
|  |   </head> | ||||||
|  |   <body hidden> | ||||||
|  |     <div class="column-container wide"> | ||||||
|  |       <div class="column-row"> | ||||||
|  |         <img src="img/greenlock-146.png"> | ||||||
|  |       </div> | ||||||
|  |       <div class="column-row"> | ||||||
|  |           <h1>Get the green lock for your website</h1> | ||||||
|  | 
 | ||||||
|  |         <!-- Step 1 Choose Domain(s) --> | ||||||
|  |         <form class="js-acme-form js-acme-form-domains"> | ||||||
|  |           <h1><label>What's your domain?</label></h1> | ||||||
|  |           <h4>Certificates are valid for 90 days. Renewal is free :)</h4> | ||||||
|  |           <input class="js-acme-domains" type="text" placeholder="example.com,*.example.com" required> | ||||||
|  |           <br> | ||||||
|  |           <button type="submit">Next</button> | ||||||
|  | 
 | ||||||
|  |           <br> | ||||||
|  |           <br> | ||||||
|  |           <br> | ||||||
|  |           <label><input class="js-acme-api-type" name="acme-api-type" type="radio" value="v02" checked required> | ||||||
|  |             Production</label> | ||||||
|  |           <label><input class="js-acme-api-type" name="acme-api-type" type="radio" value="staging-v02" required> | ||||||
|  |             Testing</label> | ||||||
|  |           <br> | ||||||
|  |           <input class="js-acme-directory-url" type="url" placeholder="ACME directory url"> | ||||||
|  |         </form> | ||||||
|  | 
 | ||||||
|  |         <!-- Step 2 Create Account --> | ||||||
|  |         <form class="js-acme-form js-acme-form-account"> | ||||||
|  |           <h1><label>What's your email?</label></h1> | ||||||
|  |           <input class="js-acme-account-email" type="email" placeholder="john@doe.family" required> | ||||||
|  |           <br> | ||||||
|  |           <br> | ||||||
|  |           <label><input class="js-acme-account-tos" type="checkbox" required> | ||||||
|  |             Agree to <a class="js-acme-tos-url" target="acme-tos">Let's Encrypt™ Terms of Service</a>?</label> | ||||||
|  |           <br> | ||||||
|  |           <br> | ||||||
|  |           <label><input class="js-greenlock-account-tos" type="checkbox" required> | ||||||
|  |             Agree to <a class="js-gl-tos" target="_blank" href="./legal.html">Greenlock™ Terms of Service</a>?</label> | ||||||
|  |           <br> | ||||||
|  |           <br> | ||||||
|  |           <!-- | ||||||
|  |           <a href="#">advanced (use existing account)</a> | ||||||
|  |           <br> | ||||||
|  |           <br> | ||||||
|  |           --> | ||||||
|  |           <button type="submit">Next</button> | ||||||
|  |         </form> | ||||||
|  | 
 | ||||||
|  |         <!-- Step 3 Set Challanges --> | ||||||
|  |         <form class="js-acme-form js-acme-form-challenges"> | ||||||
|  | 
 | ||||||
|  |           <h1>How will you validate your domain?</h1> | ||||||
|  |           <br> | ||||||
|  |           <label><input class="js-acme-challenge-type" name="acme-challenge-type" type="radio" value="http-01" checked required> | ||||||
|  |             File Upload to HTTP Web Server</label> | ||||||
|  |           <br> | ||||||
|  |           <label><input class="js-acme-challenge-type" name="acme-challenge-type" type="radio" value="dns-01" required> | ||||||
|  |             TXT Records on DNS Name Server</label> | ||||||
|  |           <br> | ||||||
|  | 
 | ||||||
|  |           <div class="js-acme-challenges"> | ||||||
|  | 
 | ||||||
|  |           <h2>Verify Domains & Sub-Domains</h2> | ||||||
|  | 
 | ||||||
|  |           <table class="js-acme-table-http-01"> | ||||||
|  |             <thead> | ||||||
|  |               <tr> | ||||||
|  |                 <th>Hostname</th> | ||||||
|  |                 <th>File Location</th> | ||||||
|  |                 <th>File Contents</th> | ||||||
|  |               </tr> | ||||||
|  |             </thead> | ||||||
|  |             <tbody> | ||||||
|  |               <tr> | ||||||
|  |                 <td>example.com</td> | ||||||
|  |                 <td>.well-known/acme-challenge/xxx</td> | ||||||
|  |                 <td>sec.ret</td> | ||||||
|  |               </tr> | ||||||
|  |             </tbody> | ||||||
|  |           </table> | ||||||
|  | 
 | ||||||
|  |           <table class="js-acme-table-dns-01"> | ||||||
|  |             <thead> | ||||||
|  |               <tr> | ||||||
|  |                 <th>Hostname</th> | ||||||
|  |                 <th>TXT Host</th> | ||||||
|  |                 <th>TXT Value</th> | ||||||
|  |               </tr> | ||||||
|  |             </thead> | ||||||
|  |             <tbody> | ||||||
|  |               <tr> | ||||||
|  |                 <td>example.com</td> | ||||||
|  |                 <td>_acme-challenge.example.com</td> | ||||||
|  |                 <td>4A54</td> | ||||||
|  |               </tr> | ||||||
|  |             </tbody> | ||||||
|  |           </table> | ||||||
|  |           </div> | ||||||
|  | 
 | ||||||
|  |           <div class="js-acme-wildcard"> | ||||||
|  |             <h2>Verify Wildcard Domains</h2> | ||||||
|  | 
 | ||||||
|  |             <table class="js-acme-table-wildcard"> | ||||||
|  |               <thead> | ||||||
|  |                 <tr> | ||||||
|  |                   <th>Hostname</th> | ||||||
|  |                   <th>TXT Host</th> | ||||||
|  |                   <th>TXT Value</th> | ||||||
|  |                 </tr> | ||||||
|  |               </thead> | ||||||
|  |               <tbody> | ||||||
|  |                 <tr> | ||||||
|  |                   <td>example.com</td> | ||||||
|  |                   <td>_acme-challenge.example.com</td> | ||||||
|  |                   <td>4A54</td> | ||||||
|  |                 </tr> | ||||||
|  |               </tbody> | ||||||
|  |             </table> | ||||||
|  |           </div> | ||||||
|  | 
 | ||||||
|  |           <button type="submit">Next</button> | ||||||
|  |         </form> | ||||||
|  | 
 | ||||||
|  |         <!-- Step 4 Process Challanges --> | ||||||
|  |         <form class="js-acme-form js-acme-form-poll"> | ||||||
|  |           Verifying Domains... (give us 5 seconds or so...) | ||||||
|  | 
 | ||||||
|  |           <!-- | ||||||
|  |           <table class="js-acme-table-verifying"> | ||||||
|  |             <thead> | ||||||
|  |               <tr> | ||||||
|  |                 <th>Hostname</th> | ||||||
|  |                 <th>Type</th> | ||||||
|  |                 <th>Pass</th> | ||||||
|  |               </tr> | ||||||
|  |             </thead> | ||||||
|  |             <tbody> | ||||||
|  |               <tr> | ||||||
|  |                 <td>example.com</td> | ||||||
|  |                 <td>http-01</td> | ||||||
|  |                 <td>-</td> | ||||||
|  |               </tr> | ||||||
|  |             </tbody> | ||||||
|  |           </table> | ||||||
|  | 
 | ||||||
|  |           <a href="#">advanced (use existing keypair for domain)</a> | ||||||
|  | 
 | ||||||
|  |           <button type="submit">Next</button> | ||||||
|  |           --> | ||||||
|  |         </form> | ||||||
|  | 
 | ||||||
|  |         <!-- Step 5 Get Certs --> | ||||||
|  |         <form class="js-acme-form js-acme-form-download"> | ||||||
|  |           <div> | ||||||
|  |           <h2><label>privkey.pem</label></h2> | ||||||
|  |           <textarea cols="80" rows="10" class="js-privkey">-</textarea> | ||||||
|  |           </div> | ||||||
|  | 
 | ||||||
|  |           <div> | ||||||
|  |           <h2><label>fullchain.pem</label></h2> | ||||||
|  |           <textarea cols="80" rows="60" class="js-fullchain">-</textarea> | ||||||
|  |           </div> | ||||||
|  | 
 | ||||||
|  |           <div> | ||||||
|  |           <h3>node.js https server example</h3> | ||||||
|  |           <pre><code>'use strict'; | ||||||
|  | 
 | ||||||
|  |     var https = require('https'); | ||||||
|  |     var server = https.createServer({ | ||||||
|  |       key: require('fs').readFileSync('./privkey.pem') | ||||||
|  |     , cert: require('fs').readFileSync('./fullchain.pem') | ||||||
|  |     }, function (req, res) { | ||||||
|  |       res.end("Hello, World!"); | ||||||
|  |     }).listen(443, function () { | ||||||
|  |       console.log('Listening on', this.address()); | ||||||
|  |     }) | ||||||
|  |     </code></pre> | ||||||
|  |           </div> | ||||||
|  | 
 | ||||||
|  |           <!-- | ||||||
|  |             TODO | ||||||
|  |           <label>cert.pem</label> | ||||||
|  |           <textarea class="js-cert">-</textarea> | ||||||
|  | 
 | ||||||
|  |           <label>chain.pem</label> | ||||||
|  |           <textarea class="js-chain">-</textarea> | ||||||
|  | 
 | ||||||
|  |           <button type="button">Download SSL Certificates</button> | ||||||
|  |           <br> | ||||||
|  |           <a href="#">Advanced (copy and paste)</a> | ||||||
|  |           <br> | ||||||
|  |           <button type="submit">Start Over</button> | ||||||
|  |           --> | ||||||
|  |         </form> | ||||||
|  | 
 | ||||||
|  |           <br> | ||||||
|  |           <br> | ||||||
|  |           <br> | ||||||
|  |           <div><small> | ||||||
|  |           <h3></h3> | ||||||
|  |           <a href="https://git.coolaj86.com/coolaj86/greenlock.html">View Source</a> (git) | ||||||
|  |           <!-- or | ||||||
|  |           <pre><code>git clone https://git.coolaj86.com/coolaj86/greenlock.html.git</code></pre> | ||||||
|  |           Or view the live site code (same as live-site branch): | ||||||
|  |           <pre><code>wget https://greenlock.ppl.family --mirror --convert-links --adjust-extension --page-requisites --no-parent</code></pre> | ||||||
|  |           --> | ||||||
|  |           </small></div> | ||||||
|  | 
 | ||||||
|  |         <script src="./js/bacme.js"></script> | ||||||
|  |         <script src="./js/app.js"></script> | ||||||
|  | 
 | ||||||
|  |         <script src="./js/pkijs.org/v1.3.33/common.js"></script> | ||||||
|  |         <script src="./js/pkijs.org/v1.3.33/asn1.js"></script> | ||||||
|  |         <script src="./js/pkijs.org/v1.3.33/x509_schema.js"></script> | ||||||
|  |         <script src="./js/pkijs.org/v1.3.33/x509_simpl.js"></script> | ||||||
|  |         <script src="./js/browser-csr/v1.0.0-alpha/csr.js"></script> | ||||||
|  | 
 | ||||||
|  |         <!-- Global site tag (gtag.js) - Google Analytics --> | ||||||
|  |         <script async src="https://www.googletagmanager.com/gtag/js?id=UA-118745161-2"></script> | ||||||
|  |         <script> | ||||||
|  |           window.dataLayer = window.dataLayer || []; | ||||||
|  |           function gtag(){dataLayer.push(arguments);} | ||||||
|  |           gtag('js', new Date()); | ||||||
|  | 
 | ||||||
|  |           gtag('config', 'UA-118745161-2'); | ||||||
|  |         </script> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </body> | ||||||
|  | </html> | ||||||
							
								
								
									
										514
									
								
								app/js/app.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										514
									
								
								app/js/app.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,514 @@ | |||||||
|  | (function () { | ||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  |   var $qs = function (s) { return window.document.querySelector(s); }; | ||||||
|  |   var $qsa = function (s) { return window.document.querySelectorAll(s); }; | ||||||
|  |   var info = {}; | ||||||
|  |   var steps = {}; | ||||||
|  |   var nonce; | ||||||
|  |   var kid; | ||||||
|  |   var i = 1; | ||||||
|  | 
 | ||||||
|  |   var apiUrl = 'https://acme-{{env}}.api.letsencrypt.org/directory'; | ||||||
|  |   function updateApiType() { | ||||||
|  |     var input = this || Array.prototype.filter.call( | ||||||
|  |       $qsa('.js-acme-api-type'), function ($el) { return $el.checked; } | ||||||
|  |     )[0]; | ||||||
|  |     console.log('ACME api type radio:', input.value); | ||||||
|  |     $qs('.js-acme-directory-url').value = apiUrl.replace(/{{env}}/g, input.value); | ||||||
|  |   } | ||||||
|  |   $qsa('.js-acme-api-type').forEach(function ($el) { | ||||||
|  |     $el.addEventListener('change', updateApiType); | ||||||
|  |   }); | ||||||
|  |   updateApiType(); | ||||||
|  | 
 | ||||||
|  |   function hideForms() { | ||||||
|  |     $qsa('.js-acme-form').forEach(function (el) { | ||||||
|  |       el.hidden = true; | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function submitForm(ev) { | ||||||
|  |     var j = i; | ||||||
|  |     i += 1; | ||||||
|  |     steps[j].submit(ev); | ||||||
|  |   } | ||||||
|  |   $qsa('.js-acme-form').forEach(function ($el) { | ||||||
|  |     $el.addEventListener('submit', function (ev) { | ||||||
|  |       ev.preventDefault(); | ||||||
|  |       submitForm(ev); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  |   function updateChallengeType() { | ||||||
|  |     var input = this || Array.prototype.filter.call( | ||||||
|  |       $qsa('.js-acme-challenge-type'), function ($el) { return $el.checked; } | ||||||
|  |     )[0]; | ||||||
|  |     console.log('ch type radio:', input.value); | ||||||
|  |     $qs('.js-acme-table-wildcard').hidden = true; | ||||||
|  |     $qs('.js-acme-table-http-01').hidden = true; | ||||||
|  |     $qs('.js-acme-table-dns-01').hidden = true; | ||||||
|  |     if (info.challenges.wildcard) { | ||||||
|  |       $qs('.js-acme-table-wildcard').hidden = false; | ||||||
|  |     } | ||||||
|  |     if (info.challenges[input.value]) { | ||||||
|  |       $qs('.js-acme-table-' + input.value).hidden = false; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   $qsa('.js-acme-challenge-type').forEach(function ($el) { | ||||||
|  |     $el.addEventListener('change', updateChallengeType); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   function saveContact(email, domains) { | ||||||
|  |     // to be used for good, not evil
 | ||||||
|  |     return window.fetch('https://api.ppl.family/api/ppl.family/public/list', { | ||||||
|  |       method: 'POST' | ||||||
|  |     , cors: true | ||||||
|  |     , headers: new Headers({ 'Content-Type': 'application/json' }) | ||||||
|  |     , body: JSON.stringify({ address: email, comment: 'greenlock sub for ' + domains.join(',') }) | ||||||
|  |     }).then(function (resp) { | ||||||
|  |       return resp.json().then(function (data) { | ||||||
|  |         /* | ||||||
|  |         if (data.error) { | ||||||
|  |           window.alert("Couldn't save your contact. Email coolaj86@gmail.com instead."); | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  |         */ | ||||||
|  |       }); | ||||||
|  |     }, function () { | ||||||
|  |       /* | ||||||
|  |       window.alert("Didn't get your contact. Bad network connection? Email coolaj86@gmail.com instead."); | ||||||
|  |       */ | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   steps[1] = function () { | ||||||
|  |     hideForms(); | ||||||
|  |     $qs('.js-acme-form-domains').hidden = false; | ||||||
|  |   }; | ||||||
|  |   steps[1].submit = function () { | ||||||
|  |     info.identifiers = $qs('.js-acme-domains').value.split(/\s*,\s*/g).map(function (hostname) { | ||||||
|  |       return { type: 'dns', value: hostname.toLowerCase().trim() }; | ||||||
|  |     }); | ||||||
|  |     info.identifiers.sort(function (a, b) { | ||||||
|  |       if (a === b) { return 0; } | ||||||
|  |       if (a < b) { return 1; } | ||||||
|  |       if (a > b) { return -1; } | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     return BACME.directory({ directoryUrl: $qs('.js-acme-directory-url').value }).then(function (directory) { | ||||||
|  |       $qs('.js-acme-tos-url').href = directory.meta.termsOfService; | ||||||
|  |       return BACME.nonce().then(function (_nonce) { | ||||||
|  |         nonce = _nonce; | ||||||
|  | 
 | ||||||
|  |         console.log("MAGIC STEP NUMBER in 1 is:", i); | ||||||
|  |         steps[i](); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   steps[2] = function () { | ||||||
|  |     hideForms(); | ||||||
|  |     $qs('.js-acme-form-account').hidden = false; | ||||||
|  |   }; | ||||||
|  |   steps[2].submit = function () { | ||||||
|  |     var email = $qs('.js-acme-account-email').value.toLowerCase().trim(); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     info.contact = [ 'mailto:' + email ]; | ||||||
|  |     info.agree = $qs('.js-acme-account-tos').checked; | ||||||
|  |     info.greenlockAgree = $qs('.js-gl-tos').checked; | ||||||
|  |     // TODO
 | ||||||
|  |     // options for
 | ||||||
|  |     // * regenerate key
 | ||||||
|  |     // * ECDSA / RSA / bitlength
 | ||||||
|  | 
 | ||||||
|  |     // TODO ping with version and account creation
 | ||||||
|  |     setTimeout(saveContact, 100, email, info.identifiers.map(function (ident) { return ident.value; })); | ||||||
|  | 
 | ||||||
|  |     var jwk = JSON.parse(localStorage.getItem('account:' + email) || 'null'); | ||||||
|  |     var p; | ||||||
|  | 
 | ||||||
|  |     function createKeypair() { | ||||||
|  |       return BACME.accounts.generateKeypair({ | ||||||
|  |         type: 'ECDSA' | ||||||
|  |       , bitlength: '256' | ||||||
|  |       }).then(function (jwk) { | ||||||
|  |         localStorage.setItem('account:' + email, JSON.stringify(jwk)); | ||||||
|  |         return jwk; | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (jwk) { | ||||||
|  |       p = Promise.resolve(jwk); | ||||||
|  |     } else { | ||||||
|  |       p = createKeypair(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     function createAccount(jwk) { | ||||||
|  |       console.log('account jwk:'); | ||||||
|  |       console.log(jwk); | ||||||
|  |       delete jwk.key_ops; | ||||||
|  |       info.jwk = jwk; | ||||||
|  |       return BACME.accounts.sign({ | ||||||
|  |         jwk: jwk | ||||||
|  |       , contacts: [ 'mailto:' + email ] | ||||||
|  |       , agree: info.agree | ||||||
|  |       , nonce: nonce | ||||||
|  |       , kid: kid | ||||||
|  |       }).then(function (signedAccount) { | ||||||
|  |         return BACME.accounts.set({ | ||||||
|  |           signedAccount: signedAccount | ||||||
|  |         }).then(function (account) { | ||||||
|  |           console.log('account:'); | ||||||
|  |           console.log(account); | ||||||
|  |           kid = account.kid; | ||||||
|  |           return kid; | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return p.then(function (_jwk) { | ||||||
|  |       jwk = _jwk; | ||||||
|  |       kid = JSON.parse(localStorage.getItem('account-kid:' + email) || 'null'); | ||||||
|  |       var p2 | ||||||
|  | 
 | ||||||
|  |       // TODO save account id rather than always retrieving it
 | ||||||
|  |       if (kid) { | ||||||
|  |         p2 = Promise.resolve(kid); | ||||||
|  |       } else { | ||||||
|  |         p2 = createAccount(jwk); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return p2.then(function (_kid) { | ||||||
|  |         kid = _kid; | ||||||
|  |         info.kid = kid; | ||||||
|  |         return BACME.orders.sign({ | ||||||
|  |           jwk: jwk | ||||||
|  |         , identifiers: info.identifiers | ||||||
|  |         , kid: kid | ||||||
|  |         }).then(function (signedOrder) { | ||||||
|  |           return BACME.orders.create({ | ||||||
|  |             signedOrder: signedOrder | ||||||
|  |           }).then(function (order) { | ||||||
|  |             info.finalizeUrl = order.finalize; | ||||||
|  |             info.orderUrl = order.url; // from header Location ???
 | ||||||
|  |             return BACME.thumbprint({ jwk: jwk }).then(function (thumbprint) { | ||||||
|  |               return BACME.challenges.all().then(function (claims) { | ||||||
|  |                 console.log('claims:'); | ||||||
|  |                 console.log(claims); | ||||||
|  |                 var obj = { 'dns-01': [], 'http-01': [], 'wildcard': [] }; | ||||||
|  |                 var map = { | ||||||
|  |                   'http-01': '.js-acme-table-http-01' | ||||||
|  |                 , 'dns-01': '.js-acme-table-dns-01' | ||||||
|  |                 , 'wildcard': '.js-acme-table-wildcard' | ||||||
|  |                 } | ||||||
|  |                 var tpls = {}; | ||||||
|  |                 info.challenges = obj; | ||||||
|  |                 Object.keys(map).forEach(function (k) { | ||||||
|  |                   var sel = map[k] + ' tbody'; | ||||||
|  |                   console.log(sel); | ||||||
|  |                   tpls[k] = $qs(sel).innerHTML; | ||||||
|  |                   $qs(map[k] + ' tbody').innerHTML = ''; | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |                 // TODO make Promise-friendly
 | ||||||
|  |                 return Promise.all(claims.map(function (claim) { | ||||||
|  |                   var hostname = claim.identifier.value; | ||||||
|  |                   return Promise.all(claim.challenges.map(function (c) { | ||||||
|  |                     var keyAuth = BACME.challenges['http-01']({ | ||||||
|  |                       token: c.token | ||||||
|  |                     , thumbprint: thumbprint | ||||||
|  |                     , challengeDomain: hostname | ||||||
|  |                     }); | ||||||
|  |                     return BACME.challenges['dns-01']({ | ||||||
|  |                       keyAuth: keyAuth.value | ||||||
|  |                     , challengeDomain: hostname | ||||||
|  |                     }).then(function (dnsAuth) { | ||||||
|  |                       var data = { | ||||||
|  |                         type: c.type | ||||||
|  |                       , hostname: hostname | ||||||
|  |                       , url: c.url | ||||||
|  |                       , token: c.token | ||||||
|  |                       , keyAuthorization: keyAuth | ||||||
|  |                       , httpPath: keyAuth.path | ||||||
|  |                       , httpAuth: keyAuth.value | ||||||
|  |                       , dnsType: dnsAuth.type | ||||||
|  |                       , dnsHost: dnsAuth.host | ||||||
|  |                       , dnsAnswer: dnsAuth.answer | ||||||
|  |                       }; | ||||||
|  | 
 | ||||||
|  |                       console.log(''); | ||||||
|  |                       console.log('CHALLENGE'); | ||||||
|  |                       console.log(claim); | ||||||
|  |                       console.log(c); | ||||||
|  |                       console.log(data); | ||||||
|  |                       console.log(''); | ||||||
|  | 
 | ||||||
|  |                       if (claim.wildcard) { | ||||||
|  |                         obj.wildcard.push(data); | ||||||
|  |                         $qs(map.wildcard).innerHTML += '<tr><td>' + data.hostname + '</td><td>' + data.dnsHost + '</td><td>' + data.dnsAnswer + '</td></tr>'; | ||||||
|  |                       } else if(obj[data.type]) { | ||||||
|  | 
 | ||||||
|  |                         obj[data.type].push(data); | ||||||
|  |                         if ('dns-01' === data.type) { | ||||||
|  |                           $qs(map[data.type]).innerHTML += '<tr><td>' + data.hostname + '</td><td>' + data.dnsHost + '</td><td>' + data.dnsAnswer + '</td></tr>'; | ||||||
|  |                         } else if ('http-01' === data.type) { | ||||||
|  |                           $qs(map[data.type]).innerHTML += '<tr><td>' + data.hostname + '</td><td>' + data.httpPath + '</td><td>' + data.httpAuth + '</td></tr>'; | ||||||
|  |                         } | ||||||
|  |                       } | ||||||
|  | 
 | ||||||
|  |                     }); | ||||||
|  | 
 | ||||||
|  |                   })); | ||||||
|  |                 })).then(function () { | ||||||
|  | 
 | ||||||
|  |                   // hide wildcard if no wildcard
 | ||||||
|  |                   // hide http-01 and dns-01 if only wildcard
 | ||||||
|  |                   if (!obj.wildcard.length) { | ||||||
|  |                     $qs('.js-acme-wildcard').hidden = true; | ||||||
|  |                   } | ||||||
|  |                   if (!obj['http-01'].length) { | ||||||
|  |                     $qs('.js-acme-challenges').hidden = true; | ||||||
|  |                   } | ||||||
|  | 
 | ||||||
|  |                   updateChallengeType(); | ||||||
|  | 
 | ||||||
|  |                   console.log("MAGIC STEP NUMBER in 2 is:", i); | ||||||
|  |                   steps[i](); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |               }); | ||||||
|  |             }); | ||||||
|  |           }); | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  |     }).catch(function (err) { | ||||||
|  |       console.error('Step \'' + i + '\' Error:'); | ||||||
|  |       console.error(err); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   steps[3] = function () { | ||||||
|  |     hideForms(); | ||||||
|  |     $qs('.js-acme-form-challenges').hidden = false; | ||||||
|  |   }; | ||||||
|  |   steps[3].submit = function () { | ||||||
|  |     var chType; | ||||||
|  |     Array.prototype.some.call($qsa('.js-acme-challenge-type'), function ($el) { | ||||||
|  |       if ($el.checked) { | ||||||
|  |         chType = $el.value; | ||||||
|  |         return true; | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |     console.log('chType is:', chType); | ||||||
|  |     var chs = []; | ||||||
|  | 
 | ||||||
|  |     // do each wildcard, if any
 | ||||||
|  |     // do each challenge, by selected type only
 | ||||||
|  |     [ 'wildcard', chType].forEach(function (typ) { | ||||||
|  |       info.challenges[typ].forEach(function (ch) { | ||||||
|  |         // { jwk, challengeUrl, accountId (kid) }
 | ||||||
|  |         chs.push({ | ||||||
|  |           jwk: info.jwk | ||||||
|  |         , challengeUrl: ch.url | ||||||
|  |         , accountId: info.kid | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |     console.log("INFO.challenges !!!!!", info.challenges); | ||||||
|  | 
 | ||||||
|  |     var results = []; | ||||||
|  |     function nextChallenge() { | ||||||
|  |       var ch = chs.pop(); | ||||||
|  |       if (!ch) { return results; } | ||||||
|  |       return BACME.challenges.accept(ch).then(function (result) { | ||||||
|  |         results.push(result); | ||||||
|  |         return nextChallenge(); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // for now just show the next page immediately (its a spinner)
 | ||||||
|  |     steps[i](); | ||||||
|  |     return nextChallenge().then(function (results) { | ||||||
|  |       console.log('challenge status:', results); | ||||||
|  |       var polls = results.slice(0); | ||||||
|  |       var allsWell = true; | ||||||
|  | 
 | ||||||
|  |       function checkPolls() { | ||||||
|  |         return new Promise(function (resolve) { | ||||||
|  |           setTimeout(resolve, 1000); | ||||||
|  |         }).then(function () { | ||||||
|  |           return Promise.all(polls.map(function (poll) { | ||||||
|  |             return BACME.challenges.check({ challengePollUrl: poll.url }); | ||||||
|  |           })).then(function (polls) { | ||||||
|  |             console.log(polls); | ||||||
|  | 
 | ||||||
|  |             polls = polls.filter(function (poll) { | ||||||
|  |               //return 'valid' !== poll.status && 'invalid' !== poll.status;
 | ||||||
|  |               if ('pending' === poll.status) { | ||||||
|  |                 return true; | ||||||
|  |               } | ||||||
|  |               if ('valid' !== poll.status) { | ||||||
|  |                 allsWell = false; | ||||||
|  |                 console.warn('BAD POLL STATUS', poll); | ||||||
|  |               } | ||||||
|  |               // TODO show status in HTML
 | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             if (polls.length) { | ||||||
|  |               return checkPolls(); | ||||||
|  |             } | ||||||
|  |             return true; | ||||||
|  |           }); | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return checkPolls().then(function () { | ||||||
|  |         if (allsWell) { | ||||||
|  |           return submitForm(); | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   // spinner
 | ||||||
|  |   steps[4] = function () { | ||||||
|  |     hideForms(); | ||||||
|  |     $qs('.js-acme-form-poll').hidden = false; | ||||||
|  |   } | ||||||
|  |   steps[4].submit = function () { | ||||||
|  |     console.log('Congrats! Auto advancing...'); | ||||||
|  | 
 | ||||||
|  |     var key = info.identifiers.map(function (ident) { return ident.value; }).join(','); | ||||||
|  |     var serverJwk = JSON.parse(localStorage.getItem('server:' + key) || 'null'); | ||||||
|  |     var p; | ||||||
|  | 
 | ||||||
|  |     function createKeypair() { | ||||||
|  |       return BACME.accounts.generateKeypair({ | ||||||
|  |         type: 'ECDSA' | ||||||
|  |       , bitlength: '256' | ||||||
|  |       }).then(function (serverJwk) { | ||||||
|  |         localStorage.setItem('server:' + key, JSON.stringify(serverJwk)); | ||||||
|  |         return serverJwk; | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (serverJwk) { | ||||||
|  |       p = Promise.resolve(serverJwk); | ||||||
|  |     } else { | ||||||
|  |       p = createKeypair(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return p.then(function (_serverJwk) { | ||||||
|  |       serverJwk = _serverJwk; | ||||||
|  |       info.serverJwk = serverJwk; | ||||||
|  |       // { serverJwk, domains }
 | ||||||
|  |       return BACME.orders.generateCsr({ | ||||||
|  |         serverJwk: serverJwk | ||||||
|  |       , domains: info.identifiers.map(function (ident) { | ||||||
|  |           return ident.value; | ||||||
|  |         }) | ||||||
|  |       }).then(function (csrweb64) { | ||||||
|  |         return BACME.orders.finalize({ | ||||||
|  |           csr: csrweb64 | ||||||
|  |         , jwk: info.jwk | ||||||
|  |         , finalizeUrl: info.finalizeUrl | ||||||
|  |         , accountId: info.kid | ||||||
|  |         }); | ||||||
|  |       }).then(function () { | ||||||
|  |         function checkCert() { | ||||||
|  |           return new Promise(function (resolve) { | ||||||
|  |             setTimeout(resolve, 1000); | ||||||
|  |           }).then(function () { | ||||||
|  |             return BACME.orders.check({ orderUrl: info.orderUrl }); | ||||||
|  |           }).then(function (reply) { | ||||||
|  |             if ('processing' === reply) { | ||||||
|  |               return checkCert(); | ||||||
|  |             } | ||||||
|  |             return reply; | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return checkCert(); | ||||||
|  |       }).then(function (reply) { | ||||||
|  |         return BACME.orders.receive({ certificateUrl: reply.certificate }); | ||||||
|  |       }).then(function (certs) { | ||||||
|  |         console.log('WINNING!'); | ||||||
|  |         console.log(certs); | ||||||
|  |         $qs('.js-fullchain').value = certs; | ||||||
|  | 
 | ||||||
|  |         // https://stackoverflow.com/questions/40314257/export-webcrypto-key-to-pem-format
 | ||||||
|  | 				function spkiToPEM(keydata){ | ||||||
|  | 						var keydataS = arrayBufferToString(keydata); | ||||||
|  | 						var keydataB64 = window.btoa(keydataS); | ||||||
|  | 						var keydataB64Pem = formatAsPem(keydataB64); | ||||||
|  | 						return keydataB64Pem; | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				function arrayBufferToString( buffer ) { | ||||||
|  | 						var binary = ''; | ||||||
|  | 						var bytes = new Uint8Array( buffer ); | ||||||
|  | 						var len = bytes.byteLength; | ||||||
|  | 						for (var i = 0; i < len; i++) { | ||||||
|  | 								binary += String.fromCharCode( bytes[ i ] ); | ||||||
|  | 						} | ||||||
|  | 						return binary; | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 				function formatAsPem(str) { | ||||||
|  | 						var finalString = '-----BEGIN ' + pemName + ' PRIVATE KEY-----\n'; | ||||||
|  | 
 | ||||||
|  | 						while(str.length > 0) { | ||||||
|  | 								finalString += str.substring(0, 64) + '\n'; | ||||||
|  | 								str = str.substring(64); | ||||||
|  | 						} | ||||||
|  | 
 | ||||||
|  | 						finalString = finalString + '-----END ' + pemName + ' PRIVATE KEY-----'; | ||||||
|  | 
 | ||||||
|  | 						return finalString; | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  |         var wcOpts; | ||||||
|  |         var pemName; | ||||||
|  |         if (/^R/.test(info.serverJwk.kty)) { | ||||||
|  |           pemName = 'RSA'; | ||||||
|  |           wcOpts = { | ||||||
|  |             name: "RSASSA-PKCS1-v1_5" | ||||||
|  |           , hash: { name: "SHA-256" } | ||||||
|  |           }; | ||||||
|  |         } else { | ||||||
|  |           pemName = 'EC'; | ||||||
|  |           wcOpts = { | ||||||
|  |             name: "ECDSA" | ||||||
|  |           , namedCurve: "P-256" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  | 				return crypto.subtle.importKey( | ||||||
|  |           "jwk" | ||||||
|  |         , info.serverJwk | ||||||
|  |         , wcOpts | ||||||
|  |         , true | ||||||
|  |         , ["sign"] | ||||||
|  | 				).then(function (privateKey) { | ||||||
|  | 				  return window.crypto.subtle.exportKey("pkcs8", privateKey); | ||||||
|  | 				}).then (function (keydata) { | ||||||
|  | 					var pem = spkiToPEM(keydata); | ||||||
|  | 					$qs('.js-privkey').value = pem; | ||||||
|  |           steps[i](); | ||||||
|  | 				}).catch(function(err){ | ||||||
|  | 					console.error(err); | ||||||
|  | 				}); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   steps[5] = function () { | ||||||
|  |     hideForms(); | ||||||
|  |     $qs('.js-acme-form-download').hidden = false; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   steps[1](); | ||||||
|  | 
 | ||||||
|  |   $qs('body').hidden = false; | ||||||
|  | }()); | ||||||
							
								
								
									
										
											BIN
										
									
								
								fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										303
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										303
									
								
								index.html
									
									
									
									
									
								
							| @ -2,236 +2,93 @@ | |||||||
|   <head> |   <head> | ||||||
|     <title>Greenlock™</title> |     <title>Greenlock™</title> | ||||||
|     <meta property="og:image" content="https://greenlock.ppl.family/img/greenlock-mark-400x400.png" /> |     <meta property="og:image" content="https://greenlock.ppl.family/img/greenlock-mark-400x400.png" /> | ||||||
|     <link href="style/main.css" rel="stylesheet"> |     <link href="styles/main.css" rel="stylesheet"> | ||||||
|  |     <style> | ||||||
|  |       @font-face { | ||||||
|  |         font-family: 'Source Sans Pro'; | ||||||
|  |         font-style: normal; | ||||||
|  |         font-display: block; | ||||||
|  |         font-weight: 400; | ||||||
|  |         src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(./fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2) format('woff2'); | ||||||
|  |         unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; | ||||||
|  |       } | ||||||
|  |       @font-face { | ||||||
|  |         font-family: 'Source Sans Pro'; | ||||||
|  |         font-style: normal; | ||||||
|  |         font-weight: 700; | ||||||
|  |         font-display: block; | ||||||
|  |         src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(./fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2) format('woff2'); | ||||||
|  |         unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; | ||||||
|  |       } | ||||||
|  |     </style> | ||||||
|  |     <link rel="preload" href="./fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2" as="font" crossorigin="anonymous"> | ||||||
|  |     <link rel="preload" href="./fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2" as="font" crossorigin="anonymous"> | ||||||
|  | 
 | ||||||
|  |     <link rel="prefetch" href="./app/js/app.js"> | ||||||
|  |     <link rel="prefetch" href="./app/js/bacme.js"> | ||||||
|  |     <link rel="prefetch" href="./app/js/pkijs.org/v1.3.33/common.js"> | ||||||
|  |     <link rel="prefetch" href="./app/js/pkijs.org/v1.3.33/asn1.js"> | ||||||
|  |     <link rel="prefetch" href="./app/js/pkijs.org/v1.3.33/x509_schema.js"> | ||||||
|  |     <link rel="prefetch" href="./app/js/pkijs.org/v1.3.33/x509_simpl.js"> | ||||||
|  |     <link rel="prefetch" href="./app/js/browser-csr/v1.0.0-alpha/csr.js"> | ||||||
|   </head> |   </head> | ||||||
|   <body hidden> |   <body class="js-app-ready"> | ||||||
|  |     <script> | ||||||
|  |       document.querySelector('body').classList.remove("js-app-ready"); | ||||||
|  |     </script> | ||||||
|     <div class="column-container wide"> |     <div class="column-container wide"> | ||||||
|  | 
 | ||||||
|       <div class="column-row"> |       <div class="column-row"> | ||||||
|         <img src="img/greenlock-146.png"> |         <img src="img/greenlock-146.png"> | ||||||
|       </div> |       </div> | ||||||
|       <div class="column-row"> |       <div class="column-row"> | ||||||
|           <h1>Get the green lock for your website</h1> |         <h1>Get the green lock for your website</h1> | ||||||
| 
 |  | ||||||
|         <!-- Step 1 Choose Domain(s) --> |  | ||||||
|         <form class="js-acme-form js-acme-form-domains"> |  | ||||||
|           <h1><label>What's your domain?</label></h1> |  | ||||||
|           <h4>Certificates are valid for 90 days. Renewal is free :)</h4> |  | ||||||
|           <input class="js-acme-domains" type="text" placeholder="example.com,*.example.com" required> |  | ||||||
|           <br> |  | ||||||
|           <button type="submit">Next</button> |  | ||||||
| 
 |  | ||||||
|           <br> |  | ||||||
|           <br> |  | ||||||
|           <br> |  | ||||||
|           <label><input class="js-acme-api-type" name="acme-api-type" type="radio" value="v02" checked required> |  | ||||||
|             Production</label> |  | ||||||
|           <label><input class="js-acme-api-type" name="acme-api-type" type="radio" value="staging-v02" required> |  | ||||||
|             Testing</label> |  | ||||||
|           <br> |  | ||||||
|           <input class="js-acme-directory-url" type="url" placeholder="ACME directory url"> |  | ||||||
|         </form> |  | ||||||
| 
 |  | ||||||
|         <!-- Step 2 Create Account --> |  | ||||||
|         <form class="js-acme-form js-acme-form-account"> |  | ||||||
|           <h1><label>What's your email?</label></h1> |  | ||||||
|           <input class="js-acme-account-email" type="email" placeholder="john@doe.family" required> |  | ||||||
|           <br> |  | ||||||
|           <br> |  | ||||||
|           <label><input class="js-acme-account-tos" type="checkbox" required> |  | ||||||
|             Agree to <a class="js-acme-tos-url" target="acme-tos">Let's Encrypt™ Terms of Service</a>?</label> |  | ||||||
|           <br> |  | ||||||
|           <br> |  | ||||||
|           <label><input class="js-greenlock-account-tos" type="checkbox" required> |  | ||||||
|             Agree to <a class="js-gl-tos" target="_blank" href="./legal.html">Greenlock™ Terms of Service</a>?</label> |  | ||||||
|           <br> |  | ||||||
|           <br> |  | ||||||
|           <!-- |  | ||||||
|           <a href="#">advanced (use existing account)</a> |  | ||||||
|           <br> |  | ||||||
|           <br> |  | ||||||
|           --> |  | ||||||
|           <button type="submit">Next</button> |  | ||||||
|         </form> |  | ||||||
| 
 |  | ||||||
|         <!-- Step 3 Set Challanges --> |  | ||||||
|         <form class="js-acme-form js-acme-form-challenges"> |  | ||||||
| 
 |  | ||||||
|           <h1>How will you validate your domain?</h1> |  | ||||||
|           <br> |  | ||||||
|           <label><input class="js-acme-challenge-type" name="acme-challenge-type" type="radio" value="http-01" checked required> |  | ||||||
|             File Upload to HTTP Web Server</label> |  | ||||||
|           <br> |  | ||||||
|           <label><input class="js-acme-challenge-type" name="acme-challenge-type" type="radio" value="dns-01" required> |  | ||||||
|             TXT Records on DNS Name Server</label> |  | ||||||
|           <br> |  | ||||||
| 
 |  | ||||||
|           <div class="js-acme-challenges"> |  | ||||||
| 
 |  | ||||||
|           <h2>Verify Domains & Sub-Domains</h2> |  | ||||||
| 
 |  | ||||||
|           <table class="js-acme-table-http-01"> |  | ||||||
|             <thead> |  | ||||||
|               <tr> |  | ||||||
|                 <th>Hostname</th> |  | ||||||
|                 <th>File Location</th> |  | ||||||
|                 <th>File Contents</th> |  | ||||||
|               </tr> |  | ||||||
|             </thead> |  | ||||||
|             <tbody> |  | ||||||
|               <tr> |  | ||||||
|                 <td>example.com</td> |  | ||||||
|                 <td>.well-known/acme-challenge/xxx</td> |  | ||||||
|                 <td>sec.ret</td> |  | ||||||
|               </tr> |  | ||||||
|             </tbody> |  | ||||||
|           </table> |  | ||||||
| 
 |  | ||||||
|           <table class="js-acme-table-dns-01"> |  | ||||||
|             <thead> |  | ||||||
|               <tr> |  | ||||||
|                 <th>Hostname</th> |  | ||||||
|                 <th>TXT Host</th> |  | ||||||
|                 <th>TXT Value</th> |  | ||||||
|               </tr> |  | ||||||
|             </thead> |  | ||||||
|             <tbody> |  | ||||||
|               <tr> |  | ||||||
|                 <td>example.com</td> |  | ||||||
|                 <td>_acme-challenge.example.com</td> |  | ||||||
|                 <td>4A54</td> |  | ||||||
|               </tr> |  | ||||||
|             </tbody> |  | ||||||
|           </table> |  | ||||||
|           </div> |  | ||||||
| 
 |  | ||||||
|           <div class="js-acme-wildcard"> |  | ||||||
|             <h2>Verify Wildcard Domains</h2> |  | ||||||
| 
 |  | ||||||
|             <table class="js-acme-table-wildcard"> |  | ||||||
|               <thead> |  | ||||||
|                 <tr> |  | ||||||
|                   <th>Hostname</th> |  | ||||||
|                   <th>TXT Host</th> |  | ||||||
|                   <th>TXT Value</th> |  | ||||||
|                 </tr> |  | ||||||
|               </thead> |  | ||||||
|               <tbody> |  | ||||||
|                 <tr> |  | ||||||
|                   <td>example.com</td> |  | ||||||
|                   <td>_acme-challenge.example.com</td> |  | ||||||
|                   <td>4A54</td> |  | ||||||
|                 </tr> |  | ||||||
|               </tbody> |  | ||||||
|             </table> |  | ||||||
|           </div> |  | ||||||
| 
 |  | ||||||
|           <button type="submit">Next</button> |  | ||||||
|         </form> |  | ||||||
| 
 |  | ||||||
|         <!-- Step 4 Process Challanges --> |  | ||||||
|         <form class="js-acme-form js-acme-form-poll"> |  | ||||||
|           Verifying Domains... (give us 5 seconds or so...) |  | ||||||
| 
 |  | ||||||
|           <!-- |  | ||||||
|           <table class="js-acme-table-verifying"> |  | ||||||
|             <thead> |  | ||||||
|               <tr> |  | ||||||
|                 <th>Hostname</th> |  | ||||||
|                 <th>Type</th> |  | ||||||
|                 <th>Pass</th> |  | ||||||
|               </tr> |  | ||||||
|             </thead> |  | ||||||
|             <tbody> |  | ||||||
|               <tr> |  | ||||||
|                 <td>example.com</td> |  | ||||||
|                 <td>http-01</td> |  | ||||||
|                 <td>-</td> |  | ||||||
|               </tr> |  | ||||||
|             </tbody> |  | ||||||
|           </table> |  | ||||||
| 
 |  | ||||||
|           <a href="#">advanced (use existing keypair for domain)</a> |  | ||||||
| 
 |  | ||||||
|           <button type="submit">Next</button> |  | ||||||
|           --> |  | ||||||
|         </form> |  | ||||||
| 
 |  | ||||||
|         <!-- Step 5 Get Certs --> |  | ||||||
|         <form class="js-acme-form js-acme-form-download"> |  | ||||||
|           <div> |  | ||||||
|           <h2><label>privkey.pem</label></h2> |  | ||||||
|           <textarea cols="80" rows="10" class="js-privkey">-</textarea> |  | ||||||
|           </div> |  | ||||||
| 
 |  | ||||||
|           <div> |  | ||||||
|           <h2><label>fullchain.pem</label></h2> |  | ||||||
|           <textarea cols="80" rows="60" class="js-fullchain">-</textarea> |  | ||||||
|           </div> |  | ||||||
| 
 |  | ||||||
|           <div> |  | ||||||
|           <h3>node.js https server example</h3> |  | ||||||
|           <pre><code>'use strict'; |  | ||||||
| 
 |  | ||||||
|     var https = require('https'); |  | ||||||
|     var server = https.createServer({ |  | ||||||
|       key: require('fs').readFileSync('./privkey.pem') |  | ||||||
|     , cert: require('fs').readFileSync('./fullchain.pem') |  | ||||||
|     }, function (req, res) { |  | ||||||
|       res.end("Hello, World!"); |  | ||||||
|     }).listen(443, function () { |  | ||||||
|       console.log('Listening on', this.address()); |  | ||||||
|     }) |  | ||||||
|     </code></pre> |  | ||||||
|           </div> |  | ||||||
| 
 |  | ||||||
|           <!-- |  | ||||||
|             TODO |  | ||||||
|           <label>cert.pem</label> |  | ||||||
|           <textarea class="js-cert">-</textarea> |  | ||||||
| 
 |  | ||||||
|           <label>chain.pem</label> |  | ||||||
|           <textarea class="js-chain">-</textarea> |  | ||||||
| 
 |  | ||||||
|           <button type="button">Download SSL Certificates</button> |  | ||||||
|           <br> |  | ||||||
|           <a href="#">Advanced (copy and paste)</a> |  | ||||||
|           <br> |  | ||||||
|           <button type="submit">Start Over</button> |  | ||||||
|           --> |  | ||||||
|         </form> |  | ||||||
| 
 |  | ||||||
|           <br> |  | ||||||
|           <br> |  | ||||||
|           <br> |  | ||||||
|           <div><small> |  | ||||||
|           <h3></h3> |  | ||||||
|           <a href="https://git.coolaj86.com/coolaj86/greenlock.html">View Source</a> (git) |  | ||||||
|           <!-- or |  | ||||||
|           <pre><code>git clone https://git.coolaj86.com/coolaj86/greenlock.html.git</code></pre> |  | ||||||
|           Or view the live site code (same as live-site branch): |  | ||||||
|           <pre><code>wget https://greenlock.ppl.family --mirror --convert-links --adjust-extension --page-requisites --no-parent</code></pre> |  | ||||||
|           --> |  | ||||||
|           </small></div> |  | ||||||
| 
 |  | ||||||
|         <script src="./js/bacme.js"></script> |  | ||||||
|         <script src="./js/app.js"></script> |  | ||||||
| 
 |  | ||||||
|         <script src="./js/pkijs.org/v1.3.33/common.js"></script> |  | ||||||
|         <script src="./js/pkijs.org/v1.3.33/asn1.js"></script> |  | ||||||
|         <script src="./js/pkijs.org/v1.3.33/x509_schema.js"></script> |  | ||||||
|         <script src="./js/pkijs.org/v1.3.33/x509_simpl.js"></script> |  | ||||||
|         <script src="./js/browser-csr/v1.0.0-alpha/csr.js"></script> |  | ||||||
| 
 |  | ||||||
|         <!-- Global site tag (gtag.js) - Google Analytics --> |  | ||||||
|         <script async src="https://www.googletagmanager.com/gtag/js?id=UA-118745161-2"></script> |  | ||||||
|         <script> |  | ||||||
|           window.dataLayer = window.dataLayer || []; |  | ||||||
|           function gtag(){dataLayer.push(arguments);} |  | ||||||
|           gtag('js', new Date()); |  | ||||||
| 
 |  | ||||||
|           gtag('config', 'UA-118745161-2'); |  | ||||||
|         </script> |  | ||||||
|       </div> |       </div> | ||||||
|  |       <div class="column-row"> | ||||||
|  |         <!-- Step 1 Choose Domain(s) --> | ||||||
|  |         <form id="js-acme-form" action="./app/" method=> | ||||||
|  |           <div class="domain-psuedo-input"> | ||||||
|  |             <span class="secure-green">Secure</span> | <span class="secure-green">https:</span>//<input aria-label="domains to secure" id="acme-domains" type="text" name="acme-domains" placeholder="Your domain name" required> | ||||||
|  |           </div> | ||||||
|  |           <button type="submit">Go</button> | ||||||
|  |           <div class="domain-subtext">Domain, subdomain, or wildcard domain</div> | ||||||
|  |            | ||||||
|  |           <div class="acme-advanced-fields"> | ||||||
|  |             <label><input name="acme-api-type" type="radio" value="v02" checked required> | ||||||
|  |               Production | ||||||
|  |             </label> | ||||||
|  |             <label><input name="acme-api-type" type="radio" value="staging-v02" required> | ||||||
|  |               Testing</label> | ||||||
|  |             <input id="js-acme-api-url" name="acme-api-url" type="url" placeholder="ACME directory url"> | ||||||
|  |             <div> | ||||||
|  |               <a href="https://git.coolaj86.com/coolaj86/greenlock.html">View Source</a> (git) | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         </form> | ||||||
|  |       </div> | ||||||
|  |       <div class="column-row"> | ||||||
|  |         <div class="why-you-need"> | ||||||
|  |           <h2>Why you need SSL certificates</h2> | ||||||
|  |           If your website doesn't have the green lock from an SSL Certificate, Google Chrome will soon label your website as not secure. | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <!-- or | ||||||
|  |       <pre><code>git clone https://git.coolaj86.com/coolaj86/greenlock.html.git</code></pre> | ||||||
|  |       Or view the live site code (same as live-site branch): | ||||||
|  |       <pre><code>wget https://greenlock.ppl.family --mirror --convert-links --adjust-extension --page-requisites --no-parent</code></pre> | ||||||
|  |       --> | ||||||
|  | 
 | ||||||
|  |       <script src="./js/app.js"></script> | ||||||
|  | 
 | ||||||
|  |       <!-- Global site tag (gtag.js) - Google Analytics --> | ||||||
|  |       <script async src="https://www.googletagmanager.com/gtag/js?id=UA-118745161-2"></script> | ||||||
|  |       <script> | ||||||
|  |         window.dataLayer = window.dataLayer || []; | ||||||
|  |         function gtag(){dataLayer.push(arguments);} | ||||||
|  |         gtag('js', new Date()); | ||||||
|  | 
 | ||||||
|  |         gtag('config', 'UA-118745161-2'); | ||||||
|  |       </script> | ||||||
|     </div> |     </div> | ||||||
|   </body> |   </body> | ||||||
| </html> | </html> | ||||||
|  | |||||||
| @ -1,14 +1,14 @@ | |||||||
| #!/bin/bash | #!/bin/bash | ||||||
| 
 | 
 | ||||||
| mkdir -p js/pkijs.org/v1.3.33/ | mkdir -p app/js/pkijs.org/v1.3.33/ | ||||||
| pushd js/pkijs.org/v1.3.33/ | pushd app/js/pkijs.org/v1.3.33/ | ||||||
|   wget -c https://raw.githubusercontent.com/PeculiarVentures/PKI.js/41b63af760cacb565dd850fb3466ada4ca163eff/org/pkijs/common.js |   wget -c https://raw.githubusercontent.com/PeculiarVentures/PKI.js/41b63af760cacb565dd850fb3466ada4ca163eff/org/pkijs/common.js | ||||||
|   wget -c https://raw.githubusercontent.com/PeculiarVentures/PKI.js/41b63af760cacb565dd850fb3466ada4ca163eff/org/pkijs/x509_schema.js |   wget -c https://raw.githubusercontent.com/PeculiarVentures/PKI.js/41b63af760cacb565dd850fb3466ada4ca163eff/org/pkijs/x509_schema.js | ||||||
|   wget -c https://raw.githubusercontent.com/PeculiarVentures/PKI.js/41b63af760cacb565dd850fb3466ada4ca163eff/org/pkijs/x509_simpl.js |   wget -c https://raw.githubusercontent.com/PeculiarVentures/PKI.js/41b63af760cacb565dd850fb3466ada4ca163eff/org/pkijs/x509_simpl.js | ||||||
|   wget -c https://raw.githubusercontent.com/PeculiarVentures/ASN1.js/f7181c21c61e53a940ea24373ab489ad86d51bc1/org/pkijs/asn1.js |   wget -c https://raw.githubusercontent.com/PeculiarVentures/ASN1.js/f7181c21c61e53a940ea24373ab489ad86d51bc1/org/pkijs/asn1.js | ||||||
| popd | popd | ||||||
| 
 | 
 | ||||||
| mkdir -p js/browser-csr/v1.0.0-alpha/ | mkdir -p app/js/browser-csr/v1.0.0-alpha/ | ||||||
| pushd js/browser-csr/v1.0.0-alpha/ | pushd app/js/browser-csr/v1.0.0-alpha/ | ||||||
|   wget -c https://git.coolaj86.com/coolaj86/browser-csr.js/raw/commit/01cdc0e91b5bf03f12e1b25b4129e3cde927987c/csr.js |   wget -c https://git.coolaj86.com/coolaj86/browser-csr.js/raw/commit/01cdc0e91b5bf03f12e1b25b4129e3cde927987c/csr.js | ||||||
| popd | popd | ||||||
|  | |||||||
							
								
								
									
										516
									
								
								js/app.js
									
									
									
									
									
								
							
							
						
						
									
										516
									
								
								js/app.js
									
									
									
									
									
								
							| @ -3,512 +3,26 @@ | |||||||
| 
 | 
 | ||||||
|   var $qs = function (s) { return window.document.querySelector(s); }; |   var $qs = function (s) { return window.document.querySelector(s); }; | ||||||
|   var $qsa = function (s) { return window.document.querySelectorAll(s); }; |   var $qsa = function (s) { return window.document.querySelectorAll(s); }; | ||||||
|   var info = {}; |  | ||||||
|   var steps = {}; |  | ||||||
|   var nonce; |  | ||||||
|   var kid; |  | ||||||
|   var i = 1; |  | ||||||
| 
 | 
 | ||||||
|   var apiUrl = 'https://acme-{{env}}.api.letsencrypt.org/directory'; |   var apiUrl = 'https://acme-{{env}}.api.letsencrypt.org/directory'; | ||||||
|   function updateApiType() { |   function updateApiType() { | ||||||
|     var input = this || Array.prototype.filter.call( |     var formData = new FormData($qs("#js-acme-form")); | ||||||
|       $qsa('.js-acme-api-type'), function ($el) { return $el.checked; } | 
 | ||||||
|     )[0]; |     console.log('ACME api type radio:'); | ||||||
|     console.log('ACME api type radio:', input.value); | 
 | ||||||
|     $qs('.js-acme-directory-url').value = apiUrl.replace(/{{env}}/g, input.value); |     var value = formData.get("acme-api-type"); | ||||||
|  |     $qs('#js-acme-api-url').value = apiUrl.replace(/{{env}}/g, value); | ||||||
|   } |   } | ||||||
|   $qsa('.js-acme-api-type').forEach(function ($el) { |   $qs('#js-acme-form').addEventListener('change', updateApiType); | ||||||
|     $el.addEventListener('change', updateApiType); | 
 | ||||||
|   }); |  | ||||||
|   updateApiType(); |   updateApiType(); | ||||||
| 
 |   try { | ||||||
|   function hideForms() { |     document.fonts.load().then(function() { | ||||||
|     $qsa('.js-acme-form').forEach(function (el) { |       $qs('body').classList.add("js-app-ready"); | ||||||
|       el.hidden = true; |     }).catch(function(error) { | ||||||
|  |       $qs('body').classList.add("js-app-ready"); | ||||||
|     }); |     }); | ||||||
|  |   } catch(e) { | ||||||
|  |     setTimeout(function() {$qs('body').classList.add("js-app-ready");}, 200); | ||||||
|   } |   } | ||||||
| 
 |  | ||||||
|   function submitForm(ev) { |  | ||||||
|     var j = i; |  | ||||||
|     i += 1; |  | ||||||
|     steps[j].submit(ev); |  | ||||||
|   } |  | ||||||
|   $qsa('.js-acme-form').forEach(function ($el) { |  | ||||||
|     $el.addEventListener('submit', function (ev) { |  | ||||||
|       ev.preventDefault(); |  | ||||||
|       submitForm(ev); |  | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
|   function updateChallengeType() { |  | ||||||
|     var input = this || Array.prototype.filter.call( |  | ||||||
|       $qsa('.js-acme-challenge-type'), function ($el) { return $el.checked; } |  | ||||||
|     )[0]; |  | ||||||
|     console.log('ch type radio:', input.value); |  | ||||||
|     $qs('.js-acme-table-wildcard').hidden = true; |  | ||||||
|     $qs('.js-acme-table-http-01').hidden = true; |  | ||||||
|     $qs('.js-acme-table-dns-01').hidden = true; |  | ||||||
|     if (info.challenges.wildcard) { |  | ||||||
|       $qs('.js-acme-table-wildcard').hidden = false; |  | ||||||
|     } |  | ||||||
|     if (info.challenges[input.value]) { |  | ||||||
|       $qs('.js-acme-table-' + input.value).hidden = false; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   $qsa('.js-acme-challenge-type').forEach(function ($el) { |  | ||||||
|     $el.addEventListener('change', updateChallengeType); |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   function saveContact(email, domains) { |  | ||||||
|     // to be used for good, not evil
 |  | ||||||
|     return window.fetch('https://api.ppl.family/api/ppl.family/public/list', { |  | ||||||
|       method: 'POST' |  | ||||||
|     , cors: true |  | ||||||
|     , headers: new Headers({ 'Content-Type': 'application/json' }) |  | ||||||
|     , body: JSON.stringify({ address: email, comment: 'greenlock sub for ' + domains.join(',') }) |  | ||||||
|     }).then(function (resp) { |  | ||||||
|       return resp.json().then(function (data) { |  | ||||||
|         /* |  | ||||||
|         if (data.error) { |  | ||||||
|           window.alert("Couldn't save your contact. Email coolaj86@gmail.com instead."); |  | ||||||
|           return; |  | ||||||
|         } |  | ||||||
|         */ |  | ||||||
|       }); |  | ||||||
|     }, function () { |  | ||||||
|       /* |  | ||||||
|       window.alert("Didn't get your contact. Bad network connection? Email coolaj86@gmail.com instead."); |  | ||||||
|       */ |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   steps[1] = function () { |  | ||||||
|     hideForms(); |  | ||||||
|     $qs('.js-acme-form-domains').hidden = false; |  | ||||||
|   }; |  | ||||||
|   steps[1].submit = function () { |  | ||||||
|     info.identifiers = $qs('.js-acme-domains').value.split(/\s*,\s*/g).map(function (hostname) { |  | ||||||
|       return { type: 'dns', value: hostname.toLowerCase().trim() }; |  | ||||||
|     }); |  | ||||||
|     info.identifiers.sort(function (a, b) { |  | ||||||
|       if (a === b) { return 0; } |  | ||||||
|       if (a < b) { return 1; } |  | ||||||
|       if (a > b) { return -1; } |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     return BACME.directory({ directoryUrl: $qs('.js-acme-directory-url').value }).then(function (directory) { |  | ||||||
|       $qs('.js-acme-tos-url').href = directory.meta.termsOfService; |  | ||||||
|       return BACME.nonce().then(function (_nonce) { |  | ||||||
|         nonce = _nonce; |  | ||||||
| 
 |  | ||||||
|         console.log("MAGIC STEP NUMBER in 1 is:", i); |  | ||||||
|         steps[i](); |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   steps[2] = function () { |  | ||||||
|     hideForms(); |  | ||||||
|     $qs('.js-acme-form-account').hidden = false; |  | ||||||
|   }; |  | ||||||
|   steps[2].submit = function () { |  | ||||||
|     var email = $qs('.js-acme-account-email').value.toLowerCase().trim(); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     info.contact = [ 'mailto:' + email ]; |  | ||||||
|     info.agree = $qs('.js-acme-account-tos').checked; |  | ||||||
|     info.greenlockAgree = $qs('.js-gl-tos').checked; |  | ||||||
|     // TODO
 |  | ||||||
|     // options for
 |  | ||||||
|     // * regenerate key
 |  | ||||||
|     // * ECDSA / RSA / bitlength
 |  | ||||||
| 
 |  | ||||||
|     // TODO ping with version and account creation
 |  | ||||||
|     setTimeout(saveContact, 100, email, info.identifiers.map(function (ident) { return ident.value; })); |  | ||||||
| 
 |  | ||||||
|     var jwk = JSON.parse(localStorage.getItem('account:' + email) || 'null'); |  | ||||||
|     var p; |  | ||||||
| 
 |  | ||||||
|     function createKeypair() { |  | ||||||
|       return BACME.accounts.generateKeypair({ |  | ||||||
|         type: 'ECDSA' |  | ||||||
|       , bitlength: '256' |  | ||||||
|       }).then(function (jwk) { |  | ||||||
|         localStorage.setItem('account:' + email, JSON.stringify(jwk)); |  | ||||||
|         return jwk; |  | ||||||
|       }) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (jwk) { |  | ||||||
|       p = Promise.resolve(jwk); |  | ||||||
|     } else { |  | ||||||
|       p = createKeypair(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     function createAccount(jwk) { |  | ||||||
|       console.log('account jwk:'); |  | ||||||
|       console.log(jwk); |  | ||||||
|       delete jwk.key_ops; |  | ||||||
|       info.jwk = jwk; |  | ||||||
|       return BACME.accounts.sign({ |  | ||||||
|         jwk: jwk |  | ||||||
|       , contacts: [ 'mailto:' + email ] |  | ||||||
|       , agree: info.agree |  | ||||||
|       , nonce: nonce |  | ||||||
|       , kid: kid |  | ||||||
|       }).then(function (signedAccount) { |  | ||||||
|         return BACME.accounts.set({ |  | ||||||
|           signedAccount: signedAccount |  | ||||||
|         }).then(function (account) { |  | ||||||
|           console.log('account:'); |  | ||||||
|           console.log(account); |  | ||||||
|           kid = account.kid; |  | ||||||
|           return kid; |  | ||||||
|         }); |  | ||||||
|       }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return p.then(function (_jwk) { |  | ||||||
|       jwk = _jwk; |  | ||||||
|       kid = JSON.parse(localStorage.getItem('account-kid:' + email) || 'null'); |  | ||||||
|       var p2 |  | ||||||
| 
 |  | ||||||
|       // TODO save account id rather than always retrieving it
 |  | ||||||
|       if (kid) { |  | ||||||
|         p2 = Promise.resolve(kid); |  | ||||||
|       } else { |  | ||||||
|         p2 = createAccount(jwk); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       return p2.then(function (_kid) { |  | ||||||
|         kid = _kid; |  | ||||||
|         info.kid = kid; |  | ||||||
|         return BACME.orders.sign({ |  | ||||||
|           jwk: jwk |  | ||||||
|         , identifiers: info.identifiers |  | ||||||
|         , kid: kid |  | ||||||
|         }).then(function (signedOrder) { |  | ||||||
|           return BACME.orders.create({ |  | ||||||
|             signedOrder: signedOrder |  | ||||||
|           }).then(function (order) { |  | ||||||
|             info.finalizeUrl = order.finalize; |  | ||||||
|             info.orderUrl = order.url; // from header Location ???
 |  | ||||||
|             return BACME.thumbprint({ jwk: jwk }).then(function (thumbprint) { |  | ||||||
|               return BACME.challenges.all().then(function (claims) { |  | ||||||
|                 console.log('claims:'); |  | ||||||
|                 console.log(claims); |  | ||||||
|                 var obj = { 'dns-01': [], 'http-01': [], 'wildcard': [] }; |  | ||||||
|                 var map = { |  | ||||||
|                   'http-01': '.js-acme-table-http-01' |  | ||||||
|                 , 'dns-01': '.js-acme-table-dns-01' |  | ||||||
|                 , 'wildcard': '.js-acme-table-wildcard' |  | ||||||
|                 } |  | ||||||
|                 var tpls = {}; |  | ||||||
|                 info.challenges = obj; |  | ||||||
|                 Object.keys(map).forEach(function (k) { |  | ||||||
|                   var sel = map[k] + ' tbody'; |  | ||||||
|                   console.log(sel); |  | ||||||
|                   tpls[k] = $qs(sel).innerHTML; |  | ||||||
|                   $qs(map[k] + ' tbody').innerHTML = ''; |  | ||||||
|                 }); |  | ||||||
| 
 |  | ||||||
|                 // TODO make Promise-friendly
 |  | ||||||
|                 return Promise.all(claims.map(function (claim) { |  | ||||||
|                   var hostname = claim.identifier.value; |  | ||||||
|                   return Promise.all(claim.challenges.map(function (c) { |  | ||||||
|                     var keyAuth = BACME.challenges['http-01']({ |  | ||||||
|                       token: c.token |  | ||||||
|                     , thumbprint: thumbprint |  | ||||||
|                     , challengeDomain: hostname |  | ||||||
|                     }); |  | ||||||
|                     return BACME.challenges['dns-01']({ |  | ||||||
|                       keyAuth: keyAuth.value |  | ||||||
|                     , challengeDomain: hostname |  | ||||||
|                     }).then(function (dnsAuth) { |  | ||||||
|                       var data = { |  | ||||||
|                         type: c.type |  | ||||||
|                       , hostname: hostname |  | ||||||
|                       , url: c.url |  | ||||||
|                       , token: c.token |  | ||||||
|                       , keyAuthorization: keyAuth |  | ||||||
|                       , httpPath: keyAuth.path |  | ||||||
|                       , httpAuth: keyAuth.value |  | ||||||
|                       , dnsType: dnsAuth.type |  | ||||||
|                       , dnsHost: dnsAuth.host |  | ||||||
|                       , dnsAnswer: dnsAuth.answer |  | ||||||
|                       }; |  | ||||||
| 
 |  | ||||||
|                       console.log(''); |  | ||||||
|                       console.log('CHALLENGE'); |  | ||||||
|                       console.log(claim); |  | ||||||
|                       console.log(c); |  | ||||||
|                       console.log(data); |  | ||||||
|                       console.log(''); |  | ||||||
| 
 |  | ||||||
|                       if (claim.wildcard) { |  | ||||||
|                         obj.wildcard.push(data); |  | ||||||
|                         $qs(map.wildcard).innerHTML += '<tr><td>' + data.hostname + '</td><td>' + data.dnsHost + '</td><td>' + data.dnsAnswer + '</td></tr>'; |  | ||||||
|                       } else if(obj[data.type]) { |  | ||||||
| 
 |  | ||||||
|                         obj[data.type].push(data); |  | ||||||
|                         if ('dns-01' === data.type) { |  | ||||||
|                           $qs(map[data.type]).innerHTML += '<tr><td>' + data.hostname + '</td><td>' + data.dnsHost + '</td><td>' + data.dnsAnswer + '</td></tr>'; |  | ||||||
|                         } else if ('http-01' === data.type) { |  | ||||||
|                           $qs(map[data.type]).innerHTML += '<tr><td>' + data.hostname + '</td><td>' + data.httpPath + '</td><td>' + data.httpAuth + '</td></tr>'; |  | ||||||
|                         } |  | ||||||
|                       } |  | ||||||
| 
 |  | ||||||
|                     }); |  | ||||||
| 
 |  | ||||||
|                   })); |  | ||||||
|                 })).then(function () { |  | ||||||
| 
 |  | ||||||
|                   // hide wildcard if no wildcard
 |  | ||||||
|                   // hide http-01 and dns-01 if only wildcard
 |  | ||||||
|                   if (!obj.wildcard.length) { |  | ||||||
|                     $qs('.js-acme-wildcard').hidden = true; |  | ||||||
|                   } |  | ||||||
|                   if (!obj['http-01'].length) { |  | ||||||
|                     $qs('.js-acme-challenges').hidden = true; |  | ||||||
|                   } |  | ||||||
| 
 |  | ||||||
|                   updateChallengeType(); |  | ||||||
| 
 |  | ||||||
|                   console.log("MAGIC STEP NUMBER in 2 is:", i); |  | ||||||
|                   steps[i](); |  | ||||||
|                 }); |  | ||||||
| 
 |  | ||||||
|               }); |  | ||||||
|             }); |  | ||||||
|           }); |  | ||||||
|         }); |  | ||||||
|       }); |  | ||||||
|     }).catch(function (err) { |  | ||||||
|       console.error('Step \'' + i + '\' Error:'); |  | ||||||
|       console.error(err); |  | ||||||
|     }); |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   steps[3] = function () { |  | ||||||
|     hideForms(); |  | ||||||
|     $qs('.js-acme-form-challenges').hidden = false; |  | ||||||
|   }; |  | ||||||
|   steps[3].submit = function () { |  | ||||||
|     var chType; |  | ||||||
|     Array.prototype.some.call($qsa('.js-acme-challenge-type'), function ($el) { |  | ||||||
|       if ($el.checked) { |  | ||||||
|         chType = $el.value; |  | ||||||
|         return true; |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
|     console.log('chType is:', chType); |  | ||||||
|     var chs = []; |  | ||||||
| 
 |  | ||||||
|     // do each wildcard, if any
 |  | ||||||
|     // do each challenge, by selected type only
 |  | ||||||
|     [ 'wildcard', chType].forEach(function (typ) { |  | ||||||
|       info.challenges[typ].forEach(function (ch) { |  | ||||||
|         // { jwk, challengeUrl, accountId (kid) }
 |  | ||||||
|         chs.push({ |  | ||||||
|           jwk: info.jwk |  | ||||||
|         , challengeUrl: ch.url |  | ||||||
|         , accountId: info.kid |  | ||||||
|         }); |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|     console.log("INFO.challenges !!!!!", info.challenges); |  | ||||||
| 
 |  | ||||||
|     var results = []; |  | ||||||
|     function nextChallenge() { |  | ||||||
|       var ch = chs.pop(); |  | ||||||
|       if (!ch) { return results; } |  | ||||||
|       return BACME.challenges.accept(ch).then(function (result) { |  | ||||||
|         results.push(result); |  | ||||||
|         return nextChallenge(); |  | ||||||
|       }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // for now just show the next page immediately (its a spinner)
 |  | ||||||
|     steps[i](); |  | ||||||
|     return nextChallenge().then(function (results) { |  | ||||||
|       console.log('challenge status:', results); |  | ||||||
|       var polls = results.slice(0); |  | ||||||
|       var allsWell = true; |  | ||||||
| 
 |  | ||||||
|       function checkPolls() { |  | ||||||
|         return new Promise(function (resolve) { |  | ||||||
|           setTimeout(resolve, 1000); |  | ||||||
|         }).then(function () { |  | ||||||
|           return Promise.all(polls.map(function (poll) { |  | ||||||
|             return BACME.challenges.check({ challengePollUrl: poll.url }); |  | ||||||
|           })).then(function (polls) { |  | ||||||
|             console.log(polls); |  | ||||||
| 
 |  | ||||||
|             polls = polls.filter(function (poll) { |  | ||||||
|               //return 'valid' !== poll.status && 'invalid' !== poll.status;
 |  | ||||||
|               if ('pending' === poll.status) { |  | ||||||
|                 return true; |  | ||||||
|               } |  | ||||||
|               if ('valid' !== poll.status) { |  | ||||||
|                 allsWell = false; |  | ||||||
|                 console.warn('BAD POLL STATUS', poll); |  | ||||||
|               } |  | ||||||
|               // TODO show status in HTML
 |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
|             if (polls.length) { |  | ||||||
|               return checkPolls(); |  | ||||||
|             } |  | ||||||
|             return true; |  | ||||||
|           }); |  | ||||||
|         }); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       return checkPolls().then(function () { |  | ||||||
|         if (allsWell) { |  | ||||||
|           return submitForm(); |  | ||||||
|         } |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   // spinner
 |  | ||||||
|   steps[4] = function () { |  | ||||||
|     hideForms(); |  | ||||||
|     $qs('.js-acme-form-poll').hidden = false; |  | ||||||
|   } |  | ||||||
|   steps[4].submit = function () { |  | ||||||
|     console.log('Congrats! Auto advancing...'); |  | ||||||
| 
 |  | ||||||
|     var key = info.identifiers.map(function (ident) { return ident.value; }).join(','); |  | ||||||
|     var serverJwk = JSON.parse(localStorage.getItem('server:' + key) || 'null'); |  | ||||||
|     var p; |  | ||||||
| 
 |  | ||||||
|     function createKeypair() { |  | ||||||
|       return BACME.accounts.generateKeypair({ |  | ||||||
|         type: 'ECDSA' |  | ||||||
|       , bitlength: '256' |  | ||||||
|       }).then(function (serverJwk) { |  | ||||||
|         localStorage.setItem('server:' + key, JSON.stringify(serverJwk)); |  | ||||||
|         return serverJwk; |  | ||||||
|       }) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (serverJwk) { |  | ||||||
|       p = Promise.resolve(serverJwk); |  | ||||||
|     } else { |  | ||||||
|       p = createKeypair(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return p.then(function (_serverJwk) { |  | ||||||
|       serverJwk = _serverJwk; |  | ||||||
|       info.serverJwk = serverJwk; |  | ||||||
|       // { serverJwk, domains }
 |  | ||||||
|       return BACME.orders.generateCsr({ |  | ||||||
|         serverJwk: serverJwk |  | ||||||
|       , domains: info.identifiers.map(function (ident) { |  | ||||||
|           return ident.value; |  | ||||||
|         }) |  | ||||||
|       }).then(function (csrweb64) { |  | ||||||
|         return BACME.orders.finalize({ |  | ||||||
|           csr: csrweb64 |  | ||||||
|         , jwk: info.jwk |  | ||||||
|         , finalizeUrl: info.finalizeUrl |  | ||||||
|         , accountId: info.kid |  | ||||||
|         }); |  | ||||||
|       }).then(function () { |  | ||||||
|         function checkCert() { |  | ||||||
|           return new Promise(function (resolve) { |  | ||||||
|             setTimeout(resolve, 1000); |  | ||||||
|           }).then(function () { |  | ||||||
|             return BACME.orders.check({ orderUrl: info.orderUrl }); |  | ||||||
|           }).then(function (reply) { |  | ||||||
|             if ('processing' === reply) { |  | ||||||
|               return checkCert(); |  | ||||||
|             } |  | ||||||
|             return reply; |  | ||||||
|           }); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return checkCert(); |  | ||||||
|       }).then(function (reply) { |  | ||||||
|         return BACME.orders.receive({ certificateUrl: reply.certificate }); |  | ||||||
|       }).then(function (certs) { |  | ||||||
|         console.log('WINNING!'); |  | ||||||
|         console.log(certs); |  | ||||||
|         $qs('.js-fullchain').value = certs; |  | ||||||
| 
 |  | ||||||
|         // https://stackoverflow.com/questions/40314257/export-webcrypto-key-to-pem-format
 |  | ||||||
| 				function spkiToPEM(keydata){ |  | ||||||
| 						var keydataS = arrayBufferToString(keydata); |  | ||||||
| 						var keydataB64 = window.btoa(keydataS); |  | ||||||
| 						var keydataB64Pem = formatAsPem(keydataB64); |  | ||||||
| 						return keydataB64Pem; |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				function arrayBufferToString( buffer ) { |  | ||||||
| 						var binary = ''; |  | ||||||
| 						var bytes = new Uint8Array( buffer ); |  | ||||||
| 						var len = bytes.byteLength; |  | ||||||
| 						for (var i = 0; i < len; i++) { |  | ||||||
| 								binary += String.fromCharCode( bytes[ i ] ); |  | ||||||
| 						} |  | ||||||
| 						return binary; |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 				function formatAsPem(str) { |  | ||||||
| 						var finalString = '-----BEGIN ' + pemName + ' PRIVATE KEY-----\n'; |  | ||||||
| 
 |  | ||||||
| 						while(str.length > 0) { |  | ||||||
| 								finalString += str.substring(0, 64) + '\n'; |  | ||||||
| 								str = str.substring(64); |  | ||||||
| 						} |  | ||||||
| 
 |  | ||||||
| 						finalString = finalString + '-----END ' + pemName + ' PRIVATE KEY-----'; |  | ||||||
| 
 |  | ||||||
| 						return finalString; |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
|         var wcOpts; |  | ||||||
|         var pemName; |  | ||||||
|         if (/^R/.test(info.serverJwk.kty)) { |  | ||||||
|           pemName = 'RSA'; |  | ||||||
|           wcOpts = { |  | ||||||
|             name: "RSASSA-PKCS1-v1_5" |  | ||||||
|           , hash: { name: "SHA-256" } |  | ||||||
|           }; |  | ||||||
|         } else { |  | ||||||
|           pemName = 'EC'; |  | ||||||
|           wcOpts = { |  | ||||||
|             name: "ECDSA" |  | ||||||
|           , namedCurve: "P-256" |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
| 				return crypto.subtle.importKey( |  | ||||||
|           "jwk" |  | ||||||
|         , info.serverJwk |  | ||||||
|         , wcOpts |  | ||||||
|         , true |  | ||||||
|         , ["sign"] |  | ||||||
| 				).then(function (privateKey) { |  | ||||||
| 				  return window.crypto.subtle.exportKey("pkcs8", privateKey); |  | ||||||
| 				}).then (function (keydata) { |  | ||||||
| 					var pem = spkiToPEM(keydata); |  | ||||||
| 					$qs('.js-privkey').value = pem; |  | ||||||
|           steps[i](); |  | ||||||
| 				}).catch(function(err){ |  | ||||||
| 					console.error(err); |  | ||||||
| 				}); |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   steps[5] = function () { |  | ||||||
|     hideForms(); |  | ||||||
|     $qs('.js-acme-form-download').hidden = false; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   steps[1](); |  | ||||||
| 
 |  | ||||||
|   $qs('body').hidden = false; |  | ||||||
| }()); | }()); | ||||||
|  | |||||||
							
								
								
									
										100
									
								
								styles/main.css
									
									
									
									
									
								
							
							
						
						
									
										100
									
								
								styles/main.css
									
									
									
									
									
								
							| @ -1,5 +1,105 @@ | |||||||
| .column-row { | .column-row { | ||||||
|   display: flex; |   display: flex; | ||||||
|   flex-direction: column; |   flex-direction: column; | ||||||
|  |   text-align: center; | ||||||
|   align-items: center; |   align-items: center; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | body { | ||||||
|  |   position: relative; | ||||||
|  |   margin-top: 5.777777778em; | ||||||
|  |   min-height: 36em; | ||||||
|  |   font-size: 18px; | ||||||
|  |   font-family: 'Source Sans Pro', sans-serif; | ||||||
|  |   font-stretch: normal; | ||||||
|  |   line-height: 1.33; | ||||||
|  |   letter-spacing:  -0.4px; | ||||||
|  |   color: #1a1a1a; | ||||||
|  |   opacity: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | h1 { | ||||||
|  |   font-size: 2.666666667em; | ||||||
|  |   max-width: 8em; | ||||||
|  |   text-align: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input { | ||||||
|  |   font-size: 1em; | ||||||
|  |   padding: 0.444444444em; | ||||||
|  |   border: solid #d9d9d9 1px; | ||||||
|  |   border-radius: 2px; | ||||||
|  |   font-family: inherit; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button { | ||||||
|  |   padding: 0.444444444em 1.2em; | ||||||
|  |   font-size: 1em; | ||||||
|  |   background-color: #5bc17f; | ||||||
|  |   border: solid 1px #5bc17f; | ||||||
|  |   border-radius: 2px; | ||||||
|  |   font-weight: normal; | ||||||
|  |   font-stretch: normal; | ||||||
|  |   letter-spacing: -0.4px; | ||||||
|  |   font-family: inherit; | ||||||
|  |   text-align: center; | ||||||
|  |   color: white; | ||||||
|  |   height: 40px; | ||||||
|  |   line-height: 1.13; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .acme-advanced-fields { | ||||||
|  |   position: absolute; | ||||||
|  |   bottom: 0; | ||||||
|  |   padding: 1em; | ||||||
|  |   text-align: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .domain-subtext { | ||||||
|  |   font-size: 0.833333333em; | ||||||
|  |   color: #666; | ||||||
|  |   text-align: center; | ||||||
|  |   margin: 0.5em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input#acme-domains:before { | ||||||
|  |   content: "Secure | https://"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .domain-psuedo-input { | ||||||
|  |   display: inline-block; | ||||||
|  |   margin-right: .6666667em; | ||||||
|  |   border: solid #d9d9d9 1px; | ||||||
|  |   border-radius: 2px; | ||||||
|  |   padding: 0.44444444em; | ||||||
|  |   color: #d9d9d9; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input#acme-domains { | ||||||
|  |   border: none; | ||||||
|  |   padding: 0; | ||||||
|  |   padding-right: 0; | ||||||
|  |   width: 17.2222222em; | ||||||
|  |   color: #222; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input#acme-domains:focus { | ||||||
|  |   outline: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | span.secure-green { | ||||||
|  |   color: #5bc17f; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .why-you-need { | ||||||
|  |   width: 26.555556em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | body.js-app-ready { | ||||||
|  |   transition: opacity 0.2s; | ||||||
|  |   opacity: 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .acme-advanced-fields > * { | ||||||
|  |   margin: 0 0.5em; | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user