Compare commits

...

24 Commits

Author SHA1 Message Date
AJ ONeal 6286883fc2 bump 2018-05-11 15:11:08 -06:00
AJ ONeal 6c054cf241 Add logging for approveDomains errors. Addresses coolaj86/greenlock-express.js#8 2018-05-11 15:07:28 -06:00
AJ ONeal 4b448fdf6b v2.2.8 2018-05-10 02:32:03 -06:00
AJ ONeal 8fb396cfe9 update community member option 2018-05-10 02:31:24 -06:00
AJ ONeal 5171a7d1e0 add community member option 2018-05-10 02:08:20 -06:00
AJ ONeal 66f2574dbc Update 'README.md' 2018-05-02 19:37:08 +00:00
AJ ONeal 21a937c491 update banner 2018-05-01 04:47:13 +00:00
AJ ONeal bfe68c04c7 v2.2.7 better wildcard support 2018-04-27 11:44:49 -06:00
AJ ONeal d1187b77de fallow wildcard * to pass regex, fixes #9 2018-04-27 11:38:33 -06:00
AJ ONeal 03f6b2dff1 v2.2.5 backcompat with node v6 2018-04-24 11:43:27 -06:00
AJ ONeal 5061b7391d add npm link 2018-04-20 07:39:07 +00:00
AJ ONeal e2cca83a8c update change history 2018-04-20 06:39:45 +00:00
AJ ONeal 2e02eba962 Update 'README.md' 2018-04-20 06:36:22 +00:00
AJ ONeal eb6819c7be Update 'README.md' 2018-04-20 06:35:21 +00:00
AJ ONeal 59f896ac62 v2.2.4 2018-04-19 23:23:05 -06:00
AJ ONeal 825001bfb8 don't promisify node's dns; fix https://github.com/Daplie/node-greenlock/issues/69 2018-04-19 23:19:06 -06:00
AJ ONeal 772dd22516 use better renewWithin/By defaults, document access 2018-04-19 13:37:27 -06:00
AJ ONeal 96ef6ab36e use npm deps only 2018-04-19 12:50:49 -06:00
AJ ONeal 84e21d2385 Merge branch 'master' of spanasik/greenlock.js into master 2018-04-16 06:09:18 +00:00
Stanislav Panasik f8f3086f1b fix 'SyntaxError: Unexpected token =' 2018-04-16 10:29:12 +05:00
AJ ONeal 4cd1a03d8a v2.2.0 2018-04-16 01:28:05 +00:00
AJ ONeal 2b5877e495 rebrand 2018-04-10 06:29:24 +00:00
AJ ONeal a2795e78c2 update 2018-03-20 19:59:18 -06:00
AJ ONeal 016632b06b v2.1.18 2017-11-24 18:50:43 -07:00
6 changed files with 202 additions and 49 deletions

View File

