WIP Building out all features necessary for Let's Encrypt #6
4
app.js
4
app.js
|
@ -114,7 +114,7 @@
|
||||||
acme.init('https://acme-staging-v02.api.letsencrypt.org/directory').then(function (result) {
|
acme.init('https://acme-staging-v02.api.letsencrypt.org/directory').then(function (result) {
|
||||||
console.log('acme result', result);
|
console.log('acme result', result);
|
||||||
var privJwk = JSON.parse($('.js-jwk').innerText).private;
|
var privJwk = JSON.parse($('.js-jwk').innerText).private;
|
||||||
var email = $('.js-email').innerText;
|
var email = $('.js-email').value;
|
||||||
function checkTos(tos) {
|
function checkTos(tos) {
|
||||||
console.log("TODO checkbox for agree to terms");
|
console.log("TODO checkbox for agree to terms");
|
||||||
return tos;
|
return tos;
|
||||||
|
@ -130,7 +130,7 @@
|
||||||
, modulusLength: 2048
|
, modulusLength: 2048
|
||||||
}).then(function (pair) {
|
}).then(function (pair) {
|
||||||
console.log('domain keypair:', pair);
|
console.log('domain keypair:', pair);
|
||||||
var domains = ($('.js-domains').innerText||'example.com').split(/[, ]+/g);
|
var domains = ($('.js-domains').value||'example.com').split(/[, ]+/g);
|
||||||
return acme.certificates.create({
|
return acme.certificates.create({
|
||||||
accountKeypair: { privateKeyJwk: privJwk }
|
accountKeypair: { privateKeyJwk: privJwk }
|
||||||
, account: account
|
, account: account
|
||||||
|
|
85
lib/acme.js
85
lib/acme.js
|
@ -29,20 +29,19 @@ ACME.challengePrefixes = {
|
||||||
};
|
};
|
||||||
ACME.challengeTests = {
|
ACME.challengeTests = {
|
||||||
'http-01': function (me, auth) {
|
'http-01': function (me, auth) {
|
||||||
var url = 'http://' + auth.hostname + ACME.challengePrefixes['http-01'] + '/' + auth.token;
|
return me.http01(auth).then(function (keyAuth) {
|
||||||
return me.request({ method: 'GET', url: url }).then(function (resp) {
|
|
||||||
var err;
|
var err;
|
||||||
|
|
||||||
// TODO limit the number of bytes that are allowed to be downloaded
|
// TODO limit the number of bytes that are allowed to be downloaded
|
||||||
if (auth.keyAuthorization === resp.body.toString('utf8').trim()) {
|
if (auth.keyAuthorization === (keyAuth||'').trim()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = new Error(
|
err = new Error(
|
||||||
"Error: Failed HTTP-01 Pre-Flight / Dry Run.\n"
|
"Error: Failed HTTP-01 Pre-Flight / Dry Run.\n"
|
||||||
+ "curl '" + url + "'\n"
|
+ "curl '" + auth.challengeUrl + "'\n"
|
||||||
+ "Expected: '" + auth.keyAuthorization + "'\n"
|
+ "Expected: '" + auth.keyAuthorization + "'\n"
|
||||||
+ "Got: '" + resp.body + "'\n"
|
+ "Got: '" + keyAuth + "'\n"
|
||||||
+ "See https://git.coolaj86.com/coolaj86/acme-v2.js/issues/4"
|
+ "See https://git.coolaj86.com/coolaj86/acme-v2.js/issues/4"
|
||||||
);
|
);
|
||||||
err.code = 'E_FAIL_DRY_CHALLENGE';
|
err.code = 'E_FAIL_DRY_CHALLENGE';
|
||||||
|
@ -51,10 +50,7 @@ ACME.challengeTests = {
|
||||||
}
|
}
|
||||||
, 'dns-01': function (me, auth) {
|
, 'dns-01': function (me, auth) {
|
||||||
// remove leading *. on wildcard domains
|
// remove leading *. on wildcard domains
|
||||||
return me.dig({
|
return me.dns01(auth).then(function (ans) {
|
||||||
type: 'TXT'
|
|
||||||
, name: auth.dnsHost
|
|
||||||
}).then(function (ans) {
|
|
||||||
var err;
|
var err;
|
||||||
|
|
||||||
if (ans.answer.some(function (txt) {
|
if (ans.answer.some(function (txt) {
|
||||||
|
@ -154,7 +150,7 @@ ACME._registerAccount = function (me, options) {
|
||||||
, kid: options.externalAccount.id
|
, kid: options.externalAccount.id
|
||||||
, url: me._directoryUrls.newAccount
|
, url: me._directoryUrls.newAccount
|
||||||
}
|
}
|
||||||
, payload: Enc.strToBuf(JSON.stringify(pair.public))
|
, payload: Enc.binToBuf(JSON.stringify(pair.public))
|
||||||
}).then(function (jws) {
|
}).then(function (jws) {
|
||||||
body.externalAccountBinding = jws;
|
body.externalAccountBinding = jws;
|
||||||
return body;
|
return body;
|
||||||
|
@ -390,6 +386,7 @@ ACME._challengeToAuth = function (me, options, request, challenge, dryrun) {
|
||||||
// keyAuthorization = token || '.' || base64url(JWK_Thumbprint(accountKey))
|
// keyAuthorization = token || '.' || base64url(JWK_Thumbprint(accountKey))
|
||||||
auth.keyAuthorization = challenge.token + '.' + auth.thumbprint;
|
auth.keyAuthorization = challenge.token + '.' + auth.thumbprint;
|
||||||
// conflicts with ACME challenge id url is already in use, so we call this challengeUrl instead
|
// 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.challengeUrl = 'http://' + auth.identifier.value + ACME.challengePrefixes['http-01'] + '/' + auth.token;
|
||||||
auth.dnsHost = dnsPrefix + '.' + auth.hostname.replace('*.', '');
|
auth.dnsHost = dnsPrefix + '.' + auth.hostname.replace('*.', '');
|
||||||
|
|
||||||
|
@ -440,7 +437,7 @@ ACME._postChallenge = function (me, options, auth) {
|
||||||
options: options
|
options: options
|
||||||
, url: auth.url
|
, url: auth.url
|
||||||
, protected: { kid: options._kid }
|
, protected: { kid: options._kid }
|
||||||
, payload: Enc.strToBuf(JSON.stringify({ "status": "deactivated" }))
|
, payload: Enc.binToBuf(JSON.stringify({ "status": "deactivated" }))
|
||||||
}).then(function (resp) {
|
}).then(function (resp) {
|
||||||
if (me.debug) { console.debug('deactivate challenge: resp.body:'); }
|
if (me.debug) { console.debug('deactivate challenge: resp.body:'); }
|
||||||
if (me.debug) { console.debug(resp.body); }
|
if (me.debug) { console.debug(resp.body); }
|
||||||
|
@ -515,7 +512,7 @@ ACME._postChallenge = function (me, options, auth) {
|
||||||
options: options
|
options: options
|
||||||
, url: auth.url
|
, url: auth.url
|
||||||
, protected: { kid: options._kid }
|
, protected: { kid: options._kid }
|
||||||
, payload: Enc.strToBuf(JSON.stringify({}))
|
, payload: Enc.binToBuf(JSON.stringify({}))
|
||||||
}).then(function (resp) {
|
}).then(function (resp) {
|
||||||
if (me.debug) { console.debug('respond to challenge: resp.body:'); }
|
if (me.debug) { console.debug('respond to challenge: resp.body:'); }
|
||||||
if (me.debug) { console.debug(resp.body); }
|
if (me.debug) { console.debug(resp.body); }
|
||||||
|
@ -576,7 +573,7 @@ ACME._finalizeOrder = function (me, options, validatedDomains) {
|
||||||
options: options
|
options: options
|
||||||
, url: options._finalize
|
, url: options._finalize
|
||||||
, protected: { kid: options._kid }
|
, protected: { kid: options._kid }
|
||||||
, payload: Enc.strToBuf(payload)
|
, payload: Enc.binToBuf(payload)
|
||||||
}).then(function (resp) {
|
}).then(function (resp) {
|
||||||
if (me.debug) { console.debug('order finalized: resp.body:'); }
|
if (me.debug) { console.debug('order finalized: resp.body:'); }
|
||||||
if (me.debug) { console.debug(resp.body); }
|
if (me.debug) { console.debug(resp.body); }
|
||||||
|
@ -717,7 +714,7 @@ ACME._getCertificate = function (me, options) {
|
||||||
options: options
|
options: options
|
||||||
, url: me._directoryUrls.newOrder
|
, url: me._directoryUrls.newOrder
|
||||||
, protected: { kid: options._kid }
|
, protected: { kid: options._kid }
|
||||||
, payload: Enc.strToBuf(payload)
|
, payload: Enc.binToBuf(payload)
|
||||||
}).then(function (resp) {
|
}).then(function (resp) {
|
||||||
var location = resp.headers.location;
|
var location = resp.headers.location;
|
||||||
var setAuths;
|
var setAuths;
|
||||||
|
@ -818,21 +815,21 @@ ACME.create = function create(me) {
|
||||||
me.challengePrefixes = ACME.challengePrefixes;
|
me.challengePrefixes = ACME.challengePrefixes;
|
||||||
me.Keypairs = me.Keypairs || me.RSA || require('rsa-compat').RSA;
|
me.Keypairs = me.Keypairs || me.RSA || require('rsa-compat').RSA;
|
||||||
me._nonces = [];
|
me._nonces = [];
|
||||||
|
if (!me._baseUrl) {
|
||||||
|
me._baseUrl = "";
|
||||||
|
}
|
||||||
//me.Keypairs = me.Keypairs || require('keypairs');
|
//me.Keypairs = me.Keypairs || require('keypairs');
|
||||||
//me.request = me.request || require('@root/request');
|
//me.request = me.request || require('@root/request');
|
||||||
if (!me.dig) {
|
if (!me.dns01) {
|
||||||
me.dig = function (query) {
|
me.dns01 = function (auth) {
|
||||||
// TODO use digd.js
|
return ACME._dns01(me, auth);
|
||||||
return new me.request({ url: "/api/dns/" + query.name + "?type=" + query.type }).then(function (resp) {
|
|
||||||
if (!resp.body || !Array.isArray(resp.body.answer)) {
|
|
||||||
throw new Error("failed to get DNS response");
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
answer: resp.body.answer.map(function (ans) {
|
|
||||||
return { data: ans.data, ttl: ans.ttl };
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
// backwards compat
|
||||||
|
if (!me.dig) { me.dig = me.dns01; }
|
||||||
|
if (!me.http01) {
|
||||||
|
me.http01 = function (auth) {
|
||||||
|
return ACME._http01(me, auth);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -853,9 +850,22 @@ ACME.create = function create(me) {
|
||||||
if ('string' !== typeof me.directoryUrl) {
|
if ('string' !== typeof me.directoryUrl) {
|
||||||
throw new Error("you must supply either the ACME directory url as a string or an object of the ACME urls");
|
throw new Error("you must supply either the ACME directory url as a string or an object of the ACME urls");
|
||||||
}
|
}
|
||||||
|
var p = Promise.resolve();
|
||||||
|
if (!me.skipChallengeTest) {
|
||||||
|
p = me.request({ url: me._baseUrl + "/api/_acme_api_/" }).then(function (resp) {
|
||||||
|
if (resp.body.success) {
|
||||||
|
me._canCheckHttp01 = true;
|
||||||
|
me._canCheckDns01 = true;
|
||||||
|
}
|
||||||
|
}).catch(function () {
|
||||||
|
// ignore
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return p.then(function () {
|
||||||
return ACME._directory(me).then(function (resp) {
|
return ACME._directory(me).then(function (resp) {
|
||||||
return fin(resp.body);
|
return fin(resp.body);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
me.accounts = {
|
me.accounts = {
|
||||||
create: function (options) {
|
create: function (options) {
|
||||||
|
@ -992,6 +1002,29 @@ ACME._prnd = function (n) {
|
||||||
ACME._toHex = function (pair) {
|
ACME._toHex = function (pair) {
|
||||||
return parseInt(pair, 10).toString(16);
|
return parseInt(pair, 10).toString(16);
|
||||||
};
|
};
|
||||||
|
ACME._dns01 = function (me, auth) {
|
||||||
|
return new me.request({ url: me._baseUrl + "/api/dns/" + auth.dnsHost + "?type=TXT" }).then(function (resp) {
|
||||||
|
var err;
|
||||||
|
if (!resp.body || !Array.isArray(resp.body.answer)) {
|
||||||
|
err = new Error("failed to get DNS response");
|
||||||
|
console.error(err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
var result = {
|
||||||
|
answer: resp.body.answer.map(function (ans) {
|
||||||
|
return { data: ans.data, ttl: ans.ttl };
|
||||||
|
})
|
||||||
|
};
|
||||||
|
console.log(result);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
ACME._http01 = function (me, auth) {
|
||||||
|
var url = encodeURIComponent(auth.challengeUrl);
|
||||||
|
return new me.request({ url: me._baseUrl + "/api/http?url=" + url }).then(function (resp) {
|
||||||
|
return resp.body;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
Enc.bufToUrlBase64 = function (u8) {
|
Enc.bufToUrlBase64 = function (u8) {
|
||||||
return Enc.bufToBase64(u8)
|
return Enc.bufToBase64(u8)
|
||||||
|
|
Loading…
Reference in New Issue