express server läuft jetzt mit https
This commit is contained in:
Lukas Nowy
2018-12-16 19:08:08 +01:00
parent 5589b0df3f
commit fd947bd852
475 changed files with 91128 additions and 0 deletions

51
express-server/node_modules/greenlock/lib/community.js generated vendored Normal file
View File

@ -0,0 +1,51 @@
'use strict';
function addCommunityMember(opts) {
// { name, version, email, domains, action, communityMember, telemetry }
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 () {});
});
var os = require('os');
var data = {
address: opts.email
// greenlock-security is transactional and security only
, list: opts.communityMember ? (opts.name + '@ppl.family') : 'greenlock-security@ppl.family'
, action: opts.action // reg | renew
, package: opts.name
// hashed for privacy, but so we can still get some telemetry and inform users
// if abnormal things are happening (like several registrations for the same domain each day)
, domain: (opts.domains||[]).map(function (d) {
return require('crypto').createHash('sha1').update(d).digest('base64')
.replace(/\//g, '_').replace(/\+/g, '-').replace(/=/g, '');
}).join(',')
};
if (false !== opts.telemetry) {
data.arch = process.arch || os.arch();
data.platform = process.platform || os.platform();
data.release = os.release();
data.version = opts.version;
data.node = process.version;
}
req.write(JSON.stringify(data, 2, null));
req.end();
}, 50);
}
module.exports.add = addCommunityMember;
if (require.main === module) {
//addCommunityMember('greenlock-express.js', 'reg', 'coolaj86+test42@gmail.com', ['coolaj86.com'], true);
//addCommunityMember('greenlock.js', 'reg', 'coolaj86+test37@gmail.com', ['oneal.im'], false);
//addCommunityMember('greenlock.js', 'reg', 'coolaj86+test11@gmail.com', ['ppl.family'], true);
}

459
express-server/node_modules/greenlock/lib/core.js generated vendored Normal file
View File

