v3.0.2: various bugfixes

This commit is contained in:
AJ ONeal 2019-10-29 05:18:13 +00:00
parent 6fdafec304
commit 3f2e0cd6e4
12 changed files with 481 additions and 357 deletions

8
.prettierrc Normal file
View File

@ -0,0 +1,8 @@
{
"bracketSpacing": true,
"printWidth": 80,
"singleQuote": true,
"tabWidth": 4,
"trailingComma": "none",
"useTabs": true
}

View File

@ -55,35 +55,50 @@ var gl = Greenlock.create({
// This should be the contact who receives critical bug and security notifications // This should be the contact who receives critical bug and security notifications
// Optionally, you may receive other (very few) updates, such as important new features // Optionally, you may receive other (very few) updates, such as important new features
maintainerEmail: 'jon@example.com', maintainerEmail: 'jon@example.com'
maintainerUpdates: true, // default: false });
```
| Parameter | Description |
| --------------- | ------------------------------------------------------------------------------------ |
| maintainerEmail | the developer contact for critical bug and security notifications |
| packageAgent | if you publish your package for others to use, `require('./package.json').name` here |
<!--
| maintainerUpdates | (default: false) receive occasional non-critical notifications |
maintainerUpdates: true // default: false
-->
### Add Approved Domains
```js
greenlock.manager.defaults({
// The "Let's Encrypt Subscriber" (often the same as the maintainer) // The "Let's Encrypt Subscriber" (often the same as the maintainer)
// NOT the end customer (except where that is also the maintainer) // NOT the end customer (except where that is also the maintainer)
subscriberEmail: 'jon@example.com', subscriberEmail: 'jon@example.com',
agreeToTerms: true // default: false agreeToTerms: true
}); });
``` ```
| Parameter | Description | | Parameter | Description |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| servername | the default servername to use for non-sni requests (many IoT clients) |
| maintainerEmail | the developer contact for critical bug and security notifications |
| maintainerUpdates | (default: false) receive occasional non-critical notifications |
| maintainerPackage | if you publish your package for others to use, `require('./package.json').name` here |
| maintainerPackageVersion | if you publish your package for others to use, `require('./package.json').version` here |
| subscriberEmail | the contact who agrees to the Let's Encrypt Subscriber Agreement and the Greenlock Terms of Service<br>this contact receives renewal failure notifications |
| agreeToTerms | (default: false) either 'true' or a function that presents the Terms of Service and returns it once accepted | | agreeToTerms | (default: false) either 'true' or a function that presents the Terms of Service and returns it once accepted |
| store | override the default storage module |
| store.module | the name of your storage module |
| store.xxxx | options specific to your storage module |
| challenges['http-01'] | provide an http-01 challenge module | | challenges['http-01'] | provide an http-01 challenge module |
| challenges['dns-01'] | provide a dns-01 challenge module | | challenges['dns-01'] | provide a dns-01 challenge module |
| challenges['tls-alpn-01'] | provide a tls-alpn-01 challenge module | | challenges['tls-alpn-01'] | provide a tls-alpn-01 challenge module |
| challenges[type].module | the name of your challenge module | | challenges[type].module | the name of your challenge module |
| challenges[type].xxxx | module-specific options | | challenges[type].xxxx | module-specific options |
| servername | the default servername to use for non-sni requests (many IoT clients) |
| subscriberEmail | the contact who agrees to the Let's Encrypt Subscriber Agreement and the Greenlock Terms of Service<br>this contact receives renewal failure notifications |
| store | override the default storage module |
| store.module | the name of your storage module |
| store.xxxx | options specific to your storage module |
### Add Approved Domains <!--
| serverId | an arbitrary name to distinguish this server within a cluster of servers |
-->
```js ```js
gl.add({ gl.add({
@ -104,26 +119,24 @@ gl.add({
This will renew only domains that have reached their `renewAt` or are within the befault `renewOffset`. This will renew only domains that have reached their `renewAt` or are within the befault `renewOffset`.
```js ```js
return greenlock return greenlock.renew().then(function(results) {
.renew() results.forEach(function(site) {
.then(function(pems) { if (site.error) {
console.info(pems); console.error(site.subject, site.error);
}) return;
.then(function(results) { }
results.forEach(function(site) {
if (site.error) {
console.error(site.subject, site.error);
return;
}
});
}); });
});
``` ```
| Parameter | Type | Description | | Parameter | Type | Description |
| ---------- | ---- | ---------------------------------------------------------- | | ------------- | ---- | ------------------------------------------------------------------------------- |
| (optional) | - | ALL parameters are optional, but some should be paired | | (optional) | | ALL parameters are optional, but some should be paired |
| force | bool | force silly options, such as tiny durations | | force | bool | force silly options, such as tiny durations |
| duplicate | bool | force the domain to renew, regardless of age or expiration | | duplicate | bool | force the domain to renew, regardless of age or expiration |
| issuedBefore | ms | Check domains issued before the given date in milliseconds |
| expiresBefore | ms | Check domains that expire before the given date in milliseconds |
| renewBefore | ms | Check domains that are scheduled to renew before the given date in milliseconds |
<!-- <!--
| servername | string<br>hostname | renew the certificate that has this domain in its altnames (for ServerName Indication / SNI lookup) | | servername | string<br>hostname | renew the certificate that has this domain in its altnames (for ServerName Indication / SNI lookup) |

View File

@ -178,7 +178,7 @@ function toCamel(str) {
function toBagName(bag) { function toBagName(bag) {
// trim leading and trailing '-' // trim leading and trailing '-'
bag = bag.replace(/^-+/g, '').replace(/-+$/g, '') bag = bag.replace(/^-+/g, '').replace(/-+$/g, '');
return toCamel(bag) + 'Opts'; // '--bag-option-' => bagOptionOpts return toCamel(bag) + 'Opts'; // '--bag-option-' => bagOptionOpts
} }

View File

@ -4,6 +4,6 @@
var args = process.argv.slice(2); var args = process.argv.slice(2);
console.log(args); console.log(args);
if ('certonly' === args[0]) { if ('certonly' === args[0]) {
require('./certonly.js'); require('./certonly.js');
return; return;
} }

View File

@ -56,60 +56,48 @@ C._getOrOrder = function(gnlck, mconf, db, acme, chs, acc, args) {
// Certificates // Certificates
C._rawGetOrOrder = function(gnlck, mconf, db, acme, chs, acc, email, args) { C._rawGetOrOrder = function(gnlck, mconf, db, acme, chs, acc, email, args) {
return C._check(gnlck, mconf, db, args).then(function(pems) { return C._check(gnlck, mconf, db, args).then(function(pems) {
// No pems? get some!
if (!pems) {
return C._rawOrder(
gnlck,
mconf,
db,
acme,
chs,
acc,
email,
args
).then(function(newPems) {
// do not wait on notify
gnlck._notify('cert_issue', {
options: args,
subject: args.subject,
altnames: args.altnames,
account: acc,
email: email,
pems: newPems
});
return newPems;
});
}
// Nice and fresh? We're done! // Nice and fresh? We're done!
if (!C._isStale(gnlck, mconf, args, pems)) { if (pems) {
// return existing unexpired (although potentially stale) certificates when available if (!C._isStale(gnlck, mconf, args, pems)) {
// there will be an additional .renewing property if the certs are being asynchronously renewed // return existing unexpired (although potentially stale) certificates when available
//pems._type = 'current'; // there will be an additional .renewing property if the certs are being asynchronously renewed
return pems; //pems._type = 'current';
return pems;
}
} }
// Getting stale? Let's renew to freshen up! // We're either starting fresh or freshening up...
var p = C._rawOrder(gnlck, mconf, db, acme, chs, acc, email, args).then( var p = C._rawOrder(gnlck, mconf, db, acme, chs, acc, email, args);
function(renewedPems) { var evname = pems ? 'cert_renewal' : 'cert_issue';
// do not wait on notify p.then(function(newPems) {
gnlck._notify('cert_renewal', { // notify in the background
options: args, var renewAt = C._renewableAt(gnlck, mconf, args, newPems);
subject: args.subject, gnlck._notify(evname, {
altnames: args.altnames, renewAt: renewAt,
account: acc, subject: args.subject,
email: email, altnames: args.altnames
pems: renewedPems });
}); }).catch(function(err) {
return renewedPems; if (!err.context) {
err.context = evname;
} }
); err.subject = args.subject;
err.altnames = args.altnames;
gnlck._notify('error', err);
});
// TODO what should this be? // No choice but to hang tight and wait for it
if (!pems) {
return p;
}
// Wait it out
// TODO should we call this waitForRenewal?
if (args.waitForRenewal) { if (args.waitForRenewal) {
return p; return p;
} }
// Let the certs renew in the background
return pems; return pems;
}); });
}; };
@ -177,6 +165,15 @@ C._rawOrder = function(gnlck, mconf, db, acme, chs, acc, email, args) {
.then(U._attachCertInfo); .then(U._attachCertInfo);
}) })
.then(function(pems) { .then(function(pems) {
var renewAt = C._renewableAt(gnlck, mconf, args, pems);
gnlck._notify('_cert_issue', {
renewAt: renewAt,
subject: args.subject,
altnames: args.altnames,
pems: pems
});
if (kresult.exists) { if (kresult.exists) {
return pems; return pems;
} }

View File

@ -1,42 +0,0 @@
'use strict';
var Greenlock = module.exports;
Greenlock.server = function (opts) {
var opts = Greenlock.create(opts);
opts.plainMiddleware = function(req, res) {
return Greenlock._plainMiddleware(opts, req, res);
};
opts.secureMiddleware = function(req, res) {
return Greenlock._secureMiddleware(opts, req, res);
};
opts.tlsOptions = {
SNICallback: function(servername, cb) {
return Greenlock._sniCallback(opts, servername)
.then(function() {
cb(null);
})
.catch(function(err) {
cb(err);
});
}
};
return opts;
};
// must handle http-01 challenges
Greenlock._plainMiddleware = function(opts, req, res) {};
// should check for domain fronting
Greenlock._secureMiddleware = function(opts, req, res) {};
// should check to see if domain is allowed, and if domain should be renewed
// manage should be able to clear the internal cache
Greenlock._sniCallback = function(opts, servername) {};
Greenlock._onSniRejection(function () {
});

View File

@ -65,12 +65,13 @@ G.create = function(gconf) {
var defaults = G._defaults(gconf); var defaults = G._defaults(gconf);
greenlock.manager = Manager.create(defaults); greenlock.manager = Manager.create(defaults);
//console.log('debug greenlock.manager', Object.keys(greenlock.manager));
greenlock._init = function() { greenlock._init = function() {
var p; var p;
greenlock._init = function() { greenlock._init = function() {
return p; return p;
}; };
p = greenlock.manager.config().then(function(conf) { p = greenlock.manager.defaults().then(function(conf) {
var changed = false; var changed = false;
if (!conf.challenges) { if (!conf.challenges) {
changed = true; changed = true;
@ -81,7 +82,7 @@ G.create = function(gconf) {
conf.store = defaults.store; conf.store = defaults.store;
} }
if (changed) { if (changed) {
return greenlock.manager.config(conf); return greenlock.manager.defaults(conf);
} }
}); });
return p; return p;
@ -154,6 +155,26 @@ G.create = function(gconf) {
greenlock._notify = function(ev, params) { greenlock._notify = function(ev, params) {
var mng = greenlock.manager; var mng = greenlock.manager;
if ('_' === String(ev)[0]) {
if ('_cert_issue' === ev) {
try {
mng.update({
subject: params.subject,
renewAt: params.renewAt
}).catch(function(e) {
e.context = '_cert_issue';
greenlock._notify('error', e);
});
} catch (e) {
e.context = '_cert_issue';
greenlock._notify('error', e);
}
}
// trap internal events internally
return;
}
if (mng.notify || greenlock._defaults.notify) { if (mng.notify || greenlock._defaults.notify) {
try { try {
var p = (mng.notify || greenlock._defaults.notify)(ev, params); var p = (mng.notify || greenlock._defaults.notify)(ev, params);
@ -193,23 +214,109 @@ G.create = function(gconf) {
// { name, version, email, domains, action, communityMember, telemetry } // { name, version, email, domains, action, communityMember, telemetry }
// TODO look at the other one // TODO look at the other one
UserEvents.notify({ UserEvents.notify({
/*
// maintainer should be only on pre-publish, or maybe install, I think // maintainer should be only on pre-publish, or maybe install, I think
maintainerEmail: greenlock._defaults._maintainerEmail, maintainerEmail: greenlock._defaults._maintainerEmail,
name: greenlock._defaults._maintainerPackage, name: greenlock._defaults._maintainerPackage,
version: greenlock._defaults._maintainerPackageVersion, version: greenlock._defaults._maintainerPackageVersion,
action: params.pems._type, //action: params.pems._type,
domains: params.altnames, domains: params.altnames,
subscriberEmail: greenlock._defaults._subscriberEmail, subscriberEmail: greenlock._defaults._subscriberEmail,
// TODO enable for Greenlock Pro // TODO enable for Greenlock Pro
//customerEmail: args.customerEmail //customerEmail: args.customerEmail
telemetry: greenlock._defaults.telemetry telemetry: greenlock._defaults.telemetry
*/
}); });
} }
}; };
greenlock._single = function(args) {
if (!args.servername) {
return Promise.reject(new Error('no servername given'));
}
if (
args.servernames ||
args.subject ||
args.renewBefore ||
args.issueBefore ||
args.expiresBefore
) {
return Promise.reject(
new Error(
'bad arguments, did you mean to call greenlock.renew()?'
)
);
}
// duplicate, force, and others still allowed
return Promise.resolve(args);
};
greenlock.get = function(args) {
return greenlock
._single(args)
.then(function() {
args._includePems = true;
return greenlock.renew(args);
})
.then(function(results) {
if (!results || !results.length) {
return null;
}
// just get the first one
var result = results[0];
// (there should be only one, ideally)
if (results.length > 1) {
var err = new Error(
"a search for '" +
args.servername +
"' returned multiple certificates"
);
err.context = 'duplicate_certs';
err.servername = args.servername;
err.subjects = results.map(function(r) {
return (r.site || {}).subject || 'N/A';
});
greenlock._notify('warning', err);
}
if (result.error) {
return Promise.reject(result.error);
}
// site for plugin options, such as http-01 challenge
// pems for the obvious reasons
return result;
});
};
greenlock._config = function(args) {
return greenlock
._single(args)
.then(function() {
return greenlock.manager.find(args);
})
.then(function(sites) {
if (!sites || !sites.length) {
return null;
}
var site = sites[0];
site = JSON.parse(JSON.stringify(site));
if (!site.store) {
site.store = greenlock._defaults.store;
}
if (!site.challenges) {
site.challenges = greenlock._defaults.challenges;
}
return site;
});
};
// needs to get info about the renewal, such as which store and challenge(s) to use // needs to get info about the renewal, such as which store and challenge(s) to use
greenlock.renew = function(args) { greenlock.renew = function(args) {
return greenlock.manager.config().then(function(mconf) { return greenlock.manager.defaults().then(function(mconf) {
return greenlock._renew(mconf, args); return greenlock._renew(mconf, args);
}); });
}; };
@ -226,15 +333,16 @@ G.create = function(gconf) {
args.renewStagger = U._parseDuration(args.renewStagger); args.renewStagger = U._parseDuration(args.renewStagger);
} }
if (args.domain) { if (args.servername) {
// this doesn't have to be the subject, it can be anything // this doesn't have to be the subject, it can be anything
// however, not sure how useful this really is... // however, not sure how useful this really is...
args.domain = args.toLowerCase(); args.servername = args.servername.toLowerCase();
} }
args.defaults = greenlock.defaults; //console.log('greenlock._renew find', args);
return greenlock.manager.find(args).then(function(sites) { return greenlock.manager.find(args).then(function(sites) {
// Note: the manager must guaranteed that these are mutable copies // Note: the manager must guaranteed that these are mutable copies
//console.log('greenlock._renew found', sites);
var renewedOrFailed = []; var renewedOrFailed = [];
@ -244,25 +352,28 @@ G.create = function(gconf) {
return Promise.resolve(null); return Promise.resolve(null);
} }
var order = { var order = { site: site };
site: site
};
renewedOrFailed.push(order); renewedOrFailed.push(order);
// TODO merge args + result? // TODO merge args + result?
return greenlock return greenlock
._order(mconf, site) ._order(mconf, site)
.then(function(pems) { .then(function(pems) {
order.pems = pems; if (args._includePems) {
order.pems = pems;
}
}) })
.catch(function(err) { .catch(function(err) {
order.error = err;
// For greenlock express serialization // For greenlock express serialization
err.toJSON = errorToJSON; err.toJSON = errorToJSON;
err.context = err.context || 'cert_order';
err.subject = site.subject; err.subject = site.subject;
if (args.servername) { if (args.servername) {
err.servername = args.servername; err.servername = args.servername;
} }
// for debugging, but not to be relied on // for debugging, but not to be relied on
err._order = order; err._site = site;
// TODO err.context = err.context || 'renew_certificate' // TODO err.context = err.context || 'renew_certificate'
greenlock._notify('error', err); greenlock._notify('error', err);
}) })
@ -312,20 +423,16 @@ G.create = function(gconf) {
greenlock.order = function(args) { greenlock.order = function(args) {
return greenlock._init().then(function() { return greenlock._init().then(function() {
return greenlock.manager.config().then(function(mconf) { return greenlock.manager.defaults().then(function(mconf) {
return greenlock._order(mconf, args); return greenlock._order(mconf, args);
}); });
}); });
}; };
greenlock._order = function(mconf, args) { greenlock._order = function(mconf, args) {
// packageAgent, maintainerEmail
return greenlock._acme(args).then(function(acme) { return greenlock._acme(args).then(function(acme) {
var storeConf = args.store || greenlock._defaults.store; var storeConf = args.store || greenlock._defaults.store;
return P._load(storeConf.module).then(function(plugin) { return P._loadStore(storeConf).then(function(store) {
var store = Greenlock._normalizeStore(
storeConf.module,
plugin.create(storeConf)
);
return A._getOrCreate( return A._getOrCreate(
greenlock, greenlock,
mconf, mconf,
@ -339,17 +446,7 @@ G.create = function(gconf) {
greenlock._defaults.challenges; greenlock._defaults.challenges;
return Promise.all( return Promise.all(
Object.keys(challengeConfs).map(function(typ01) { Object.keys(challengeConfs).map(function(typ01) {
var chConf = challengeConfs[typ01]; return P._loadChallenge(challengeConfs, typ01);
return P._load(chConf.module).then(function(
plugin
) {
var ch = Greenlock._normalizeChallenge(
chConf.module,
plugin.create(chConf)
);
ch._type = typ01;
return ch;
});
}) })
).then(function(arr) { ).then(function(arr) {
var challenges = {}; var challenges = {};
@ -390,6 +487,8 @@ G.create = function(gconf) {
return greenlock; return greenlock;
}; };
G._loadChallenge = P._loadChallenge;
G._defaults = function(opts) { G._defaults = function(opts) {
var defaults = {}; var defaults = {};
@ -503,114 +602,6 @@ G._defaults = function(opts) {
return defaults; return defaults;
}; };
Greenlock._normalizeStore = function(name, store) {
var acc = store.accounts;
var crt = store.certificates;
var warned = false;
function warn() {
if (warned) {
return;
}
warned = true;
console.warn(
"'" +
name +
"' may have incorrect function signatures, or contains deprecated use of callbacks"
);
}
// accs
if (acc.check && 2 === acc.check.length) {
warn();
acc._thunk_check = acc.check;
acc.check = promisify(acc._thunk_check);
}
if (acc.set && 3 === acc.set.length) {
warn();
acc._thunk_set = acc.set;
acc.set = promisify(acc._thunk_set);
}
if (2 === acc.checkKeypair.length) {
warn();
acc._thunk_checkKeypair = acc.checkKeypair;
acc.checkKeypair = promisify(acc._thunk_checkKeypair);
}
if (3 === acc.setKeypair.length) {
warn();
acc._thunk_setKeypair = acc.setKeypair;
acc.setKeypair = promisify(acc._thunk_setKeypair);
}
// certs
if (2 === crt.check.length) {
warn();
crt._thunk_check = crt.check;
crt.check = promisify(crt._thunk_check);
}
if (3 === crt.set.length) {
warn();
crt._thunk_set = crt.set;
crt.set = promisify(crt._thunk_set);
}
if (2 === crt.checkKeypair.length) {
warn();
crt._thunk_checkKeypair = crt.checkKeypair;
crt.checkKeypair = promisify(crt._thunk_checkKeypair);
}
if (2 === crt.setKeypair.length) {
warn();
crt._thunk_setKeypair = crt.setKeypair;
crt.setKeypair = promisify(crt._thunk_setKeypair);
}
return store;
};
Greenlock._normalizeChallenge = function(name, ch) {
var warned = false;
function warn() {
if (warned) {
return;
}
warned = true;
console.warn(
"'" +
name +
"' may have incorrect function signatures, or contains deprecated use of callbacks"
);
}
// init, zones, set, get, remove
if (ch.init && 2 === ch.init.length) {
warn();
ch._thunk_init = ch.init;
ch.init = promisify(ch._thunk_init);
}
if (ch.zones && 2 === ch.zones.length) {
warn();
ch._thunk_zones = ch.zones;
ch.zones = promisify(ch._thunk_zones);
}
if (2 === ch.set.length) {
warn();
ch._thunk_set = ch.set;
ch.set = promisify(ch._thunk_set);
}
if (2 === ch.remove.length) {
warn();
ch._thunk_remove = ch.remove;
ch.remove = promisify(ch._thunk_remove);
}
if (ch.get && 2 === ch.get.length) {
warn();
ch._thunk_get = ch.get;
ch.get = promisify(ch._thunk_get);
}
return ch;
};
function errorToJSON(e) { function errorToJSON(e) {
var error = {}; var error = {};
Object.getOwnPropertyNames(e).forEach(function(k) { Object.getOwnPropertyNames(e).forEach(function(k) {

176
order.js
View File

@ -1,97 +1,95 @@
var accountKeypair = await Keypairs.generate({ kty: accKty }); var accountKeypair = await Keypairs.generate({ kty: accKty });
if (config.debug) {
console.info('Account Key Created');
console.info(JSON.stringify(accountKeypair, null, 2));
console.info();
console.info();
}
var account = await acme.accounts.create({
agreeToTerms: agree,
// TODO detect jwk/pem/der?
accountKeypair: { privateKeyJwk: accountKeypair.private },
subscriberEmail: config.email
});
// TODO top-level agree
function agree(tos) {
if (config.debug) { if (config.debug) {
console.info('Account Key Created'); console.info('Agreeing to Terms of Service:');
console.info(JSON.stringify(accountKeypair, null, 2)); console.info(tos);
console.info(); console.info();
console.info(); console.info();
} }
agreed = true;
return Promise.resolve(tos);
}
if (config.debug) {
console.info('New Subscriber Account');
console.info(JSON.stringify(account, null, 2));
console.info();
console.info();
}
if (!agreed) {
throw new Error('Failed to ask the user to agree to terms');
}
var account = await acme.accounts.create({ var certKeypair = await Keypairs.generate({ kty: srvKty });
agreeToTerms: agree, var pem = await Keypairs.export({
// TODO detect jwk/pem/der? jwk: certKeypair.private,
accountKeypair: { privateKeyJwk: accountKeypair.private }, encoding: 'pem'
subscriberEmail: config.email });
}); if (config.debug) {
console.info('Server Key Created');
console.info('privkey.jwk.json');
console.info(JSON.stringify(certKeypair, null, 2));
// This should be saved as `privkey.pem`
console.info();
console.info('privkey.' + srvKty.toLowerCase() + '.pem:');
console.info(pem);
console.info();
}
// TODO top-level agree // 'subject' should be first in list
function agree(tos) { var domains = randomDomains(rnd);
if (config.debug) { if (config.debug) {
console.info('Agreeing to Terms of Service:'); console.info('Get certificates for random domains:');
console.info(tos); console.info(
console.info(); domains
console.info(); .map(function(puny) {
} var uni = punycode.toUnicode(puny);
agreed = true; if (puny !== uni) {
return Promise.resolve(tos); return puny + ' (' + uni + ')';
} }
if (config.debug) { return puny;
console.info('New Subscriber Account'); })
console.info(JSON.stringify(account, null, 2)); .join('\n')
console.info(); );
console.info(); console.info();
} }
if (!agreed) {
throw new Error('Failed to ask the user to agree to terms');
}
var certKeypair = await Keypairs.generate({ kty: srvKty });
var pem = await Keypairs.export({
jwk: certKeypair.private,
encoding: 'pem'
});
if (config.debug) {
console.info('Server Key Created');
console.info('privkey.jwk.json');
console.info(JSON.stringify(certKeypair, null, 2));
// This should be saved as `privkey.pem`
console.info();
console.info('privkey.' + srvKty.toLowerCase() + '.pem:');
console.info(pem);
console.info();
}
// 'subject' should be first in list
var domains = randomDomains(rnd);
if (config.debug) {
console.info('Get certificates for random domains:');
console.info(
domains
.map(function(puny) {
var uni = punycode.toUnicode(puny);
if (puny !== uni) {
return puny + ' (' + uni + ')';
}
return puny;
})
.join('\n')
);
console.info();
}
// Create CSR
var csrDer = await CSR.csr({
jwk: certKeypair.private,
domains: domains,
encoding: 'der'
});
var csr = Enc.bufToUrlBase64(csrDer);
var csrPem = PEM.packBlock({
type: 'CERTIFICATE REQUEST',
bytes: csrDer /* { jwk: jwk, domains: opts.domains } */
});
if (config.debug) {
console.info('Certificate Signing Request');
console.info(csrPem);
console.info();
}
var results = await acme.certificates.create({
account: account,
accountKeypair: { privateKeyJwk: accountKeypair.private },
csr: csr,
domains: domains,
challenges: challenges, // must be implemented
customerEmail: null
});
// Create CSR
var csrDer = await CSR.csr({
jwk: certKeypair.private,
domains: domains,
encoding: 'der'
});
var csr = Enc.bufToUrlBase64(csrDer);
var csrPem = PEM.packBlock({
type: 'CERTIFICATE REQUEST',
bytes: csrDer /* { jwk: jwk, domains: opts.domains } */
});
if (config.debug) {
console.info('Certificate Signing Request');
console.info(csrPem);
console.info();
}
var results = await acme.certificates.create({
account: account,
accountKeypair: { privateKeyJwk: accountKeypair.private },
csr: csr,
domains: domains,
challenges: challenges, // must be implemented
customerEmail: null
});

8
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "@root/greenlock", "name": "@root/greenlock",
"version": "3.0.1", "version": "3.0.2",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -74,9 +74,9 @@
} }
}, },
"acme-http-01-standalone": { "acme-http-01-standalone": {
"version": "3.0.0", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/acme-http-01-standalone/-/acme-http-01-standalone-3.0.0.tgz", "resolved": "https://registry.npmjs.org/acme-http-01-standalone/-/acme-http-01-standalone-3.0.5.tgz",
"integrity": "sha512-lZqVab2UZ1Dp36HemfhGEvdYOcVNg5wyVXNjtPUqGSAOVUOKqwi3gDrTGwqz+FBrEEEEpTngDPaZn2g3hfmPLA==" "integrity": "sha512-W4GfK+39GZ+u0mvxRVUcVFCG6gposfzEnSBF20T/NUwWAKG59wQT1dUbS1NixRIAsRuhpGc4Jx659cErFQH0Pg=="
}, },
"cert-info": { "cert-info": {
"version": "1.5.1", "version": "1.5.1",

View File

@ -1,6 +1,6 @@
{ {
"name": "@root/greenlock", "name": "@root/greenlock",
"version": "3.0.1", "version": "3.0.2",
"description": "The easiest Let's Encrypt client for Node.js and Browsers", "description": "The easiest Let's Encrypt client for Node.js and Browsers",
"homepage": "https://rootprojects.org/greenlock/", "homepage": "https://rootprojects.org/greenlock/",
"main": "greenlock.js", "main": "greenlock.js",
@ -40,7 +40,7 @@
"@root/keypairs": "^0.9.0", "@root/keypairs": "^0.9.0",
"@root/mkdirp": "^1.0.0", "@root/mkdirp": "^1.0.0",
"@root/request": "^1.3.10", "@root/request": "^1.3.10",
"acme-http-01-standalone": "^3.0.0", "acme-http-01-standalone": "^3.0.5",
"cert-info": "^1.5.1", "cert-info": "^1.5.1",
"greenlock-manager-fs": "^0.6.0", "greenlock-manager-fs": "^0.6.0",
"greenlock-store-fs": "^3.2.0", "greenlock-store-fs": "^3.2.0",

View File

@ -8,7 +8,22 @@ var spawnSync = require('child_process').spawnSync;
// Exported for CLIs and such to override // Exported for CLIs and such to override
P.PKG_DIR = __dirname; P.PKG_DIR = __dirname;
P._load = function(modname) { P._loadStore = function(storeConf) {
return P._loadHelper(storeConf.module).then(function(plugin) {
return P._normalizeStore(storeConf.module, plugin.create(storeConf));
});
};
P._loadChallenge = function(chConfs, typ01) {
return P._loadHelper(chConfs[typ01].module).then(function(plugin) {
var ch = P._normalizeChallenge(
chConfs[typ01].module,
plugin.create(chConfs[typ01])
);
ch._type = typ01;
return ch;
});
};
P._loadHelper = function(modname) {
try { try {
return Promise.resolve(require(modname)); return Promise.resolve(require(modname));
} catch (e) { } catch (e) {
@ -18,6 +33,150 @@ P._load = function(modname) {
} }
}; };
P._normalizeStore = function(name, store) {
var acc = store.accounts;
var crt = store.certificates;
var warned = false;
function warn() {
if (warned) {
return;
}
warned = true;
console.warn(
"'" +
name +
"' may have incorrect function signatures, or contains deprecated use of callbacks"
);
}
// accs
if (acc.check && 2 === acc.check.length) {
warn();
acc._thunk_check = acc.check;
acc.check = promisify(acc._thunk_check);
}
if (acc.set && 3 === acc.set.length) {
warn();
acc._thunk_set = acc.set;
acc.set = promisify(acc._thunk_set);
}
if (2 === acc.checkKeypair.length) {
warn();
acc._thunk_checkKeypair = acc.checkKeypair;
acc.checkKeypair = promisify(acc._thunk_checkKeypair);
}
if (3 === acc.setKeypair.length) {
warn();
acc._thunk_setKeypair = acc.setKeypair;
acc.setKeypair = promisify(acc._thunk_setKeypair);
}
// certs
if (2 === crt.check.length) {
warn();
crt._thunk_check = crt.check;
crt.check = promisify(crt._thunk_check);
}
if (3 === crt.set.length) {
warn();
crt._thunk_set = crt.set;
crt.set = promisify(crt._thunk_set);
}
if (2 === crt.checkKeypair.length) {
warn();
crt._thunk_checkKeypair = crt.checkKeypair;
crt.checkKeypair = promisify(crt._thunk_checkKeypair);
}
if (2 === crt.setKeypair.length) {
warn();
crt._thunk_setKeypair = crt.setKeypair;
crt.setKeypair = promisify(crt._thunk_setKeypair);
}
return store;
};
P._normalizeChallenge = function(name, ch) {
var gch = {};
var warned = false;
function warn() {
if (warned) {
return;
}
warned = true;
console.warn(
"'" +
name +
"' may have incorrect function signatures, or contains deprecated use of callbacks"
);
}
var warned2 = false;
function warn2() {
if (warned2) {
return;
}
warned2 = true;
console.warn(
"'" +
name +
"' did not return a Promise when called. This should be fixed by the maintainer."
);
}
function wrappy(fn) {
return function(_params) {
return Promise.resolve().then(function() {
var result = fn.call(ch, _params);
if (!result || !result.then) {
warn2();
}
return result;
});
};
}
// init, zones, set, get, remove
if (ch.init) {
if (2 === ch.init.length) {
warn();
ch._thunk_init = ch.init;
ch.init = promisify(ch._thunk_init);
}
gch.init = wrappy(ch.init);
}
if (ch.zones) {
if (2 === ch.zones.length) {
warn();
ch._thunk_zones = ch.zones;
ch.zones = promisify(ch._thunk_zones);
}
gch.zones = wrappy(ch.zones);
}
if (2 === ch.set.length) {
warn();
ch._thunk_set = ch.set;
ch.set = promisify(ch._thunk_set);
}
gch.set = wrappy(ch.set);
if (2 === ch.remove.length) {
warn();
ch._thunk_remove = ch.remove;
ch.remove = promisify(ch._thunk_remove);
}
gch.remove = wrappy(ch.remove);
if (ch.get) {
if (2 === ch.get.length) {
warn();
ch._thunk_get = ch.get;
ch.get = promisify(ch._thunk_get);
}
gch.get = wrappy(ch.get);
}
return gch;
};
P._loadSync = function(modname) { P._loadSync = function(modname) {
var mod; var mod;
try { try {

View File

@ -33,9 +33,9 @@ greenlock
subscriberEmail: email subscriberEmail: email
}) })
.then(function() { .then(function() {
return greenlock.renew().then(function (pems) { return greenlock.renew().then(function(pems) {
console.info(pems); console.info(pems);
}); });
}) })
.catch(function(e) { .catch(function(e) {
console.error('yo', e.code); console.error('yo', e.code);