Compare commits

...

6 Commits

4 changed files with 62 additions and 29 deletions

View File

@ -443,6 +443,14 @@ The following variables will be tempalted in any strings passed to the options o
* `~/` replaced with `os.homedir()` i.e. `/Users/aj`
* `:hostname` replaced with the first domain in the list i.e. `example.com`
### Dangerous Options
By default SNI is made to lowercase and is automatically rejected if it contains invalid characters for a domain.
This behavior can be modified:
* `__dns_allow_dangerous_names` allow SNI names like "Robert'); DROP TABLE Students;"
* `__dns_preserve_case` passes SNI names such as "ExAMpLE.coM" without converting to lower case
Developer API
-------------

View File

@ -432,20 +432,21 @@ Greenlock.create = function (gl) {
if (gl.sni.create) {
gl.sni = gl.sni.create(gl);
}
gl.tlsOptions.SNICallback = function (domain, cb) {
gl.tlsOptions.SNICallback = function (_domain, cb) {
// format and (lightly) sanitize sni so that users can be naive
// and not have to worry about SQL injection or fs discovery
domain = (domain||'').toLowerCase();
var domain = (_domain||'').toLowerCase();
// hostname labels allow a-z, 0-9, -, and are separated by dots
// _ is sometimes allowed
if (!/^[a-z0-9_\.\-]+$/i.test(domain) || -1 !== domain.indexOf('..')) {
// REGEX // https://www.codeproject.com/Questions/1063023/alphanumeric-validation-javascript-without-regex
if (!gl.__sni_allow_dangerous_names && (!/^[a-z0-9_\.\-]+$/i.test(domain) || -1 !== domain.indexOf('..'))) {
log(gl.debug, "invalid sni '" + domain + "'");
cb(new Error("invalid SNI"));
return;
}
try {
gl.sni.sniCallback(domain, cb);
gl.sni.sniCallback(gl.__sni_preserve_case && _domain || domain, cb);
} catch(e) {
console.error("[ERROR] Something went wrong in the SNICallback:");
console.error(e);
@ -480,5 +481,46 @@ Greenlock.create = function (gl) {
gl.middleware = gl.middleware.create(gl);
}
//var SERVERNAME_RE = /^[a-z0-9\.\-_]+$/;
var SERVERNAME_G = /[^a-z0-9\.\-_]/;
gl.middleware.sanitizeHost = function (app) {
return function (req, res, next) {
function realNext() {
if ('function' === typeof app) {
app(req, res);
} else if ('function' === typeof next) {
next();
} else {
res.statusCode = 500;
res.end("Error: no middleware assigned");
}
}
// Get the host:port combo, if it exists
var host = (req.headers.host||'').split(':');
// if not, move along
if (!host[0]) { realNext(); return; }
// if so, remove non-allowed characters
var safehost = host[0].toLowerCase().replace(SERVERNAME_G, '');
// if there were unallowed characters, complain
if (!gl.__sni_allow_dangerous_names && safehost.length !== host[0].length) {
res.statusCode = 400;
res.end("Malformed HTTP Header: 'Host: " + host[0] + "'");
return;
}
// make lowercase
if (!gl.__sni_preserve_case) {
host[0] = safehost;
req.headers.host = host.join(':');
}
// carry on
realNext();
};
};
return gl;
};

View File

@ -80,8 +80,6 @@ module.exports.create = function (gl) {
}
return utils.testEmail(args.email).then(function () {
var keypairOpts = { public: true, pem: true };
var promise = gl.store.accounts.checkKeypairAsync(args).then(function (keypair) {
if (keypair) {
return RSA.import(keypair);
@ -91,7 +89,8 @@ module.exports.create = function (gl) {
return gl.store.accounts.setKeypairAsync(args, RSA.import(args.accountKeypair));
}
return RSA.generateKeypairAsync(args.rsaKeySize, 65537, keypairOpts).then(function (keypair) {
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);
@ -253,8 +252,8 @@ module.exports.create = function (gl) {
return gl.store.certificates.setKeypairAsync(args, RSA.import(args.domainKeypair));
}
var keypairOpts = { public: true, pem: true };
return RSA.generateKeypairAsync(args.rsaKeySize, 65537, keypairOpts).then(function (keypair) {
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);

View File

@ -1,6 +1,6 @@
{
"name": "greenlock",
"version": "2.3.7",
"version": "2.3.13",
"description": "Let's Encrypt for node.js on npm",
"main": "index.js",
"files": [
@ -18,25 +18,12 @@
"letsencrypt",
"ACME",
"v2",
"v02",
"draft-11",
"draft-12",
"auto-sni",
"draft",
"11",
"12",
"Free SSL",
"Automated HTTPS",
"tls",
"https",
"Greenlock",
"letsencrypt.org",
"le",
"le.js",
"node",
"nodejs",
"node.js",
"client"
"Greenlock"
],
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
"license": "(MIT OR Apache-2.0)",
@ -55,14 +42,11 @@
"dependencies": {
"acme": "^1.0.6",
"acme-v2": "^1.2.0",
"asn1js": "^1.2.12",
"certpem": "^1.0.0",
"certpem": "^1.1.0",
"le-challenge-fs": "^2.0.2",
"le-sni-auto": "^2.1.3",
"le-store-certbot": "^2.1.7",
"node.extend": "^1.1.5",
"pkijs": "^1.3.27",
"rsa-compat": "^1.4.0"
"rsa-compat": "^1.5.0"
},
"engines": {
"node": ">=4.5"