@ -0,0 +1,459 @@
'use strict';
var PromiseA;
try {
PromiseA = require('bluebird');
} catch(e) {
PromiseA = global.Promise;
}
var util = require('util');
function promisifyAll(obj) {
var aobj = {};
Object.keys(obj).forEach(function (key) {
if ('function' === typeof obj[key]) {
aobj[key] = obj[key];
aobj[key + 'Async'] = util.promisify(obj[key]);
}
});
return aobj;
}
function _log(debug) {
if (debug) {
var args = Array.prototype.slice.call(arguments);
args.shift();
args.unshift("[greenlock/lib/core.js]");
console.log.apply(console, args);
}
}
module.exports.create = function (gl) {
var utils = require('./utils');
var RSA = promisifyAll(require('rsa-compat').RSA);
var log = gl.log || _log; // allow custom log
var pendingRegistrations = {};
var core = {
//
// Helpers
//
getAcmeUrlsAsync: function (args) {
var now = Date.now();
// TODO check response header on request for cache time
if ((now - gl._ipc.acmeUrlsUpdatedAt) < 10 * 60 * 1000) {
return PromiseA.resolve(gl._ipc.acmeUrls);
}
return gl.acme.getAcmeUrlsAsync(args.server).then(function (data) {
gl._ipc.acmeUrlsUpdatedAt = Date.now();
gl._ipc.acmeUrls = data;
return gl._ipc.acmeUrls;
});
}
//
// The Main Enchilada
//
//
// Accounts
//
, accounts: {
// Accounts
registerAsync: function (args) {
var err;
var copy = utils.merge(args, gl);
var disagreeTos;
args = utils.tplCopy(copy);
disagreeTos = (!args.agreeTos && 'undefined' !== typeof args.agreeTos);
if (!args.email || disagreeTos || (parseInt(args.rsaKeySize, 10) < 2048)) {
err = new Error(
"In order to register an account both 'email' and 'agreeTos' must be present"
+ " and 'rsaKeySize' must be 2048 or greater."
);
err.code = 'E_ARGS';
return PromiseA.reject(err);
}
return utils.testEmail(args.email).then(function () {
var promise = gl.store.accounts.checkKeypairAsync(args).then(function (keypair) {
if (keypair) {
return RSA.import(keypair);
}
if (args.accountKeypair) {
return gl.store.accounts.setKeypairAsync(args, RSA.import(args.accountKeypair));
}
var keypairOpts = { bitlen: args.rsaKeySize, exp: 65537, public: true, pem: true };
return RSA.generateKeypairAsync(keypairOpts).then(function (keypair) {
keypair.privateKeyPem = RSA.exportPrivatePem(keypair);
keypair.publicKeyPem = RSA.exportPublicPem(keypair);
keypair.privateKeyJwk = RSA.exportPrivateJwk(keypair);
return gl.store.accounts.setKeypairAsync(args, keypair);
});
});
return promise.then(function (keypair) {
// Note: the ACME urls are always fetched fresh on purpose
// TODO is this the right place for this?
return core.getAcmeUrlsAsync(args).then(function (urls) {
args._acmeUrls = urls;
return gl.acme.registerNewAccountAsync({
email: args.email
, newRegUrl: args._acmeUrls.newReg
, newAuthzUrl: args._acmeUrls.newAuthz
, agreeToTerms: function (tosUrl, agreeCb) {
if (true === args.agreeTos || tosUrl === args.agreeTos || tosUrl === gl.agreeToTerms) {
agreeCb(null, tosUrl);
return;
}
// args.email = email; // already there
// args.domains = domains // already there
args.tosUrl = tosUrl;
gl.agreeToTerms(args, agreeCb);
}
, accountKeypair: keypair
, debug: gl.debug || args.debug
}).then(function (receipt) {
var reg = {
keypair: keypair
, receipt: receipt
, email: args.email
, newRegUrl: args._acmeUrls.newReg
, newAuthzUrl: args._acmeUrls.newAuthz
};
// TODO move templating of arguments to right here?
return gl.store.accounts.setAsync(args, reg).then(function (account) {
// should now have account.id and account.accountId
args.account = account;
args.accountId = account.id;
return account;
});
});
});
});
});
}
// Accounts
, getAsync: function (args) {
return core.accounts.checkAsync(args).then(function (account) {
if (account) {
return account;
} else {
return core.accounts.registerAsync(args);
}
});
}
// Accounts
, checkAsync: function (args) {
var requiredArgs = ['accountId', 'email', 'domains', 'domain'];
if (!requiredArgs.some(function (key) { return -1 !== Object.keys(args).indexOf(key); })) {
return PromiseA.reject(new Error(
"In order to register or retrieve an account one of '" + requiredArgs.join("', '") + "' must be present"
));
}
var copy = utils.merge(args, gl);
args = utils.tplCopy(copy);
return gl.store.accounts.checkAsync(args).then(function (account) {
if (!account) {
return null;
}
args.account = account;
args.accountId = account.id;
return account;
});
}
}
, certificates: {
// Certificates
registerAsync: function (args) {
var err;
var challengeDefaults = gl['_challengeOpts_' + (args.challengeType || gl.challengeType)] || {};
var copy = utils.merge(args, challengeDefaults || {});
copy = utils.merge(copy, gl);
args = utils.tplCopy(copy);
if (!Array.isArray(args.domains)) {
return PromiseA.reject(new Error('args.domains should be an array of domains'));
}
if (!(args.domains.length && args.domains.every(utils.isValidDomain))) {
// NOTE: this library can't assume to handle the http loopback
// (or dns-01 validation may be used)
// so we do not check dns records or attempt a loopback here
err = new Error("invalid domain name(s): '" + args.domains + "'");
err.code = "INVALID_DOMAIN";
return PromiseA.reject(err);
}
// If a previous request to (re)register a certificate is already underway we need
// to return the same promise created before rather than registering things twice.
// I'm not 100% sure how to properly handle the case where someone registers domain
// lists with some but not all elements common, nor am I sure that's even a case that
// is allowed to happen anyway. But for now we act like the list is completely the
// same if any elements are the same.
var promise;
args.domains.some(function (name) {
if (pendingRegistrations.hasOwnProperty(name)) {
promise = pendingRegistrations[name];
return true;
}
});
if (promise) {
return promise;
}
promise = core.certificates._runRegistration(args);
// Now that the registration is actually underway we need to make sure any subsequent
// registration attempts return the same promise until it is completed (but not after
// it is completed).
args.domains.forEach(function (name) {
pendingRegistrations[name] = promise;
});
function clearPending() {
args.domains.forEach(function (name) {
delete pendingRegistrations[name];
});
}
promise.then(clearPending, clearPending);
return promise;
}
, _runRegistration: function (args) {
// TODO renewal cb
// accountId and or email
return core.accounts.getAsync(args).then(function (account) {
args.account = account;
var promise = gl.store.certificates.checkKeypairAsync(args).then(function (keypair) {
if (keypair) {
return RSA.import(keypair);
}
if (args.domainKeypair) {
return gl.store.certificates.setKeypairAsync(args, RSA.import(args.domainKeypair));
}
var keypairOpts = { bitlen: args.rsaKeySize, exp: 65537, public: true, pem: true };
return RSA.generateKeypairAsync(keypairOpts).then(function (keypair) {
keypair.privateKeyPem = RSA.exportPrivatePem(keypair);
keypair.publicKeyPem = RSA.exportPublicPem(keypair);
keypair.privateKeyJwk = RSA.exportPrivateJwk(keypair);
return gl.store.certificates.setKeypairAsync(args, keypair);
});
});
return promise.then(function (domainKeypair) {
args.domainKeypair = domainKeypair;
//args.registration = domainKey;
// Note: the ACME urls are always fetched fresh on purpose
// TODO is this the right place for this?
return core.getAcmeUrlsAsync(args).then(function (urls) {
args._acmeUrls = urls;
var certReq = {
debug: args.debug || gl.debug
, newAuthzUrl: args._acmeUrls.newAuthz
, newCertUrl: args._acmeUrls.newCert
, accountKeypair: RSA.import(account.keypair)
, domainKeypair: domainKeypair
, domains: args.domains
, challengeType: args.challengeType
};
//
// IMPORTANT
//
// setChallenge and removeChallenge are handed defaults
// instead of args because getChallenge does not have
// access to args
// (args is per-request, defaults is per instance)
//
// Each of these fires individually for each domain,
// even though the certificate on the whole may have many domains
//
certReq.setChallenge = function (domain, key, value, done) {
log(args.debug, "setChallenge called for '" + domain + "'");
var copy = utils.merge({ domains: [domain] }, args);
copy = utils.merge(copy, gl);
utils.tplCopy(copy);
// TODO need to save challengeType
gl.challenges[args.challengeType].set(copy, domain, key, value, done);
};
certReq.removeChallenge = function (domain, key, done) {
log(args.debug, "removeChallenge called for '" + domain + "'");
var copy = utils.merge({ domains: [domain] }, gl);
utils.tplCopy(copy);
gl.challenges[args.challengeType].remove(copy, domain, key, done);
};
log(args.debug, 'calling greenlock.acme.getCertificateAsync', certReq.domains);
return gl.acme.getCertificateAsync(certReq).then(utils.attachCertInfo);
});
}).then(function (results) {
// { cert, chain, privkey /*TODO, subject, altnames, issuedAt, expiresAt */ }
// args.certs.privkey = RSA.exportPrivatePem(options.domainKeypair);
args.certs = results;
// args.pems is deprecated
args.pems = results;
return gl.store.certificates.setAsync(args).then(function () {
return results;
});
});
});
}
// Certificates
, renewAsync: function (args, certs) {
var renewableAt = core.certificates._getRenewableAt(args, certs);
var err;
//var halfLife = (certs.expiresAt - certs.issuedAt) / 2;
//var renewable = (Date.now() - certs.issuedAt) > halfLife;
log(args.debug, "(Renew) Expires At", new Date(certs.expiresAt).toISOString());
log(args.debug, "(Renew) Renewable At", new Date(renewableAt).toISOString());
if (!args.duplicate && Date.now() < renewableAt) {
err = new Error(
"[ERROR] Certificate issued at '"
+ new Date(certs.issuedAt).toISOString() + "' and expires at '"
+ new Date(certs.expiresAt).toISOString() + "'. Ignoring renewal attempt until '"
+ new Date(renewableAt).toISOString() + "'. Set { duplicate: true } to force."
);
err.code = 'E_NOT_RENEWABLE';
return PromiseA.reject(err);
}
// Either the cert has entered its renewal period
// or we're forcing a refresh via 'dupliate: true'
log(args.debug, "Renewing!");
if (!args.domains || !args.domains.length) {
args.domains = args.servernames || [certs.subject].concat(certs.altnames);
}
return core.certificates.registerAsync(args);
}
// Certificates
, _isRenewable: function (args, certs) {
var renewableAt = core.certificates._getRenewableAt(args, certs);
log(args.debug, "Check Expires At", new Date(certs.expiresAt).toISOString());
log(args.debug, "Check Renewable At", new Date(renewableAt).toISOString());
if (args.duplicate || Date.now() >= renewableAt) {
log(args.debug, "certificates are renewable");
return true;
}
return false;
}
, _getRenewableAt: function (args, certs) {
return certs.expiresAt - (args.renewWithin || gl.renewWithin);
}
, checkAsync: function (args) {
var copy = utils.merge(args, gl);
utils.tplCopy(copy);
// returns pems
return gl.store.certificates.checkAsync(copy).then(function (cert) {
if (cert) {
log(args.debug, 'checkAsync found existing certificates');
return utils.attachCertInfo(cert);
}
log(args.debug, 'checkAsync failed to find certificates');
return null;
});
}
// Certificates
, getAsync: function (args) {
var copy = utils.merge(args, gl);
args = utils.tplCopy(copy);
return core.certificates.checkAsync(args).then(function (certs) {
if (!certs) {
// There is no cert available
if (false !== args.securityUpdates && !args._communityMemberAdded) {
try {
// We will notify all greenlock users of mandatory and security updates
// We'll keep track of versions and os so we can make sure things work well
// { name, version, email, domains, action, communityMember, telemetry }
require('./community').add({
name: args._communityPackage
, version: args._communityPackageVersion
, email: args.email
, domains: args.domains || args.servernames
, action: 'reg'
, communityMember: args.communityMember
, telemetry: args.telemetry
});
} catch(e) { /* ignore */ }
args._communityMemberAdded = true;
}
return core.certificates.registerAsync(args);
}
if (core.certificates._isRenewable(args, certs)) {
// it's time to renew the available cert
if (false !== args.securityUpdates && !args._communityMemberAdded) {
try {
// We will notify all greenlock users of mandatory and security updates
// We'll keep track of versions and os so we can make sure things work well
// { name, version, email, domains, action, communityMember, telemetry }
require('./community').add({
name: args._communityPackage
, version: args._communityPackageVersion
, email: args.email
, domains: args.domains || args.servernames
, action: 'renew'
, communityMember: args.communityMember
, telemetry: args.telemetry
});
} catch(e) { /* ignore */ }
args._communityMemberAdded = true;
}
certs.renewing = core.certificates.renewAsync(args, certs);
if (args.waitForRenewal) {
return certs.renewing;
}
}
// return existing unexpired (although potentially stale) certificates when available
// there will be an additional .renewing property if the certs are being asynchronously renewed
return certs;
}).then(function (results) {
// returns pems
return results;
});
}
}
};
return core;
};

