diff --git a/app/index.html b/app/index.html index 44ee91d..4249014 100644 --- a/app/index.html +++ b/app/index.html @@ -218,6 +218,10 @@
loading...

+

Warning: + You should wait at least 30 seconds after setting DNS records before continuing.

+

Google DNS Users: + You may need to wait up to 5 minutes.

@@ -231,6 +235,10 @@
loading...

+

Warning: + You should wait at least 30 seconds after setting DNS records before continuing.

+

Google DNS: + You may need to wait up to 5 minutes.

diff --git a/app/js/bluecrypt-acme.js b/app/js/bluecrypt-acme.js index f78b066..7453c14 100644 --- a/app/js/bluecrypt-acme.js +++ b/app/js/bluecrypt-acme.js @@ -1907,6 +1907,9 @@ ACME._registerAccount = function (me, options) { } return Promise.resolve().then(function () { + if (true === options.agreeToTerms) { + options.agreeToTerms = function (tos) { return tos; }; + } return options.agreeToTerms(me._tos); }).then(agree); }; @@ -1977,7 +1980,7 @@ ACME._testChallengeOptions = function () { ]; }; ACME._testChallenges = function (me, reals) { - console.log('[DEBUG] testChallenges'); + //#console.log('[DEBUG] testChallenges'); if (me.skipDryRun || me.skipChallengeTest) { return Promise.resolve(); } @@ -2043,7 +2046,7 @@ ACME._chooseType = function(options, auths) { }; ACME._challengesMap = {'http-01':0,'dns-01':0,'tls-alpn-01':0}; ACME._computeAuths = function (me, options, request, dryrun) { - console.log('[DEBUG] computeAuths'); + //#console.log('[DEBUG] computeAuths'); // we don't poison the dns cache with our dummy request var dnsPrefix = ACME.challengePrefixes['dns-01']; if (dryrun) { @@ -2226,7 +2229,7 @@ ACME._postChallenge = function (me, options, auth) { // options = { domains, claims, challenges, challengePriority } ACME._setChallengesAll = function (me, options) { - console.log("[DEBUG] setChallengesAll"); + //#console.log("[DEBUG] setChallengesAll"); var claims = options.order.claims.slice(0); var valids = []; var auths = []; @@ -2331,8 +2334,8 @@ ACME._finalizeOrder = function (me, options) { } return challengeNext().then(function () { //#console.debug("[getCertificate] next.then"); - console.log('DEBUG 1 order:'); - console.log(options.order); + //#console.log('DEBUG order:'); + //#console.log(options.order); return options.order.identifiers.map(function (ident) { return ident.value; }); diff --git a/app/js/greenlock.js b/app/js/greenlock.js index 5142ce9..5f1d680 100644 --- a/app/js/greenlock.js +++ b/app/js/greenlock.js @@ -4,18 +4,18 @@ /*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; - var ECDSA_OPTS = { kty: 'EC', namedCurve: 'P-256' }; - var RSA_OPTS = { kty: 'RSA', modulusLength: 2048 }; + // 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 acme; var info = {}; var steps = {}; var i = 1; @@ -29,36 +29,12 @@ } localStorage.setItem('version', VERSION); - var challenges = { - 'http-01': { - set: function (auth) { - console.log('Chose http-01 for', auth.altname, auth); - return Promise.resolve(); - } - , remove: function (auth) { - console.log('Can remove http-01 for', auth.altname, auth); - return Promise.resolve(); - } - } - , 'dns-01': { - set: function (auth) { - console.log('Chose dns-01 for', auth.altname, auth); - return Promise.resolve(); - } - , remove: function (auth) { - console.log('Can remove dns-01 for', auth.altname, auth); - return Promise.resolve(); - } - } - }; - function updateApiType() { - console.log("type updated"); /*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); + //#console.log('ACME api type radio:', input.value); $qs('.js-acme-directory-url').value = apiUrl.replace(/{{env}}/g, input.value); } @@ -70,7 +46,7 @@ function updateProgress(currentStep) { var progressSteps = $qs("#js-progress-bar").children; - var j; + var j; for (j = 0; j < progressSteps.length; j += 1) { if (j < currentStep) { progressSteps[j].classList.add("js-progress-step-complete"); @@ -116,28 +92,18 @@ }); } - function testEcdsaSupport() { - /* - var opts = { - kty: $('input[name="kty"]:checked').value - , namedCurve: $('input[name="ec-crv"]:checked').value - , modulusLength: $('input[name="rsa-len"]:checked').value - }; - */ - } - function testRsaSupport() { - return Keypairs.generate(RSA_OPTS); - } function testKeypairSupport() { - - return testRsaSupport().then(function () { + return Keypairs.generate(RSA_OPTS).then(function () { console.info("[crypto] RSA is supported"); BROWSER_SUPPORTS_RSA = true; - return BROWSER_SUPPORTS_RSA; }).catch(function () { - console.warn("[crypto] RSA is NOT fully supported"); - BROWSER_SUPPORTS_RSA = false; - return BROWSER_SUPPORTS_RSA; + 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; + }); }); } @@ -159,36 +125,35 @@ 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; - }); + }).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)); - } + 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; - }); - } + 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]; - console.log('ch type radio:', input.value); $qs('.js-acme-verification-wildcard').hidden = true; $qs('.js-acme-verification-http-01').hidden = true; $qs('.js-acme-verification-dns-01').hidden = true; @@ -209,7 +174,7 @@ , body: JSON.stringify({ address: email , project: 'greenlock-domains@rootprojects.org' - , timezone: new Intl.DateTimeFormat().resolvedOptions().timeZone + , timezone: new Intl.DateTimeFormat().resolvedOptions().timeZone , domain: domains.join(',') }) }).catch(function (err) { @@ -218,12 +183,17 @@ } steps[1] = function () { + console.info("1. Show domains form"); updateProgress(0); hideForms(); $qs('.js-acme-form-domains').hidden = false; }; steps[1].submit = function () { - info.domains = $qs('.js-acme-domains').value.replace(/https?:\/\//g, ' ').replace(/[,+]/g, ' ').trim().split(/\s+/g); + 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() }; }); @@ -233,90 +203,77 @@ 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) { + 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; - console.log("MAGIC STEP NUMBER in 1 is:", i); - steps[i](); + steps[i](); }); }; steps[2] = function () { + console.info("2. Show account (email, ToS) form"); + updateProgress(0); hideForms(); $qs('.js-acme-form-account').hidden = false; }; steps[2].submit = function () { - var email = $qs('.js-acme-account-email').value.toLowerCase().trim(); + 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; - info.domains = info.identifiers.map(function (ident) { return ident.value; }); - console.log("domains:"); - console.log(info.domains); // TODO ping with version and account creation setTimeout(saveContact, 100, email, info.domains); - function checkTos(tos) { - if (info.agree) { - return tos; - } else { - return ''; - } - } - $qs('.js-account-next').disabled = true; - return getAccountKeypair(email).then(function (jwk) { - // TODO save account id rather than always retrieving it? - return acme.accounts.create({ - email: email - , agreeToTerms: checkTos - , accountKeypair: { privateKeyJwk: jwk } - }).then(function (account) { - console.log("account created result:", account); - info.account = account; - info.privateJwk = jwk; - info.email = email; - info.acme = acme; // TODO XXX remove - }).catch(function (err) { - console.error("A bad thing happened:"); - console.error(err); - window.alert(err.message || JSON.stringify(err, null, 2)); - return new Promise(function () { - // stop the process cold - console.warn('TODO: resume at ask email?'); - }); - }); - }).then(function () { + 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); + window.alert(err.message || JSON.stringify(err, null, 2)); + return new Promise(function () { + if (window.confirm("Start over?")) { + document.location.reload(); + } + }); + }); + }); + }).then(function () { var jwk = info.privateJwk; var account = info.account; - return acme.orders.request({ - account: account - , accountKeypair: { privateKeyJwk: jwk } - , domains: info.domains - , challenges: challenges - }).then(function (order) { + 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; - console.log('claims:'); - console.log(claims); var obj = { 'dns-01': [], 'http-01': [], 'wildcard': [] }; info.challenges = obj; - /* - var map = { - 'http-01': '.js-acme-verification-http-01' - , 'dns-01': '.js-acme-verification-dns-01' - , 'wildcard': '.js-acme-verification-wildcard' - }; - */ + var $httpList = $qs('.js-acme-http'); var $dnsList = $qs('.js-acme-dns'); var $wildList = $qs('.js-acme-wildcard'); @@ -328,8 +285,7 @@ $wildList.innerHTML = ''; claims.forEach(function (claim) { - console.log("Challenge (claim):"); - console.log(claim); + //#console.log("claims[i]", claim); var hostname = claim.identifier.value; claim.challenges.forEach(function (c) { var auth = c; @@ -344,41 +300,33 @@ , dnsHost: auth.dnsHost , dnsAnswer: auth.keyAuthorizationDigest }; + //#console.log("claims[i].challenge", data); - console.log(''); - console.log('CHALLENGE'); - console.log(claim); - console.log(c); - console.log(data); - console.log(''); - - var verification = document.createElement("div"); + var $tpl = document.createElement("div"); if (claim.wildcard) { obj.wildcard.push(data); - verification.innerHTML = wildTpl; - //verification.querySelector(".js-acme-ver-hostname").innerHTML = data.hostname; - verification.querySelector(".js-acme-ver-txt-host").innerHTML = data.dnsHost; - verification.querySelector(".js-acme-ver-txt-value").innerHTML = data.dnsAnswer; - $wildList.appendChild(verification); + $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) { - verification.innerHTML = dnsTpl; - //verification.querySelector(".js-acme-ver-hostname").innerHTML = data.hostname; - verification.querySelector(".js-acme-ver-txt-host").innerHTML = data.dnsHost; - verification.querySelector(".js-acme-ver-txt-value").innerHTML = data.dnsAnswer; - $dnsList.appendChild(verification); + $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) { - verification.innerHTML = httpTpl; - verification.querySelector(".js-acme-ver-file-location").innerHTML = data.httpPath.split("/").slice(-1); - verification.querySelector(".js-acme-ver-content").innerHTML = data.httpAuth; - verification.querySelector(".js-acme-ver-uri").innerHTML = data.httpPath; - verification.querySelector(".js-download-verify-link").href = + $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); - verification.querySelector(".js-download-verify-link").download = data.httpPath.split("/").slice(-1); - $httpList.appendChild(verification); + $tpl.querySelector(".js-download-verify-link").download = data.httpPath.split("/").slice(-1); + $httpList.appendChild($tpl); } } }); @@ -393,9 +341,9 @@ $qs('.js-acme-challenges').hidden = true; } - updateChallengeType(); + console.info("[housekeeping] challenges", info.challenges); - console.log("MAGIC STEP NUMBER in 2 is:", i); + updateChallengeType(); steps[i](); }); }).catch(function (err) { @@ -407,23 +355,28 @@ }; steps[3] = function () { + console.info("3. 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.log('primary challenge type is:', challengePriority[0]); + 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) { - // for now just show the next page immediately (its a spinner) // 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 getServerKeypair().then(function (serverJwk) { return acme.orders.complete({ account: info.account , accountKeypair: { privateKeyJwk: jwk } @@ -431,10 +384,10 @@ , domains: info.domains , domainKeypair: { privateKeyJwk: serverJwk } , challengePriority: challengePriority - , challenges: challenges + , challenges: false }).then(function (certs) { return Keypairs.export({ jwk: serverJwk }).then(function (keyPem) { - console.log('WINNING!'); + console.info('WINNING!'); console.log(certs); $qs('#js-fullchain').innerHTML = [ certs.cert.trim() + "\n" @@ -455,76 +408,76 @@ // spinner steps[4] = function () { + console.info('4. Show loading spinner'); updateProgress(1); hideForms(); $qs('.js-acme-form-poll').hidden = false; }; steps[4].submit = function () { - console.log('Congrats! Auto advancing...'); + console.info('[submit] 4. Order complete'); steps[i](); }; steps[5] = function () { + console.info('5. Present certificates (yay!)'); updateProgress(2); hideForms(); $qs('.js-acme-form-download').hidden = false; }; - var params = new URLSearchParams(window.location.search); - var apiType = params.get('acme-api-type') || "staging-v02"; + function init() { + var params = new URLSearchParams(window.location.search); + var apiType = params.get('acme-api-type') || "staging-v02"; - $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(); - submitForm(ev); - }); - }); - - - $qsa('.js-acme-challenge-type').forEach(function ($el) { - $el.addEventListener('change', updateChallengeType); - }); - - if (params.has('acme-domains')) { - console.log("acme-domains param: ", params.get('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; - } + $qsa('.js-acme-api-type').forEach(function ($el) { + $el.addEventListener('change', updateApiType); }); updateApiType(); - steps[2](); - submitForm(); - } - $qs('body').hidden = false; + $qsa('.js-acme-form').forEach(function ($el) { + $el.addEventListener('submit', function (ev) { + ev.preventDefault(); + submitForm(ev); + }); + }); + + + $qsa('.js-acme-challenge-type').forEach(function ($el) { + $el.addEventListener('change', updateChallengeType); + }); + + 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](); + submitForm(); + } + + } // The kickoff steps[1](); - return testKeypairSupport().then(function (rsaSupport) { - if (rsaSupport) { - return true; - } + init(); + $qs('body').hidden = false; - return testEcdsaSupport().then(function () { - console.info('[crypto] ECDSA is supported'); - }).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; - }); + // 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; }); }());