diff --git a/app/index.html b/app/index.html
index 4218741..72771c3 100644
--- a/app/index.html
+++ b/app/index.html
@@ -33,13 +33,8 @@
-
-
-
-
-
-
-
+
+
@@ -342,14 +337,8 @@
-
-
-
-
-
-
-
-
+
+
diff --git a/app/js/bluecrypt-acme.js b/app/js/bluecrypt-acme.js
index 8984883..8d58e6e 100644
--- a/app/js/bluecrypt-acme.js
+++ b/app/js/bluecrypt-acme.js
@@ -2,8 +2,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-;
-(function (exports) {
+;(function (exports) {
var Enc = exports.Enc = {};
@@ -1962,7 +1961,12 @@ ACME._getChallenges = function (me, options, authUrl) {
, payload: ''
, url: authUrl
}).then(function (resp) {
- return resp.body;
+ // Pre-emptive rather than lazy for interfaces that need to show the challenges to the user first
+ return ACME._challengesToAuth(me, options, resp.body, false).then(function (auths) {
+ resp.body._rawChallenges = resp.body.challenges;
+ resp.body.challenges = auths;
+ return resp.body;
+ });
});
};
ACME._wait = function wait(ms) {
@@ -1987,12 +1991,6 @@ ACME._testChallengeOptions = function () {
"token": "test-" + chToken + "-1",
"_wildcard": true
}
- , {
- "type": "tls-sni-01",
- "status": "pending",
- "url": "https://acme-staging-v02.example.com/2",
- "token": "test-" + chToken + "-2"
- }
, {
"type": "tls-alpn-01",
"status": "pending",
@@ -2010,47 +2008,49 @@ ACME._testChallenges = function (me, options) {
challenges = challenges.filter(function (ch) { return ch._wildcard; });
}
- var challenge = ACME._chooseChallenge(options, { challenges: challenges });
- if (!challenge) {
- // For example, wildcards require dns-01 and, if we don't have that, we have to bail
- var enabled = Object.keys(options.challenges).join(', ') || 'none';
- var suitable = challenges.map(function (r) { return r.type; }).join(', ') || 'none';
- return Promise.reject(new Error(
- "None of the challenge types that you've enabled ( " + enabled + " )"
- + " are suitable for validating the domain you've selected (" + identifierValue + ")."
- + " You must enable one of ( " + suitable + " )."
- ));
- }
-
- // TODO remove skipChallengeTest
- if (me.skipDryRun || me.skipChallengeTest) {
- return null;
- }
-
- if ('dns-01' === challenge.type) {
- // Give the nameservers a moment to propagate
- CHECK_DELAY = 1.5 * 1000;
- }
-
- return Promise.resolve().then(function () {
- var results = {
+ // The dry-run comes first in the spirit of "fail fast"
+ // (and protecting against challenge failure rate limits)
+ var dryrun = true;
+ var resp = {
+ body: {
identifier: {
type: "dns"
, value: identifierValue.replace(/^\*\./, '')
}
- , challenges: [ challenge ]
+ , challenges: challenges
, expires: new Date(Date.now() + (60 * 1000)).toISOString()
, wildcard: identifierValue.includes('*.') || undefined
- };
+ }
+ };
+ return ACME._challengesToAuth(me, options, resp.body, dryrun).then(function (auths) {
+ resp.body._rawChallenges = resp.body.challenges;
+ resp.body.challenges = auths;
- // The dry-run comes first in the spirit of "fail fast"
- // (and protecting against challenge failure rate limits)
- var dryrun = true;
- return ACME._challengeToAuth(me, options, results, challenge, dryrun).then(function (auth) {
- if (!me._canUse[auth.type]) { return; }
- return ACME._setChallenge(me, options, auth).then(function () {
- return auth;
- });
+ var auth = ACME._chooseAuth(options, resp.body.challenges);
+ if (!auth) {
+ // For example, wildcards require dns-01 and, if we don't have that, we have to bail
+ var enabled = Object.keys(options.challenges).join(', ') || 'none';
+ var suitable = resp.body.challenges.map(function (r) { return r.type; }).join(', ') || 'none';
+ return Promise.reject(new Error(
+ "None of the challenge types that you've enabled ( " + enabled + " )"
+ + " are suitable for validating the domain you've selected (" + identifierValue + ")."
+ + " You must enable one of ( " + suitable + " )."
+ ));
+ }
+
+ // TODO remove skipChallengeTest
+ if (me.skipDryRun || me.skipChallengeTest) {
+ return null;
+ }
+
+ if ('dns-01' === auth.type) {
+ // Give the nameservers a moment to propagate
+ CHECK_DELAY = 1.5 * 1000;
+ }
+
+ if (!me._canUse[auth.type]) { return; }
+ return ACME._setChallenge(me, options, auth).then(function () {
+ return auth;
});
});
})).then(function (auths) {
@@ -2067,93 +2067,88 @@ ACME._testChallenges = function (me, options) {
});
});
};
-ACME._chooseChallenge = function(options, results) {
+ACME._chooseAuth = function(options, auths) {
// For each of the challenge types that we support
- var challenge;
+ var auth;
var challengeTypes = Object.keys(options.challenges);
// ordered from most to least preferred
- challengeTypes = [ 'tls-alpn-01', 'http-01', 'dns-01' ].filter(function (chType) {
+ challengeTypes = (options.challengePriority||[ 'tls-alpn-01', 'http-01', 'dns-01' ]).filter(function (chType) {
return challengeTypes.includes(chType);
});
- /*
- // Lot's of error checking to inform the user of mistakes
- if (!(options.challengeTypes||[]).length) {
- options.challengeTypes = Object.keys(options.challenges||{});
- }
- if (!options.challengeTypes.length) {
- options.challengeTypes = [ options.challengeType ].filter(Boolean);
- }
- if (options.challengeType) {
- options.challengeTypes.sort(function (a, b) {
- if (a === options.challengeType) { return -1; }
- if (b === options.challengeType) { return 1; }
- return 0;
- });
- if (options.challengeType !== options.challengeTypes[0]) {
- return Promise.reject(new Error("options.challengeType is '" + options.challengeType + "',"
- + " which does not exist in the supplied types '" + options.challengeTypes.join(',') + "'"));
- }
- }
- // TODO check that all challengeTypes are represented in challenges
- if (!options.challengeTypes.length) {
- return Promise.reject(new Error("options.challengeTypes (string array) must be specified"
- + " (and in order of preferential priority)."));
- }
- */
challengeTypes.some(function (chType) {
// And for each of the challenge types that are allowed
- return results.challenges.some(function (ch) {
+ return auths.some(function (ch) {
// Check to see if there are any matches
if (ch.type === chType) {
- challenge = ch;
+ auth = ch;
return true;
}
});
});
- return challenge;
+ return auth;
};
-ACME._challengeToAuth = function (me, options, request, challenge, dryrun) {
+ACME._challengesToAuth = function (me, options, request, dryrun) {
// we don't poison the dns cache with our dummy request
var dnsPrefix = ACME.challengePrefixes['dns-01'];
if (dryrun) {
dnsPrefix = dnsPrefix.replace('acme-challenge', 'greenlock-dryrun-' + ACME._prnd(4));
}
+ var challengeTypes = Object.keys(options.challenges);
- var auth = {};
-
- // straight copy from the new order response
- // { identifier, status, expires, challenges, wildcard }
- Object.keys(request).forEach(function (key) {
- auth[key] = request[key];
- });
-
- // copy from the challenge we've chosen
- // { type, status, url, token }
- // (note the duplicate status overwrites the one above, but they should be the same)
- Object.keys(challenge).forEach(function (key) {
- // don't confused devs with the id url
- auth[key] = challenge[key];
- });
-
- // batteries-included helpers
- auth.hostname = auth.identifier.value;
- // because I'm not 100% clear if the wildcard identifier does or doesn't have the leading *. in all cases
- auth.altname = ACME._untame(auth.identifier.value, auth.wildcard);
return ACME._importKeypair(me, options.accountKeypair).then(function (pair) {
return me.Keypairs.thumbprint({ jwk: pair.public }).then(function (thumb) {
- auth.thumbprint = thumb;
- // keyAuthorization = token || '.' || base64url(JWK_Thumbprint(accountKey))
- auth.keyAuthorization = challenge.token + '.' + auth.thumbprint;
- // conflicts with ACME challenge id url is already in use, so we call this challengeUrl instead
- // TODO auth.http01Url ?
- auth.challengeUrl = 'http://' + auth.identifier.value + ACME.challengePrefixes['http-01'] + '/' + auth.token;
- auth.dnsHost = dnsPrefix + '.' + auth.hostname.replace('*.', '');
+ return Promise.all(request.challenges.map(function (challenge) {
+ // Don't do extra work for challenges that we can't satisfy
+ if (!challengeTypes.includes(challenge.type)) {
+ return null;
+ }
- return Crypto._sha('sha256', auth.keyAuthorization).then(function (hash) {
- auth.dnsAuthorization = hash;
- return auth;
+ var auth = {};
+
+ // straight copy from the new order response
+ // { identifier, status, expires, challenges, wildcard }
+ Object.keys(request).forEach(function (key) {
+ auth[key] = request[key];
+ });
+
+ // copy from the challenge we've chosen
+ // { type, status, url, token }
+ // (note the duplicate status overwrites the one above, but they should be the same)
+ Object.keys(challenge).forEach(function (key) {
+ // don't confused devs with the id url
+ auth[key] = challenge[key];
+ });
+
+ // batteries-included helpers
+ auth.hostname = auth.identifier.value;
+ // because I'm not 100% clear if the wildcard identifier does or doesn't have the leading *. in all cases
+ auth.altname = ACME._untame(auth.identifier.value, auth.wildcard);
+
+ auth.thumbprint = thumb;
+ // keyAuthorization = token || '.' || base64url(JWK_Thumbprint(accountKey))
+ auth.keyAuthorization = challenge.token + '.' + auth.thumbprint;
+
+ if ('http-01' === auth.type) {
+ // conflicts with ACME challenge id url is already in use, so we call this challengeUrl instead
+ // TODO auth.http01Url ?
+ auth.challengeUrl = 'http://' + auth.identifier.value + ACME.challengePrefixes['http-01'] + '/' + auth.token;
+ return auth;
+ }
+
+ if ('dns-01' !== auth.type) {
+ return auth;
+ }
+
+ return Crypto._sha('sha256', auth.keyAuthorization).then(function (hash) {
+ auth.dnsHost = dnsPrefix + '.' + auth.hostname.replace('*.', '');
+ auth.dnsAuthorization = hash;
+ auth.keyAuthorizationDigest = hash;
+ return auth;
+ });
+ })).then(function (auths) {
+ return auths.filter(Boolean);
});
});
});
@@ -2241,18 +2236,21 @@ ACME._postChallenge = function (me, options, auth) {
return resp.body;
}
- var errmsg;
- if (!resp.body.status) {
- errmsg = "[acme-v2] (E_STATE_EMPTY) empty challenge state for '" + altname + "':";
- }
- else if ('invalid' === resp.body.status) {
- errmsg = "[acme-v2] (E_STATE_INVALID) challenge state for '" + altname + "': '" + resp.body.status + "'";
- }
- else {
- errmsg = "[acme-v2] (E_STATE_UKN) challenge state for '" + altname + "': '" + resp.body.status + "'";
+ var err;
+ if (resp.body.error && resp.body.error.detail) {
+ err = new Error("[acme-v2] " + auth.altname + " state:" + resp.body.status + " " + resp.body.error.detail);
+ err.auth = auth;
+ err.altname = auth.altname;
+ err.type = auth.type;
+ err.urn = resp.body.error.type;
+ err.code = ('invalid' === resp.body.status) ? 'E_CHALLENGE_INVALID' : 'E_CHALLENGE_UNKNOWN';
+ err.uri = resp.body.url;
+ } else {
+ err = new Error("[acme-v2] " + auth.altname + " (E_STATE_UKN): " + JSON.stringify(resp.body, null, 2));
+ err.code = 'E_CHALLENGE_UNKNOWN';
}
- return Promise.reject(new Error(errmsg));
+ return Promise.reject(err);
});
}
@@ -2312,34 +2310,32 @@ ACME._setChallenge = function (me, options, auth) {
ACME._setChallengesAll = function (me, options) {
var order = options.order;
var setAuths = order.authorizations.slice(0);
- var challenges = order.challenges;
+ var claims = order.claims.slice(0);
var validAuths = [];
var auths = [];
function setNext() {
var authUrl = setAuths.shift();
- var results = challenges.shift();
+ var claim = claims.shift();
if (!authUrl) { return; }
- // var domain = options.domains[i]; // results.identifier.value
+ // var domain = options.domains[i]; // claim.identifier.value
// If it's already valid, we're golden it regardless
- if (results.challenges.some(function (ch) { return 'valid' === ch.status; })) {
+ if (claim.challenges.some(function (ch) { return 'valid' === ch.status; })) {
return setNext();
}
- var challenge = ACME._chooseChallenge(options, results);
- if (!challenge) {
+ var auth = ACME._chooseAuth(options, claim.challenges);
+ if (!auth) {
// For example, wildcards require dns-01 and, if we don't have that, we have to bail
return Promise.reject(new Error(
"Server didn't offer any challenge we can handle for '" + options.domains.join() + "'."
));
}
- return ACME._challengeToAuth(me, options, results, challenge, false).then(function (auth) {
- auths.push(auth);
- return ACME._setChallenge(me, options, auth).then(setNext);
- });
+ auths.push(auth);
+ return ACME._setChallenge(me, options, auth).then(setNext);
}
function checkNext() {
@@ -2358,6 +2354,7 @@ ACME._setChallengesAll = function (me, options) {
}).then(checkNext);
}
+ // Actually sets the challenge via ACME
function challengeNext() {
var auth = validAuths.shift();
if (!auth) { return; }
@@ -2519,7 +2516,7 @@ ACME._createOrder = function (me, options) {
if (me.debug) { console.debug('[ordered]', location); } // the account id url
if (me.debug) { console.debug(resp); }
- if (!options.authorizations) {
+ if (!order.authorizations) {
return Promise.reject(new Error(
"[acme-v2.js] authorizations were not fetched for '" + options.domains.join() + "':\n"
+ JSON.stringify(resp.body)
@@ -2528,25 +2525,24 @@ ACME._createOrder = function (me, options) {
return order;
}).then(function (order) {
- var challenges = [];
+ var claims = [];
if (me.debug) { console.debug("[acme-v2] POST newOrder has authorizations"); }
var challengeAuths = order.authorizations.slice(0);
function getNext() {
var authUrl = challengeAuths.shift();
- if (!authUrl) { return challenges; }
+ if (!authUrl) { return claims; }
- return ACME._getChallenges(me, options, authUrl).then(function (results) {
- // var domain = options.domains[i]; // results.identifier.value
- challenges.push(results);
+ return ACME._getChallenges(me, options, authUrl).then(function (claim) {
+ // var domain = options.domains[i]; // claim.identifier.value
+ claims.push(claim);
return getNext();
});
}
return getNext().then(function () {
- order.challenges = challenges;
+ order.claims = claims;
options.order = order;
- console.log('DEBUG 2 order (too much info for challenges?):', order);
return order;
});
});
@@ -2684,10 +2680,12 @@ ACME.create = function create(me) {
}
};
me.orders = {
- create: function (options) {
+ // create + get challlenges
+ request: function (options) {
return ACME._createOrder(me, options);
}
- , finalize: function (options) {
+ // set challenges, check challenges, finalize order, return order
+ , complete: function (options) {
return ACME._finalizeOrder(me, options);
}
};
diff --git a/app/js/greenlock.js b/app/js/greenlock.js
index a80ab03..944b1cd 100644
--- a/app/js/greenlock.js
+++ b/app/js/greenlock.js
@@ -2,6 +2,7 @@
'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)
@@ -15,11 +16,32 @@
var $qs = function (s) { return window.document.querySelector(s); };
var $qsa = function (s) { return window.document.querySelectorAll(s); };
var acme;
- var accountStuff;
var info = {};
var steps = {};
var i = 1;
var apiUrl = 'https://acme-{{env}}.api.letsencrypt.org/directory';
+ 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");
@@ -59,8 +81,29 @@
i += 1;
return PromiseA.resolve(steps[j].submit(ev)).catch(function (err) {
+ var ourfault = true;
console.error(err);
- window.alert("Something went wrong. It's our fault not yours. Please email aj@rootprojects.org and let him know that 'step " + j + "' failed.");
+ console.error(Object.keys(err));
+ if ('E_CHALLENGE_INVALID' === err.code) {
+ if ('dns-01' === err.type) {
+ ourfault = false;
+ window.alert("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;
+ window.alert("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));
+ }
});
}
@@ -178,7 +221,8 @@
$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) {
+ info.domains = $qs('.js-acme-domains').value.replace(/https?:\/\//g, ' ').replace(/,/g, ' ').trim().split(/\s+/g);
+ info.identifiers = info.domains.map(function (hostname) {
return { type: 'dns', value: hostname.toLowerCase().trim() };
}).slice(0,1); //Disable multiple values for now. We'll just take the first and work with it.
info.identifiers.sort(function (a, b) {
@@ -204,12 +248,14 @@
steps[2].submit = function () {
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; });
// TODO ping with version and account creation
- setTimeout(saveContact, 100, email, info.identifiers.map(function (ident) { return ident.value; }));
+ setTimeout(saveContact, 100, email, info.domains);
function checkTos(tos) {
if (info.agree) {
@@ -227,10 +273,10 @@
, accountKeypair: { privateKeyJwk: jwk }
}).then(function (account) {
console.log("account created result:", account);
- accountStuff.account = account;
- accountStuff.privateJwk = jwk;
- accountStuff.email = email;
- accountStuff.acme = acme; // TODO XXX remove
+ 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);
@@ -241,116 +287,104 @@
});
});
}).then(function () {
- var jwk = accountStuff.privateJwk;
- var account = accountStuff.account;
+ var jwk = info.privateJwk;
+ var account = info.account;
- return acme.orders.create({
+ return acme.orders.request({
account: account
, accountKeypair: { privateKeyJwk: jwk }
- , identifiers: info.identifiers
+ , domains: info.domains
+ , challenges: challenges
}).then(function (order) {
- return acme.orders.create({
- signedOrder: signedOrder
- }).then(function (order) {
- accountStuff.order = order;
- var claims = order.challenges;
- console.log('claims:');
- console.log(claims);
+ info.order = order;
- 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'
- };
- options.challengePriority = [ 'http-01', 'dns-01' ];
+ var claims = order.claims;
+ console.log('claims:');
+ console.log(claims);
- // TODO make Promise-friendly
- return PromiseA.all(claims.map(function (claim) {
- var hostname = claim.identifier.value;
- return PromiseA.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
- };
+ 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'
+ };
+ */
- console.log('');
- console.log('CHALLENGE');
- console.log(claim);
- console.log(c);
- console.log(data);
- console.log('');
+ claims.forEach(function (claim) {
+ console.log("Challenge (claim):");
+ console.log(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
+ };
- if (claim.wildcard) {
- obj.wildcard.push(data);
- let verification = $qs(".js-acme-verification-wildcard");
- 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;
+ console.log('');
+ console.log('CHALLENGE');
+ console.log(claim);
+ console.log(c);
+ console.log(data);
+ console.log('');
- } else if(obj[data.type]) {
+ var verification;
+ if (claim.wildcard) {
+ obj.wildcard.push(data);
+ verification = $qs(".js-acme-verification-wildcard");
+ 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;
- obj[data.type].push(data);
+ } else if(obj[data.type]) {
- if ('dns-01' === data.type) {
- let verification = $qs(".js-acme-verification-dns-01");
- 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;
- } else if ('http-01' === data.type) {
- $qs(".js-acme-ver-file-location").innerHTML = data.httpPath.split("/").slice(-1);
- $qs(".js-acme-ver-content").innerHTML = data.httpAuth;
- $qs(".js-acme-ver-uri").innerHTML = data.httpPath;
- $qs(".js-download-verify-link").href =
- "data:text/octet-stream;base64," + window.btoa(data.httpAuth);
- $qs(".js-download-verify-link").download = data.httpPath.split("/").slice(-1);
- }
- }
+ obj[data.type].push(data);
- });
-
- }));
- })).then(function () {
-
- // 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 ('dns-01' === data.type) {
+ verification = $qs(".js-acme-verification-dns-01");
+ 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;
+ } else if ('http-01' === data.type) {
+ $qs(".js-acme-ver-file-location").innerHTML = data.httpPath.split("/").slice(-1);
+ $qs(".js-acme-ver-content").innerHTML = data.httpAuth;
+ $qs(".js-acme-ver-uri").innerHTML = data.httpPath;
+ $qs(".js-download-verify-link").href =
+ "data:text/octet-stream;base64," + window.btoa(data.httpAuth);
+ $qs(".js-download-verify-link").download = data.httpPath.split("/").slice(-1);
+ }
}
- if (!obj['http-01'].length) {
- $qs('.js-acme-challenges').hidden = true;
- }
-
- updateChallengeType();
-
- console.log("MAGIC STEP NUMBER in 2 is:", i);
- steps[i]();
});
-
});
+
+ // 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;
+ }
+
+ updateChallengeType();
+
+ console.log("MAGIC STEP NUMBER in 2 is:", i);
+ steps[i]();
});
}).catch(function (err) {
console.error('Step \'\' Error:');
console.error(err, err.stack);
- window.alert("An error happened at Step " + i + ", but it's not your fault. Email aj@rootprojects.org and let him know.");
+ window.alert("An error happened (but it's not your fault)."
+ + " Email aj@rootprojects.org to let him know that 'order and get challenges' failed.");
});
};
@@ -360,58 +394,44 @@
$qs('.js-acme-form-challenges').hidden = false;
};
steps[3].submit = function () {
- options.challengeTypes = [ 'dns-01' ];
+ var challengePriority = [ 'dns-01' ];
if ('http-01' === $qs('.js-acme-challenge-type:checked').value) {
- options.challengeTypes.unshift('http-01');
+ challengePriority.unshift('http-01');
}
- console.log('primary challenge type is:', options.challengeTypes[0]);
+ console.log('primary challenge type is:', challengePriority[0]);
- return getAccountKeypair(email).then(function (jwk) {
+ 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
// TODO warn about wait-time if DNS
- steps[i]();
- return getServerKeypair().then(function () {
- return acme.orders.finalize({
- account: accountStuff.account
+ return getServerKeypair().then(function (serverJwk) {
+ return acme.orders.complete({
+ account: info.account
, accountKeypair: { privateKeyJwk: jwk }
- , order: accountStuff.order
- , domainKeypair: 'TODO'
- });
- }).then(function (certs) {
- console.log('WINNING!');
- console.log(certs);
- $qs('#js-fullchain').innerHTML = certs;
- $qs("#js-download-fullchain-link").href =
- "data:text/octet-stream;base64," + window.btoa(certs);
+ , order: info.order
+ , domains: info.domains
+ , domainKeypair: { privateKeyJwk: serverJwk }
+ , challengePriority: challengePriority
+ , challenges: challenges
+ }).then(function (certs) {
+ return Keypairs.export({ jwk: serverJwk }).then(function (keyPem) {
+ console.log('WINNING!');
+ console.log(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);
- 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, pemName);
- $qs('#js-privkey').innerHTML = pem;
- $qs("#js-download-privkey-link").href =
- "data:text/octet-stream;base64," + window.btoa(pem);
- steps[i]();
+ $qs('#js-privkey').innerHTML = keyPem;
+ $qs("#js-download-privkey-link").href =
+ "data:text/octet-stream;base64," + window.btoa(keyPem);
+ submitForm();
+ });
});
});
- }).then(function () {
- return submitForm();
});
};
@@ -424,11 +444,7 @@
steps[4].submit = function () {
console.log('Congrats! Auto advancing...');
-
- }).catch(function (err) {
- console.error(err.toString());
- window.alert("An error happened in the final step, but it's not your fault. Email aj@rootprojects.org and let him know.");
- });
+ window.alert("An error happened in the final step, but it's not your fault. Email aj@rootprojects.org and let him know.");
};
steps[5] = function () {
@@ -436,6 +452,8 @@
hideForms();
$qs('.js-acme-form-download').hidden = false;
};
+
+ // The kickoff
steps[1]();
var params = new URLSearchParams(window.location.search);
@@ -481,8 +499,8 @@
return true;
}
- return testRsaSupport().then(function () {
- console.info('[crypto] RSA is supported');
+ 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);
diff --git a/index.html b/index.html
index 18b177e..6874cc6 100644
--- a/index.html
+++ b/index.html
@@ -23,15 +23,10 @@
-
-
-
-
-
-
-
-
+
+
+