Compare commits

..

No commits in common. "master" and "v3" have entirely different histories.
master ... v3

27 changed files with 380 additions and 427 deletions

View File

@ -2,16 +2,10 @@
| Built by [Root](https://therootcompany.com) for [Hub](https://rootprojects.org/hub) | Built by [Root](https://therootcompany.com) for [Hub](https://rootprojects.org/hub)
## Automated Certificate Management Environment ACME.js is a _low-level_ client for Let's Encrypt.
ACME ([RFC 8555](https://tools.ietf.org/html/rfc8555)) is the protocol that powers **Let's Encrypt**.
ACME.js is a _low-level_ client that speaks RFC 8555 to get Free SSL certificates through Let's Encrypt.
Looking for an **easy**, _high-level_ client? Check out [Greenlock.js](https://git.rootprojects.org/root/greenlock.js). Looking for an **easy**, _high-level_ client? Check out [Greenlock.js](https://git.rootprojects.org/root/greenlock.js).
# Quick Start
```js ```js
var acme = ACME.create({ maintainerEmail, packageAgent, notify }); var acme = ACME.create({ maintainerEmail, packageAgent, notify });
await acme.init(directoryUrl); await acme.init(directoryUrl);
@ -103,7 +97,7 @@ The public API encapsulates the three high-level steps of the ACME protocol:
- Challenge Presentation - Challenge Presentation
- Certificate Redemption - Certificate Redemption
## API Overview ## Overview
The core API can be show in just four functions: The core API can be show in just four functions:
@ -183,7 +177,7 @@ These `notify` events are intended for _logging_ and debugging, NOT as a data AP
Note: DO NOT rely on **undocumented properties**. They are experimental and **will break**. Note: DO NOT rely on **undocumented properties**. They are experimental and **will break**.
If you have a use case for a particular property **open an issue** - we can lock it down and document it. If you have a use case for a particular property **open an issue** - we can lock it down and document it.
# Example (Full Walkthrough) # Example
### See [examples/README.md](https://git.rootprojects.org/root/acme.js/src/branch/master/examples/README.md) ### See [examples/README.md](https://git.rootprojects.org/root/acme.js/src/branch/master/examples/README.md)

View File

@ -5,13 +5,12 @@ var U = require('./utils.js');
var Keypairs = require('@root/keypairs'); var Keypairs = require('@root/keypairs');
var Enc = require('@root/encoding/bytes'); var Enc = require('@root/encoding/bytes');
var agreers = {};
A._getAccountKid = function (me, options) { A._getAccountKid = function(me, options) {
// It's just fine if there's no account, we'll go get the key id we need via the existing key // It's just fine if there's no account, we'll go get the key id we need via the existing key
var kid = var kid =
options.kid || options.kid ||
(options.account && options.account.key && options.account.key.kid); (options.account && (options.account.key && options.account.key.kid));
if (kid) { if (kid) {
return Promise.resolve(kid); return Promise.resolve(kid);
@ -19,7 +18,7 @@ A._getAccountKid = function (me, options) {
//return Promise.reject(new Error("must include KeyID")); //return Promise.reject(new Error("must include KeyID"));
// This is an idempotent request. It'll return the same account for the same public key. // This is an idempotent request. It'll return the same account for the same public key.
return A._registerAccount(me, options).then(function (account) { return A._registerAccount(me, options).then(function(account) {
return account.key.kid; return account.key.kid;
}); });
}; };
@ -44,7 +43,7 @@ A._getAccountKid = function (me, options) {
"signature": "RZPOnYoPs1PhjszF...-nh6X1qtOFPB519I" "signature": "RZPOnYoPs1PhjszF...-nh6X1qtOFPB519I"
} }
*/ */
A._registerAccount = function (me, options) { A._registerAccount = function(me, options) {
//#console.debug('[ACME.js] accounts.create'); //#console.debug('[ACME.js] accounts.create');
function agree(agreed) { function agree(agreed) {
@ -58,7 +57,7 @@ A._registerAccount = function (me, options) {
} }
function getAccount() { function getAccount() {
return U._importKeypair(options.accountKey).then(function (pair) { return U._importKeypair(options.accountKey).then(function(pair) {
var contact; var contact;
if (options.contact) { if (options.contact) {
contact = options.contact.slice(0); contact = options.contact.slice(0);
@ -73,14 +72,14 @@ A._registerAccount = function (me, options) {
}; };
var pub = pair.public; var pub = pair.public;
return attachExtAcc(pub, accountRequest).then(function (accReq) { return attachExtAcc(pub, accountRequest).then(function(accReq) {
var payload = JSON.stringify(accReq); var payload = JSON.stringify(accReq);
return U._jwsRequest(me, { return U._jwsRequest(me, {
accountKey: options.accountKey, accountKey: options.accountKey,
url: me._directoryUrls.newAccount, url: me._directoryUrls.newAccount,
protected: { kid: false, jwk: pair.public }, protected: { kid: false, jwk: pair.public },
payload: Enc.strToBuf(payload) payload: Enc.strToBuf(payload)
}).then(function (resp) { }).then(function(resp) {
var account = resp.body; var account = resp.body;
if (resp.statusCode < 200 || resp.statusCode >= 300) { if (resp.statusCode < 200 || resp.statusCode >= 300) {
@ -127,27 +126,20 @@ A._registerAccount = function (me, options) {
url: me._directoryUrls.newAccount url: me._directoryUrls.newAccount
}, },
payload: Enc.strToBuf(JSON.stringify(pubkey)) payload: Enc.strToBuf(JSON.stringify(pubkey))
}).then(function (jws) { }).then(function(jws) {
accountRequest.externalAccountBinding = jws; accountRequest.externalAccountBinding = jws;
return accountRequest; return accountRequest;
}); });
} }
return Promise.resolve() return Promise.resolve()
.then(function () { .then(function() {
//#console.debug('[ACME.js] agreeToTerms'); //#console.debug('[ACME.js] agreeToTerms');
var agreeToTerms = options.agreeToTerms; var agreeToTerms = options.agreeToTerms;
if (!agreeToTerms) { if (!agreeToTerms) {
agreeToTerms = function (terms) { agreeToTerms = function(terms) {
if (agreers[options.subscriberEmail]) { console.log(
return true; 'By using this software you accept this Subscriber Agreement and Terms of Service:'
}
agreers[options.subscriberEmail] = true;
console.info();
console.info(
'By using this software you (' +
options.subscriberEmail +
') are agreeing to the following:'
); );
console.info( console.info(
'ACME Subscriber Agreement:', 'ACME Subscriber Agreement:',
@ -155,19 +147,18 @@ A._registerAccount = function (me, options) {
); );
console.info( console.info(
'Greenlock/ACME.js Terms of Use:', 'Greenlock/ACME.js Terms of Use:',
terms.acmeJsTermsUrl terms.terms.acmeJsTermsUrl
); );
console.info();
return true; return true;
}; };
} else if (true === agreeToTerms) { } else if (true === agreeToTerms) {
agreeToTerms = function (terms) { agreeToTerms = function(terms) {
return terms && true; return terms && true;
}; };
} }
return agreeToTerms({ return agreeToTerms({
acmeSubscriberTermsUrl: me._tos, acmeSubscriberTosUrl: me._tos,
acmeJsTermsUrl: 'https://rootprojects.org/legal/#terms' acmeJsTosUrl: 'https://rootprojects.org/legal/#terms'
}); });
}) })
.then(agree) .then(agree)

290
acme.js
View File

@ -43,7 +43,7 @@ ACME.create = function create(me) {
} }
if (!me.dns01) { if (!me.dns01) {
me.dns01 = function (ch) { me.dns01 = function(ch) {
return native._dns01(me, ch); return native._dns01(me, ch);
}; };
} }
@ -53,7 +53,7 @@ ACME.create = function create(me) {
if (!me._baseUrl) { if (!me._baseUrl) {
me._baseUrl = ''; me._baseUrl = '';
} }
me.http01 = function (ch) { me.http01 = function(ch) {
return native._http01(me, ch); return native._http01(me, ch);
}; };
} }
@ -62,11 +62,11 @@ ACME.create = function create(me) {
me.__request = http.request; me.__request = http.request;
} }
// passed to dependencies // passed to dependencies
me.request = function (opts) { me.request = function(opts) {
return U._request(me, opts); return U._request(me, opts);
}; };
me.init = function (opts) { me.init = function(opts) {
M.init(me); M.init(me);
function fin(dir) { function fin(dir) {
@ -90,14 +90,14 @@ ACME.create = function create(me) {
if (!me.skipChallengeTest) { if (!me.skipChallengeTest) {
p = native._canCheck(me); p = native._canCheck(me);
} }
return p.then(function () { 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) {
try { try {
return A._registerAccount(me, options); return A._registerAccount(me, options);
} catch (e) { } catch (e) {
@ -126,8 +126,8 @@ ACME.create = function create(me) {
}; };
*/ */
me.certificates = { me.certificates = {
create: function (options) { create: function(options) {
return A._getAccountKid(me, options).then(function (kid) { return A._getAccountKid(me, options).then(function(kid) {
ACME._normalizePresenters(me, options, options.challenges); ACME._normalizePresenters(me, options, options.challenges);
return ACME._getCertificate(me, options, kid); return ACME._getCertificate(me, options, kid);
}); });
@ -143,9 +143,9 @@ ACME.challengePrefixes = {
'dns-01': '_acme-challenge' 'dns-01': '_acme-challenge'
}; };
ACME.challengeTests = { ACME.challengeTests = {
'http-01': function (me, auth) { 'http-01': function(me, auth) {
var ch = auth.challenge; var ch = auth.challenge;
return me.http01(ch).then(function (keyAuth) { return me.http01(ch).then(function(keyAuth) {
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
@ -170,14 +170,14 @@ ACME.challengeTests = {
throw err; throw err;
}); });
}, },
'dns-01': function (me, auth) { 'dns-01': function(me, auth) {
// remove leading *. on wildcard domains // remove leading *. on wildcard domains
var ch = auth.challenge; var ch = auth.challenge;
return me.dns01(ch).then(function (ans) { return me.dns01(ch).then(function(ans) {
var err; var err;
if ( if (
ans.answer.some(function (txt) { ans.answer.some(function(txt) {
return ch.dnsAuthorization === txt.data[0]; return ch.dnsAuthorization === txt.data[0];
}) })
) { ) {
@ -199,7 +199,7 @@ ACME.challengeTests = {
} }
}; };
ACME._directory = function (me) { ACME._directory = function(me) {
// TODO cache the directory URL // TODO cache the directory URL
// GET-as-GET ok // GET-as-GET ok
@ -210,13 +210,13 @@ ACME._directory = function (me) {
// postChallenge // postChallenge
// finalizeOrder // finalizeOrder
// getCertificate // getCertificate
ACME._getCertificate = function (me, options, kid) { ACME._getCertificate = function(me, options, kid) {
//#console.debug('[ACME.js] certificates.create'); //#console.debug('[ACME.js] certificates.create');
return ACME._orderCert(me, options, kid).then(function (order) { return ACME._orderCert(me, options, kid).then(function(order) {
return ACME._finalizeOrder(me, options, kid, order); return ACME._finalizeOrder(me, options, kid, order);
}); });
}; };
ACME._normalizePresenters = function (me, options, presenters) { ACME._normalizePresenters = function(me, options, presenters) {
// Prefer this order for efficiency: // Prefer this order for efficiency:
// * http-01 is the fasest // * http-01 is the fasest
// * tls-alpn-01 is for networks that don't allow plain traffic // * tls-alpn-01 is for networks that don't allow plain traffic
@ -224,7 +224,7 @@ ACME._normalizePresenters = function (me, options, presenters) {
// but is required for private networks and wildcards // but is required for private networks and wildcards
var presenterTypes = Object.keys(options.challenges || {}); var presenterTypes = Object.keys(options.challenges || {});
options._presenterTypes = ['http-01', 'tls-alpn-01', 'dns-01'].filter( options._presenterTypes = ['http-01', 'tls-alpn-01', 'dns-01'].filter(
function (typ) { function(typ) {
return -1 !== presenterTypes.indexOf(typ); return -1 !== presenterTypes.indexOf(typ);
} }
); );
@ -244,7 +244,7 @@ ACME._normalizePresenters = function (me, options, presenters) {
ACME._propagationDelayWarning = true; ACME._propagationDelayWarning = true;
} }
} }
Object.keys(presenters || {}).forEach(function (k) { Object.keys(presenters || {}).forEach(function(k) {
var ch = presenters[k]; var ch = presenters[k];
var warned = false; var warned = false;
@ -280,9 +280,9 @@ ACME._normalizePresenters = function (me, options, presenters) {
} }
function promisify(fn) { function promisify(fn) {
return function (opts) { return function(opts) {
new Promise(function (resolve, reject) { new Promise(function(resolve, reject) {
fn(opts, function (err, result) { fn(opts, function(err, result) {
if (err) { if (err) {
reject(err); reject(err);
return; return;
@ -344,7 +344,7 @@ ACME._normalizePresenters = function (me, options, presenters) {
"signature": "H6ZXtGjTZyUnPeKn...wEA4TklBdh3e454g" "signature": "H6ZXtGjTZyUnPeKn...wEA4TklBdh3e454g"
} }
*/ */
ACME._getAuthorization = function (me, options, kid, zonenames, authUrl) { ACME._getAuthorization = function(me, options, kid, zonenames, authUrl) {
//#console.debug('\n[DEBUG] getAuthorization\n'); //#console.debug('\n[DEBUG] getAuthorization\n');
return U._jwsRequest(me, { return U._jwsRequest(me, {
@ -352,7 +352,7 @@ ACME._getAuthorization = function (me, options, kid, zonenames, authUrl) {
url: authUrl, url: authUrl,
protected: { kid: kid }, protected: { kid: kid },
payload: '' payload: ''
}).then(function (resp) { }).then(function(resp) {
// Pre-emptive rather than lazy for interfaces that need to show the // Pre-emptive rather than lazy for interfaces that need to show the
// challenges to the user first // challenges to the user first
return ACME._computeAuths( return ACME._computeAuths(
@ -362,7 +362,7 @@ ACME._getAuthorization = function (me, options, kid, zonenames, authUrl) {
resp.body, resp.body,
zonenames, zonenames,
false false
).then(function (auths) { ).then(function(auths) {
resp.body._rawChallenges = resp.body.challenges; resp.body._rawChallenges = resp.body.challenges;
resp.body.challenges = auths; resp.body.challenges = auths;
return resp.body; return resp.body;
@ -370,7 +370,7 @@ ACME._getAuthorization = function (me, options, kid, zonenames, authUrl) {
}); });
}; };
ACME._testChallengeOptions = function () { ACME._testChallengeOptions = function() {
// we want this to be the same for the whole group // we want this to be the same for the whole group
var chToken = ACME._prnd(16); var chToken = ACME._prnd(16);
return [ return [
@ -396,9 +396,9 @@ ACME._testChallengeOptions = function () {
]; ];
}; };
ACME._thumber = function (options, thumb) { ACME._thumber = function(options, thumb) {
var thumbPromise; var thumbPromise;
return function (key) { return function(key) {
if (thumb) { if (thumb) {
return Promise.resolve(thumb); return Promise.resolve(thumb);
} }
@ -408,7 +408,7 @@ ACME._thumber = function (options, thumb) {
if (!key) { if (!key) {
key = options.accountKey || options.accountKeypair; key = options.accountKey || options.accountKeypair;
} }
thumbPromise = U._importKeypair(key).then(function (pair) { thumbPromise = U._importKeypair(key).then(function(pair) {
return Keypairs.thumbprint({ return Keypairs.thumbprint({
jwk: pair.public jwk: pair.public
}); });
@ -417,9 +417,9 @@ ACME._thumber = function (options, thumb) {
}; };
}; };
ACME._dryRun = function (me, realOptions, zonenames) { ACME._dryRun = function(me, realOptions, zonenames) {
var noopts = {}; var noopts = {};
Object.keys(realOptions).forEach(function (key) { Object.keys(realOptions).forEach(function(key) {
noopts[key] = realOptions[key]; noopts[key] = realOptions[key];
}); });
noopts.order = {}; noopts.order = {};
@ -428,20 +428,20 @@ ACME._dryRun = function (me, realOptions, zonenames) {
var getThumbprint = ACME._thumber(noopts, ''); var getThumbprint = ACME._thumber(noopts, '');
return Promise.all( return Promise.all(
noopts.domains.map(function (identifierValue) { noopts.domains.map(function(identifierValue) {
// TODO we really only need one to pass, not all to pass // TODO we really only need one to pass, not all to pass
var challenges = ACME._testChallengeOptions(); var challenges = ACME._testChallengeOptions();
var wild = '*.' === identifierValue.slice(0, 2); var wild = '*.' === identifierValue.slice(0, 2);
if (wild) { if (wild) {
challenges = challenges.filter(function (ch) { challenges = challenges.filter(function(ch) {
return ch._wildcard; return ch._wildcard;
}); });
} }
challenges = challenges.filter(function (auth) { challenges = challenges.filter(function(auth) {
return me._canCheck[auth.type]; return me._canCheck[auth.type];
}); });
return getThumbprint().then(function (accountKeyThumb) { return getThumbprint().then(function(accountKeyThumb) {
var resp = { var resp = {
body: { body: {
identifier: { identifier: {
@ -464,32 +464,31 @@ ACME._dryRun = function (me, realOptions, zonenames) {
resp.body, resp.body,
zonenames, zonenames,
dryrun dryrun
).then(function (auths) { ).then(function(auths) {
resp.body.challenges = auths; resp.body.challenges = auths;
return resp.body; return resp.body;
}); });
}); });
}) })
).then(function (claims) { ).then(function(claims) {
var selected = []; var selected = [];
noopts.order._claims = claims.slice(0); noopts.order._claims = claims.slice(0);
noopts.notify = function (ev, params) { noopts.notify = function(ev, params) {
if ('_challenge_select' === ev) { if ('challenge_select' === ev) {
selected.push(params.challenge); selected.push(params.challenge);
} }
}; };
function clear() { function clear() {
selected.forEach(function (ch) { selected.forEach(function(ch) {
ACME._notify(me, noopts, 'challenge_remove', { ACME._notify(me, noopts, 'challenge_remove', {
altname: ch.altname, altname: ch.altname,
type: ch.type type: ch.type
//challenge: ch //challenge: ch
}); });
// ignore promise return
noopts.challenges[ch.type] noopts.challenges[ch.type]
.remove({ challenge: ch }) .remove({ challenge: ch })
.catch(function (err) { .catch(function(err) {
err.action = 'challenge_remove'; err.action = 'challenge_remove';
err.altname = ch.altname; err.altname = ch.altname;
err.type = ch.type; err.type = ch.type;
@ -499,7 +498,7 @@ ACME._dryRun = function (me, realOptions, zonenames) {
} }
return ACME._setChallenges(me, noopts, noopts.order) return ACME._setChallenges(me, noopts, noopts.order)
.catch(function (err) { .catch(function(err) {
clear(); clear();
throw err; throw err;
}) })
@ -510,12 +509,12 @@ ACME._dryRun = function (me, realOptions, zonenames) {
// Get the list of challenge types we can validate, // Get the list of challenge types we can validate,
// which is already ordered by preference. // which is already ordered by preference.
// Select the first matching offered challenge type // Select the first matching offered challenge type
ACME._chooseChallenge = function (options, results) { ACME._chooseChallenge = function(options, results) {
// For each of the challenge types that we support // For each of the challenge types that we support
var challenge; var challenge;
options._presenterTypes.some(function (chType) { options._presenterTypes.some(function(chType) {
// And for each of the challenge types that are allowed // And for each of the challenge types that are allowed
return results.challenges.some(function (ch) { return results.challenges.some(function(ch) {
// Check to see if there are any matches // Check to see if there are any matches
if (ch.type === chType) { if (ch.type === chType) {
challenge = ch; challenge = ch;
@ -527,7 +526,7 @@ ACME._chooseChallenge = function (options, results) {
return challenge; return challenge;
}; };
ACME._getZones = function (me, challenges, domains) { ACME._getZones = function(me, challenges, domains) {
var presenter = challenges['dns-01']; var presenter = challenges['dns-01'];
if (!presenter) { if (!presenter) {
return Promise.resolve([]); return Promise.resolve([]);
@ -538,7 +537,7 @@ ACME._getZones = function (me, challenges, domains) {
// a little bit of random to ensure that getZones() // a little bit of random to ensure that getZones()
// actually returns the zones and not the hosts as zones // actually returns the zones and not the hosts as zones
var dnsHosts = domains.map(function (d) { var dnsHosts = domains.map(function(d) {
var rnd = ACME._prnd(2); var rnd = ACME._prnd(2);
return rnd + '.' + d; return rnd + '.' + d;
}); });
@ -552,7 +551,7 @@ ACME._getZones = function (me, challenges, domains) {
}; };
ACME._challengesMap = { 'http-01': 0, 'dns-01': 0, 'tls-alpn-01': 0 }; ACME._challengesMap = { 'http-01': 0, 'dns-01': 0, 'tls-alpn-01': 0 };
ACME._computeAuths = function (me, options, thumb, authz, zonenames, dryrun) { ACME._computeAuths = function(me, options, thumb, authz, zonenames, dryrun) {
// we don't poison the dns cache with our dummy request // we don't poison the dns cache with our dummy request
var dnsPrefix = ACME.challengePrefixes['dns-01']; var dnsPrefix = ACME.challengePrefixes['dns-01'];
if (dryrun) { if (dryrun) {
@ -565,7 +564,7 @@ ACME._computeAuths = function (me, options, thumb, authz, zonenames, dryrun) {
var getThumbprint = ACME._thumber(options, thumb); var getThumbprint = ACME._thumber(options, thumb);
return Promise.all( return Promise.all(
authz.challenges.map(function (challenge) { authz.challenges.map(function(challenge) {
// Don't do extra work for challenges that we can't satisfy // Don't do extra work for challenges that we can't satisfy
var _types = options._presenterTypes; var _types = options._presenterTypes;
if (_types && !_types.includes(challenge.type)) { if (_types && !_types.includes(challenge.type)) {
@ -576,14 +575,14 @@ ACME._computeAuths = function (me, options, thumb, authz, zonenames, dryrun) {
// straight copy from the new order response // straight copy from the new order response
// { identifier, status, expires, challenges, wildcard } // { identifier, status, expires, challenges, wildcard }
Object.keys(authz).forEach(function (key) { Object.keys(authz).forEach(function(key) {
auth[key] = authz[key]; auth[key] = authz[key];
}); });
// copy from the challenge we've chosen // copy from the challenge we've chosen
// { type, status, url, token } // { type, status, url, token }
// (note the duplicate status overwrites the one above, but they should be the same) // (note the duplicate status overwrites the one above, but they should be the same)
Object.keys(challenge).forEach(function (key) { Object.keys(challenge).forEach(function(key) {
// don't confused devs with the id url // don't confused devs with the id url
auth[key] = challenge[key]; auth[key] = challenge[key];
}); });
@ -602,19 +601,19 @@ ACME._computeAuths = function (me, options, thumb, authz, zonenames, dryrun) {
challenge: auth, challenge: auth,
zone: zone, zone: zone,
dnsPrefix: dnsPrefix dnsPrefix: dnsPrefix
}).then(function (resp) { }).then(function(resp) {
Object.keys(resp).forEach(function (k) { Object.keys(resp).forEach(function(k) {
auth[k] = resp[k]; auth[k] = resp[k];
}); });
return auth; return auth;
}); });
}) })
).then(function (auths) { ).then(function(auths) {
return auths.filter(Boolean); return auths.filter(Boolean);
}); });
}; };
ACME.computeChallenge = function (opts) { ACME.computeChallenge = function(opts) {
var auth = opts.challenge; var auth = opts.challenge;
var hostname = auth.hostname || opts.hostname; var hostname = auth.hostname || opts.hostname;
var zone = opts.zone; var zone = opts.zone;
@ -623,7 +622,7 @@ ACME.computeChallenge = function (opts) {
var getThumbprint = opts._getThumbprint || ACME._thumber(opts, thumb); var getThumbprint = opts._getThumbprint || ACME._thumber(opts, thumb);
var dnsPrefix = opts.dnsPrefix || ACME.challengePrefixes['dns-01']; var dnsPrefix = opts.dnsPrefix || ACME.challengePrefixes['dns-01'];
return getThumbprint(accountKey).then(function (thumb) { return getThumbprint(accountKey).then(function(thumb) {
var resp = {}; var resp = {};
resp.thumbprint = thumb; resp.thumbprint = thumb;
// keyAuthorization = token + '.' + base64url(JWK_Thumbprint(accountKey)) // keyAuthorization = token + '.' + base64url(JWK_Thumbprint(accountKey))
@ -651,10 +650,10 @@ ACME.computeChallenge = function (opts) {
// _as part of_ the decision making process // _as part of_ the decision making process
return sha2 return sha2
.sum(256, resp.keyAuthorization) .sum(256, resp.keyAuthorization)
.then(function (hash) { .then(function(hash) {
return Enc.bufToUrlBase64(Uint8Array.from(hash)); return Enc.bufToUrlBase64(Uint8Array.from(hash));
}) })
.then(function (hash64) { .then(function(hash64) {
resp.dnsHost = dnsPrefix + '.' + hostname; // .replace('*.', ''); resp.dnsHost = dnsPrefix + '.' + hostname; // .replace('*.', '');
// deprecated // deprecated
@ -674,7 +673,7 @@ ACME.computeChallenge = function (opts) {
}); });
}; };
ACME._untame = function (name, wild) { ACME._untame = function(name, wild) {
if (wild) { if (wild) {
name = '*.' + name.replace('*.', ''); name = '*.' + name.replace('*.', '');
} }
@ -682,7 +681,7 @@ ACME._untame = function (name, wild) {
}; };
// https://tools.ietf.org/html/draft-ietf-acme-acme-10#section-7.5.1 // https://tools.ietf.org/html/draft-ietf-acme-acme-10#section-7.5.1
ACME._postChallenge = function (me, options, kid, auth) { ACME._postChallenge = function(me, options, kid, auth) {
var RETRY_INTERVAL = me.retryInterval || 1000; var RETRY_INTERVAL = me.retryInterval || 1000;
var DEAUTH_INTERVAL = me.deauthWait || 10 * 1000; var DEAUTH_INTERVAL = me.deauthWait || 10 * 1000;
var MAX_POLL = me.retryPoll || 8; var MAX_POLL = me.retryPoll || 8;
@ -716,7 +715,7 @@ ACME._postChallenge = function (me, options, kid, auth) {
url: auth.url, url: auth.url,
protected: { kid: kid }, protected: { kid: kid },
payload: Enc.strToBuf(JSON.stringify({ status: 'deactivated' })) payload: Enc.strToBuf(JSON.stringify({ status: 'deactivated' }))
}).then(function (/*#resp*/) { }).then(function(/*#resp*/) {
//#console.debug('deactivate challenge: resp.body:'); //#console.debug('deactivate challenge: resp.body:');
//#console.debug(resp.body); //#console.debug(resp.body);
return ACME._wait(DEAUTH_INTERVAL); return ACME._wait(DEAUTH_INTERVAL);
@ -756,8 +755,12 @@ ACME._postChallenge = function (me, options, kid, auth) {
altname: altname altname: altname
}); });
// State can be pending while waiting ACME server to transition to if ('processing' === resp.body.status) {
// processing //#console.debug('poll: again', auth.url);
return ACME._wait(RETRY_INTERVAL).then(pollStatus);
}
// This state should never occur
if ('pending' === resp.body.status) { if ('pending' === resp.body.status) {
if (count >= MAX_PEND) { if (count >= MAX_PEND) {
return ACME._wait(RETRY_INTERVAL) return ACME._wait(RETRY_INTERVAL)
@ -765,25 +768,13 @@ ACME._postChallenge = function (me, options, kid, auth) {
.then(respondToChallenge); .then(respondToChallenge);
} }
//#console.debug('poll: again', auth.url); //#console.debug('poll: again', auth.url);
return ACME._wait(RETRY_INTERVAL).then(pollStatus); return ACME._wait(RETRY_INTERVAL).then(respondToChallenge);
}
if ('processing' === resp.body.status) {
//#console.debug('poll: again', auth.url);
return ACME._wait(RETRY_INTERVAL).then(pollStatus);
} }
// REMOVE DNS records as soon as the state is non-processing // REMOVE DNS records as soon as the state is non-processing
// (valid or invalid or other) // (valid or invalid or other)
try { try {
options.challenges[auth.type] options.challenges[auth.type].remove({ challenge: auth });
.remove({ challenge: auth })
.catch(function (err) {
err.action = 'challenge_remove';
err.altname = auth.altname;
err.type = auth.type;
ACME._notify(me, options, 'error', err);
});
} catch (e) {} } catch (e) {}
if ('valid' === resp.body.status) { if ('valid' === resp.body.status) {
@ -859,7 +850,7 @@ ACME._postChallenge = function (me, options, kid, auth) {
}; };
// options = { domains, claims, challenges } // options = { domains, claims, challenges }
ACME._setChallenges = function (me, options, order) { ACME._setChallenges = function(me, options, order) {
var claims = order._claims.slice(0); var claims = order._claims.slice(0);
var valids = []; var valids = [];
var auths = []; var auths = [];
@ -876,11 +867,11 @@ ACME._setChallenges = function (me, options, order) {
} }
return Promise.resolve() return Promise.resolve()
.then(function () { .then(function() {
// For any challenges that are already valid, // For any challenges that are already valid,
// add to the list and skip any checks. // add to the list and skip any checks.
if ( if (
claim.challenges.some(function (ch) { claim.challenges.some(function(ch) {
if ('valid' === ch.status) { if ('valid' === ch.status) {
valids.push(ch); valids.push(ch);
return true; return true;
@ -902,15 +893,6 @@ ACME._setChallenges = function (me, options, order) {
placed.push(selected); placed.push(selected);
ACME._notify(me, options, 'challenge_select', { ACME._notify(me, options, 'challenge_select', {
// API-locked // API-locked
altname: ACME._untame(
claim.identifier.value,
claim.wildcard
),
type: selected.type,
dnsHost: selected.dnsHost,
keyAuthorization: selected.keyAuthorization
});
ACME._notify(me, options, '_challenge_select', {
altname: ACME._untame( altname: ACME._untame(
claim.identifier.value, claim.identifier.value,
claim.wildcard claim.wildcard
@ -960,7 +942,7 @@ ACME._setChallenges = function (me, options, order) {
} }
return ACME.challengeTests[auth.type](me, { challenge: auth }) return ACME.challengeTests[auth.type](me, { challenge: auth })
.then(function () { .then(function() {
valids.push(auth); valids.push(auth);
}) })
.then(checkNext); .then(checkNext);
@ -969,7 +951,7 @@ ACME._setChallenges = function (me, options, order) {
function removeAll(ch) { function removeAll(ch) {
options.challenges[ch.type] options.challenges[ch.type]
.remove({ challenge: ch }) .remove({ challenge: ch })
.catch(function (err) { .catch(function(err) {
err.action = 'challenge_remove'; err.action = 'challenge_remove';
err.altname = ch.altname; err.altname = ch.altname;
err.type = ch.type; err.type = ch.type;
@ -982,7 +964,7 @@ ACME._setChallenges = function (me, options, order) {
return setNext() return setNext()
.then(waitAll) .then(waitAll)
.then(checkNext) .then(checkNext)
.catch(function (err) { .catch(function(err) {
if (!options.debug) { if (!options.debug) {
placed.forEach(removeAll); placed.forEach(removeAll);
} }
@ -990,7 +972,7 @@ ACME._setChallenges = function (me, options, order) {
}); });
}; };
ACME._presentChallenges = function (me, options, kid, readyToPresent) { ACME._presentChallenges = function(me, options, kid, readyToPresent) {
// Actually sets the challenge via ACME // Actually sets the challenge via ACME
function challengeNext() { function challengeNext() {
// First set, First presented // First set, First presented
@ -1003,17 +985,24 @@ ACME._presentChallenges = function (me, options, kid, readyToPresent) {
// BTW, these are done serially rather than parallel on purpose // BTW, these are done serially rather than parallel on purpose
// (rate limits, propagation delays, etc) // (rate limits, propagation delays, etc)
return challengeNext().then(function () { return challengeNext().then(function() {
return readyToPresent; return readyToPresent;
}); });
}; };
ACME._pollOrderStatus = function (me, options, kid, order, verifieds) { ACME._pollOrderStatus = function(me, options, kid, order, verifieds) {
var csr64 = ACME._csrToUrlBase64(options.csr); var csr64 = ACME._csrToUrlBase64(options.csr);
var body = { csr: csr64 }; var body = { csr: csr64 };
var payload = JSON.stringify(body); var payload = JSON.stringify(body);
function processResponse(resp) { function pollCert() {
//#console.debug('[ACME.js] pollCert:', order._finalizeUrl);
return U._jwsRequest(me, {
accountKey: options.accountKey,
url: order._finalizeUrl,
protected: { kid: kid },
payload: Enc.strToBuf(payload)
}).then(function(resp) {
ACME._notify(me, options, 'certificate_status', { ACME._notify(me, options, 'certificate_status', {
subject: options.domains[0], subject: options.domains[0],
status: resp.body.status status: resp.body.status
@ -1029,7 +1018,7 @@ ACME._pollOrderStatus = function (me, options, kid, order, verifieds) {
} }
if ('processing' === resp.body.status) { if ('processing' === resp.body.status) {
return ACME._wait().then(pollStatus); return ACME._wait().then(pollCert);
} }
if (me.debug) { if (me.debug) {
@ -1069,31 +1058,13 @@ ACME._pollOrderStatus = function (me, options, kid, order, verifieds) {
return Promise.reject( return Promise.reject(
E.UNHANDLED_ORDER_STATUS(options, verifieds, resp) E.UNHANDLED_ORDER_STATUS(options, verifieds, resp)
); );
});
} }
function pollStatus() { return pollCert();
return U._jwsRequest(me, {
accountKey: options.accountKey,
url: order._orderUrl,
protected: { kid: kid },
payload: Enc.binToBuf('')
}).then(processResponse);
}
function finalizeOrder() {
//#console.debug('[ACME.js] pollCert:', order._finalizeUrl);
return U._jwsRequest(me, {
accountKey: options.accountKey,
url: order._finalizeUrl,
protected: { kid: kid },
payload: Enc.strToBuf(payload)
}).then(processResponse);
}
return finalizeOrder();
}; };
ACME._redeemCert = function (me, options, kid, voucher) { ACME._redeemCert = function(me, options, kid, voucher) {
//#console.debug('ACME.js: order was finalized'); //#console.debug('ACME.js: order was finalized');
// POST-as-GET // POST-as-GET
@ -1103,7 +1074,7 @@ ACME._redeemCert = function (me, options, kid, voucher) {
protected: { kid: kid }, protected: { kid: kid },
payload: Enc.binToBuf(''), payload: Enc.binToBuf(''),
json: true json: true
}).then(function (resp) { }).then(function(resp) {
//#console.debug('ACME.js: csr submitted and cert received:'); //#console.debug('ACME.js: csr submitted and cert received:');
// https://github.com/certbot/certbot/issues/5721 // https://github.com/certbot/certbot/issues/5721
@ -1122,12 +1093,12 @@ ACME._redeemCert = function (me, options, kid, voucher) {
}); });
}; };
ACME._finalizeOrder = function (me, options, kid, order) { ACME._finalizeOrder = function(me, options, kid, order) {
//#console.debug('[ACME.js] finalizeOrder:'); //#console.debug('[ACME.js] finalizeOrder:');
var readyToPresent; var readyToPresent;
return A._getAccountKid(me, options).then(function (kid) { return A._getAccountKid(me, options).then(function(kid) {
return ACME._setChallenges(me, options, order) return ACME._setChallenges(me, options, order)
.then(function (_readyToPresent) { .then(function(_readyToPresent) {
readyToPresent = _readyToPresent; readyToPresent = _readyToPresent;
return ACME._presentChallenges( return ACME._presentChallenges(
me, me,
@ -1136,28 +1107,28 @@ ACME._finalizeOrder = function (me, options, kid, order) {
readyToPresent readyToPresent
); );
}) })
.then(function () { .then(function() {
return ACME._pollOrderStatus( return ACME._pollOrderStatus(
me, me,
options, options,
kid, kid,
order, order,
readyToPresent.map(function (ch) { readyToPresent.map(function(ch) {
return ACME._untame(ch.identifier.value, ch.wildcard); return ACME._untame(ch.identifier.value, ch.wildcard);
}) })
); );
}) })
.then(function (voucher) { .then(function(voucher) {
return ACME._redeemCert(me, options, kid, voucher); return ACME._redeemCert(me, options, kid, voucher);
}); });
}); });
}; };
// Order a certificate request with all domains // Order a certificate request with all domains
ACME._orderCert = function (me, options, kid) { ACME._orderCert = function(me, options, kid) {
var certificateRequest = { var certificateRequest = {
// raw wildcard syntax MUST be used here // raw wildcard syntax MUST be used here
identifiers: options.domains.map(function (hostname) { identifiers: options.domains.map(function(hostname) {
return { type: 'dns', value: hostname }; return { type: 'dns', value: hostname };
}) })
//, "notBefore": "2016-01-01T00:00:00Z" //, "notBefore": "2016-01-01T00:00:00Z"
@ -1165,10 +1136,10 @@ ACME._orderCert = function (me, options, kid) {
}; };
return ACME._prepRequest(me, options) return ACME._prepRequest(me, options)
.then(function () { .then(function() {
return ACME._getZones(me, options.challenges, options.domains); return ACME._getZones(me, options.challenges, options.domains);
}) })
.then(function (zonenames) { .then(function(zonenames) {
var p; var p;
// Do a little dry-run / self-test // Do a little dry-run / self-test
if (!me.skipDryRun && !options.skipDryRun) { if (!me.skipDryRun && !options.skipDryRun) {
@ -1177,9 +1148,9 @@ ACME._orderCert = function (me, options, kid) {
p = Promise.resolve(null); p = Promise.resolve(null);
} }
return p.then(function () { return p.then(function() {
return A._getAccountKid(me, options) return A._getAccountKid(me, options)
.then(function (kid) { .then(function(kid) {
ACME._notify(me, options, 'certificate_order', { ACME._notify(me, options, 'certificate_order', {
// API-locked // API-locked
account: { key: { kid: kid } }, account: { key: { kid: kid } },
@ -1197,7 +1168,7 @@ ACME._orderCert = function (me, options, kid) {
payload: Enc.binToBuf(payload) payload: Enc.binToBuf(payload)
}); });
}) })
.then(function (resp) { .then(function(resp) {
var order = resp.body; var order = resp.body;
order._orderUrl = resp.headers.location; order._orderUrl = resp.headers.location;
order._finalizeUrl = resp.body.finalize; order._finalizeUrl = resp.body.finalize;
@ -1213,14 +1184,14 @@ ACME._orderCert = function (me, options, kid) {
return order; return order;
}) })
.then(function (order) { .then(function(order) {
return ACME._getAllChallenges( return ACME._getAllChallenges(
me, me,
options, options,
kid, kid,
zonenames, zonenames,
order order
).then(function (claims) { ).then(function(claims) {
order._claims = claims; order._claims = claims;
return order; return order;
}); });
@ -1229,8 +1200,8 @@ ACME._orderCert = function (me, options, kid) {
}); });
}; };
ACME._prepRequest = function (me, options) { ACME._prepRequest = function(me, options) {
return Promise.resolve().then(function () { return Promise.resolve().then(function() {
// TODO check that all presenterTypes are represented in challenges // TODO check that all presenterTypes are represented in challenges
if (!options._presenterTypes.length) { if (!options._presenterTypes.length) {
return Promise.reject( return Promise.reject(
@ -1248,8 +1219,14 @@ ACME._prepRequest = function (me, options) {
options.domains = options.domains || _csr.altnames; options.domains = options.domains || _csr.altnames;
_csr.altnames = _csr.altnames || []; _csr.altnames = _csr.altnames || [];
if ( if (
options.domains.slice(0).sort().join(' ') !== options.domains
_csr.altnames.slice(0).sort().join(' ') .slice(0)
.sort()
.join(' ') !==
_csr.altnames
.slice(0)
.sort()
.join(' ')
) { ) {
return Promise.reject( return Promise.reject(
new Error('certificate altnames do not match requested domains') new Error('certificate altnames do not match requested domains')
@ -1272,7 +1249,7 @@ ACME._prepRequest = function (me, options) {
} }
// a cheap check to see if there are non-ascii characters in any of the domains // a cheap check to see if there are non-ascii characters in any of the domains
var nonAsciiDomains = options.domains.some(function (d) { var nonAsciiDomains = options.domains.some(function(d) {
// IDN / unicode / utf-8 / punycode // IDN / unicode / utf-8 / punycode
return Enc.strToBin(d) !== d; return Enc.strToBin(d) !== d;
}); });
@ -1283,7 +1260,7 @@ ACME._prepRequest = function (me, options) {
} }
// TODO Promise.all()? // TODO Promise.all()?
(options._presenterTypes || []).forEach(function (key) { (options._presenterTypes || []).forEach(function(key) {
var presenter = options.challenges[key]; var presenter = options.challenges[key];
if ( if (
'function' === typeof presenter.init && 'function' === typeof presenter.init &&
@ -1297,7 +1274,7 @@ ACME._prepRequest = function (me, options) {
}; };
// Request a challenge for each authorization in the order // Request a challenge for each authorization in the order
ACME._getAllChallenges = function (me, options, kid, zonenames, order) { ACME._getAllChallenges = function(me, options, kid, zonenames, order) {
var claims = []; var claims = [];
//#console.debug("[acme-v2] POST newOrder has authorizations"); //#console.debug("[acme-v2] POST newOrder has authorizations");
var challengeAuths = order.authorizations.slice(0); var challengeAuths = order.authorizations.slice(0);
@ -1314,14 +1291,14 @@ ACME._getAllChallenges = function (me, options, kid, zonenames, order) {
kid, kid,
zonenames, zonenames,
authUrl authUrl
).then(function (claim) { ).then(function(claim) {
// var domain = options.domains[i]; // claim.identifier.value // var domain = options.domains[i]; // claim.identifier.value
claims.push(claim); claims.push(claim);
return getNext(); return getNext();
}); });
} }
return getNext().then(function () { return getNext().then(function() {
return claims; return claims;
}); });
}; };
@ -1339,12 +1316,12 @@ ACME.splitPemChain = function splitPemChain(str) {
return str return str
.trim() .trim()
.split(/[\r\n]{2,}/g) .split(/[\r\n]{2,}/g)
.map(function (str) { .map(function(str) {
return str + '\n'; return str + '\n';
}); });
}; };
ACME._csrToUrlBase64 = function (csr) { ACME._csrToUrlBase64 = function(csr) {
// if der, convert to base64 // if der, convert to base64
if ('string' !== typeof csr) { if ('string' !== typeof csr) {
csr = Enc.bufToUrlBase64(csr); csr = Enc.bufToUrlBase64(csr);
@ -1353,16 +1330,21 @@ ACME._csrToUrlBase64 = function (csr) {
// TODO use PEM.parseBlock() // TODO use PEM.parseBlock()
// nix PEM headers, if any // nix PEM headers, if any
if ('-' === csr[0]) { if ('-' === csr[0]) {
csr = csr.split(/\n+/).slice(1, -1).join(''); csr = csr
.split(/\n+/)
.slice(1, -1)
.join('');
} }
return Enc.base64ToUrlBase64(csr.trim().replace(/\s+/g, '')); return Enc.base64ToUrlBase64(csr.trim().replace(/\s+/g, ''));
}; };
// In v8 this is crypto random, but we're just using it for pseudorandom // In v8 this is crypto random, but we're just using it for pseudorandom
ACME._prnd = function (n) { ACME._prnd = function(n) {
var rnd = ''; var rnd = '';
while (rnd.length / 2 < n) { while (rnd.length / 2 < n) {
var i = Math.random().toString().substr(2); var i = Math.random()
.toString()
.substr(2);
var h = parseInt(i, 10).toString(16); var h = parseInt(i, 10).toString(16);
if (h.length % 2) { if (h.length % 2) {
h = '0' + h; h = '0' + h;
@ -1372,7 +1354,7 @@ ACME._prnd = function (n) {
return rnd.substr(0, n * 2); return rnd.substr(0, n * 2);
}; };
ACME._notify = function (me, options, ev, params) { ACME._notify = function(me, options, ev, params) {
if (!options.notify && !me.notify) { if (!options.notify && !me.notify) {
//console.info(ev, params); //console.info(ev, params);
return; return;
@ -1386,7 +1368,7 @@ ACME._notify = function (me, options, ev, params) {
}; };
ACME._wait = function wait(ms) { ACME._wait = function wait(ms) {
return new Promise(function (resolve) { return new Promise(function(resolve) {
setTimeout(resolve, ms || 1100); setTimeout(resolve, ms || 1100);
}); });
}; };
@ -1403,12 +1385,12 @@ function newZoneRegExp(zonename) {
function pluckZone(zonenames, dnsHost) { function pluckZone(zonenames, dnsHost) {
return zonenames return zonenames
.filter(function (zonename) { .filter(function(zonename) {
// the only character that needs to be escaped for regex // the only character that needs to be escaped for regex
// and is allowed in a domain name is '.' // and is allowed in a domain name is '.'
return newZoneRegExp(zonename).test(dnsHost); return newZoneRegExp(zonename).test(dnsHost);
}) })
.sort(function (a, b) { .sort(function(a, b) {
// longest match first // longest match first
return b.length - a.length; return b.length - a.length;
})[0]; })[0];

View File

@ -1,5 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
(async function () { (async function() {
'use strict'; 'use strict';
var UglifyJS = require('uglify-js'); var UglifyJS = require('uglify-js');
@ -22,7 +22,7 @@
'../lib/asn1-parser.js', '../lib/asn1-parser.js',
'../lib/csr.js', '../lib/csr.js',
'../lib/acme.js' '../lib/acme.js'
].map(async function (file) { ].map(async function(file) {
return (await readFile(path.join(__dirname, file), 'utf8')).trim(); return (await readFile(path.join(__dirname, file), 'utf8')).trim();
}) })
); );

View File

@ -2,13 +2,13 @@
var E = module.exports; var E = module.exports;
E.NO_SUITABLE_CHALLENGE = function (domain, challenges, presenters) { E.NO_SUITABLE_CHALLENGE = function(domain, challenges, presenters) {
// Bail with a descriptive message if no usable challenge could be selected // Bail with a descriptive message if no usable challenge could be selected
// For example, wildcards require dns-01 and, if we don't have that, we have to bail // For example, wildcards require dns-01 and, if we don't have that, we have to bail
var enabled = presenters.join(', ') || 'none'; var enabled = presenters.join(', ') || 'none';
var suitable = var suitable =
challenges challenges
.map(function (r) { .map(function(r) {
return r.type; return r.type;
}) })
.join(', ') || 'none'; .join(', ') || 'none';
@ -24,7 +24,7 @@ E.NO_SUITABLE_CHALLENGE = function (domain, challenges, presenters) {
' ).' ' ).'
); );
}; };
E.UNHANDLED_ORDER_STATUS = function (options, domains, resp) { E.UNHANDLED_ORDER_STATUS = function(options, domains, resp) {
return new Error( return new Error(
"Didn't finalize order: Unhandled status '" + "Didn't finalize order: Unhandled status '" +
resp.body.status + resp.body.status +
@ -41,7 +41,7 @@ E.UNHANDLED_ORDER_STATUS = function (options, domains, resp) {
'Please open an issue at https://git.rootprojects.org/root/acme.js' 'Please open an issue at https://git.rootprojects.org/root/acme.js'
); );
}; };
E.DOUBLE_READY_ORDER = function (options, domains, resp) { E.DOUBLE_READY_ORDER = function(options, domains, resp) {
return new Error( return new Error(
"Did not finalize order: status 'ready'." + "Did not finalize order: status 'ready'." +
" Hmmm... this state shouldn't be possible here. That was the last state." + " Hmmm... this state shouldn't be possible here. That was the last state." +
@ -57,7 +57,7 @@ E.DOUBLE_READY_ORDER = function (options, domains, resp) {
'Please open an issue at https://git.rootprojects.org/root/acme.js' 'Please open an issue at https://git.rootprojects.org/root/acme.js'
); );
}; };
E.ORDER_INVALID = function (options, domains, resp) { E.ORDER_INVALID = function(options, domains, resp) {
return new Error( return new Error(
"Did not finalize order: status 'invalid'." + "Did not finalize order: status 'invalid'." +
' Best guess: One or more of the domain challenges could not be verified' + ' Best guess: One or more of the domain challenges could not be verified' +
@ -71,7 +71,7 @@ E.ORDER_INVALID = function (options, domains, resp) {
JSON.stringify(resp.body, null, 2) JSON.stringify(resp.body, null, 2)
); );
}; };
E.NO_AUTHORIZATIONS = function (options, resp) { E.NO_AUTHORIZATIONS = function(options, resp) {
return new Error( return new Error(
"[acme-v2.js] authorizations were not fetched for '" + "[acme-v2.js] authorizations were not fetched for '" +
options.domains.join() + options.domains.join() +

View File

@ -1,5 +1,5 @@
/*global Promise*/ /*global Promise*/
(function () { (function() {
'use strict'; 'use strict';
var Keypairs = require('@root/keypairs'); var Keypairs = require('@root/keypairs');
@ -29,8 +29,8 @@
console.log('hello'); console.log('hello');
// Show different options for ECDSA vs RSA // Show different options for ECDSA vs RSA
$$('input[name="kty"]').forEach(function ($el) { $$('input[name="kty"]').forEach(function($el) {
$el.addEventListener('change', function (ev) { $el.addEventListener('change', function(ev) {
console.log(this); console.log(this);
console.log(ev); console.log(ev);
if ('RSA' === ev.target.value) { if ('RSA' === ev.target.value) {
@ -44,20 +44,20 @@
}); });
// Generate a key on submit // Generate a key on submit
$('form.js-keygen').addEventListener('submit', function (ev) { $('form.js-keygen').addEventListener('submit', function(ev) {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
$('.js-loading').hidden = false; $('.js-loading').hidden = false;
$('.js-jwk').hidden = true; $('.js-jwk').hidden = true;
$('.js-toc-der-public').hidden = true; $('.js-toc-der-public').hidden = true;
$('.js-toc-der-private').hidden = true; $('.js-toc-der-private').hidden = true;
$$('.js-toc-pem').forEach(function ($el) { $$('.js-toc-pem').forEach(function($el) {
$el.hidden = true; $el.hidden = true;
}); });
$$('input').map(function ($el) { $$('input').map(function($el) {
$el.disabled = true; $el.disabled = true;
}); });
$$('button').map(function ($el) { $$('button').map(function($el) {
$el.disabled = true; $el.disabled = true;
}); });
var opts = { var opts = {
@ -67,7 +67,7 @@
}; };
var then = Date.now(); var then = Date.now();
console.log('opts', opts); console.log('opts', opts);
Keypairs.generate(opts).then(function (results) { Keypairs.generate(opts).then(function(results) {
console.log('Key generation time:', Date.now() - then + 'ms'); console.log('Key generation time:', Date.now() - then + 'ms');
var pubDer; var pubDer;
var privDer; var privDer;
@ -77,19 +77,19 @@
Eckles.export({ Eckles.export({
jwk: results.private, jwk: results.private,
format: 'sec1' format: 'sec1'
}).then(function (pem) { }).then(function(pem) {
$('.js-input-pem-sec1-private').innerText = pem; $('.js-input-pem-sec1-private').innerText = pem;
$('.js-toc-pem-sec1-private').hidden = false; $('.js-toc-pem-sec1-private').hidden = false;
}); });
Eckles.export({ Eckles.export({
jwk: results.private, jwk: results.private,
format: 'pkcs8' format: 'pkcs8'
}).then(function (pem) { }).then(function(pem) {
$('.js-input-pem-pkcs8-private').innerText = pem; $('.js-input-pem-pkcs8-private').innerText = pem;
$('.js-toc-pem-pkcs8-private').hidden = false; $('.js-toc-pem-pkcs8-private').hidden = false;
}); });
Eckles.export({ jwk: results.public, public: true }).then( Eckles.export({ jwk: results.public, public: true }).then(
function (pem) { function(pem) {
$('.js-input-pem-spki-public').innerText = pem; $('.js-input-pem-spki-public').innerText = pem;
$('.js-toc-pem-spki-public').hidden = false; $('.js-toc-pem-spki-public').hidden = false;
} }
@ -100,25 +100,25 @@
Rasha.export({ Rasha.export({
jwk: results.private, jwk: results.private,
format: 'pkcs1' format: 'pkcs1'
}).then(function (pem) { }).then(function(pem) {
$('.js-input-pem-pkcs1-private').innerText = pem; $('.js-input-pem-pkcs1-private').innerText = pem;
$('.js-toc-pem-pkcs1-private').hidden = false; $('.js-toc-pem-pkcs1-private').hidden = false;
}); });
Rasha.export({ Rasha.export({
jwk: results.private, jwk: results.private,
format: 'pkcs8' format: 'pkcs8'
}).then(function (pem) { }).then(function(pem) {
$('.js-input-pem-pkcs8-private').innerText = pem; $('.js-input-pem-pkcs8-private').innerText = pem;
$('.js-toc-pem-pkcs8-private').hidden = false; $('.js-toc-pem-pkcs8-private').hidden = false;
}); });
Rasha.export({ jwk: results.public, format: 'pkcs1' }).then( Rasha.export({ jwk: results.public, format: 'pkcs1' }).then(
function (pem) { function(pem) {
$('.js-input-pem-pkcs1-public').innerText = pem; $('.js-input-pem-pkcs1-public').innerText = pem;
$('.js-toc-pem-pkcs1-public').hidden = false; $('.js-toc-pem-pkcs1-public').hidden = false;
} }
); );
Rasha.export({ jwk: results.public, format: 'spki' }).then( Rasha.export({ jwk: results.public, format: 'spki' }).then(
function (pem) { function(pem) {
$('.js-input-pem-spki-public').innerText = pem; $('.js-input-pem-spki-public').innerText = pem;
$('.js-toc-pem-spki-public').hidden = false; $('.js-toc-pem-spki-public').hidden = false;
} }
@ -132,10 +132,10 @@
$('.js-jwk').innerText = JSON.stringify(results, null, 2); $('.js-jwk').innerText = JSON.stringify(results, null, 2);
$('.js-loading').hidden = true; $('.js-loading').hidden = true;
$('.js-jwk').hidden = false; $('.js-jwk').hidden = false;
$$('input').map(function ($el) { $$('input').map(function($el) {
$el.disabled = false; $el.disabled = false;
}); });
$$('button').map(function ($el) { $$('button').map(function($el) {
$el.disabled = false; $el.disabled = false;
}); });
$('.js-toc-jwk').hidden = false; $('.js-toc-jwk').hidden = false;
@ -145,7 +145,7 @@
}); });
}); });
$('form.js-acme-account').addEventListener('submit', function (ev) { $('form.js-acme-account').addEventListener('submit', function(ev) {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
$('.js-loading').hidden = false; $('.js-loading').hidden = false;
@ -155,7 +155,7 @@
}); });
acme.init( acme.init(
'https://acme-staging-v02.api.letsencrypt.org/directory' 'https://acme-staging-v02.api.letsencrypt.org/directory'
).then(function (result) { ).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').value; var email = $('.js-email').value;
@ -165,7 +165,7 @@
agreeToTerms: checkTos, agreeToTerms: checkTos,
accountKeypair: { privateKeyJwk: privJwk } accountKeypair: { privateKeyJwk: privJwk }
}) })
.then(function (account) { .then(function(account) {
console.log('account created result:', account); console.log('account created result:', account);
accountStuff.account = account; accountStuff.account = account;
accountStuff.privateJwk = privJwk; accountStuff.privateJwk = privJwk;
@ -177,7 +177,7 @@
'.js-acme-account-response' '.js-acme-account-response'
).innerText = JSON.stringify(account, null, 2); ).innerText = JSON.stringify(account, null, 2);
}) })
.catch(function (err) { .catch(function(err) {
console.error('A bad thing happened:'); console.error('A bad thing happened:');
console.error(err); console.error(err);
window.alert( window.alert(
@ -187,13 +187,13 @@
}); });
}); });
$('form.js-csr').addEventListener('submit', function (ev) { $('form.js-csr').addEventListener('submit', function(ev) {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
generateCsr(); generateCsr();
}); });
$('form.js-acme-order').addEventListener('submit', function (ev) { $('form.js-acme-order').addEventListener('submit', function(ev) {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
var account = accountStuff.account; var account = accountStuff.account;
@ -204,7 +204,7 @@
var domains = ($('.js-domains').value || 'example.com').split( var domains = ($('.js-domains').value || 'example.com').split(
/[, ]+/g /[, ]+/g
); );
return getDomainPrivkey().then(function (domainPrivJwk) { return getDomainPrivkey().then(function(domainPrivJwk) {
console.log('Has CSR already?'); console.log('Has CSR already?');
console.log(accountStuff.csr); console.log(accountStuff.csr);
return acme.certificates return acme.certificates
@ -219,11 +219,11 @@
agreeToTerms: checkTos, agreeToTerms: checkTos,
challenges: { challenges: {
'dns-01': { 'dns-01': {
set: function (opts) { set: function(opts) {
console.info('dns-01 set challenge:'); console.info('dns-01 set challenge:');
console.info('TXT', opts.dnsHost); console.info('TXT', opts.dnsHost);
console.info(opts.dnsAuthorization); console.info(opts.dnsAuthorization);
return new Promise(function (resolve) { return new Promise(function(resolve) {
while ( while (
!window.confirm( !window.confirm(
'Did you set the challenge?' 'Did you set the challenge?'
@ -232,11 +232,11 @@
resolve(); resolve();
}); });
}, },
remove: function (opts) { remove: function(opts) {
console.log('dns-01 remove challenge:'); console.log('dns-01 remove challenge:');
console.info('TXT', opts.dnsHost); console.info('TXT', opts.dnsHost);
console.info(opts.dnsAuthorization); console.info(opts.dnsAuthorization);
return new Promise(function (resolve) { return new Promise(function(resolve) {
while ( while (
!window.confirm( !window.confirm(
'Did you delete the challenge?' 'Did you delete the challenge?'
@ -247,11 +247,11 @@
} }
}, },
'http-01': { 'http-01': {
set: function (opts) { set: function(opts) {
console.info('http-01 set challenge:'); console.info('http-01 set challenge:');
console.info(opts.challengeUrl); console.info(opts.challengeUrl);
console.info(opts.keyAuthorization); console.info(opts.keyAuthorization);
return new Promise(function (resolve) { return new Promise(function(resolve) {
while ( while (
!window.confirm( !window.confirm(
'Did you set the challenge?' 'Did you set the challenge?'
@ -260,11 +260,11 @@
resolve(); resolve();
}); });
}, },
remove: function (opts) { remove: function(opts) {
console.log('http-01 remove challenge:'); console.log('http-01 remove challenge:');
console.info(opts.challengeUrl); console.info(opts.challengeUrl);
console.info(opts.keyAuthorization); console.info(opts.keyAuthorization);
return new Promise(function (resolve) { return new Promise(function(resolve) {
while ( while (
!window.confirm( !window.confirm(
'Did you delete the challenge?' 'Did you delete the challenge?'
@ -279,7 +279,7 @@
$('input[name="acme-challenge-type"]:checked').value $('input[name="acme-challenge-type"]:checked').value
] ]
}) })
.then(function (results) { .then(function(results) {
console.log('Got Certificates:'); console.log('Got Certificates:');
console.log(results); console.log(results);
$('.js-toc-acme-order-response').hidden = false; $('.js-toc-acme-order-response').hidden = false;
@ -289,7 +289,7 @@
2 2
); );
}) })
.catch(function (err) { .catch(function(err) {
console.error('challenge failed:'); console.error('challenge failed:');
console.error(err); console.error(err);
window.alert( window.alert(
@ -310,7 +310,7 @@
kty: $('input[name="kty"]:checked').value, kty: $('input[name="kty"]:checked').value,
namedCurve: $('input[name="ec-crv"]:checked').value, namedCurve: $('input[name="ec-crv"]:checked').value,
modulusLength: $('input[name="rsa-len"]:checked').value modulusLength: $('input[name="rsa-len"]:checked').value
}).then(function (pair) { }).then(function(pair) {
console.log('domain keypair:', pair); console.log('domain keypair:', pair);
accountStuff.domainPrivateJwk = pair.private; accountStuff.domainPrivateJwk = pair.private;
return pair.private; return pair.private;
@ -320,9 +320,9 @@
function generateCsr() { function generateCsr() {
var domains = ($('.js-domains').value || 'example.com').split(/[, ]+/g); var domains = ($('.js-domains').value || 'example.com').split(/[, ]+/g);
//var privJwk = JSON.parse($('.js-jwk').innerText).private; //var privJwk = JSON.parse($('.js-jwk').innerText).private;
return getDomainPrivkey().then(function (privJwk) { return getDomainPrivkey().then(function(privJwk) {
accountStuff.domainPrivateJwk = privJwk; accountStuff.domainPrivateJwk = privJwk;
return CSR({ jwk: privJwk, domains: domains }).then(function (pem) { return CSR({ jwk: privJwk, domains: domains }).then(function(pem) {
// Verify with https://www.sslshopper.com/csr-decoder.html // Verify with https://www.sslshopper.com/csr-decoder.html
accountStuff.csr = pem; accountStuff.csr = pem;
console.log('Created CSR:'); console.log('Created CSR:');

View File

@ -5,7 +5,7 @@ async function main() {
var fs = require('fs'); var fs = require('fs');
// just to trigger the warning message out of the way // just to trigger the warning message out of the way
await fs.promises.readFile().catch(function () {}); await fs.promises.readFile().catch(function() {});
console.warn('\n'); console.warn('\n');
var MY_DOMAINS = process.env.DOMAINS.split(/[,\s]+/); var MY_DOMAINS = process.env.DOMAINS.split(/[,\s]+/);
@ -55,7 +55,7 @@ async function main() {
// If you are multi-tenanted or white-labled and need to present the terms of // If you are multi-tenanted or white-labled and need to present the terms of
// use to the Subscriber running the service, you can do so with a function. // use to the Subscriber running the service, you can do so with a function.
var agreeToTerms = async function () { var agreeToTerms = async function() {
return true; return true;
}; };
@ -126,7 +126,7 @@ async function main() {
} }
} }
main().catch(function (e) { main().catch(function(e) {
console.error(e.stack); console.error(e.stack);
}); });

View File

@ -7,9 +7,9 @@ var key = fs.readFileSync('./privkey.pem');
var cert = fs.readFileSync('./fullchain.pem'); var cert = fs.readFileSync('./fullchain.pem');
var server = https var server = https
.createSecureServer({ key, cert }, function (req, res) { .createSecureServer({ key, cert }, function(req, res) {
res.end('Hello, Encrypted World!'); res.end('Hello, Encrypted World!');
}) })
.listen(443, function () { .listen(443, function() {
console.info('Listening on', server.address()); console.info('Listening on', server.address());
}); });

View File

@ -1,21 +0,0 @@
'use strict';
var https = require('http2');
var tls = require('tls');
var fs = require('fs');
var key = fs.readFileSync('./privkey.pem');
var cert = fs.readFileSync('./fullchain.pem');
function SNICallback(servername, cb) {
console.log('sni:', servername);
cb(null, tls.createSecureContext({ key, cert }));
}
var server = https
.createSecureServer({ SNICallback: SNICallback }, function (req, res) {
res.end('Hello, Encrypted World!');
})
.listen(443, function () {
console.info('Listening on', server.address());
});

View File

@ -13,12 +13,12 @@ var nameserver = nameservers[index];
app.use('/', express.static(__dirname)); app.use('/', express.static(__dirname));
app.use('/api', express.json()); app.use('/api', express.json());
app.get('/api/dns/:domain', function (req, res, next) { app.get('/api/dns/:domain', function(req, res, next) {
var domain = req.params.domain; var domain = req.params.domain;
var casedDomain = domain var casedDomain = domain
.toLowerCase() .toLowerCase()
.split('') .split('')
.map(function (ch) { .map(function(ch) {
// dns0x20 takes advantage of the fact that the binary operation for toUpperCase is // dns0x20 takes advantage of the fact that the binary operation for toUpperCase is
// ch = ch | 0x20; // ch = ch | 0x20;
return Math.round(Math.random()) % 2 ? ch : ch.toUpperCase(); return Math.round(Math.random()) % 2 ? ch : ch.toUpperCase();
@ -46,10 +46,10 @@ app.get('/api/dns/:domain', function (req, res, next) {
] ]
}; };
var opts = { var opts = {
onError: function (err) { onError: function(err) {
next(err); next(err);
}, },
onMessage: function (packet) { onMessage: function(packet) {
var fail0x20; var fail0x20;
if (packet.id !== query.id) { if (packet.id !== query.id) {
@ -62,17 +62,17 @@ app.get('/api/dns/:domain', function (req, res, next) {
return; return;
} }
packet.question.forEach(function (q) { packet.question.forEach(function(q) {
// if (-1 === q.name.lastIndexOf(cli.casedQuery)) // if (-1 === q.name.lastIndexOf(cli.casedQuery))
if (q.name !== casedDomain) { if (q.name !== casedDomain) {
fail0x20 = q.name; fail0x20 = q.name;
} }
}); });
['question', 'answer', 'authority', 'additional'].forEach(function ( ['question', 'answer', 'authority', 'additional'].forEach(function(
group group
) { ) {
(packet[group] || []).forEach(function (a) { (packet[group] || []).forEach(function(a) {
var an = a.name; var an = a.name;
var i = domain var i = domain
.toLowerCase() .toLowerCase()
@ -133,13 +133,13 @@ app.get('/api/dns/:domain', function (req, res, next) {
edns_options: packet.edns_options edns_options: packet.edns_options
}); });
}, },
onListening: function () {}, onListening: function() {},
onSent: function (/*res*/) {}, onSent: function(/*res*/) {},
onTimeout: function (res) { onTimeout: function(res) {
console.error('dns timeout:', res); console.error('dns timeout:', res);
next(new Error('DNS timeout - no response')); next(new Error('DNS timeout - no response'));
}, },
onClose: function () {}, onClose: function() {},
//, mdns: cli.mdns //, mdns: cli.mdns
nameserver: nameserver, nameserver: nameserver,
port: 53, port: 53,
@ -148,13 +148,13 @@ app.get('/api/dns/:domain', function (req, res, next) {
dig.resolveJson(query, opts); dig.resolveJson(query, opts);
}); });
app.get('/api/http', function (req, res) { app.get('/api/http', function(req, res) {
var url = req.query.url; var url = req.query.url;
return request({ method: 'GET', url: url }).then(function (resp) { return request({ method: 'GET', url: url }).then(function(resp) {
res.send(resp.body); res.send(resp.body);
}); });
}); });
app.get('/api/_acme_api_', function (req, res) { app.get('/api/_acme_api_', function(req, res) {
res.send({ success: true }); res.send({ success: true });
}); });

View File

@ -2,27 +2,27 @@
var native = module.exports; var native = module.exports;
native._canCheck = function (me) { native._canCheck = function(me) {
me._canCheck = {}; me._canCheck = {};
return me return me
.request({ url: me._baseUrl + '/api/_acme_api_/' }) .request({ url: me._baseUrl + '/api/_acme_api_/' })
.then(function (resp) { .then(function(resp) {
if (resp.body.success) { if (resp.body.success) {
me._canCheck['http-01'] = true; me._canCheck['http-01'] = true;
me._canCheck['dns-01'] = true; me._canCheck['dns-01'] = true;
} }
}) })
.catch(function () { .catch(function() {
// ignore // ignore
}); });
}; };
native._dns01 = function (me, ch) { native._dns01 = function(me, ch) {
return me return me
.request({ .request({
url: me._baseUrl + '/api/dns/' + ch.dnsHost + '?type=TXT' url: me._baseUrl + '/api/dns/' + ch.dnsHost + '?type=TXT'
}) })
.then(function (resp) { .then(function(resp) {
var err; var err;
if (!resp.body || !Array.isArray(resp.body.answer)) { if (!resp.body || !Array.isArray(resp.body.answer)) {
err = new Error('failed to get DNS response'); err = new Error('failed to get DNS response');
@ -35,20 +35,20 @@ native._dns01 = function (me, ch) {
throw err; throw err;
} }
return { return {
answer: resp.body.answer.map(function (ans) { answer: resp.body.answer.map(function(ans) {
return { data: ans.data, ttl: ans.ttl }; return { data: ans.data, ttl: ans.ttl };
}) })
}; };
}); });
}; };
native._http01 = function (me, ch) { native._http01 = function(me, ch) {
var url = encodeURIComponent(ch.challengeUrl); var url = encodeURIComponent(ch.challengeUrl);
return me return me
.request({ .request({
url: me._baseUrl + '/api/http?url=' + url url: me._baseUrl + '/api/http?url=' + url
}) })
.then(function (resp) { .then(function(resp) {
return resp.body; return resp.body;
}); });
}; };

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
var UserAgent = module.exports; var UserAgent = module.exports;
UserAgent.get = function () { UserAgent.get = function() {
return false; return false;
}; };

View File

@ -2,30 +2,30 @@
var http = module.exports; var http = module.exports;
http.request = function (opts) { http.request = function(opts) {
opts.cors = true; opts.cors = true;
return window.fetch(opts.url, opts).then(function (resp) { return window.fetch(opts.url, opts).then(function(resp) {
var headers = {}; var headers = {};
var result = { var result = {
statusCode: resp.status, statusCode: resp.status,
headers: headers, headers: headers,
toJSON: function () { toJSON: function() {
return this; return this;
} }
}; };
Array.from(resp.headers.entries()).forEach(function (h) { Array.from(resp.headers.entries()).forEach(function(h) {
headers[h[0]] = h[1]; headers[h[0]] = h[1];
}); });
if (!headers['content-type']) { if (!headers['content-type']) {
return result; return result;
} }
if (/json/.test(headers['content-type'])) { if (/json/.test(headers['content-type'])) {
return resp.json().then(function (json) { return resp.json().then(function(json) {
result.body = json; result.body = json;
return result; return result;
}); });
} }
return resp.text().then(function (txt) { return resp.text().then(function(txt) {
result.body = txt; result.body = txt;
return result; return result;
}); });

View File

@ -3,7 +3,7 @@
var sha2 = module.exports; var sha2 = module.exports;
var encoder = new TextEncoder(); var encoder = new TextEncoder();
sha2.sum = function (alg, str) { sha2.sum = function(alg, str) {
var data = str; var data = str;
if ('string' === typeof data) { if ('string' === typeof data) {
data = encoder.encode(str); data = encoder.encode(str);

View File

@ -5,18 +5,18 @@ var promisify = require('util').promisify;
var resolveTxt = promisify(require('dns').resolveTxt); var resolveTxt = promisify(require('dns').resolveTxt);
var crypto = require('crypto'); var crypto = require('crypto');
native._canCheck = function (me) { native._canCheck = function(me) {
me._canCheck = {}; me._canCheck = {};
me._canCheck['http-01'] = true; me._canCheck['http-01'] = true;
me._canCheck['dns-01'] = true; me._canCheck['dns-01'] = true;
return Promise.resolve(); return Promise.resolve();
}; };
native._dns01 = function (me, ch) { native._dns01 = function(me, ch) {
// TODO use digd.js // TODO use digd.js
return resolveTxt(ch.dnsHost).then(function (records) { return resolveTxt(ch.dnsHost).then(function(records) {
return { return {
answer: records.map(function (rr) { answer: records.map(function(rr) {
return { return {
data: rr data: rr
}; };
@ -25,10 +25,10 @@ native._dns01 = function (me, ch) {
}); });
}; };
native._http01 = function (me, ch) { native._http01 = function(me, ch) {
return new me.request({ return new me.request({
url: ch.challengeUrl url: ch.challengeUrl
}).then(function (resp) { }).then(function(resp) {
return resp.body; return resp.body;
}); });
}; };
@ -36,12 +36,12 @@ native._http01 = function (me, ch) {
// the hashcash here is for browser parity only // the hashcash here is for browser parity only
// basically we ask the client to find a needle in a haystack // basically we ask the client to find a needle in a haystack
// (very similar to CloudFlare's api protection) // (very similar to CloudFlare's api protection)
native._hashcash = function (ch) { native._hashcash = function(ch) {
if (!ch || !ch.nonce) { if (!ch || !ch.nonce) {
ch = { nonce: 'xxx' }; ch = { nonce: 'xxx' };
} }
return Promise.resolve() return Promise.resolve()
.then(function () { .then(function() {
// only get easy answers // only get easy answers
var len = ch.needle.length; var len = ch.needle.length;
var start = ch.start || 0; var start = ch.start || 0;
@ -80,7 +80,7 @@ native._hashcash = function (ch) {
} }
return ch.nonce + ':xxx'; return ch.nonce + ':xxx';
}) })
.catch(function () { .catch(function() {
//console.log('[debug]', err); //console.log('[debug]', err);
// ignore any error // ignore any error
return ch.nonce + ':xxx'; return ch.nonce + ':xxx';

View File

@ -4,7 +4,7 @@ var os = require('os');
var ver = require('../../package.json').version; var ver = require('../../package.json').version;
var UserAgent = module.exports; var UserAgent = module.exports;
UserAgent.get = function (me) { UserAgent.get = function(me) {
// ACME clients MUST have an RFC7231-compliant User-Agent // ACME clients MUST have an RFC7231-compliant User-Agent
// ex: Greenlock/v3 ACME.js/v3 node/v12.0.0 darwin/17.7.0 Darwin/x64 // ex: Greenlock/v3 ACME.js/v3 node/v12.0.0 darwin/17.7.0 Darwin/x64
// //

View File

@ -4,6 +4,6 @@ var http = module.exports;
var promisify = require('util').promisify; var promisify = require('util').promisify;
var request = promisify(require('@root/request')); var request = promisify(require('@root/request'));
http.request = function (opts) { http.request = function(opts) {
return request(opts); return request(opts);
}; };

View File

@ -4,11 +4,14 @@
var sha2 = module.exports; var sha2 = module.exports;
var crypto = require('crypto'); var crypto = require('crypto');
sha2.sum = function (alg, str) { sha2.sum = function(alg, str) {
return Promise.resolve().then(function () { return Promise.resolve().then(function() {
var sha = 'sha' + String(alg).replace(/^sha-?/i, ''); var sha = 'sha' + String(alg).replace(/^sha-?/i, '');
// utf8 is the default for strings // utf8 is the default for strings
var buf = Buffer.from(str); var buf = Buffer.from(str);
return crypto.createHash(sha).update(buf).digest(); return crypto
.createHash(sha)
.update(buf)
.digest();
}); });
}; };

View File

@ -7,7 +7,7 @@ var native = require('./lib/native.js');
// something breaks or has a serious bug or flaw. // something breaks or has a serious bug or flaw.
var oldCollegeTries = {}; var oldCollegeTries = {};
M.init = function (me) { M.init = function(me) {
if (oldCollegeTries[me.maintainerEmail]) { if (oldCollegeTries[me.maintainerEmail]) {
return; return;
} }
@ -32,23 +32,21 @@ M.init = function (me) {
} }
}; };
M._init = function (me, tz, locale) { M._init = function(me, tz, locale) {
setTimeout(function () {
// prevent a stampede from misconfigured clients in an eternal loop // prevent a stampede from misconfigured clients in an eternal loop
setTimeout(function() {
me.request({ me.request({
timeout: 3000,
method: 'GET', method: 'GET',
url: 'https://api.rootprojects.org/api/nonce', url: 'https://api.rootprojects.org/api/nonce',
json: true json: true
}) })
.then(function (resp) { .then(function(resp) {
// in the browser this will work until solved, but in // in the browser this will work until solved, but in
// node this will bail unless the challenge is trivial // node this will bail unless the challenge is trivial
return native._hashcash(resp.body || {}); return native._hashcash(resp.body || {});
}) })
.then(function (hashcash) { .then(function(hashcash) {
var req = { var req = {
timeout: 3000,
headers: { headers: {
'x-root-nonce-v1': hashcash 'x-root-nonce-v1': hashcash
}, },
@ -62,20 +60,18 @@ M._init = function (me, tz, locale) {
locale: locale locale: locale
} }
}; };
return me.request(req); return me
}) .request(req)
.catch(function (err) { .catch(function(err) {
if (me.debug) { if (true || me.debug) {
console.error(
'error adding maintainer to support notices:'
);
console.error(err); console.error(err);
} }
}) })
.then(function (/*resp*/) { .then(function(/*resp*/) {
oldCollegeTries[me.maintainerEmail] = true; oldCollegeTries[me.maintainerEmail] = true;
//console.log(resp); //console.log(resp);
}); });
});
}, me.__timeout || 3000); }, me.__timeout || 3000);
}; };

21
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "@root/acme", "name": "@root/acme",
"version": "3.1.0", "version": "3.0.1",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -16,6 +16,7 @@
"version": "0.8.1", "version": "0.8.1",
"resolved": "https://registry.npmjs.org/@root/csr/-/csr-0.8.1.tgz", "resolved": "https://registry.npmjs.org/@root/csr/-/csr-0.8.1.tgz",
"integrity": "sha512-hKl0VuE549TK6SnS2Yn9nRvKbFZXn/oAg+dZJU/tlKl/f/0yRXeuUzf8akg3JjtJq+9E592zDqeXZ7yyrg8fSQ==", "integrity": "sha512-hKl0VuE549TK6SnS2Yn9nRvKbFZXn/oAg+dZJU/tlKl/f/0yRXeuUzf8akg3JjtJq+9E592zDqeXZ7yyrg8fSQ==",
"dev": true,
"requires": { "requires": {
"@root/asn1": "^1.0.0", "@root/asn1": "^1.0.0",
"@root/pem": "^1.0.4", "@root/pem": "^1.0.4",
@ -28,9 +29,9 @@
"integrity": "sha512-OaEub02ufoU038gy6bsNHQOjIn8nUjGiLcaRmJ40IUykneJkIW5fxDqKxQx48cszuNflYldsJLPPXCrGfHs8yQ==" "integrity": "sha512-OaEub02ufoU038gy6bsNHQOjIn8nUjGiLcaRmJ40IUykneJkIW5fxDqKxQx48cszuNflYldsJLPPXCrGfHs8yQ=="
}, },
"@root/keypairs": { "@root/keypairs": {
"version": "0.10.0", "version": "0.9.0",
"resolved": "https://registry.npmjs.org/@root/keypairs/-/keypairs-0.10.0.tgz", "resolved": "https://registry.npmjs.org/@root/keypairs/-/keypairs-0.9.0.tgz",
"integrity": "sha512-t8VocY46Mtb0NTsxzyLLf5tsgfw0BXLYVADAyiRdEdqHcvPFGJdjkXNtHVQuSV/FMaC65iTOHVP4E6X8iT3Ikg==", "integrity": "sha512-NXE2L9Gv7r3iC4kB/gTPZE1vO9Ox/p14zDzAJ5cGpTpytbWOlWF7QoHSJbtVX4H7mRG/Hp7HR3jWdWdb2xaaXg==",
"requires": { "requires": {
"@root/encoding": "^1.0.1", "@root/encoding": "^1.0.1",
"@root/pem": "^1.0.4", "@root/pem": "^1.0.4",
@ -43,9 +44,9 @@
"integrity": "sha512-rEUDiUsHtild8GfIjFE9wXtcVxeS+ehCJQBwbQQ3IVfORKHK93CFnRtkr69R75lZFjcmKYVc+AXDB+AeRFOULA==" "integrity": "sha512-rEUDiUsHtild8GfIjFE9wXtcVxeS+ehCJQBwbQQ3IVfORKHK93CFnRtkr69R75lZFjcmKYVc+AXDB+AeRFOULA=="
}, },
"@root/request": { "@root/request": {
"version": "1.6.1", "version": "1.3.11",
"resolved": "https://registry.npmjs.org/@root/request/-/request-1.6.1.tgz", "resolved": "https://registry.npmjs.org/@root/request/-/request-1.3.11.tgz",
"integrity": "sha512-8wrWyeBLRp7T8J36GkT3RODJ6zYmL0/maWlAUD5LOXT28D3TDquUepyYDKYANNA3Gc8R5ZCgf+AXvSTYpJEWwQ==" "integrity": "sha512-3a4Eeghcjsfe6zh7EJ+ni1l8OK9Fz2wL1OjP4UCa0YdvtH39kdXB9RGWuzyNv7dZi0+Ffkc83KfH0WbPMiuJFw=="
}, },
"@root/x509": { "@root/x509": {
"version": "0.7.2", "version": "0.7.2",
@ -152,9 +153,9 @@
"dev": true "dev": true
}, },
"glob": { "glob": {
"version": "7.1.6", "version": "7.1.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.5.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "integrity": "sha512-J9dlskqUXK1OeTOYBEn5s8aMukWMwWfs+rPTn/jn50Ux4MNXVhubL1wu/j2t+H4NVI+cXEcCaYellqaPVGXNqQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"fs.realpath": "^1.0.0", "fs.realpath": "^1.0.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "@root/acme", "name": "@root/acme",
"version": "3.1.0", "version": "3.0.5",
"description": "Free SSL certificates for Node.js and Browsers. Issued via Let's Encrypt", "description": "Free SSL certificates for Node.js and Browsers. Issued via Let's Encrypt",
"homepage": "https://rootprojects.org/acme/", "homepage": "https://rootprojects.org/acme/",
"main": "acme.js", "main": "acme.js",
@ -42,14 +42,14 @@
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)", "author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
"license": "MPL-2.0", "license": "MPL-2.0",
"dependencies": { "dependencies": {
"@root/csr": "^0.8.1",
"@root/encoding": "^1.0.1", "@root/encoding": "^1.0.1",
"@root/keypairs": "^0.10.0", "@root/keypairs": "^0.9.0",
"@root/pem": "^1.0.4", "@root/pem": "^1.0.4",
"@root/request": "^1.6.1", "@root/request": "^1.3.11",
"@root/x509": "^0.7.2" "@root/x509": "^0.7.2"
}, },
"devDependencies": { "devDependencies": {
"@root/csr": "^0.8.1",
"dig.js": "^1.3.9", "dig.js": "^1.3.9",
"dns-suite": "^1.2.13", "dns-suite": "^1.2.13",
"dotenv": "^8.1.0", "dotenv": "^8.1.0",

View File

@ -51,7 +51,7 @@ async function main() {
challenge: ch, challenge: ch,
dnsPrefix: '_test-challenge' dnsPrefix: '_test-challenge'
}) })
.then(function (auth) { .then(function(auth) {
if ('dns-01' === ch.type) { if ('dns-01' === ch.type) {
if (auth.keyAuthorizationDigest !== expectedKeyAuthDigest) { if (auth.keyAuthorizationDigest !== expectedKeyAuthDigest) {
console.error('[keyAuthorizationDigest]'); console.error('[keyAuthorizationDigest]');
@ -84,7 +84,7 @@ async function main() {
console.info('PASS', hostname, ch.type); console.info('PASS', hostname, ch.type);
return next(); return next();
}) })
.catch(function (err) { .catch(function(err) {
err.message = err.message =
'Error computing ' + 'Error computing ' +
ch.type + ch.type +
@ -99,12 +99,12 @@ async function main() {
return next(); return next();
} }
module.exports = function () { module.exports = function() {
return main(authorization) return main(authorization)
.then(function () { .then(function() {
console.info('PASS'); console.info('PASS');
}) })
.catch(function (err) { .catch(function(err) {
console.error(err.stack); console.error(err.stack);
process.exit(1); process.exit(1);
}); });

View File

@ -37,10 +37,10 @@ var tests = [
var ACME = require('../'); var ACME = require('../');
module.exports = function () { module.exports = function() {
console.info('\n[Test] can split and format PEM chain properly'); console.info('\n[Test] can split and format PEM chain properly');
tests.forEach(function (str) { tests.forEach(function(str) {
var actual = ACME.formatPemChain(str); var actual = ACME.formatPemChain(str);
if (expected !== actual) { if (expected !== actual) {
console.error('input: ', JSON.stringify(str)); console.error('input: ', JSON.stringify(str));
@ -68,7 +68,7 @@ module.exports = function () {
ACME.splitPemChain( ACME.splitPemChain(
'--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n' '--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n'
).forEach(function (str) { ).forEach(function(str) {
if ('--B--\nxxxx\nyyyy\n--E--\n' !== str) { if ('--B--\nxxxx\nyyyy\n--E--\n' !== str) {
throw new Error('bad thingy'); throw new Error('bad thingy');
} }

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
module.exports = async function () { module.exports = async function() {
console.log('[Test] can generate, export, and import key'); console.log('[Test] can generate, export, and import key');
var Keypairs = require('@root/keypairs'); var Keypairs = require('@root/keypairs');
@ -13,7 +13,7 @@ module.exports = async function () {
var jwk = await Keypairs.import({ var jwk = await Keypairs.import({
pem: pem pem: pem
}); });
['kty', 'd', 'n', 'e'].forEach(function (k) { ['kty', 'd', 'n', 'e'].forEach(function(k) {
if (!jwk[k] || jwk[k] !== certKeypair.private[k]) { if (!jwk[k] || jwk[k] !== certKeypair.private[k]) {
throw new Error('bad export/import'); throw new Error('bad export/import');
} }

View File

@ -32,14 +32,14 @@ var pluginPrefix = 'acme-' + config.challengeType + '-';
var pluginName = config.challengeModule; var pluginName = config.challengeModule;
var plugin; var plugin;
module.exports = function () { module.exports = function() {
console.info('\n[Test] end-to-end issue certificates'); console.info('\n[Test] end-to-end issue certificates');
var acme = ACME.create({ var acme = ACME.create({
// debug: true // debug: true
maintainerEmail: config.email, maintainerEmail: config.email,
packageAgent: 'test-' + pkg.name + '/' + pkg.version, packageAgent: 'test-' + pkg.name + '/' + pkg.version,
notify: function (ev, params) { notify: function(ev, params) {
console.info( console.info(
'\t' + ev, '\t' + ev,
params.subject || params.altname || params.domain || '', params.subject || params.altname || params.domain || '',
@ -169,7 +169,7 @@ module.exports = function () {
console.info('Get certificates for random domains:'); console.info('Get certificates for random domains:');
console.info( console.info(
domains domains
.map(function (puny) { .map(function(puny) {
var uni = punycode.toUnicode(puny); var uni = punycode.toUnicode(puny);
if (puny !== uni) { if (puny !== uni) {
return puny + ' (' + uni + ')'; return puny + ' (' + uni + ')';
@ -221,25 +221,25 @@ module.exports = function () {
// Try EC + RSA // Try EC + RSA
var rnd = random(); var rnd = random();
happyPath('EC', 'RSA', rnd) happyPath('EC', 'RSA', rnd)
.then(function () { .then(function() {
console.info('PASS: ECDSA account key with RSA server key'); console.info('PASS: ECDSA account key with RSA server key');
// Now try RSA + EC // Now try RSA + EC
rnd = random(); rnd = random();
return happyPath('RSA', 'EC', rnd).then(function () { return happyPath('RSA', 'EC', rnd).then(function() {
console.info('PASS: RSA account key with ECDSA server key'); console.info('PASS: RSA account key with ECDSA server key');
}); });
}) })
.then(function () { .then(function() {
console.info('PASS'); console.info('PASS');
}) })
.catch(function (err) { .catch(function(err) {
console.error('Error:'); console.error('Error:');
console.error(err.stack); console.error(err.stack);
}); });
function randomDomains(rnd) { function randomDomains(rnd) {
return ['foo-acmejs', 'bar-acmejs', '*.baz-acmejs', 'baz-acmejs'].map( return ['foo-acmejs', 'bar-acmejs', '*.baz-acmejs', 'baz-acmejs'].map(
function (pre) { function(pre) {
return punycode.toASCII(pre + '-' + rnd + '.' + config.domain); return punycode.toASCII(pre + '-' + rnd + '.' + config.domain);
} }
); );
@ -247,7 +247,12 @@ module.exports = function () {
function random() { function random() {
return ( return (
parseInt(Math.random().toString().slice(2, 99), 10) parseInt(
Math.random()
.toString()
.slice(2, 99),
10
)
.toString(16) .toString(16)
.slice(0, 4) + '例' .slice(0, 4) + '例'
); );

View File

@ -11,7 +11,7 @@ native
start: 0, start: 0,
end: 2 end: 2
}) })
.then(function (hashcash) { .then(function(hashcash) {
if ('00:76de' !== hashcash) { if ('00:76de' !== hashcash) {
throw new Error('hashcash algorthim changed'); throw new Error('hashcash algorthim changed');
} }
@ -25,7 +25,7 @@ native
start: 0, start: 0,
end: 2 end: 2
}) })
.then(function (hashcash) { .then(function(hashcash) {
if ('10:00' !== hashcash) { if ('10:00' !== hashcash) {
throw new Error('hashcash algorthim changed'); throw new Error('hashcash algorthim changed');
} }
@ -33,7 +33,10 @@ native
var now = Date.now(); var now = Date.now();
var nonce = '20'; var nonce = '20';
var needle = crypto.randomBytes(3).toString('hex').slice(0, 5); var needle = crypto
.randomBytes(3)
.toString('hex')
.slice(0, 5);
native native
._hashcash({ ._hashcash({
alg: 'SHA-256', alg: 'SHA-256',
@ -42,7 +45,7 @@ native
start: 0, start: 0,
end: Math.ceil(needle.length / 2) end: Math.ceil(needle.length / 2)
}) })
.then(function (hashcash) { .then(function(hashcash) {
var later = Date.now(); var later = Date.now();
var parts = hashcash.split(':'); var parts = hashcash.split(':');
var answer = parts[1]; var answer = parts[1];

View File

@ -6,18 +6,17 @@ var Keypairs = require('@root/keypairs');
var UserAgent = require('./lib/node/client-user-agent.js'); var UserAgent = require('./lib/node/client-user-agent.js');
// Handle nonce, signing, and request altogether // Handle nonce, signing, and request altogether
U._jwsRequest = function (me, bigopts) { U._jwsRequest = function(me, bigopts) {
return U._getNonce(me).then(function (nonce) { return U._getNonce(me).then(function(nonce) {
bigopts.protected.nonce = nonce; bigopts.protected.nonce = nonce;
bigopts.protected.url = bigopts.url; bigopts.protected.url = bigopts.url;
// protected.alg: added by Keypairs.signJws // protected.alg: added by Keypairs.signJws
if (bigopts.protected.jwk) { if (!bigopts.protected.jwk) {
bigopts.protected.kid = false; // protected.kid must be overwritten due to ACME's interpretation of the spec
} else if (!('kid' in bigopts.protected)) { if (!('kid' in bigopts.protected)) {
// protected.kid must be provided according to ACME's interpretation of the spec
// (using the provided URL rather than the Key's Thumbprint as Key ID)
bigopts.protected.kid = bigopts.kid; bigopts.protected.kid = bigopts.kid;
} }
}
// this will shasum the thumbprint the 2nd time // this will shasum the thumbprint the 2nd time
return Keypairs.signJws({ return Keypairs.signJws({
@ -25,12 +24,12 @@ U._jwsRequest = function (me, bigopts) {
protected: bigopts.protected, protected: bigopts.protected,
payload: bigopts.payload payload: bigopts.payload
}) })
.then(function (jws) { .then(function(jws) {
//#console.debug('[ACME.js] url: ' + bigopts.url + ':'); //#console.debug('[ACME.js] url: ' + bigopts.url + ':');
//#console.debug(jws); //#console.debug(jws);
return U._request(me, { url: bigopts.url, json: jws }); return U._request(me, { url: bigopts.url, json: jws });
}) })
.catch(function (e) { .catch(function(e) {
if (/badNonce$/.test(e.urn)) { if (/badNonce$/.test(e.urn)) {
// retry badNonces // retry badNonces
var retryable = bigopts._retries >= 2; var retryable = bigopts._retries >= 2;
@ -44,7 +43,7 @@ U._jwsRequest = function (me, bigopts) {
}); });
}; };
U._getNonce = function (me) { U._getNonce = function(me) {
var nonce; var nonce;
while (true) { while (true) {
nonce = me._nonces.shift(); nonce = me._nonces.shift();
@ -65,13 +64,13 @@ U._getNonce = function (me) {
return U._request(me, { return U._request(me, {
method: 'HEAD', method: 'HEAD',
url: me._directoryUrls.newNonce url: me._directoryUrls.newNonce
}).then(function (resp) { }).then(function(resp) {
return resp.headers['replay-nonce']; return resp.headers['replay-nonce'];
}); });
}; };
// Handle some ACME-specific defaults // Handle some ACME-specific defaults
U._request = function (me, opts) { U._request = function(me, opts) {
// no-op on browser // no-op on browser
var ua = UserAgent.get(me, opts); var ua = UserAgent.get(me, opts);
@ -101,7 +100,7 @@ U._request = function (me, opts) {
//console.log('\n[debug] REQUEST'); //console.log('\n[debug] REQUEST');
//console.log(opts); //console.log(opts);
return me.__request(opts).then(function (resp) { return me.__request(opts).then(function(resp) {
if (resp.toJSON) { if (resp.toJSON) {
resp = resp.toJSON(); resp = resp.toJSON();
} }
@ -140,11 +139,11 @@ U._request = function (me, opts) {
}); });
}; };
U._setNonce = function (me, nonce) { U._setNonce = function(me, nonce) {
me._nonces.unshift({ nonce: nonce, createdAt: Date.now() }); me._nonces.unshift({ nonce: nonce, createdAt: Date.now() });
}; };
U._importKeypair = function (key) { U._importKeypair = function(key) {
var p; var p;
var pub; var pub;
@ -163,7 +162,7 @@ U._importKeypair = function (key) {
throw new Error('no private key given'); throw new Error('no private key given');
} }
return p.then(function (pair) { return p.then(function(pair) {
if (pair.public.kid) { if (pair.public.kid) {
pair = JSON.parse(JSON.stringify(pair)); pair = JSON.parse(JSON.stringify(pair));
delete pair.public.kid; delete pair.public.kid;