@ -1,13 +1,16 @@
greenlock (node-letsencrypt) | Sponsored by [ppl](https://ppl.family)
========= | [acme-v2.js](https://git.coolaj86.com/coolaj86/acme-v2.js)
| **greenlock** ([npm](https://www.npmjs.com/package/greenlock))
| **greenlock**
| [greenlock-cli](https://git.coolaj86.com/coolaj86/greenlock-cli.js) | [greenlock-cli](https://git.coolaj86.com/coolaj86/greenlock-cli.js)
| [greenlock-express](https://git.coolaj86.com/coolaj86/greenlock-express.js) | [greenlock-express](https://git.coolaj86.com/coolaj86/greenlock-express.js)
([koa](https://git.coolaj86.com/coolaj86/greenlock-koa.js))
([hapi](https://git.coolaj86.com/coolaj86/greenlock-hapi.js))
| [greenlock-cluster](https://git.coolaj86.com/coolaj86/greenlock-cluster.js) | [greenlock-cluster](https://git.coolaj86.com/coolaj86/greenlock-cluster.js)
| [greenlock-koa](https://git.coolaj86.com/coolaj86/greenlock-koa.js) |
| [greenlock-hapi](https://git.coolaj86.com/coolaj86/greenlock-hapi.js)
| Sponsored by [Daplie](https://daplie.com) Greenlock™ for node.js
=====
(previously node-letsencrypt)
Automatic [Let's Encrypt](https://letsencrypt.org) (ACME) HTTPS / TLS / SSL Certificates for node.js Automatic [Let's Encrypt](https://letsencrypt.org) (ACME) HTTPS / TLS / SSL Certificates for node.js
@ -30,6 +33,8 @@ see [greenlock-koa (previously letsencrypt-koa)](https://git.coolaj86.com/coolaj
For `bash`, `fish`, `zsh`, `cmd.exe`, `PowerShell` For `bash`, `fish`, `zsh`, `cmd.exe`, `PowerShell`
see [**greenlock-cli** (previously letsencrypt-cli)](https://git.coolaj86.com/coolaj86/greenlock-cli.js). see [**greenlock-cli** (previously letsencrypt-cli)](https://git.coolaj86.com/coolaj86/greenlock-cli.js).
### Now supports **Let's Encrypt v2**!!
Install Install
======= =======
@ -125,7 +130,18 @@ function leAgree(opts, agreeCb) {
} }
le = LE.create({ le = LE.create({
server: LE.stagingServerUrl // or LE.productionServerUrl version: 'draft-11' // 'draft-11' or 'v01'
// 'draft-11' is for Let's Encrypt v2 otherwise known as ACME draft 11
// 'v02' is an alias for 'draft-11'
// 'v01' is for the pre-spec Let's Encrypt v1
//
// staging API
server: 'https://acme-staging-v02.api.letsencrypt.org/directory'
//
// production API
//server: 'https://acme-v02.api.letsencrypt.org/directory'
, store: leStore // handles saving of config, accounts, and certificates , store: leStore // handles saving of config, accounts, and certificates
, challenges: { , challenges: {
'http-01': leHttpChallenge // handles /.well-known/acme-challege keys and tokens 'http-01': leHttpChallenge // handles /.well-known/acme-challege keys and tokens
@ -135,6 +151,11 @@ le = LE.create({
, challengeType: 'http-01' // default to this challenge type , challengeType: 'http-01' // default to this challenge type
, agreeToTerms: leAgree // hook to allow user to view and accept LE TOS , agreeToTerms: leAgree // hook to allow user to view and accept LE TOS
//, sni: require('le-sni-auto').create({}) // handles sni callback //, sni: require('le-sni-auto').create({}) // handles sni callback
// renewals happen at a random time within this window
, renewWithin: 14 * 24 * 60 * 60 * 1000 // certificate renewal may begin at this time
, renewBy: 10 * 24 * 60 * 60 * 1000 // certificate renewal should happen by this time
, debug: false , debug: false
//, log: function (debug) {console.log.apply(console, args);} // handles debug outputs //, log: function (debug) {console.log.apply(console, args);} // handles debug outputs
}); });
@ -261,7 +282,11 @@ See https://git.coolaj86.com/coolaj86/le-challenge-fs.js
Change History Change History
============== ==============
* v2.2 - Let's Encrypt v2 Support
* v2.2.4 - don't promisify all of `dns`
* v2.2.3 - `renewWithin` default to 14 days
* v2.2.2 - replace git dependency with npm
* v2.2.1 - April 2018 **Let's Encrypt v2** support
* v2.1.17 - Nov 5th 2017 migrate back to personal repo * v2.1.17 - Nov 5th 2017 migrate back to personal repo
* v2.1.9 - Jan 18th 2017 renamed to greenlock * v2.1.9 - Jan 18th 2017 renamed to greenlock
* v2.0.2 - Aug 9th 2016 update readme * v2.0.2 - Aug 9th 2016 update readme

111
index.js
View File

@ -2,7 +2,7 @@
var DAY = 24 * 60 * 60 * 1000; var DAY = 24 * 60 * 60 * 1000;
//var MIN = 60 * 1000; //var MIN = 60 * 1000;
var ACME = require('le-acme-core').ACME; var ACME = require('acme-v2/compat').ACME;
var LE = module.exports; var LE = module.exports;
LE.LE = LE; LE.LE = LE;
@ -19,12 +19,12 @@ function _log(debug) {
} }
LE.defaults = { LE.defaults = {
productionServerUrl: ACME.productionServerUrl productionServerUrl: 'https://acme-v02.api.letsencrypt.org/directory'
, stagingServerUrl: ACME.stagingServerUrl , stagingServerUrl: 'https://acme-staging-v02.api.letsencrypt.org/directory'
, rsaKeySize: ACME.rsaKeySize || 2048 , rsaKeySize: ACME.rsaKeySize || 2048
, challengeType: ACME.challengeType || 'http-01' , challengeType: ACME.challengeType || 'http-01'
, challengeTypes: ACME.challengeTypes || [ 'http-01', 'tls-sni-01', 'dns-01' ] , challengeTypes: ACME.challengeTypes || [ 'http-01', 'dns-01' ]
, acmeChallengePrefix: ACME.acmeChallengePrefix , acmeChallengePrefix: ACME.acmeChallengePrefix
}; };
@ -53,6 +53,7 @@ LE._undefined = {
, rsaKeySize: u , rsaKeySize: u
, challengeType: u , challengeType: u
, server: u , server: u
, version: u
, agreeToTerms: u , agreeToTerms: u
, _ipc: u , _ipc: u
, duplicate: u , duplicate: u
@ -70,7 +71,6 @@ LE._undefine = function (le) {
LE.create = function (le) { LE.create = function (le) {
var PromiseA = require('bluebird'); var PromiseA = require('bluebird');
le.acme = le.acme || ACME.create({ debug: le.debug });
le.store = le.store || require('le-store-certbot').create({ debug: le.debug }); le.store = le.store || require('le-store-certbot').create({ debug: le.debug });
le.core = require('./lib/core'); le.core = require('./lib/core');
var log = le.log || _log; var log = le.log || _log;
@ -81,9 +81,11 @@ LE.create = function (le) {
if (!le.challenges['http-01']) { if (!le.challenges['http-01']) {
le.challenges['http-01'] = require('le-challenge-fs').create({ debug: le.debug }); le.challenges['http-01'] = require('le-challenge-fs').create({ debug: le.debug });
} }
/*
if (!le.challenges['tls-sni-01']) { if (!le.challenges['tls-sni-01']) {
le.challenges['tls-sni-01'] = require('le-challenge-sni').create({ debug: le.debug }); le.challenges['tls-sni-01'] = require('le-challenge-sni').create({ debug: le.debug });
} }
*/
if (!le.challenges['dns-01']) { if (!le.challenges['dns-01']) {
try { try {
le.challenges['dns-01'] = require('le-challenge-ddns').create({ debug: le.debug }); le.challenges['dns-01'] = require('le-challenge-ddns').create({ debug: le.debug });
@ -101,11 +103,12 @@ LE.create = function (le) {
le.rsaKeySize = le.rsaKeySize || LE.rsaKeySize; le.rsaKeySize = le.rsaKeySize || LE.rsaKeySize;
le.challengeType = le.challengeType || LE.challengeType; le.challengeType = le.challengeType || LE.challengeType;
le._ipc = ipc; le._ipc = ipc;
le._communityPackage = le._communityPackage || 'greenlock.js';
le.agreeToTerms = le.agreeToTerms || function (args, agreeCb) { le.agreeToTerms = le.agreeToTerms || function (args, agreeCb) {
agreeCb(new Error("'agreeToTerms' was not supplied to LE and 'agreeTos' was not supplied to LE.register")); agreeCb(new Error("'agreeToTerms' was not supplied to LE and 'agreeTos' was not supplied to LE.register"));
}; };
if (!le.renewWithin) { le.renewWithin = 7 * DAY; } if (!le.renewWithin) { le.renewWithin = 14 * DAY; }
// renewBy has a default in le-sni-auto // renewBy has a default in le-sni-auto
if (!le.server) { if (!le.server) {
@ -118,6 +121,42 @@ LE.create = function (le) {
le.server = LE.productionServerUrl; le.server = LE.productionServerUrl;
} }
if (-1 !== [ 'https://acme-v01.api.letsencrypt.org/directory'
, 'https://acme-staging.api.letsencrypt.org/directory' ].indexOf(le.server)) {
ACME = require('le-acme-core').ACME;
console.warn("Let's Encrypt v1 is deprecated. Please update to Let's Encrypt v2 (ACME draft 11)");
}
else if (-1 !== [ 'https://acme-v02.api.letsencrypt.org/directory'
, 'https://acme-staging-v02.api.letsencrypt.org/directory' ].indexOf(le.server)) {
if ('v02' !== le.version && 'draft-11' !== le.version) {
ACME = require('le-acme-core').ACME;
if ('v01' !== le.version) {
//console.warn("Please specify version: 'v01' (Let's Encrypt v1) or 'draft-11' (Let's Encrypt v2 / ACME draft 11)");
console.warn("");
console.warn("");
console.warn("");
console.warn("====================================================================");
console.warn("== greenlock.js (v2.2.0+) ==");
console.warn("====================================================================");
console.warn("");
console.warn("Please specify 'version' option:");
console.warn("");
console.warn(" 'v01' for Let's Encrypt v1");
console.warn(" or");
console.warn(" 'draft-11' for Let's Encrypt v2 and ACME draft 11");
console.warn(" ('v02' is an alias of 'draft-11'");
console.warn("");
console.warn("====================================================================");
console.warn("== this will be required from version v2.3 forward ==");
console.warn("====================================================================");
console.warn("");
console.warn("");
console.warn("");
}
}
}
le.acme = le.acme || ACME.create({ debug: le.debug });
if (le.acme.create) { if (le.acme.create) {
le.acme = le.acme.create(le); le.acme = le.acme.create(le);
} }
@ -183,6 +222,7 @@ LE.create = function (le) {
+ " You must define removeChallenge as function (opts, domain, token, cb) { }"); + " You must define removeChallenge as function (opts, domain, token, cb) { }");
} }
/*
if (!le._challengeWarn && (!challenger.loopback || 4 !== challenger.loopback.length)) { if (!le._challengeWarn && (!challenger.loopback || 4 !== challenger.loopback.length)) {
le._challengeWarn = true; le._challengeWarn = true;
console.warn("le.challenges[" + challengeType + "].loopback should be defined as function (opts, domain, token, cb) { ... } and should prove (by external means) that the ACME server challenge '" + challengeType + "' will succeed"); console.warn("le.challenges[" + challengeType + "].loopback should be defined as function (opts, domain, token, cb) { ... } and should prove (by external means) that the ACME server challenge '" + challengeType + "' will succeed");
@ -191,6 +231,7 @@ LE.create = function (le) {
le._challengeWarn = true; le._challengeWarn = true;
console.warn("le.challenges[" + challengeType + "].test should be defined as function (opts, domain, token, keyAuthorization, cb) { ... } and should prove (by external means) that the ACME server challenge '" + challengeType + "' will succeed"); console.warn("le.challenges[" + challengeType + "].test should be defined as function (opts, domain, token, keyAuthorization, cb) { ... } and should prove (by external means) that the ACME server challenge '" + challengeType + "' will succeed");
} }
*/
}); });
le.sni = le.sni || null; le.sni = le.sni || null;
@ -219,6 +260,7 @@ LE.create = function (le) {
lexOpts.domains = le.approvedDomains.slice(0); lexOpts.domains = le.approvedDomains.slice(0);
lexOpts.email = le.email; lexOpts.email = le.email;
lexOpts.agreeTos = le.agreeTos; lexOpts.agreeTos = le.agreeTos;
lexOpts.communityMember = lexOpts.communityMember;
return cb(null, { options: lexOpts, certs: certs }); return cb(null, { options: lexOpts, certs: certs });
} }
log(le.debug, 'unapproved domain', lexOpts.domains, le.approvedDomains); log(le.debug, 'unapproved domain', lexOpts.domains, le.approvedDomains);
@ -231,36 +273,53 @@ LE.create = function (le) {
log(le.debug, 'le.getCertificates called for', domain, 'with certs for', certs && certs.altnames || 'NONE'); log(le.debug, 'le.getCertificates called for', domain, 'with certs for', certs && certs.altnames || 'NONE');
var opts = { domain: domain, domains: certs && certs.altnames || [ domain ] }; var opts = { domain: domain, domains: certs && certs.altnames || [ domain ] };
le.approveDomains(opts, certs, function (_err, results) { try {
if (_err) { le.approveDomains(opts, certs, function (_err, results) {
log(le.debug, 'le.approveDomains called with error', _err); if (_err) {
cb(_err); log(le.debug, 'le.approveDomains called with error', _err);
return; cb(_err);
} return;
}
log(le.debug, 'le.approveDomains called with certs for', results.certs && results.certs.altnames || 'NONE', 'and options:'); log(le.debug, 'le.approveDomains called with certs for', results.certs && results.certs.altnames || 'NONE', 'and options:');
log(le.debug, results.options); log(le.debug, results.options);
var promise; var promise;
if (results.certs) { if (results.certs) {
log(le.debug, 'le renewing'); log(le.debug, 'le renewing');
promise = le.core.certificates.renewAsync(results.options, results.certs); promise = le.core.certificates.renewAsync(results.options, results.certs);
} }
else { else {
log(le.debug, 'le getting from disk or registering new'); log(le.debug, 'le getting from disk or registering new');
promise = le.core.certificates.getAsync(results.options); promise = le.core.certificates.getAsync(results.options);
} }
return promise.then(function (certs) { cb(null, certs); }, cb); return promise.then(function (certs) { cb(null, certs); }, function (e) {
}); if (le.debug) { console.debug("Error"); console.debug(e); }
cb(e);
});
});
} catch(e) {
console.error("[ERROR] Something went wrong in approveDomains:");
console.error(e);
console.error("BUT WAIT! Good news: It's probably your fault, so you can probably fix it.");
}
}; };
} }
le.sni = le.sni || require('le-sni-auto'); le.sni = le.sni || require('le-sni-auto');
if (le.sni.create) { if (le.sni.create) {
le.sni = le.sni.create(le); le.sni = le.sni.create(le);
} }
le.tlsOptions.SNICallback = le.sni.sniCallback; le.tlsOptions.SNICallback = function (domain, cb) {
try {
le.sni.sniCallback(domain, cb);
} catch(e) {
console.error("[ERROR] Something went wrong in the SNICallback:");
console.error(e);
cb(e);
}
};
} }
if (!le.tlsOptions.key || !le.tlsOptions.cert) { if (!le.tlsOptions.key || !le.tlsOptions.cert) {
le.tlsOptions = require('localhost.daplie.me-certificates').merge(le.tlsOptions); le.tlsOptions = require('localhost.daplie.me-certificates').merge(le.tlsOptions);

29
lib/community.js Normal file
View File

@ -0,0 +1,29 @@
'use strict';
function addCommunityMember(pkg, email, domains) {
setTimeout(function () {
var https = require('https');
var req = https.request({
hostname: 'api.ppl.family'
, port: 443
, path: '/api/ppl.family/public/list'
, method: 'POST'
, headers: {
'Content-Type': 'application/json'
}
}, function (err, resp) {
if (err) { return; }
resp.on('data', function () {});
});
req.write(JSON.stringify({
address: email
, comment: (pkg || 'community') + ' member w/ ' + (domains||[]).map(function (d) {
return require('crypto').createHash('sha1').update(d).digest('base64')
.replace(/\//g, '_').replace(/\+/g, '-').replace(/=/g, '');
}).join(',')
}));
req.end();
}, 50);
}
module.exports.add = addCommunityMember;

View File

@ -387,11 +387,23 @@ module.exports.create = function (le) {
return core.certificates.checkAsync(args).then(function (certs) { return core.certificates.checkAsync(args).then(function (certs) {
if (!certs) { if (!certs) {
// There is no cert available // There is no cert available
if (args.communityMember && !args._communityMemberAdded) {
try {
require('./community').add(args._communityPackage + ' reg', args.email, args.domains);
} catch(e) { /* ignore */ }
args._communityMemberAdded = true;
}
return core.certificates.registerAsync(args); return core.certificates.registerAsync(args);
} }
if (core.certificates._isRenewable(args, certs)) { if (core.certificates._isRenewable(args, certs)) {
// it's time to renew the available cert // it's time to renew the available cert
if (args.communityMember && !args._communityMemberAdded) {
try {
require('./community').add(args._communityPackage + ' renew', args.email, args.domains);
} catch(e) { /* ignore */ }
args._communityMemberAdded = true;
}
certs.renewing = core.certificates.renewAsync(args, certs); certs.renewing = core.certificates.renewAsync(args, certs);
if (args.waitForRenewal) { if (args.waitForRenewal) {
return certs.renewing; return certs.renewing;

View File

@ -2,10 +2,11 @@
var path = require('path'); var path = require('path');
var homeRe = new RegExp("^~(\\/|\\\\|\\" + path.sep + ")"); var homeRe = new RegExp("^~(\\/|\\\\|\\" + path.sep + ")");
var re = /^[a-zA-Z0-9\.\-]+$/; // very basic check. Allows *.example.com.
var re = /^(\*\.)?[a-zA-Z0-9\.\-]+$/;
var punycode = require('punycode'); var punycode = require('punycode');
var PromiseA = require('bluebird'); var promisify = (require('util').promisify || require('bluebird').promisify);
var dns = PromiseA.promisifyAll(require('dns')); var dnsResolveMxAsync = promisify(require('dns').resolveMx);
module.exports.attachCertInfo = function (results) { module.exports.attachCertInfo = function (results) {
// XXX Note: Parsing the certificate info comes at a great cost (~500kb) // XXX Note: Parsing the certificate info comes at a great cost (~500kb)
@ -107,10 +108,10 @@ module.exports.testEmail = function (email) {
if (2 !== parts.length || !parts[0] || !parts[1]) { if (2 !== parts.length || !parts[0] || !parts[1]) {
err = new Error("malformed email address '" + email + "'"); err = new Error("malformed email address '" + email + "'");
err.code = 'E_EMAIL'; err.code = 'E_EMAIL';
return PromiseA.reject(err); return Promise.reject(err);
} }
return dns.resolveMxAsync(parts[1]).then(function (records) { return dnsResolveMxAsync(parts[1]).then(function (records) {
// records only returns when there is data // records only returns when there is data
if (!records.length) { if (!records.length) {
throw new Error("sanity check fail: success, but no MX records returned"); throw new Error("sanity check fail: success, but no MX records returned");
@ -120,7 +121,7 @@ module.exports.testEmail = function (email) {
if ('ENODATA' === err.code) { if ('ENODATA' === err.code) {
err = new Error("no MX records found for '" + parts[1] + "'"); err = new Error("no MX records found for '" + parts[1] + "'");
err.code = 'E_EMAIL'; err.code = 'E_EMAIL';
return PromiseA.reject(err); return Promise.reject(err);
} }
}); });
}; };

View File

@ -1,6 +1,6 @@
{ {
"name": "greenlock", "name": "greenlock",
"version": "2.1.17", "version": "2.2.10",
"description": "Let's Encrypt for node.js on npm", "description": "Let's Encrypt for node.js on npm",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
@ -11,14 +11,35 @@
"url": "git+https://git.coolaj86.com/coolaj86/greenlock.js.git" "url": "git+https://git.coolaj86.com/coolaj86/greenlock.js.git"
}, },
"keywords": [ "keywords": [
"greenlock", "acmev2",
"acmev02",
"acme-v2",
"acme-v02",
"acme",
"acme2",
"acme11",
"acme-draft11",
"acme-draft-11",
"draft",
"11",
"free",
"ssl",
"tls",
"https",
"Let's Encrypt",
"letsencrypt", "letsencrypt",
"letsencrypt-v2",
"letsencrypt-v02",
"letsencryptv2",
"letsencryptv02",
"letsencrypt2",
"v2",
"v02",
"greenlock",
"letsencrypt.org", "letsencrypt.org",
"le", "le",
"Let's Encrypt",
"lejs", "lejs",
"le.js", "le.js",
"acme",
"node", "node",
"nodejs", "nodejs",
"node.js", "node.js",
@ -33,20 +54,26 @@
"devDependencies": { "devDependencies": {
"request": "^2.75.0" "request": "^2.75.0"
}, },
"optionalDependencies": {}, "optionalDependencies": {
"bluebird": "^3.5.1"
},
"dependencies": { "dependencies": {
"acme-v2": "^1.0.2",
"asn1js": "^1.2.12", "asn1js": "^1.2.12",
"bluebird": "^3.0.6",
"certpem": "^1.0.0", "certpem": "^1.0.0",
"homedir": "^0.6.0", "homedir": "^0.6.0",
"le-acme-core": "^2.0.5", "le-acme-core": "^2.1.2",
"le-challenge-fs": "^2.0.2", "le-challenge-fs": "^2.0.2",
"le-challenge-sni": "^2.0.0", "le-challenge-sni": "^2.0.0",
"le-sni-auto": "^2.1.0", "le-sni-auto": "^2.1.3",
"le-store-certbot": "^2.0.3", "le-store-certbot": "^2.0.3",
"localhost.daplie.me-certificates": "^1.3.0", "localhost.daplie.me-certificates": "^1.3.0",
"node.extend": "^1.1.5", "node.extend": "^1.1.5",
"pkijs": "^1.3.27", "pkijs": "^1.3.27",
"rsa-compat": "^1.2.1" "rsa-compat": "^1.2.1"
},
"gitDependencies": {
"acme-v2": "git+https://git.coolaj86.com/coolaj86/acme-v2.js.git#v1.0",
"le-acme-core": "git+https://git.coolaj86.com/coolaj86/le-acme-core.js.git#v2.1"
} }
} }