diff --git a/app.js b/app.js index 3531ee2..43eb90c 100644 --- a/app.js +++ b/app.js @@ -114,7 +114,7 @@ acme.init('https://acme-staging-v02.api.letsencrypt.org/directory').then(function (result) { console.log('acme result', result); var privJwk = JSON.parse($('.js-jwk').innerText).private; - var email = $('.js-email').innerText; + var email = $('.js-email').value; function checkTos(tos) { console.log("TODO checkbox for agree to terms"); return tos; @@ -130,7 +130,7 @@ , modulusLength: 2048 }).then(function (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({ accountKeypair: { privateKeyJwk: privJwk } , account: account diff --git a/lib/acme.js b/lib/acme.js index d5b3438..467e53c 100644 --- a/lib/acme.js +++ b/lib/acme.js @@ -29,20 +29,19 @@ ACME.challengePrefixes = { }; ACME.challengeTests = { 'http-01': function (me, auth) { - var url = 'http://' + auth.hostname + ACME.challengePrefixes['http-01'] + '/' + auth.token; - return me.request({ method: 'GET', url: url }).then(function (resp) { + return me.http01(auth).then(function (keyAuth) { var err; // 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; } err = new Error( "Error: Failed HTTP-01 Pre-Flight / Dry Run.\n" - + "curl '" + url + "'\n" + + "curl '" + auth.challengeUrl + "'\n" + "Expected: '" + auth.keyAuthorization + "'\n" - + "Got: '" + resp.body + "'\n" + + "Got: '" + keyAuth + "'\n" + "See https://git.coolaj86.com/coolaj86/acme-v2.js/issues/4" ); err.code = 'E_FAIL_DRY_CHALLENGE'; @@ -51,10 +50,7 @@ ACME.challengeTests = { } , 'dns-01': function (me, auth) { // remove leading *. on wildcard domains - return me.dig({ - type: 'TXT' - , name: auth.dnsHost - }).then(function (ans) { + return me.dns01(auth).then(function (ans) { var err; if (ans.answer.some(function (txt) { @@ -154,7 +150,7 @@ ACME._registerAccount = function (me, options) { , kid: options.externalAccount.id , url: me._directoryUrls.newAccount } - , payload: Enc.strToBuf(JSON.stringify(pair.public)) + , payload: Enc.binToBuf(JSON.stringify(pair.public)) }).then(function (jws) { body.externalAccountBinding = jws; return body; @@ -390,6 +386,7 @@ ACME._challengeToAuth = function (me, options, request, challenge, dryrun) { // 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('*.', ''); @@ -440,7 +437,7 @@ ACME._postChallenge = function (me, options, auth) { options: options , url: auth.url , protected: { kid: options._kid } - , payload: Enc.strToBuf(JSON.stringify({ "status": "deactivated" })) + , payload: Enc.binToBuf(JSON.stringify({ "status": "deactivated" })) }).then(function (resp) { if (me.debug) { console.debug('deactivate challenge: resp.body:'); } if (me.debug) { console.debug(resp.body); } @@ -515,7 +512,7 @@ ACME._postChallenge = function (me, options, auth) { options: options , url: auth.url , protected: { kid: options._kid } - , payload: Enc.strToBuf(JSON.stringify({})) + , payload: Enc.binToBuf(JSON.stringify({})) }).then(function (resp) { if (me.debug) { console.debug('respond to challenge: resp.body:'); } if (me.debug) { console.debug(resp.body); } @@ -576,7 +573,7 @@ ACME._finalizeOrder = function (me, options, validatedDomains) { options: options , url: options._finalize , protected: { kid: options._kid } - , payload: Enc.strToBuf(payload) + , payload: Enc.binToBuf(payload) }).then(function (resp) { if (me.debug) { console.debug('order finalized: resp.body:'); } if (me.debug) { console.debug(resp.body); } @@ -717,7 +714,7 @@ ACME._getCertificate = function (me, options) { options: options , url: me._directoryUrls.newOrder , protected: { kid: options._kid } - , payload: Enc.strToBuf(payload) + , payload: Enc.binToBuf(payload) }).then(function (resp) { var location = resp.headers.location; var setAuths; @@ -818,21 +815,21 @@ ACME.create = function create(me) { me.challengePrefixes = ACME.challengePrefixes; me.Keypairs = me.Keypairs || me.RSA || require('rsa-compat').RSA; me._nonces = []; + if (!me._baseUrl) { + me._baseUrl = ""; + } //me.Keypairs = me.Keypairs || require('keypairs'); //me.request = me.request || require('@root/request'); - if (!me.dig) { - me.dig = function (query) { - // TODO use digd.js - 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 }; - }) - }; - }); + if (!me.dns01) { + me.dns01 = function (auth) { + return ACME._dns01(me, auth); + }; + } + // backwards compat + if (!me.dig) { me.dig = me.dns01; } + if (!me.http01) { + me.http01 = function (auth) { + return ACME._http01(me, auth); }; } @@ -853,8 +850,21 @@ ACME.create = function create(me) { 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"); } - return ACME._directory(me).then(function (resp) { - return fin(resp.body); + 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 fin(resp.body); + }); }); }; me.accounts = { @@ -992,6 +1002,29 @@ ACME._prnd = function (n) { ACME._toHex = function (pair) { 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) { return Enc.bufToBase64(u8)