View File

@ -0,0 +1,68 @@
'use strict';
var utils = require('./utils');
function _log(debug) {
if (debug) {
var args = Array.prototype.slice.call(arguments);
args.shift();
args.unshift("[greenlock/lib/middleware.js]");
console.log.apply(console, args);
}
}
module.exports.create = function (gl) {
if (!gl.challenges['http-01'] || !gl.challenges['http-01'].get) {
throw new Error("middleware requires challenge plugin with get method");
}
var log = gl.log || _log;
log(gl.debug, "created middleware");
return function (_app) {
if (_app && 'function' !== typeof _app) {
throw new Error("use greenlock.middleware() or greenlock.middleware(function (req, res) {})");
}
var prefix = gl.acmeChallengePrefix || '/.well-known/acme-challenge/';
return function (req, res, next) {
if (0 !== req.url.indexOf(prefix)) {
log(gl.debug, "no match, skipping middleware");
if ('function' === typeof _app) {
_app(req, res, next);
}
else if ('function' === typeof next) {
next();
}
else {
res.statusCode = 500;
res.end("[500] Developer Error: app.use('/', greenlock.middleware()) or greenlock.middleware(app)");
}
return;
}
log(gl.debug, "this must be tinder, 'cuz it's a match!");
var token = req.url.slice(prefix.length);
var hostname = req.hostname || (req.headers.host || '').toLowerCase().replace(/:.*/, '');
log(gl.debug, "hostname", hostname, "token", token);
var copy = utils.merge({ domains: [ hostname ] }, gl);
copy = utils.tplCopy(copy);
// TODO tpl copy?
// TODO need to restore challengeType
gl.challenges['http-01'].get(copy, hostname, token, function (err, secret) {
if (err || !token) {
res.statusCode = 404;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end('{ "error": { "message": "Error: These aren\'t the tokens you\'re looking for. Move along." } }');
return;
}
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.end(secret);
});
};
};
};

