show challenges
Cette révision appartient à :
Parent
ec9a2606f6
révision
d646edf045
11
index.html
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 & 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
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
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;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Chargement…
Référencer dans un nouveau ticket