show challenges

This commit is contained in:
AJ ONeal 2018-05-04 08:40:04 +00:00
parent ec9a2606f6
commit d646edf045
3 changed files with 235 additions and 70 deletions

View File

@ -25,10 +25,12 @@
<!-- Step 3 Set Challanges --> <!-- Step 3 Set Challanges -->
<form class="js-acme-form js-acme-form-challenges"> <form class="js-acme-form js-acme-form-challenges">
<div class="js-acme-challenges">
<label>How will you validate your domain?</label> <label>How will you validate your domain?</label>
<label><input class="js-acme-challenge-type" type="radio" value="http-01" checked required> <label><input class="js-acme-challenge-type" name="acme-challenge-type" type="radio" value="http-01" checked required>
File Upload to HTTP Web Server</label> File Upload to HTTP Web Server</label>
<label><input class="js-acme-challenge-type" type="radio" value="dns-01" required> <label><input class="js-acme-challenge-type" name="acme-challenge-type" type="radio" value="dns-01" required>
TXT Records on DNS Name Server</label> TXT Records on DNS Name Server</label>
Verify Domains &amp; Sub-Domains: Verify Domains &amp; Sub-Domains:
@ -66,11 +68,12 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div>
<div class="js-acme-wildcards"> <div class="js-acme-wildcard">
Verify Wildcard Domains: Verify Wildcard Domains:
<table class="js-acme-table-wildcards"> <table class="js-acme-table-wildcard">
<thead> <thead>
<tr> <tr>
<th>Hostname</th> <th>Hostname</th>

181
js/app.js
View File

