| 
									
										
										
										
											2019-05-31 21:52:12 -06:00
										 |  |  | (function() { | 
					
						
							|  |  |  | 	"use strict"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/*global URLSearchParams,Headers*/ | 
					
						
							|  |  |  | 	var PromiseA = window.Promise; | 
					
						
							|  |  |  | 	var VERSION = "2"; | 
					
						
							|  |  |  | 	// ACME recommends ECDSA P-256, but RSA 2048 is still required by some old servers (like what replicated.io uses )
 | 
					
						
							|  |  |  | 	// ECDSA P-384, P-521, and RSA 3072, 4096 are NOT recommend standards (and not properly supported)
 | 
					
						
							|  |  |  | 	var BROWSER_SUPPORTS_RSA = false; | 
					
						
							|  |  |  | 	var ECDSA_OPTS = { kty: "EC", namedCurve: "P-256" }; | 
					
						
							|  |  |  | 	var RSA_OPTS = { kty: "RSA", modulusLength: 2048 }; | 
					
						
							|  |  |  | 	var Promise = window.Promise; | 
					
						
							|  |  |  | 	var Keypairs = window.Keypairs; | 
					
						
							|  |  |  | 	var ACME = window.ACME; | 
					
						
							|  |  |  | 	var CSR = window.CSR; | 
					
						
							|  |  |  | 	var $qs = function(s) { | 
					
						
							|  |  |  | 		return window.document.querySelector(s); | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 	var $qsa = function(s) { | 
					
						
							|  |  |  | 		return window.document.querySelectorAll(s); | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 	var acme; | 
					
						
							|  |  |  | 	var info = {}; | 
					
						
							|  |  |  | 	var steps = {}; | 
					
						
							|  |  |  | 	var i = 1; | 
					
						
							|  |  |  | 	var apiUrl = "https://acme-{{env}}.api.letsencrypt.org/directory"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// fix previous browsers
 | 
					
						
							|  |  |  | 	var isCurrent = localStorage.getItem("version") === VERSION; | 
					
						
							|  |  |  | 	if (!isCurrent) { | 
					
						
							|  |  |  | 		localStorage.clear(); | 
					
						
							|  |  |  | 		localStorage.setItem("version", VERSION); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	localStorage.setItem("version", VERSION); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	function updateApiType() { | 
					
						
							|  |  |  | 		/*jshint validthis: true */ | 
					
						
							|  |  |  | 		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 | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	function hideForms() { | 
					
						
							|  |  |  | 		$qsa(".js-acme-form").forEach(function(el) { | 
					
						
							|  |  |  | 			el.hidden = true; | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	function updateProgress(currentStep) { | 
					
						
							|  |  |  | 		var progressSteps = $qs("#js-progress-bar").children; | 
					
						
							|  |  |  | 		var j; | 
					
						
							|  |  |  | 		for (j = 0; j < progressSteps.length; j += 1) { | 
					
						
							|  |  |  | 			if (j < currentStep) { | 
					
						
							|  |  |  | 				progressSteps[j].classList.add("js-progress-step-complete"); | 
					
						
							|  |  |  | 				progressSteps[j].classList.remove("js-progress-step-started"); | 
					
						
							|  |  |  | 			} else if (j === currentStep) { | 
					
						
							|  |  |  | 				progressSteps[j].classList.remove("js-progress-step-complete"); | 
					
						
							|  |  |  | 				progressSteps[j].classList.add("js-progress-step-started"); | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				progressSteps[j].classList.remove("js-progress-step-complete"); | 
					
						
							|  |  |  | 				progressSteps[j].classList.remove("js-progress-step-started"); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	function newAlert(str) { | 
					
						
							|  |  |  | 		return new Promise(function() { | 
					
						
							|  |  |  | 			setTimeout(function() { | 
					
						
							|  |  |  | 				window.alert(str); | 
					
						
							|  |  |  | 				if (window.confirm("Start over?")) { | 
					
						
							|  |  |  | 					document.location.href = document.location.href.replace( | 
					
						
							|  |  |  | 						/\/app.*/, | 
					
						
							|  |  |  | 						"/" | 
					
						
							|  |  |  | 					); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}, 10); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	function submitForm(ev) { | 
					
						
							|  |  |  | 		var j = i; | 
					
						
							|  |  |  | 		i += 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return PromiseA.resolve() | 
					
						
							|  |  |  | 			.then(function() { | 
					
						
							|  |  |  | 				return steps[j].submit(ev); | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			.catch(function(err) { | 
					
						
							|  |  |  | 				var ourfault = true; | 
					
						
							|  |  |  | 				console.error(err); | 
					
						
							|  |  |  | 				if (/failed to fetch/i.test(err.message)) { | 
					
						
							|  |  |  | 					return newAlert("Network connection failure."); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				if ("E_ACME_CHALLENGE" === err.code) { | 
					
						
							|  |  |  | 					if ("dns-01" === err.type) { | 
					
						
							|  |  |  | 						ourfault = false; | 
					
						
							|  |  |  | 						return newAlert( | 
					
						
							|  |  |  | 							"It looks like the DNS record you set for " + | 
					
						
							|  |  |  | 								err.altname + | 
					
						
							|  |  |  | 								" was incorrect or did not propagate. " + | 
					
						
							|  |  |  | 								"The error message was '" + | 
					
						
							|  |  |  | 								err.message + | 
					
						
							|  |  |  | 								"'" | 
					
						
							|  |  |  | 						); | 
					
						
							|  |  |  | 					} else if ("http-01" === err.type) { | 
					
						
							|  |  |  | 						ourfault = false; | 
					
						
							|  |  |  | 						return newAlert( | 
					
						
							|  |  |  | 							"It looks like the file you uploaded for " + | 
					
						
							|  |  |  | 								err.altname + | 
					
						
							|  |  |  | 								" was incorrect or could not be downloaded. " + | 
					
						
							|  |  |  | 								"The error message was '" + | 
					
						
							|  |  |  | 								err.message + | 
					
						
							|  |  |  | 								"'" | 
					
						
							|  |  |  | 						); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				if (ourfault) { | 
					
						
							|  |  |  | 					err.auth = undefined; | 
					
						
							|  |  |  | 					window.alert( | 
					
						
							|  |  |  | 						"Something went wrong. It's probably our fault, not yours." + | 
					
						
							|  |  |  | 							" Please email aj@rootprojects.org to let him know. The error message is: \n" + | 
					
						
							|  |  |  | 							JSON.stringify(err, null, 2) | 
					
						
							|  |  |  | 					); | 
					
						
							|  |  |  | 					return new Promise(function() {}); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	function testKeypairSupport() { | 
					
						
							|  |  |  | 		return Keypairs.generate(RSA_OPTS) | 
					
						
							|  |  |  | 			.then(function() { | 
					
						
							|  |  |  | 				console.info("[crypto] RSA is supported"); | 
					
						
							|  |  |  | 				BROWSER_SUPPORTS_RSA = true; | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			.catch(function() { | 
					
						
							|  |  |  | 				console.warn("[crypto] RSA is NOT supported"); | 
					
						
							|  |  |  | 				return Keypairs.generate(ECDSA_OPTS) | 
					
						
							|  |  |  | 					.then(function() { | 
					
						
							|  |  |  | 						console.info("[crypto] ECDSA is supported"); | 
					
						
							|  |  |  | 					}) | 
					
						
							|  |  |  | 					.catch(function(e) { | 
					
						
							|  |  |  | 						console.warn("[crypto] EC is NOT supported"); | 
					
						
							|  |  |  | 						throw e; | 
					
						
							|  |  |  | 					}); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	function getServerKeypair() { | 
					
						
							|  |  |  | 		var sortedAltnames = info.identifiers | 
					
						
							|  |  |  | 			.map(function(ident) { | 
					
						
							|  |  |  | 				return ident.value; | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			.sort() | 
					
						
							|  |  |  | 			.join(","); | 
					
						
							|  |  |  | 		var serverJwk = JSON.parse( | 
					
						
							|  |  |  | 			localStorage.getItem("server:" + sortedAltnames) || "null" | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 		if (serverJwk) { | 
					
						
							|  |  |  | 			return PromiseA.resolve(serverJwk); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var keypairOpts; | 
					
						
							|  |  |  | 		// TODO allow for user preference
 | 
					
						
							|  |  |  | 		if (BROWSER_SUPPORTS_RSA) { | 
					
						
							|  |  |  | 			keypairOpts = RSA_OPTS; | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			keypairOpts = ECDSA_OPTS; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return Keypairs.generate(RSA_OPTS) | 
					
						
							|  |  |  | 			.catch(function(err) { | 
					
						
							|  |  |  | 				console.error( | 
					
						
							|  |  |  | 					"[Error] Keypairs.generate(" + JSON.stringify(RSA_OPTS) + "):" | 
					
						
							|  |  |  | 				); | 
					
						
							|  |  |  | 				throw err; | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			.then(function(pair) { | 
					
						
							|  |  |  | 				localStorage.setItem( | 
					
						
							|  |  |  | 					"server:" + sortedAltnames, | 
					
						
							|  |  |  | 					JSON.stringify(pair.private) | 
					
						
							|  |  |  | 				); | 
					
						
							|  |  |  | 				return pair.private; | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	function getAccountKeypair(email) { | 
					
						
							|  |  |  | 		var json = localStorage.getItem("account:" + email); | 
					
						
							|  |  |  | 		if (json) { | 
					
						
							|  |  |  | 			return Promise.resolve(JSON.parse(json)); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return Keypairs.generate(ECDSA_OPTS) | 
					
						
							|  |  |  | 			.catch(function(err) { | 
					
						
							|  |  |  | 				console.warn( | 
					
						
							|  |  |  | 					"[Error] Keypairs.generate(" + JSON.stringify(ECDSA_OPTS) + "):\n", | 
					
						
							|  |  |  | 					err | 
					
						
							|  |  |  | 				); | 
					
						
							|  |  |  | 				return Keypairs.generate(RSA_OPTS).catch(function(err) { | 
					
						
							|  |  |  | 					console.error( | 
					
						
							|  |  |  | 						"[Error] Keypairs.generate(" + JSON.stringify(RSA_OPTS) + "):" | 
					
						
							|  |  |  | 					); | 
					
						
							|  |  |  | 					throw err; | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			.then(function(pair) { | 
					
						
							|  |  |  | 				localStorage.setItem("account:" + email, JSON.stringify(pair.private)); | 
					
						
							|  |  |  | 				return pair.private; | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	function updateChallengeType() { | 
					
						
							|  |  |  | 		/*jshint validthis: true*/ | 
					
						
							|  |  |  | 		var input = | 
					
						
							|  |  |  | 			this || | 
					
						
							|  |  |  | 			Array.prototype.filter.call($qsa(".js-acme-challenge-type"), function( | 
					
						
							|  |  |  | 				$el | 
					
						
							|  |  |  | 			) { | 
					
						
							|  |  |  | 				return $el.checked; | 
					
						
							|  |  |  | 			})[0]; | 
					
						
							|  |  |  | 		$qs(".js-acme-verification-wildcard").hidden = true; | 
					
						
							|  |  |  | 		$qs(".js-acme-verification-http-01").hidden = true; | 
					
						
							|  |  |  | 		$qs(".js-acme-verification-dns-01").hidden = true; | 
					
						
							|  |  |  | 		if (info.challenges.wildcard) { | 
					
						
							|  |  |  | 			$qs(".js-acme-verification-wildcard").hidden = false; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if (info.challenges[input.value]) { | 
					
						
							|  |  |  | 			$qs(".js-acme-verification-" + input.value).hidden = false; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	function saveContact(email, domains) { | 
					
						
							|  |  |  | 		// to be used for good, not evil
 | 
					
						
							|  |  |  | 		return window | 
					
						
							|  |  |  | 			.fetch( | 
					
						
							|  |  |  | 				"https://api.rootprojects.org/api/rootprojects.org/public/community", | 
					
						
							|  |  |  | 				{ | 
					
						
							|  |  |  | 					method: "POST", | 
					
						
							|  |  |  | 					cors: true, | 
					
						
							|  |  |  | 					headers: new Headers({ "Content-Type": "application/json" }), | 
					
						
							|  |  |  | 					body: JSON.stringify({ | 
					
						
							|  |  |  | 						address: email, | 
					
						
							|  |  |  | 						project: "greenlock-domains@rootprojects.org", | 
					
						
							|  |  |  | 						timezone: new Intl.DateTimeFormat().resolvedOptions().timeZone, | 
					
						
							|  |  |  | 						domain: domains.join(",") | 
					
						
							|  |  |  | 					}) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			) | 
					
						
							|  |  |  | 			.catch(function(err) { | 
					
						
							|  |  |  | 				console.error(err); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	steps[1] = function() { | 
					
						
							|  |  |  | 		console.info("\n1. Show domains form"); | 
					
						
							|  |  |  | 		updateProgress(0); | 
					
						
							|  |  |  | 		hideForms(); | 
					
						
							|  |  |  | 		$qs(".js-acme-form-domains").hidden = false; | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 	steps[1].submit = function() { | 
					
						
							|  |  |  | 		console.info( | 
					
						
							|  |  |  | 			"[submit] 1. Process domains, create ACME client", | 
					
						
							|  |  |  | 			info.domains | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 		info.domains = $qs(".js-acme-domains") | 
					
						
							|  |  |  | 			.value.replace(/https?:\/\//g, " ") | 
					
						
							|  |  |  | 			.replace(/[,+]/g, " ") | 
					
						
							|  |  |  | 			.trim() | 
					
						
							|  |  |  | 			.split(/\s+/g); | 
					
						
							|  |  |  | 		console.info("[domains]", info.domains.join(" ")); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		info.identifiers = info.domains.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; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var acmeDirectoryUrl = $qs(".js-acme-directory-url").value; | 
					
						
							|  |  |  | 		acme = ACME.create({ Keypairs: Keypairs, CSR: CSR }); | 
					
						
							|  |  |  | 		return acme.init(acmeDirectoryUrl).then(function(directory) { | 
					
						
							|  |  |  | 			$qs(".js-acme-tos-url").href = directory.meta.termsOfService; | 
					
						
							|  |  |  | 			return steps[i](); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	steps[2] = function() { | 
					
						
							|  |  |  | 		console.info("\n2. Show account (email, ToS) form"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		updateProgress(0); | 
					
						
							|  |  |  | 		hideForms(); | 
					
						
							|  |  |  | 		$qs(".js-acme-form-account").hidden = false; | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 	steps[2].submit = function() { | 
					
						
							|  |  |  | 		console.info("[submit] 2. Create ACME account (get Key ID)"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var email = $qs(".js-acme-account-email") | 
					
						
							|  |  |  | 			.value.toLowerCase() | 
					
						
							|  |  |  | 			.trim(); | 
					
						
							|  |  |  | 		info.email = email; | 
					
						
							|  |  |  | 		info.contact = ["mailto:" + email]; | 
					
						
							|  |  |  | 		info.agree = $qs(".js-acme-account-tos").checked; | 
					
						
							|  |  |  | 		//info.greenlockAgree = $qs('.js-gl-tos').checked;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// TODO ping with version and account creation
 | 
					
						
							|  |  |  | 		setTimeout(saveContact, 100, email, info.domains); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		$qs(".js-account-next").disabled = true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return info.cryptoCheck | 
					
						
							|  |  |  | 			.then(function() { | 
					
						
							|  |  |  | 				return getAccountKeypair(email).then(function(jwk) { | 
					
						
							|  |  |  | 					// TODO save account id rather than always retrieving it?
 | 
					
						
							|  |  |  | 					console.info("[accounts] upsert for", email); | 
					
						
							|  |  |  | 					return acme.accounts | 
					
						
							|  |  |  | 						.create({ | 
					
						
							|  |  |  | 							email: email, | 
					
						
							|  |  |  | 							agreeToTerms: info.agree && true, | 
					
						
							|  |  |  | 							accountKeypair: { privateKeyJwk: jwk } | 
					
						
							|  |  |  | 						}) | 
					
						
							|  |  |  | 						.then(function(account) { | 
					
						
							|  |  |  | 							console.info("[accounts] result:", account); | 
					
						
							|  |  |  | 							info.account = account; | 
					
						
							|  |  |  | 							info.privateJwk = jwk; | 
					
						
							|  |  |  | 							info.email = email; | 
					
						
							|  |  |  | 						}) | 
					
						
							|  |  |  | 						.catch(function(err) { | 
					
						
							|  |  |  | 							console.error("[accounts] failed to upsert account:"); | 
					
						
							|  |  |  | 							console.error(err); | 
					
						
							|  |  |  | 							return newAlert(err.message || JSON.stringify(err, null, 2)); | 
					
						
							|  |  |  | 						}); | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			.then(function() { | 
					
						
							|  |  |  | 				var jwk = info.privateJwk; | 
					
						
							|  |  |  | 				var account = info.account; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				console.info("[orders] requesting"); | 
					
						
							|  |  |  | 				return acme.orders | 
					
						
							|  |  |  | 					.request({ | 
					
						
							|  |  |  | 						account: account, | 
					
						
							|  |  |  | 						accountKeypair: { privateKeyJwk: jwk }, | 
					
						
							|  |  |  | 						domains: info.domains | 
					
						
							|  |  |  | 					}) | 
					
						
							|  |  |  | 					.then(function(order) { | 
					
						
							|  |  |  | 						info.order = order; | 
					
						
							|  |  |  | 						console.info("[orders] created ", order); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 						var claims = order.claims; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 						var obj = { "dns-01": [], "http-01": [], wildcard: [] }; | 
					
						
							|  |  |  | 						info.challenges = obj; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 						var $httpList = $qs(".js-acme-http"); | 
					
						
							|  |  |  | 						var $dnsList = $qs(".js-acme-dns"); | 
					
						
							|  |  |  | 						var $wildList = $qs(".js-acme-wildcard"); | 
					
						
							|  |  |  | 						var httpTpl = $httpList.innerHTML; | 
					
						
							|  |  |  | 						var dnsTpl = $dnsList.innerHTML; | 
					
						
							|  |  |  | 						var wildTpl = $wildList.innerHTML; | 
					
						
							|  |  |  | 						$httpList.innerHTML = ""; | 
					
						
							|  |  |  | 						$dnsList.innerHTML = ""; | 
					
						
							|  |  |  | 						$wildList.innerHTML = ""; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 						claims.forEach(function(claim) { | 
					
						
							|  |  |  | 							//#console.log("claims[i]", claim);
 | 
					
						
							|  |  |  | 							var hostname = claim.identifier.value; | 
					
						
							|  |  |  | 							claim.challenges.forEach(function(c) { | 
					
						
							|  |  |  | 								var auth = c; | 
					
						
							|  |  |  | 								var data = { | 
					
						
							|  |  |  | 									type: c.type, | 
					
						
							|  |  |  | 									hostname: hostname, | 
					
						
							|  |  |  | 									url: c.url, | 
					
						
							|  |  |  | 									token: c.token, | 
					
						
							|  |  |  | 									httpPath: auth.challengeUrl, | 
					
						
							|  |  |  | 									httpAuth: auth.keyAuthorization, | 
					
						
							|  |  |  | 									dnsType: "TXT", | 
					
						
							|  |  |  | 									dnsHost: auth.dnsHost, | 
					
						
							|  |  |  | 									dnsAnswer: auth.keyAuthorizationDigest | 
					
						
							|  |  |  | 								}; | 
					
						
							|  |  |  | 								//#console.log("claims[i].challenge", data);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 								var $tpl = document.createElement("div"); | 
					
						
							|  |  |  | 								if (claim.wildcard) { | 
					
						
							|  |  |  | 									obj.wildcard.push(data); | 
					
						
							|  |  |  | 									$tpl.innerHTML = wildTpl; | 
					
						
							|  |  |  | 									$tpl.querySelector(".js-acme-ver-txt-host").innerHTML = | 
					
						
							|  |  |  | 										data.dnsHost; | 
					
						
							|  |  |  | 									$tpl.querySelector(".js-acme-ver-txt-value").innerHTML = | 
					
						
							|  |  |  | 										data.dnsAnswer; | 
					
						
							|  |  |  | 									$wildList.appendChild($tpl); | 
					
						
							|  |  |  | 								} else if (obj[data.type]) { | 
					
						
							|  |  |  | 									obj[data.type].push(data); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 									if ("dns-01" === data.type) { | 
					
						
							|  |  |  | 										$tpl.innerHTML = dnsTpl; | 
					
						
							|  |  |  | 										$tpl.querySelector(".js-acme-ver-txt-host").innerHTML = | 
					
						
							|  |  |  | 											data.dnsHost; | 
					
						
							|  |  |  | 										$tpl.querySelector(".js-acme-ver-txt-value").innerHTML = | 
					
						
							|  |  |  | 											data.dnsAnswer; | 
					
						
							|  |  |  | 										$dnsList.appendChild($tpl); | 
					
						
							|  |  |  | 									} else if ("http-01" === data.type) { | 
					
						
							|  |  |  | 										$tpl.innerHTML = httpTpl; | 
					
						
							|  |  |  | 										$tpl.querySelector( | 
					
						
							|  |  |  | 											".js-acme-ver-file-location" | 
					
						
							|  |  |  | 										).innerHTML = data.httpPath.split("/").slice(-1); | 
					
						
							|  |  |  | 										$tpl.querySelector(".js-acme-ver-content").innerHTML = | 
					
						
							|  |  |  | 											data.httpAuth; | 
					
						
							|  |  |  | 										$tpl.querySelector(".js-acme-ver-uri").innerHTML = | 
					
						
							|  |  |  | 											data.httpPath; | 
					
						
							|  |  |  | 										$tpl.querySelector(".js-download-verify-link").href = | 
					
						
							|  |  |  | 											"data:text/octet-stream;base64," + | 
					
						
							|  |  |  | 											window.btoa(data.httpAuth); | 
					
						
							|  |  |  | 										$tpl.querySelector( | 
					
						
							|  |  |  | 											".js-download-verify-link" | 
					
						
							|  |  |  | 										).download = data.httpPath.split("/").slice(-1); | 
					
						
							|  |  |  | 										$httpList.appendChild($tpl); | 
					
						
							|  |  |  | 									} | 
					
						
							|  |  |  | 								} | 
					
						
							|  |  |  | 							}); | 
					
						
							|  |  |  | 						}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 						// hide wildcard if no wildcard
 | 
					
						
							|  |  |  | 						// hide http-01 and dns-01 if only wildcard
 | 
					
						
							|  |  |  | 						if (!obj.wildcard.length) { | 
					
						
							|  |  |  | 							$qs(".js-acme-wildcard-challenges").hidden = true; | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 						if (!obj["http-01"].length) { | 
					
						
							|  |  |  | 							$qs(".js-acme-challenges").hidden = true; | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 						console.info("[housekeeping] challenges", info.challenges); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 						updateChallengeType(); | 
					
						
							|  |  |  | 						return steps[i](); | 
					
						
							|  |  |  | 					}) | 
					
						
							|  |  |  | 					.catch(function(err) { | 
					
						
							|  |  |  | 						if (err.detail || err.urn) { | 
					
						
							|  |  |  | 							console.error("(Probably) User Error:"); | 
					
						
							|  |  |  | 							console.error(err); | 
					
						
							|  |  |  | 							return newAlert( | 
					
						
							|  |  |  | 								"There was an error, probably with your email or domain:\n" + | 
					
						
							|  |  |  | 									err.message | 
					
						
							|  |  |  | 							); | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 						throw err; | 
					
						
							|  |  |  | 					}); | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			.catch(function(err) { | 
					
						
							|  |  |  | 				console.error("Step '' Error:"); | 
					
						
							|  |  |  | 				console.error(err, err.stack); | 
					
						
							|  |  |  | 				return newAlert( | 
					
						
							|  |  |  | 					"An error happened (but it's not your fault)." + | 
					
						
							|  |  |  | 						" Email aj@rootprojects.org to let him know that 'order and get challenges' failed." | 
					
						
							|  |  |  | 				); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	steps[3] = function() { | 
					
						
							|  |  |  | 		console.info("\n3. Present challenge options"); | 
					
						
							|  |  |  | 		updateProgress(1); | 
					
						
							|  |  |  | 		hideForms(); | 
					
						
							|  |  |  | 		$qs(".js-acme-form-challenges").hidden = false; | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 	steps[3].submit = function() { | 
					
						
							|  |  |  | 		console.info("[submit] 3. Fulfill challenges, fetch certificate"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var challengePriority = ["dns-01"]; | 
					
						
							|  |  |  | 		if ("http-01" === $qs(".js-acme-challenge-type:checked").value) { | 
					
						
							|  |  |  | 			challengePriority.unshift("http-01"); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		console.info("[challenge] selected ", challengePriority[0]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// for now just show the next page immediately (its a spinner)
 | 
					
						
							|  |  |  | 		steps[i](); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return getAccountKeypair(info.email).then(function(jwk) { | 
					
						
							|  |  |  | 			// TODO put a test challenge in the list
 | 
					
						
							|  |  |  | 			// info.order.claims.push(...)
 | 
					
						
							|  |  |  | 			// TODO warn about wait-time if DNS
 | 
					
						
							|  |  |  | 			return getServerKeypair().then(function(serverJwk) { | 
					
						
							|  |  |  | 				return acme.orders | 
					
						
							|  |  |  | 					.complete({ | 
					
						
							|  |  |  | 						account: info.account, | 
					
						
							|  |  |  | 						accountKeypair: { privateKeyJwk: jwk }, | 
					
						
							|  |  |  | 						order: info.order, | 
					
						
							|  |  |  | 						domains: info.domains, | 
					
						
							|  |  |  | 						domainKeypair: { privateKeyJwk: serverJwk }, | 
					
						
							|  |  |  | 						challengePriority: challengePriority, | 
					
						
							|  |  |  | 						challenges: false, | 
					
						
							|  |  |  | 						onChallengeStatus: function(details) { | 
					
						
							|  |  |  | 							$qs(".js-challenge-responses").hidden = false; | 
					
						
							|  |  |  | 							$qs(".js-challenge-response-type").innerText = details.type; | 
					
						
							|  |  |  | 							$qs(".js-challenge-response-status").innerText = details.status; | 
					
						
							|  |  |  | 							$qs(".js-challenge-response-altname").innerText = details.altname; | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					}) | 
					
						
							|  |  |  | 					.then(function(certs) { | 
					
						
							|  |  |  | 						return Keypairs.export({ jwk: serverJwk }).then(function(keyPem) { | 
					
						
							|  |  |  | 							console.info("WINNING!"); | 
					
						
							|  |  |  | 							console.info(certs); | 
					
						
							|  |  |  | 							$qs("#js-fullchain").innerHTML = [ | 
					
						
							|  |  |  | 								certs.cert.trim() + "\n", | 
					
						
							|  |  |  | 								certs.chain + "\n" | 
					
						
							|  |  |  | 							].join("\n"); | 
					
						
							|  |  |  | 							$qs("#js-download-fullchain-link").href = | 
					
						
							|  |  |  | 								"data:text/octet-stream;base64," + window.btoa(certs); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 							$qs("#js-privkey").innerHTML = keyPem; | 
					
						
							|  |  |  | 							$qs("#js-download-privkey-link").href = | 
					
						
							|  |  |  | 								"data:text/octet-stream;base64," + window.btoa(keyPem); | 
					
						
							|  |  |  | 							return submitForm(); | 
					
						
							|  |  |  | 						}); | 
					
						
							|  |  |  | 					}); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// spinner
 | 
					
						
							|  |  |  | 	steps[4] = function() { | 
					
						
							|  |  |  | 		console.info("\n4. Show loading spinner"); | 
					
						
							|  |  |  | 		updateProgress(1); | 
					
						
							|  |  |  | 		hideForms(); | 
					
						
							|  |  |  | 		$qs(".js-acme-form-poll").hidden = false; | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 	steps[4].submit = function() { | 
					
						
							|  |  |  | 		console.info("[submit] 4. Order complete"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return steps[i](); | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	steps[5] = function() { | 
					
						
							|  |  |  | 		console.info("\n5. Present certificates (yay!)"); | 
					
						
							|  |  |  | 		updateProgress(2); | 
					
						
							|  |  |  | 		hideForms(); | 
					
						
							|  |  |  | 		$qs(".js-acme-form-download").hidden = false; | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	function init() { | 
					
						
							|  |  |  | 		$qsa(".js-acme-api-type").forEach(function($el) { | 
					
						
							|  |  |  | 			$el.addEventListener("change", updateApiType); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 		updateApiType(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		$qsa(".js-acme-form").forEach(function($el) { | 
					
						
							|  |  |  | 			$el.addEventListener("submit", function(ev) { | 
					
						
							|  |  |  | 				ev.preventDefault(); | 
					
						
							|  |  |  | 				return submitForm(ev); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		$qsa(".js-acme-challenge-type").forEach(function($el) { | 
					
						
							|  |  |  | 			$el.addEventListener("change", updateChallengeType); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var params = new URLSearchParams(window.location.search); | 
					
						
							|  |  |  | 		var apiType = params.get("acme-api-type") || "staging-v02"; | 
					
						
							|  |  |  | 		if (params.has("acme-domains")) { | 
					
						
							|  |  |  | 			$qs(".js-acme-domains").value = params.get("acme-domains"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			$qsa(".js-acme-api-type").forEach(function(ele) { | 
					
						
							|  |  |  | 				if (ele.value === apiType) { | 
					
						
							|  |  |  | 					ele.checked = true; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			updateApiType(); | 
					
						
							|  |  |  | 			steps[2](); | 
					
						
							|  |  |  | 			return submitForm(); | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			steps[1](); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	init(); | 
					
						
							|  |  |  | 	$qs("body").hidden = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// in the background
 | 
					
						
							|  |  |  | 	info.cryptoCheck = testKeypairSupport() | 
					
						
							|  |  |  | 		.then(function() { | 
					
						
							|  |  |  | 			console.info("[crypto] self-check: passed"); | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		.catch(function(err) { | 
					
						
							|  |  |  | 			console.error("[crypto] could not use either RSA nor EC."); | 
					
						
							|  |  |  | 			console.error(err); | 
					
						
							|  |  |  | 			window.alert( | 
					
						
							|  |  |  | 				"Generating secure certificates requires a browser with cryptography support." + | 
					
						
							|  |  |  | 					"Please consider a recent version of Chrome, Firefox, or Safari." | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 			throw err; | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | })(); |