AJ ONeal 4 лет назад
Родитель
Сommit
d646edf045
  1. 11
      index.html
  2. 181
      js/app.js
  3. 113
      js/bacme.js

11
index.html

@ -25,10 +25,12 @@
<!-- Step 3 Set Challanges -->
<form class="js-acme-form js-acme-form-challenges">
<div class="js-acme-challenges">
<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>
<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>
Verify Domains &amp; Sub-Domains:
@ -66,11 +68,12 @@
</tr>
</tbody>
</table>
</div>
<div class="js-acme-wildcards">
<div class="js-acme-wildcard">
Verify Wildcard Domains:
<table class="js-acme-table-wildcards">
<table class="js-acme-table-wildcard">
<thead>
<tr>
<th>Hostname</th>

181
js/app.js

@ -18,13 +18,32 @@
});
}
function submitForm(ev) {
steps[i].submit(ev);
i += 1;
}
$qsa('.js-acme-form').forEach(function ($el) {
$el.addEventListener('submit', function (ev) {
ev.preventDefault();
steps[i].submit(ev);
i += 1;
submitForm(ev);
});
});
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 () {
hideForms();
@ -85,6 +104,7 @@
console.log('account jwk:');
console.log(jwk);
delete jwk.key_ops;
info.jwk = jwk;
return BACME.accounts.sign({
jwk: jwk
, contacts: [ 'mailto:' + email ]
@ -117,6 +137,7 @@
return p2.then(function (_kid) {
kid = _kid;
info.kid = kid;
return BACME.orders.sign({
jwk: jwk
, identifiers: info.identifiers
@ -124,12 +145,95 @@
}).then(function (signedOrder) {
return BACME.orders.create({
signedOrder: signedOrder
}).then(function (/*challengeIndexes*/) {
return BACME.challenges.all().then(function (challenges) {
console.log('challenges:');
console.log(challenges);
// TODO populate challenges in table
steps[i]();
}).then(function (order) {
info.finalizeUrl = order.finalize;
return BACME.thumbprint({ jwk: jwk }).then(function (thumbprint) {
return BACME.challenges.all().then(function (claims) {
console.log('claims:');
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();
$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 () {
hideForms();
$qs('.js-acme-form-poll').hidden = false;
}
steps[4].submit = function () {
console.log('Congrats! Auto advancing...');
return BACME.order
};
steps[5] = function () {
hideForms();

113
js/bacme.js

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

Загрузка…
Отмена
Сохранить