@ -18,13 +18,32 @@
}); });
} }
function submitForm(ev) {
steps[i].submit(ev);
i += 1;
}
$qsa('.js-acme-form').forEach(function ($el) { $qsa('.js-acme-form').forEach(function ($el) {
$el.addEventListener('submit', function (ev) { $el.addEventListener('submit', function (ev) {
ev.preventDefault(); ev.preventDefault();
steps[i].submit(ev); submitForm(ev);
i += 1;
}); });
}); });
function updateChallengeType() {
var input = this || $qs('.js-acme-challenge-type');
console.log('ch type radio:', input.value);
$qs('.js-acme-table-wildcard').hidden = true;
$qs('.js-acme-table-http-01').hidden = true;
$qs('.js-acme-table-dns-01').hidden = true;
if (info.challenges.wildcard) {
$qs('.js-acme-table-wildcard').hidden = false;
}
if (info.challenges[input.value]) {
$qs('.js-acme-table-' + input.value).hidden = false;
}
}
$qsa('.js-acme-challenge-type').forEach(function ($el) {
$el.addEventListener('change', updateChallengeType);
});
steps[1] = function () { steps[1] = function () {
hideForms(); hideForms();
@ -85,6 +104,7 @@
console.log('account jwk:'); console.log('account jwk:');
console.log(jwk); console.log(jwk);
delete jwk.key_ops; delete jwk.key_ops;
info.jwk = jwk;
return BACME.accounts.sign({ return BACME.accounts.sign({
jwk: jwk jwk: jwk
, contacts: [ 'mailto:' + email ] , contacts: [ 'mailto:' + email ]
@ -117,6 +137,7 @@
return p2.then(function (_kid) { return p2.then(function (_kid) {
kid = _kid; kid = _kid;
info.kid = kid;
return BACME.orders.sign({ return BACME.orders.sign({
jwk: jwk jwk: jwk
, identifiers: info.identifiers , identifiers: info.identifiers
@ -124,12 +145,95 @@
}).then(function (signedOrder) { }).then(function (signedOrder) {
return BACME.orders.create({ return BACME.orders.create({
signedOrder: signedOrder signedOrder: signedOrder
}).then(function (/*challengeIndexes*/) { }).then(function (order) {
return BACME.challenges.all().then(function (challenges) { info.finalizeUrl = order.finalize;
console.log('challenges:'); return BACME.thumbprint({ jwk: jwk }).then(function (thumbprint) {
console.log(challenges); return BACME.challenges.all().then(function (claims) {
// TODO populate challenges in table console.log('claims:');
steps[i](); console.log(claims);
var obj = { 'dns-01': [], 'http-01': [], 'wildcard': [] };
var map = {
'http-01': '.js-acme-table-http-01'
, 'dns-01': '.js-acme-table-dns-01'
, 'wildcard': '.js-acme-table-wildcard'
}
var tpls = {};
info.challenges = obj;
Object.keys(map).forEach(function (k) {
var sel = map[k] + ' tbody';
console.log(sel);
tpls[k] = $qs(sel).innerHTML;
$qs(map[k] + ' tbody').innerHTML = '';
});
// TODO make Promise-friendly
return Promise.all(claims.map(function (claim) {
var hostname = claim.identifier.value;
return Promise.all(claim.challenges.map(function (c) {
var keyAuth = BACME.challenges['http-01']({
token: c.token
, thumbprint: thumbprint
, challengeDomain: hostname
});
return BACME.challenges['dns-01']({
keyAuth: keyAuth
, 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
};
obj[c.type].push(data);
console.log('');
console.log('CHALLENGE');
console.log(claim);
console.log(c);
console.log(data);
console.log('');
if (claim.wildcard) {
obj.wildcard.push(data);
$qs(map.wildcard).innerHTML += '<tr><td>' + data.hostname + '</td><td>' + data.dnsHost + '</td><td>' + data.dnsAnswer + '</td></tr>';
} else {
obj[data.type].push(data);
if ('dns-01' === data.type) {
$qs(map[data.type]).innerHTML += '<tr><td>' + data.hostname + '</td><td>' + data.dnsHost + '</td><td>' + data.dnsAnswer + '</td></tr>';
} else if ('http-01' === data.type) {
$qs(map[data.type]).innerHTML += '<tr><td>' + data.hostname + '</td><td>' + data.httpPath + '</td><td>' + data.httpAuth + '</td></tr>';
} else {
throw new Error('Unexpected type: ' + data.type);
}
}
});
}));
})).then(function () {
// hide wildcard if no wildcard
// hide http-01 and dns-01 if only wildcard
if (!obj.wildcard.length) {
$qs('.js-acme-wildcard').hidden = true;
}
if (!obj['http-01'].length) {
$qs('.js-acme-challenges').hidden = true;
}
updateChallengeType();
steps[i]();
});
});
}); });
}); });
}); });
@ -144,11 +248,72 @@
hideForms(); hideForms();
$qs('.js-acme-form-challenges').hidden = false; $qs('.js-acme-form-challenges').hidden = false;
}; };
steps[3].submit = function () {
var chType = $qs('.js-acme-challenge-type').value;
var ps = [];
// do each wildcard, if any
// do each challenge, by selected type only
[ 'wildcard', chType].forEach(function (typ) {
info.challenges[typ].forEach(function (ch) {
// { jwk, challengeUrl, accountId (kid) }
ps.push(BACME.challenges.accept({
jwk: info.jwk
, challengeUrl: ch.url
, accountId: info.kid
}));
});
});
return Promise.all(ps).then(function (results) {
console.log('challenge status:', results);
var polls = results.slice(0);
var allsWell = true;
function checkPolls() {
return new Promise(function (resolve) {
setTimeout(resolve, 1000);
}).then(function () {
return Promise.all(polls.map(function (poll) {
return BACME.challenges.check({ challengePollUrl: poll.url });
})).then(function () {
polls = polls.filter(function (poll) {
//return 'valid' !== poll.status && 'invalid' !== poll.status;
if ('pending' === poll.status) {
return true;
}
if ('valid' !== poll.status) {
allsWell = false;
console.warn('BAD POLL STATUS', poll);
}
// TODO show status in HTML
});
if (polls.length) {
return checkPolls();
}
return true;
});
});
}
return checkPolls().then(function () {
if (allsWell) {
return submitForm();
}
});
});
};
// spinner
steps[4] = function () { steps[4] = function () {
hideForms(); hideForms();
$qs('.js-acme-form-poll').hidden = false; $qs('.js-acme-form-poll').hidden = false;
} }
steps[4].submit = function () {
console.log('Congrats! Auto advancing...');
return BACME.order
};
steps[5] = function () { steps[5] = function () {
hideForms(); hideForms();

View File

@ -289,11 +289,10 @@ BACME.orders = {};
BACME.orders.sign = function (opts) { BACME.orders.sign = function (opts) {
var payload64 = BACME._jsto64({ identifiers: opts.identifiers }); var payload64 = BACME._jsto64({ identifiers: opts.identifiers });
var protected64 = BACME._jsto64(
{ nonce: nonce, alg: 'ES256', url: orderUrl, kid: opts.kid }
);
return BACME._importKey(opts.jwk).then(function (abstractKey) { return BACME._importKey(opts.jwk).then(function (abstractKey) {
var protected64 = BACME._jsto64(
{ nonce: nonce, alg: abstractKey.meta.alg/*'ES256'*/, url: orderUrl, kid: opts.kid }
);
console.log('abstractKey:'); console.log('abstractKey:');
console.log(abstractKey); console.log(abstractKey);
return BACME._sign({ return BACME._sign({
@ -378,7 +377,16 @@ BACME.challenges.view = function () {
BACME._logBody(result); BACME._logBody(result);
return { token: challenge.token, url: challenge.url, domain: result.identifier.value, challenges: result.challenges }; return {
challenges: result.challenges
, expires: result.expires
, identifier: result.identifier
, status: result.status
, wildcard: result.wildcard
//, token: challenge.token
//, url: challenge.url
//, domain: result.identifier.value,
};
}); });
}); });
}; };
@ -420,12 +428,13 @@ BACME.thumbprint = function (opts) {
}); });
}; };
BACME.challenges['http-01'] = function () { // { token, thumbprint, challengeDomain }
BACME.challenges['http-01'] = function (opts) {
// The contents of the key authorization file // The contents of the key authorization file
keyAuth = token + '.' + thumbprint; keyAuth = opts.token + '.' + opts.thumbprint;
// Where the key authorization file goes // Where the key authorization file goes
httpPath = 'http://' + challengeDomain + '/.well-known/acme-challenge/' + token; httpPath = 'http://' + opts.challengeDomain + '/.well-known/acme-challenge/' + opts.token;
console.log("echo '" + keyAuth + "' > '" + httpPath + "'"); console.log("echo '" + keyAuth + "' > '" + httpPath + "'");
@ -435,16 +444,17 @@ BACME.challenges['http-01'] = function () {
}; };
}; };
BACME.challenges['dns-01'] = function () { // { keyAuth }
BACME.challenges['dns-01'] = function (opts) {
return window.crypto.subtle.digest( return window.crypto.subtle.digest(
{ name: "SHA-256", } { name: "SHA-256", }
, textEncoder.encode(keyAuth) , textEncoder.encode(opts.keyAuth)
).then(function(hash){ ).then(function(hash){
dnsAuth = btoa(Array.prototype.map.call(new Uint8Array(hash), function (ch) { dnsAuth = btoa(Array.prototype.map.call(new Uint8Array(hash), function (ch) {
return String.fromCharCode(ch); return String.fromCharCode(ch);
}).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); }).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
dnsRecord = '_acme-challenge.' + challengeDomain; dnsRecord = '_acme-challenge.' + opts.challengeDomain;
console.log('DNS TXT Auth:'); console.log('DNS TXT Auth:');
// The name of the record // The name of the record
@ -462,55 +472,48 @@ BACME.challenges['dns-01'] = function () {
var challengePollUrl; var challengePollUrl;
BACME.challenges.accept = function () { // { jwk, challengeUrl, accountId (kid) }
BACME.challenges.accept = function (opts) {
var payload64 = BACME._jsto64( var payload64 = BACME._jsto64(
{} {}
); );
var protected64 = BACME._jsto64(
{ nonce: nonce, alg: 'ES256', url: challengeUrl, kid: accountId }
);
nonce = null; nonce = null;
return window.crypto.subtle.sign( return BACME._import(opts.jwk).then(function (abstractKey) {
{ name: "ECDSA", hash: { name: "SHA-256" } } var protected64 = BACME._jsto64(
, accountKeypair.privateKey { nonce: nonce, alg: abstractKey.meta.alg/*'ES256'*/, url: opts.challengeUrl, kid: opts.accountId }
, textEncoder.encode(protected64 + '.' + payload64) );
).then(function (signature) { return BACME._sign({
abstractKey: abstractKey
var sig64 = btoa(Array.prototype.map.call(new Uint8Array(signature), function (ch) { , payload64: payload64
return String.fromCharCode(ch); , protected64: protected64
}).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); });
}).then(function (signedAccept) {
var body = {
protected: protected64
, payload: payload64
, signature: sig64
};
return window.fetch( return window.fetch(
challengeUrl opts.challengeUrl
, { mode: 'cors' , { mode: 'cors'
, method: 'POST' , method: 'POST'
, headers: { 'Content-Type': 'application/jose+json' } , headers: { 'Content-Type': 'application/jose+json' }
, body: JSON.stringify(body) , body: JSON.stringify(signedAccept)
} }
).then(function (resp) { ).then(function (resp) {
BACME._logHeaders(resp); BACME._logHeaders(resp);
nonce = resp.headers.get('replay-nonce'); nonce = resp.headers.get('replay-nonce');
return resp.json().then(function (reply) { return resp.json().then(function (reply) {
challengePollUrl = reply.url; challengePollUrl = reply.url;
console.log('Challenge ACK:'); console.log('Challenge ACK:');
console.log(JSON.stringify(reply)); console.log(JSON.stringify(reply));
return reply;
}); });
}); });
}); });
}; };
BACME.challenges.check = function () { BACME.challenges.check = function (opts) {
return window.fetch(challengePollUrl, { mode: 'cors' }).then(function (resp) { return window.fetch(opts.challengePollUrl, { mode: 'cors' }).then(function (resp) {
BACME._logHeaders(resp); BACME._logHeaders(resp);
nonce = resp.headers.get('replay-nonce'); nonce = resp.headers.get('replay-nonce');
@ -558,38 +561,30 @@ BACME.orders.generateCsr = function (keypair, domains) {
var certificateUrl; var certificateUrl;
BACME.orders.finalize = function () { // { csr, jwk, finalizeUrl, accountId }
BACME.orders.finalize = function (opts) {
var payload64 = BACME._jsto64( var payload64 = BACME._jsto64(
{ csr: csr } { csr: opts.csr }
);
var protected64 = BACME._jsto64(
{ nonce: nonce, alg: 'ES256', url: finalizeUrl, kid: accountId }
); );
nonce = null; nonce = null;
return window.crypto.subtle.sign( return BACME._import(opts.jwk).then(function (abstractKey) {
{ name: "ECDSA", hash: { name: "SHA-256" } } var protected64 = BACME._jsto64(
, accountKeypair.privateKey { nonce: nonce, alg: abstractKey.meta.alg/*'ES256'*/, url: opts.finalizeUrl, kid: opts.accountId }
, textEncoder.encode(protected64 + '.' + payload64) );
).then(function (signature) { return BACME._sign({
abstractKey: abstractKey
var sig64 = btoa(Array.prototype.map.call(new Uint8Array(signature), function (ch) { , payload64: payload64
return String.fromCharCode(ch); , protected64: protected64
}).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); });
}).then(function (signedFinal) {
var body = {
protected: protected64
, payload: payload64
, signature: sig64
};
return window.fetch( return window.fetch(
finalizeUrl finalizeUrl
, { mode: 'cors' , { mode: 'cors'
, method: 'POST' , method: 'POST'
, headers: { 'Content-Type': 'application/jose+json' } , headers: { 'Content-Type': 'application/jose+json' }
, body: JSON.stringify(body) , body: JSON.stringify(signedFinal)
} }
).then(function (resp) { ).then(function (resp) {
BACME._logHeaders(resp); BACME._logHeaders(resp);
@ -598,6 +593,8 @@ BACME.orders.finalize = function () {
return resp.json().then(function (reply) { return resp.json().then(function (reply) {
certificateUrl = reply.certificate; certificateUrl = reply.certificate;
BACME._logBody(reply); BACME._logBody(reply);
return reply;
}); });
}); });
}); });