127
express-server/node_modules/greenlock/lib/utils.js generated vendored Normal file
View File

@ -0,0 +1,127 @@
'use strict';
var path = require('path');
var homeRe = new RegExp("^~(\\/|\\\\|\\" + path.sep + ")");
// very basic check. Allows *.example.com.
var re = /^(\*\.)?[a-zA-Z0-9\.\-]+$/;
var punycode = require('punycode');
var promisify = (require('util').promisify || require('bluebird').promisify);
var dnsResolveMxAsync = promisify(require('dns').resolveMx);
module.exports.attachCertInfo = function (results) {
// XXX Note: Parsing the certificate info comes at a great cost (~500kb)
var getCertInfo = require('certpem').info;
var certInfo = getCertInfo(results.cert);
// subject, altnames, issuedAt, expiresAt
Object.keys(certInfo).forEach(function (key) {
results[key] = certInfo[key];
});
return results;
};
module.exports.isValidDomain = function (domain) {
if (re.test(domain)) {
return domain;
}
domain = punycode.toASCII(domain);
if (re.test(domain)) {
return domain;
}
return '';
};
module.exports.merge = function (/*defaults, args*/) {
var allDefaults = Array.prototype.slice.apply(arguments);
var args = allDefaults.shift();
var copy = {};
allDefaults.forEach(function (defaults) {
Object.keys(defaults).forEach(function (key) {
copy[key] = defaults[key];
});
});
Object.keys(args).forEach(function (key) {
copy[key] = args[key];
});
return copy;
};
module.exports.tplCopy = function (copy) {
var homedir = require('os').homedir();
var tplKeys;
copy.hostnameGet = function (copy) {
return (copy.domains || [])[0] || copy.domain;
};
Object.keys(copy).forEach(function (key) {
var newName;
if (!/Get$/.test(key)) {
return;
}
newName = key.replace(/Get$/, '');
copy[newName] = copy[newName] || copy[key](copy);
});
tplKeys = Object.keys(copy);
tplKeys.sort(function (a, b) {
return b.length - a.length;
});
tplKeys.forEach(function (key) {
if ('string' !== typeof copy[key]) {
return;
}
copy[key] = copy[key].replace(homeRe, homedir + path.sep);
});
tplKeys.forEach(function (key) {
if ('string' !== typeof copy[key]) {
return;
}
tplKeys.forEach(function (tplname) {
if (!copy[tplname]) {
// what can't be templated now may be templatable later
return;
}
copy[key] = copy[key].replace(':' + tplname, copy[tplname]);
});
});
return copy;
};
module.exports.testEmail = function (email) {
var parts = (email||'').split('@');
var err;
if (2 !== parts.length || !parts[0] || !parts[1]) {
err = new Error("malformed email address '" + email + "'");
err.code = 'E_EMAIL';
return Promise.reject(err);
}
return dnsResolveMxAsync(parts[1]).then(function (records) {
// records only returns when there is data
if (!records.length) {
throw new Error("sanity check fail: success, but no MX records returned");
}
return email;
}, function (err) {
if ('ENODATA' === err.code) {
err = new Error("no MX records found for '" + parts[1] + "'");
err.code = 'E_EMAIL';
return Promise.reject(err);
}
});
};