From a7da0ad554bc6730e668c9abf049ae445df9f689 Mon Sep 17 00:00:00 2001 From: Andrew Brampton Date: Sat, 30 Jan 2021 21:29:00 -0800 Subject: [PATCH 1/4] Refactored the vows test to use a "macro". This makes it easier to read, and reduced code duplication. --- test/http-server-test.js | 375 ++++++++++----------------------------- 1 file changed, 94 insertions(+), 281 deletions(-) diff --git a/test/http-server-test.js b/test/http-server-test.js index 6d5fce462..aa4559bb9 100644 --- a/test/http-server-test.js +++ b/test/http-server-test.js @@ -10,6 +10,52 @@ process.on('uncaughtException', console.error); var root = path.join(__dirname, 'fixtures', 'root'); +// respondsWith makes a request to the URL with the optional opts, and asserts +// the returned response is code with the expected body. +function respondsWith(code, url, opts) { + var context = { + topic: function () { + opts = opts || {}; + request(url, opts, this.callback); + } + }; + context['status code should be ' + code] = function (err, res) { + assert.isNull(err); + assert.equal(res.statusCode, code); + }; + return context; +} + +function respondsWithMessage(code, message, url, opts) { + var context = respondsWith(code, url, opts); + context['and file content should be "' + message + '"'] = function (err, res, body) { + assert.equal(body, message); + }; + return context; +} + +// respondsWithFile makes a request to the URL with the optional opts, and asserts +// the returned response is 200 and matches the expectFilename. +function respondsWithFile(code, filename, url, opts) { + var context = respondsWith(code, url, opts); + context['and file content'] = { + topic: function (res, body) { + assert.isNotNull(res); + assert.isNotNull(body); + + var self = this; + fs.readFile(path.join(root, filename), 'utf8', function (err, data) { + self.callback(err, data, body); + }); + }, + 'should match content of served file': function (err, data, body) { + assert.isNull(err); + assert.equal(data.trim(), body.trim()); + } + }; + return context; +} + vows.describe('http-server').addBatch({ 'When http-server is listening on 8080,\n': { topic: function () { @@ -25,30 +71,12 @@ vows.describe('http-server').addBatch({ server.listen(8080); this.callback(null, server); }, - 'it should serve files from root directory': { - topic: function () { - request('http://127.0.0.1:8080/file', this.callback); - }, - 'status code should be 200': function (res) { - assert.equal(res.statusCode, 200); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should match content of served file': function (err, file, body) { - assert.equal(body.trim(), file.trim()); - } - } - }, + 'it should serve files from root directory': respondsWithFile(200, 'file', 'http://127.0.0.1:8080/file'), 'and a non-existent file is requested...': { topic: function () { request('http://127.0.0.1:8080/404', this.callback); }, - 'status code should be 404': function (res) { + 'status code should be 404': function (err, res) { assert.equal(res.statusCode, 404); } }, @@ -66,7 +94,7 @@ vows.describe('http-server').addBatch({ topic: function () { request('http://127.0.0.1:8080/robots.txt', this.callback); }, - 'should respond with status code 200 to /robots.txt': function (res) { + 'should respond with status code 200 to /robots.txt': function (err, res) { assert.equal(res.statusCode, 200); } }, @@ -88,44 +116,9 @@ vows.describe('http-server').addBatch({ proxyServer.listen(8081); this.callback(null, proxyServer); }, - '\nit should serve files from the proxy\'s root': { - topic: function () { - request('http://127.0.0.1:8081/root/file', this.callback); - }, - 'status code should be the endpoint code 200': function (res) { - assert.equal(res.statusCode, 200); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should match content of the served file': function (err, file, body) { - assert.equal(body.trim(), file.trim()); - } - } - }, - '\nit should fallback to the proxied server': { - topic: function () { - request('http://127.0.0.1:8081/file', this.callback); - }, - 'status code should be the endpoint code 200': function (res) { - assert.equal(res.statusCode, 200); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should match content of the proxied served file': function (err, file, body) { - assert.equal(body.trim(), file.trim()); - } - } - }, + '\nit should serve files from the proxy\'s root': respondsWithFile(200, 'file', 'http://127.0.0.1:8081/root/file'), + '\nit should fallback to the proxied server': respondsWithFile(200, 'file', 'http://127.0.0.1:8081/file'), + teardown: function (proxyServer) { proxyServer.close(); } @@ -239,121 +232,31 @@ vows.describe('http-server').addBatch({ server.listen(8083); this.callback(null, server); }, - 'and the user requests an existent file with no auth details': { - topic: function () { - request('http://127.0.0.1:8083/file', this.callback); - }, - 'status code should be 401': function (res) { - assert.equal(res.statusCode, 401); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should be a forbidden message': function (err, file, body) { - assert.equal(body, 'Access denied'); - } + 'and the user requests an existent file with no auth details': respondsWithMessage(401, 'Access denied', 'http://127.0.0.1:8083/file'), + 'and the user requests an existent file with incorrect username': respondsWithMessage(401, 'Access denied', 'http://127.0.0.1:8083/file', { + auth: { + user: 'wrong_username', + pass: 'good_password' } - }, - 'and the user requests an existent file with incorrect username': { - topic: function () { - request('http://127.0.0.1:8083/file', { - auth: { - user: 'wrong_username', - pass: 'good_password' - } - }, this.callback); - }, - 'status code should be 401': function (res) { - assert.equal(res.statusCode, 401); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should be a forbidden message': function (err, file, body) { - assert.equal(body, 'Access denied'); - } + }), + 'and the user requests an existent file with incorrect password': respondsWithMessage(401, 'Access denied', 'http://127.0.0.1:8083/file', { + auth: { + user: 'good_username', + pass: 'wrong_password' } - }, - 'and the user requests an existent file with incorrect password': { - topic: function () { - request('http://127.0.0.1:8083/file', { - auth: { - user: 'good_username', - pass: 'wrong_password' - } - }, this.callback); - }, - 'status code should be 401': function (res) { - assert.equal(res.statusCode, 401); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should be a forbidden message': function (err, file, body) { - assert.equal(body, 'Access denied'); - } - } - }, - 'and the user requests a non-existent file with incorrect password': { - topic: function () { - request('http://127.0.0.1:8083/404', { - auth: { - user: 'good_username', - pass: 'wrong_password' - } - }, this.callback); - }, - 'status code should be 401': function (res) { - assert.equal(res.statusCode, 401); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should be a forbidden message': function (err, file, body) { - assert.equal(body, 'Access denied'); - } + }), + 'and the user requests a non-existent file with incorrect password': respondsWithMessage(401, 'Access denied', 'http://127.0.0.1:8083/404', { + auth: { + user: 'good_username', + pass: 'wrong_password' } - }, - 'and the user requests an existent file with correct auth details': { - topic: function () { - request('http://127.0.0.1:8083/file', { - auth: { - user: 'good_username', - pass: 'good_password' - } - }, this.callback); - }, - 'status code should be 200': function (res) { - assert.equal(res.statusCode, 200); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should match content of served file': function (err, file, body) { - assert.equal(body.trim(), file.trim()); - } + }), + 'and the user requests an existent file with correct auth details': respondsWithFile(200, 'file', 'http://127.0.0.1:8083/file', { + auth: { + user: 'good_username', + pass: 'good_password' } - }, + }), teardown: function (server) { server.close(); } @@ -396,121 +299,31 @@ vows.describe('http-server').addBatch({ server.listen(8086); this.callback(null, server); }, - 'and the user requests an existent file with no auth details': { - topic: function () { - request('http://127.0.0.1:8086/file', this.callback); - }, - 'status code should be 401': function (res) { - assert.equal(res.statusCode, 401); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should be a forbidden message': function (err, file, body) { - assert.equal(body, 'Access denied'); - } - } - }, - 'and the user requests an existent file with incorrect username': { - topic: function () { - request('http://127.0.0.1:8086/file', { - auth: { - user: 'wrong_username', - pass: '123456' - } - }, this.callback); - }, - 'status code should be 401': function (res) { - assert.equal(res.statusCode, 401); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should be a forbidden message': function (err, file, body) { - assert.equal(body, 'Access denied'); - } + 'and the user requests an existent file with no auth details': respondsWithMessage(401, 'Access denied', 'http://127.0.0.1:8086/file'), + 'and the user requests an existent file with incorrect username': respondsWithMessage(401, 'Access denied', 'http://127.0.0.1:8083/file', { + auth: { + user: 'wrong_username', + pass: '123456' } - }, - 'and the user requests an existent file with incorrect password': { - topic: function () { - request('http://127.0.0.1:8086/file', { - auth: { - user: 'good_username', - pass: '654321' - } - }, this.callback); - }, - 'status code should be 401': function (res) { - assert.equal(res.statusCode, 401); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should be a forbidden message': function (err, file, body) { - assert.equal(body, 'Access denied'); - } + }), + 'and the user requests an existent file with incorrect password': respondsWithMessage(401, 'Access denied', 'http://127.0.0.1:8083/file', { + auth: { + user: 'good_username', + pass: '654321' } - }, - 'and the user requests a non-existent file with incorrect password': { - topic: function () { - request('http://127.0.0.1:8086/404', { - auth: { - user: 'good_username', - pass: '654321' - } - }, this.callback); - }, - 'status code should be 401': function (res) { - assert.equal(res.statusCode, 401); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should be a forbidden message': function (err, file, body) { - assert.equal(body, 'Access denied'); - } + }), + 'and the user requests a non-existent file with incorrect password': respondsWithMessage(401, 'Access denied', 'http://127.0.0.1:8083/404', { + auth: { + user: 'good_username', + pass: '654321' } - }, - 'and the user requests an existent file with correct auth details': { - topic: function () { - request('http://127.0.0.1:8086/file', { - auth: { - user: 'good_username', - pass: '123456' - } - }, this.callback); - }, - 'status code should be 200': function (res) { - assert.equal(res.statusCode, 200); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should match content of served file': function (err, file, body) { - assert.equal(body.trim(), file.trim()); - } + }), + 'and the user requests an existent file with correct auth details': respondsWithFile(200, 'file', 'http://127.0.0.1:8086/file', { + auth: { + user: 'good_username', + pass: '123456' } - }, + }), teardown: function (server) { server.close(); } From b95145e03c58b645e2aba10c216e9f306d42bbf7 Mon Sep 17 00:00:00 2001 From: Andrew Brampton Date: Sat, 30 Jan 2021 23:53:11 -0800 Subject: [PATCH 2/4] Made sure every httpServer.createServer is started, and two other minor tests related changes: * Every httpServer.createServer now has a vow to check for errors. Additionally, the server object is returned, instead of using the callbacks. This is because the server is an EventEmitter, and vows.js can capture the error correctly. * Documented respondsWithMessage function * Refactored one case of checking for a 404 with respondsWith(404, ...) --- test/http-server-test.js | 62 +++++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/test/http-server-test.js b/test/http-server-test.js index aa4559bb9..d4aa5f422 100644 --- a/test/http-server-test.js +++ b/test/http-server-test.js @@ -14,7 +14,7 @@ var root = path.join(__dirname, 'fixtures', 'root'); // the returned response is code with the expected body. function respondsWith(code, url, opts) { var context = { - topic: function () { + topic: function (server) { opts = opts || {}; request(url, opts, this.callback); } @@ -26,23 +26,24 @@ function respondsWith(code, url, opts) { return context; } +// respondsWithMessage makes a request to the URL with the optional opts, and asserts +// the returned response is `code` and the response body is `message`. function respondsWithMessage(code, message, url, opts) { var context = respondsWith(code, url, opts); context['and file content should be "' + message + '"'] = function (err, res, body) { + assert.isNull(err); assert.equal(body, message); }; return context; } // respondsWithFile makes a request to the URL with the optional opts, and asserts -// the returned response is 200 and matches the expectFilename. +// the returned status is `code` and the response body matches the expected filename +// contents. function respondsWithFile(code, filename, url, opts) { var context = respondsWith(code, url, opts); context['and file content'] = { topic: function (res, body) { - assert.isNotNull(res); - assert.isNotNull(body); - var self = this; fs.readFile(path.join(root, filename), 'utf8', function (err, data) { self.callback(err, data, body); @@ -69,17 +70,14 @@ vows.describe('http-server').addBatch({ }); server.listen(8080); - this.callback(null, server); + return server; }, - 'it should serve files from root directory': respondsWithFile(200, 'file', 'http://127.0.0.1:8080/file'), - 'and a non-existent file is requested...': { - topic: function () { - request('http://127.0.0.1:8080/404', this.callback); - }, - 'status code should be 404': function (err, res) { - assert.equal(res.statusCode, 404); - } + 'it should start without errors': function (err, server) { + assert.isNull(err); + assert.isTrue(server.server.listening); }, + 'it should serve files from root directory': respondsWithFile(200, 'file', 'http://127.0.0.1:8080/file'), + 'and a non-existent file is requested...': respondsWith(404, 'http://127.0.0.1:8080/404'), 'requesting /': { topic: function () { request('http://127.0.0.1:8080/', this.callback); @@ -114,7 +112,11 @@ vows.describe('http-server').addBatch({ root: path.join(__dirname, 'fixtures') }); proxyServer.listen(8081); - this.callback(null, proxyServer); + return proxyServer; + }, + 'it should start without errors': function (err, server) { + assert.isNull(err); + assert.isTrue(server.server.listening); }, '\nit should serve files from the proxy\'s root': respondsWithFile(200, 'file', 'http://127.0.0.1:8081/root/file'), '\nit should fallback to the proxied server': respondsWithFile(200, 'file', 'http://127.0.0.1:8081/file'), @@ -135,7 +137,11 @@ vows.describe('http-server').addBatch({ corsHeaders: 'X-Test' }); server.listen(8082); - this.callback(null, server); + return server; + }, + 'it should start without errors': function (err, server) { + assert.isNull(err); + assert.isTrue(server.server.listening); }, 'and the server is given an OPTIONS request': { topic: function () { @@ -168,7 +174,11 @@ vows.describe('http-server').addBatch({ gzip: true }); server.listen(8084); - this.callback(null, server); + return server; + }, + 'it should start without errors': function (err, server) { + assert.isNull(err); + assert.isTrue(server.server.listening); }, 'and a request accepting only gzip is made': { topic: function () { @@ -230,7 +240,11 @@ vows.describe('http-server').addBatch({ }); server.listen(8083); - this.callback(null, server); + return server; + }, + 'it should start without errors': function (err, server) { + assert.isNull(err); + assert.isTrue(server.server.listening); }, 'and the user requests an existent file with no auth details': respondsWithMessage(401, 'Access denied', 'http://127.0.0.1:8083/file'), 'and the user requests an existent file with incorrect username': respondsWithMessage(401, 'Access denied', 'http://127.0.0.1:8083/file', { @@ -268,7 +282,11 @@ vows.describe('http-server').addBatch({ ext: true }); server.listen(8085); - this.callback(null, server); + return server; + }, + 'it should start without errors': function (err, server) { + assert.isNull(err); + assert.isTrue(server.server.listening); }, 'and a file with no extension is requested with default options,': { topic: function () { @@ -297,7 +315,11 @@ vows.describe('http-server').addBatch({ }); server.listen(8086); - this.callback(null, server); + return server; + }, + 'it should start without errors': function (err, server) { + assert.isNull(err); + assert.isTrue(server.server.listening); }, 'and the user requests an existent file with no auth details': respondsWithMessage(401, 'Access denied', 'http://127.0.0.1:8086/file'), 'and the user requests an existent file with incorrect username': respondsWithMessage(401, 'Access denied', 'http://127.0.0.1:8083/file', { From 56f6d1d85fb740b448d9d5f8415849e0e5b1d83c Mon Sep 17 00:00:00 2001 From: Andrew Brampton Date: Sun, 31 Jan 2021 14:25:59 -0800 Subject: [PATCH 3/4] Add new unit tests for the SSL server support. --- test/fixtures/README.md | 32 ++++++++++++++++++++++++++++ test/fixtures/ca.pem | 45 ++++++++++++++++++++++++++++++++++++++++ test/fixtures/cert.pem | 17 +++++++++++++++ test/fixtures/key.pem | 28 +++++++++++++++++++++++++ test/http-server-test.js | 36 ++++++++++++++++++++++++++++++++ 5 files changed, 158 insertions(+) create mode 100644 test/fixtures/README.md create mode 100644 test/fixtures/ca.pem create mode 100644 test/fixtures/cert.pem create mode 100644 test/fixtures/key.pem diff --git a/test/fixtures/README.md b/test/fixtures/README.md new file mode 100644 index 000000000..164492bc2 --- /dev/null +++ b/test/fixtures/README.md @@ -0,0 +1,32 @@ +# Directory of testing fixtures + +## To create fake certificates + +```shell +# Create a fake CA cert (with key and cert in the same file) +openssl req -x509 -new \ + -subj "/CN=Fake CA" \ + -days 3650 \ + -newkey rsa:2048 -nodes \ + -keyout ca.pem -out ca.pem + +# Create CSR +openssl req -new \ + -subj "/CN=localhost" \ + -days 3650 \ + -newkey rsa:2048 -nodes \ + -keyout key.pem -out csr.pem + +# Sign the CSR to generate a certificate (with additional SubjectAltNames) +openssl x509 -req \ + -in csr.pem \ + -CA ca.pem -CAcreateserial \ + -extensions SAN \ + -extfile <(cat /etc/ssl/openssl.cnf \ + <(printf "\n[SAN]\nsubjectAltName=DNS:localhost,IP:127.0.0.1")) \ + -days 3650 -sha256 \ + -out cert.pem + +# Cleanup +rm ca.srl csr.pem +``` diff --git a/test/fixtures/ca.pem b/test/fixtures/ca.pem new file mode 100644 index 000000000..e0a6b5689 --- /dev/null +++ b/test/fixtures/ca.pem @@ -0,0 +1,45 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDqL6v04hS17g90 +CaqBQ16qx7GqTGM/29udshsW/YCgLCq0KHcl+T4SwVZIG0xHFSuU5SHafXh3CVIv +8ZuQzaYZwZRF80If6Ti5MuPkzMyGJ7IsqBYjhRYvo+wt2WYvcA6GHbHrEn2YD0OB +2sCaFizRiEnVLb9cBfBs4VWpvUj5oMaEK8veC1Ojwb9Uq9PD/ScgNOWvMWDiMoXC +xOJDt1jx38+isQ7jmr+RL44AKOd2Y64NYx1PQm5JWoq1/dZKyB1jk8fA90TEiDdY +aVBdM4Lj3GFDjwOYgX4zirQJcB4LzxX8kgis//WHtjtGXHDaCd4S3xPM4Zzl7tzl ++k//7rgtAgMBAAECggEBANFRvu9pTH24xVNAeIiFgQ5A48qF8IhZqZjwY0pPWDLS +h3D0YlssxpDZApf83lcC0yuJCpNSZuRvDmkQGa56QibvYeqMHeSL/0l59TzC2WRo +Atfrfa5N/KCkciwhDzcDf9fcnvSwWFYb/okIz/JqM7EtkmDbPRmxrU6Esp6/M4T6 +u9fDjzYFqShKaYVd8ykSXUBN0utvBZwdV1gcS/lysnmRykbkOTSLh35HRhfHb5L6 +TggwqjuQTG5EOwpKqct3H2/J+wgoK0p5u7DsIi1UdbxzhaPk7tEfb9UBt6QpXlXr +qkhufSvyQJwSEqyjtfgbRHx2TaQxMUp8LT/COoCjefkCgYEA+4g9WybRJitu0E5Q +rntUELxHzKH4THKWdUjj1RChwy81YI+O6M+vYMGX62wF9JGhvSmQsTGdNaDk+Hpc +PY1IRljtZFuZOWVmSzPYRoTNjw+fl68ecRA1K+4OXN4k4QOcL+OvlRMH1xw0Bgey +8Hw52kI0loNEBFHAslb7i4XzCmMCgYEA7liOkgcM3tu7OdglvS4inRz71DcAsvu9 +24CenWI4A5wRDnsOOFMXqIC0w/Zzl2EBCvgoc/syY43KBlkUR7ckGs1124ZJmlQA +W7ZVFSqktpScJ4ip7t3bjChfhTJwb4vfyx1q4izmNQe5axm7XUP2J+En438hQ9Cm +OgeOEQoR8C8CgYEAuElJKs91zRFlTxkR48RYAyrvL+47jUcnFSciRai529dqtCR5 +//ip9anhNIsgkd9hMMaTTD+dfv0yxRphGne4zFG7HBxAVt0D5XVGr+P89yPrOacE +FrJZQqZXv5LCUlnixPN8YSxgQipXs1NQtwFNIav/+4aQ/tkm5YL1KXQqbSECgYB9 +kmqKxOwi/eFGOHqpkQTrgbmrtM7JfZgpbToj8PtR64eQ+YQkaKKuRCD4nX+I4bKK +9PNbJ50Elk85yPTGU9bRyC2v2rAsftUxfH6XCEB/cQxUR8w/7OMelKa+pjRWkHr3 +qCgHwWAH0Gn/4y6zlHq7yAQb936vG9539EME9yk8QQKBgQDr+eK6Lf0mokcm3LtG +n5P1a0MR8gWQhSJXMxwW1emib2MaqttAR5zsLci7mA9DyioflH+s3vLkMlKDMATT +o9OTQw+yFTVCVigFORl01dG73dd9A2/58vx6/eiYfS9IHx+ZLB0Llerl/2Wv7muP +UALOcqExJmRFaz5zzpgtCY6fVw== +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIICoDCCAYgCCQDszIBncV4ARjANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdG +YWtlIENBMB4XDTIxMDEzMTIyMjQ0N1oXDTMxMDEyOTIyMjQ0N1owEjEQMA4GA1UE +AwwHRmFrZSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOovq/Ti +FLXuD3QJqoFDXqrHsapMYz/b252yGxb9gKAsKrQodyX5PhLBVkgbTEcVK5TlIdp9 +eHcJUi/xm5DNphnBlEXzQh/pOLky4+TMzIYnsiyoFiOFFi+j7C3ZZi9wDoYdsesS +fZgPQ4HawJoWLNGISdUtv1wF8GzhVam9SPmgxoQry94LU6PBv1Sr08P9JyA05a8x +YOIyhcLE4kO3WPHfz6KxDuOav5EvjgAo53Zjrg1jHU9CbklairX91krIHWOTx8D3 +RMSIN1hpUF0zguPcYUOPA5iBfjOKtAlwHgvPFfySCKz/9Ye2O0ZccNoJ3hLfE8zh +nOXu3OX6T//uuC0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAUAaeso28jVzeaN5N +ZeMB0DA2tE97AKIra9zgYXpjfFyXHQM8z9skrHvOjVAEGRi7IqrAuhvErbUoJ4r4 +KNdjVH4ZIzbnMsm3dHkpc1pKJrDwHnmkOJ38RZPJH0V2WNnoqaJXEME0zuoq6LhO +L/dpxwXY9XWa5UZ6X/Xedxq1Mm8VBsLP3Viso617yhVbZG5YHQvcxI6DqfIZZhFD +SxcpZuzpqoPIfXvXJ0pu8nMDnbdEYsEKFkrA0iwAkDy/h2GLzxZVEOGew8Bltyo8 +1eENM6kHAfZbuewiJLv0EvjpGwsA7cYm/5+3uUwIqsQSZxhyZn8jKFOqLwpNxbsr +HspwXw== +-----END CERTIFICATE----- diff --git a/test/fixtures/cert.pem b/test/fixtures/cert.pem new file mode 100644 index 000000000..4b90574e0 --- /dev/null +++ b/test/fixtures/cert.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICxzCCAa+gAwIBAgIJAKXh5y4t6LPVMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV +BAMMB0Zha2UgQ0EwHhcNMjEwMTMxMjIyNDQ3WhcNMzEwMTI5MjIyNDQ3WjAUMRIw +EAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQDC6GvrMFyBkcdDzoiKTj4MnxP+BwE3R68d7jU38r/wChsuvQ6V3HtketAD6Ocl +OUTj2i+DfXLzItOmUmL3PmynwtyKxlehwg7iDDFN/mvRThBmdclG9xvhCY+PaUzn +JJvV+CCGiYwPifNhnSRUsJqMQZ1yNkMrY+nuoO5SlhKTwISBNYACDyA6W8Qvx1rw +m5EHNdZ67Vetp7T9o6bhTHoqYSRtNRsAgu4xe0H40AlDR7xajmiaqX5u6FV4Rb7f +bkJPRjPsXMRIdN2tKS3yToL0SSoa0kGVmrSXS2Yo/mG6n3jb0/lqSqzRvQ3C1h0U +9VEpahfxi3ZDF3GvgWz3OB/jAgMBAAGjHjAcMBoGA1UdEQQTMBGCCWxvY2FsaG9z +dIcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEAgcBDvaPmpg9gJusAUo8edIAuFpQw +fp0bIJ0KY4fgCihiclAlhE7Jn0S/6Kr7XfK7ZTqxMArXygvDfOWHv4fWDlIQR1fv +pxMdQq5ef/TWGBlvSONC5Kdivmby7YSkxaCt/xcvKL5dculkpMcC24IeIamVSOFf +M/3++ma0w9K/xUSV0KwKCpLv/N4tRqlCAfv9Vk+DtwKKaloqisjwjFlxwWmjOChx +CDEfg8ys5zV6gVGcjn5Oa/SaY6FboD8t9H0janEkoFD57xTKHWBIfcbTufjeMjGT +ukU196yctwoQaDQ+fHk/brDN6h5w3HOKVj5AiMOuu9sKT5jJGbgU9NU9Ow== +-----END CERTIFICATE----- diff --git a/test/fixtures/key.pem b/test/fixtures/key.pem new file mode 100644 index 000000000..024a0b1ab --- /dev/null +++ b/test/fixtures/key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDC6GvrMFyBkcdD +zoiKTj4MnxP+BwE3R68d7jU38r/wChsuvQ6V3HtketAD6OclOUTj2i+DfXLzItOm +UmL3PmynwtyKxlehwg7iDDFN/mvRThBmdclG9xvhCY+PaUznJJvV+CCGiYwPifNh +nSRUsJqMQZ1yNkMrY+nuoO5SlhKTwISBNYACDyA6W8Qvx1rwm5EHNdZ67Vetp7T9 +o6bhTHoqYSRtNRsAgu4xe0H40AlDR7xajmiaqX5u6FV4Rb7fbkJPRjPsXMRIdN2t +KS3yToL0SSoa0kGVmrSXS2Yo/mG6n3jb0/lqSqzRvQ3C1h0U9VEpahfxi3ZDF3Gv +gWz3OB/jAgMBAAECggEAMu+YnGsUEcxuHdtQtYxDDPtZty5PdAnoytKg19E5tdp+ +RhWkRSfMm3K4//ySw5iW11ECz8MuEjpMw4+OS3zl2mXDSwUQi7ZyO0Lic7aEqLtU +7+HiSwhzIblk6h6juVhI3X8tyNsTXlA36Y+ume9ZREQ1iE6D+UfwL6ug/LY5fqEA ++NH9ofg2vT7qd42mhZjiOPvxMrKdRHn2X9gG5wb99wj3YBODjuY3EXiRLSPLSxyE +jQtwK5vVUEY05Fpe21a1HS3wixEcqQBp/KEfysVj83Aj+I49tfB/ORg++ks0cqv1 +jJ/8wviUZtg2RWM9xTXV8GdcsszipTpwuj8eiGXEOQKBgQD1lqzp033+S/3YIS0/ +WbF+JtFdXTCV5Kq4RlI75uiczUUVdRltV+yDfzarGgUTT0CpSZzEUHr7Ugd95AMR +G9KLGCjgI8hktHT0LbAjqcfOP+/azcQLaOgT5iVuT7ZlpqhlCacVUiY9G3gWFq0B +aYktbjxgjIDGFWx8oLayjN6pnwKBgQDLK7f6MaX5Jf2MDzK3lt0K4bw5pBXQCxiY +yEMaMBjJBS0uiXS5WyKAhh6Vf+D4sKI7JWRMVwlD1wHq7WbCERIqR7wo8eI8uDr/ +ltVkcjYKS2N3vNPDpZKEzoMxSFnOONGaAgNggAjxZvNasD+Z6BV5RSqPni0PXDZD +9B0PeNQrPQKBgEqYI5k6NfDBoC6/lQDC+5h3rewP3CwLMpeaNGwhbNIDv1IPKVP+ ++sXOJArAcn40+kzxIP63+0LO3ZutYAkYTFEXW4MJG1sLPOLV5cRPU3MgFHh/O2bD +zIoOw5vH9nzVrBxUXD2roBW7fDQpWw8swQ/dhdVFl++SnksUfamqBA+9AoGAL6bK +WMEKR3xUkmQCJjMZFvNI3VAR2aCwnSzjKCI9vfAb371Xhh3M4s4SIEhE8K8k7bBg +bNNBFgs4pOwXXM76LrZyeDv7LviaxdWPqSZsbE+wPaYpGMsdqU5yUL3Cam7DIlb0 +ic6dyli2HQAXeraHStEhIVwc/2xGQfvgUP+q65UCgYADYixLX4cfGGwgZyLP8ppt +rE34XVOIOYDcyfqEg/Z/1IcihGKb4uJy6QYC0HuirRss4uDllsNacJflNUsQrxoR +1UdAXHZw5Zz7BXCIo+GEAAF8Ejzyjz2+Q05wZ8RP4S9FReh7EUD/q2KuPGpBLlwI +TzwhiVaIjNPgkC+hrJZpZw== +-----END PRIVATE KEY----- diff --git a/test/http-server-test.js b/test/http-server-test.js index d4aa5f422..53cbff2f1 100644 --- a/test/http-server-test.js +++ b/test/http-server-test.js @@ -349,5 +349,41 @@ vows.describe('http-server').addBatch({ teardown: function (server) { server.close(); } + }, + 'When SSL is enabled': { + topic: function () { + var server = httpServer.createServer({ + https: { + cert: path.join(__dirname, 'fixtures', 'cert.pem'), + key: path.join(__dirname, 'fixtures', 'key.pem') + } + }); + server.listen(8087); + return server; + }, + 'the server should start without errors': function (err, server) { + assert.isNull(err); + assert.isTrue(server.server.listening); + }, + 'and it should serve files': { + topic: function (server) { + var caFile = path.join(__dirname, 'fixtures', 'ca.pem'); + request('https://127.0.0.1:8087/', { + ca: fs.readFileSync(caFile) + }, this.callback); + }, + 'over SSL': function (err, res) { + assert.isNull(err); + + var peerCert = res.socket.getPeerCertificate(); + assert.isNotNull(peerCert); + assert.equal(peerCert.subject.CN, 'localhost'); + } + }, + teardown: function (server) { + if (server) { + server.close(); + } + } } }).export(module); From 36abab9c8a883ea5f171bd10199d4abfcd1f8cba Mon Sep 17 00:00:00 2001 From: Andrew Brampton Date: Sun, 31 Jan 2021 14:30:40 -0800 Subject: [PATCH 4/4] Fixes #670: Added support for printing out the Common Name from the SSL certificate that is being used. This avoids SSL errors when going to the host (such as a IP address) that doesn't match the name in the certificate. --- README.md | 7 +++---- bin/http-server | 23 +++++++++++++++++------ package.json | 3 ++- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index eeb3f9868..82955a1dd 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ First, you need to make sure that [openssl](https://github.com/openssl/openssl) openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout key.pem -out cert.pem ``` -You will be prompted with a few questions after entering the command. Use `127.0.0.1` as value for `Common name` if you want to be able to install the certificate in your OS's root certificate store or browser so that it is trusted. +You will be prompted with a few questions after entering the command. Use `localhost` as value for `Common Name` if you want to be able to install the certificate in your OS's root certificate store or browser so that it is trusted. This generates a cert-key pair and it will be valid for 3650 days (about 10 years). @@ -126,9 +126,8 @@ This is what should be output if successful: ``` sh Starting up http-server, serving ./ through https Available on: - https:127.0.0.1:8080 - https:192.168.1.101:8080 - https:192.168.1.104:8080 + https://localhost:8080 + https://192.168.1.101:8080 Hit CTRL-C to stop the server ``` diff --git a/bin/http-server b/bin/http-server index 42ebf651f..839041dab 100755 --- a/bin/http-server +++ b/bin/http-server @@ -8,6 +8,7 @@ var colors = require('colors/safe'), portfinder = require('portfinder'), opener = require('opener'), fs = require('fs'), + x509 = require('x509.js'), argv = require('minimist')(process.argv.slice(2)); var ifaces = os.networkInterfaces(); @@ -138,23 +139,25 @@ function listen(port) { } } + var commonName; if (ssl) { options.https = { cert: argv.C || argv.cert || 'cert.pem', key: argv.K || argv.key || 'key.pem' }; try { - fs.lstatSync(options.https.cert); + var c = x509.parseCert(fs.readFileSync(options.https.cert)); + commonName = c.subject.commonName; } catch (err) { - logger.info(colors.red('Error: Could not find certificate ' + options.https.cert)); + logger.info(colors.red('Error: Could not read certificate ' + err)); process.exit(1); } try { - fs.lstatSync(options.https.key); + fs.readFileSync(options.https.key); } catch (err) { - logger.info(colors.red('Error: Could not find private key ' + options.https.key)); + logger.info(colors.red('Error: Could not read private key ' + err)); process.exit(1); } } @@ -170,19 +173,27 @@ function listen(port) { colors.yellow('\nAvailable on:') ].join('')); + var hosts = []; if (argv.a && host !== '0.0.0.0') { - logger.info((' ' + protocol + canonicalHost + ':' + colors.green(port.toString()))); + hosts.push(canonicalHost); } else { + if (ssl && commonName) { + hosts.push(commonName); + } Object.keys(ifaces).forEach(function (dev) { ifaces[dev].forEach(function (details) { if (details.family === 'IPv4') { - logger.info((' ' + protocol + details.address + ':' + colors.green(port.toString()))); + hosts.push(details.address); } }); }); } + hosts.forEach(function (host) { + logger.info((' ' + protocol + host + ':' + colors.green(port.toString()))); + }); + if (typeof proxy === 'string') { logger.info('Unhandled requests will be served from: ' + proxy); } diff --git a/package.json b/package.json index c6ecb7e1b..d2f356b25 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,8 @@ "opener": "^1.5.1", "portfinder": "^1.0.25", "secure-compare": "3.0.1", - "union": "~0.5.0" + "union": "~0.5.0", + "x509.js": "^1.0.0" }, "devDependencies": { "common-style": "^3.0.0",