WIP almost working

This commit is contained in:
AJ ONeal 2019-05-21 00:37:07 -06:00
parent 11020cbf27
commit 2ca4c984f8
4 changed files with 308 additions and 307 deletions

View File

@ -33,13 +33,8 @@
<link rel="preload" href="./fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2" as="font" crossorigin="anonymous">
<link rel="preload" href="./fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2" as="font" crossorigin="anonymous">
<link rel="preload" href="./js/bacme.js" as="script">
<link rel="preload" href="./js/app.js" as="script">
<link rel="preload" href="./js/pkijs.org/v1.3.33/common.js" as="script">
<link rel="preload" href="./js/pkijs.org/v1.3.33/asn1.js" as="script">
<link rel="preload" href="./js/pkijs.org/v1.3.33/x509_schema.js" as="script">
<link rel="preload" href="./js/pkijs.org/v1.3.33/x509_simpl.js" as="script">
<link rel="preload" href="./js/browser-csr/v1.0.0-alpha/csr.js" as="script">
<link rel="preload" href="./js/bluecrypt-acme.js" as="script">
<link rel="preload" href="./js/greenlock.js" as="script">
</head>
<body hidden>
@ -342,14 +337,8 @@
<br>
<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>
<script src="./js/bluecrypt-acme.js"></script>
<script src="./js/greenlock.js"></script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-118745161-2"></script>

View File

@ -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);
}
};

View File

@ -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);

View File

@ -23,15 +23,10 @@
</style>
<link rel="preload" href="./app/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2" as="font" crossorigin="anonymous">
<link rel="preload" href="./app/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2" as="font" crossorigin="anonymous">
<link rel="prefetch" href="./app/fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.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">
<link rel="prefetch" href="./app/js/bluecrypt-acme.js" as="script">
<link rel="prefetch" href="./app/js/greenlock.js" as="script">
<link rel="prefetch" href="./js/app.js" as="script">
</head>
<body class="js-app-ready">
<script>
@ -47,7 +42,8 @@
</div>
<div class="column-row">
<div class="js-javascript-warning">
Greenlock will process the CSR in the browser and request the certificates directly from letsencrypt.org. Please enable Javascript before continuing.
Greenlock will process the CSR in the browser and request the certificates directly from letsencrypt.org.
Please enable Javascript before continuing.
</div>
<form id="js-acme-form" action="./app/" method=>
<div class="domain-psuedo-input">