diff --git a/src/backend/index.js b/src/backend/index.js index 0de363a..5a29476 100644 --- a/src/backend/index.js +++ b/src/backend/index.js @@ -43,6 +43,6 @@ function appStart () { try { appStart(); } catch (err) { - logger.error(err.message); + logger.error(err.message, err); process.exit(1); } diff --git a/src/backend/internal/certificate.js b/src/backend/internal/certificate.js index ffa0b42..42d9a71 100644 --- a/src/backend/internal/certificate.js +++ b/src/backend/internal/certificate.js @@ -1,10 +1,13 @@ 'use strict'; +const fs = require('fs'); const _ = require('lodash'); const error = require('../lib/error'); const certificateModel = require('../models/certificate'); const internalAuditLog = require('./audit-log'); const internalHost = require('./host'); +const tempWrite = require('temp-write'); +const utils = require('../lib/utils'); function omissions () { return ['is_deleted']; @@ -200,7 +203,8 @@ const internalCertificate = { }, /** - * Validates that the certs provided are good + * Validates that the certs provided are good. + * This is probably a horrible way to do this. * * @param {Access} access * @param {Object} data @@ -208,7 +212,8 @@ const internalCertificate = { * @returns {Promise} */ validate: (access, data) => { - return new Promise((resolve, reject) => { + return new Promise(resolve => { + // Put file contents into an object let files = {}; _.map(data.files, (file, name) => { if (internalHost.allowed_ssl_files.indexOf(name) !== -1) { @@ -219,12 +224,62 @@ const internalCertificate = { resolve(files); }) .then(files => { + // For each file, create a temp file and write the contents to it + // Then test it depending on the file type + let promises = []; + _.map(files, (content, type) => { + promises.push(tempWrite(content, '/tmp') + .then(filepath => { + if (type === 'certificate_key') { + return utils.exec('openssl rsa -in ' + filepath + ' -check') + .then(result => { + return {tmp: filepath, result: result.split("\n").shift()}; + }).catch(err => { + return {tmp: filepath, result: false, err: new error.ValidationError('Certificate Key is not valid')}; + }); - // TODO: validate using openssl - // files.certificate - // files.certificate_key + } else if (type === 'certificate') { + return utils.exec('openssl x509 -in ' + filepath + ' -text -noout') + .then(result => { + return {tmp: filepath, result: result}; + }).catch(err => { + return {tmp: filepath, result: false, err: new error.ValidationError('Certificate is not valid')}; + }); + } else { + return {tmp: filepath, result: false}; + } + }) + .then(file_result => { + // Remove temp files + fs.unlinkSync(file_result.tmp); + delete file_result.tmp; - return true; + return {[type]: file_result}; + }) + ); + }); + + // With the results, delete the temp files for security mainly. + // If there was an error with any of them, wait until we've done the deleting + // before throwing it. + return Promise.all(promises) + .then(files => { + let data = {}; + let err = null; + + _.each(files, file => { + data = _.assign({}, data, file); + if (typeof file.err !== 'undefined' && file.err) { + err = file.err; + } + }); + + if (err) { + throw err; + } + + return data; + }); }); }, diff --git a/src/backend/internal/host.js b/src/backend/internal/host.js index c314c8b..b0f3bd1 100644 --- a/src/backend/internal/host.js +++ b/src/backend/internal/host.js @@ -8,7 +8,7 @@ const deadHostModel = require('../models/dead_host'); const internalHost = { - allowed_ssl_files: ['certificate', 'certificate_key'], + allowed_ssl_files: ['certificate', 'certificate_key', 'intermediate_certificate'], /** * Internal use only, checks to see if the domain is already taken by any other record diff --git a/src/backend/routes/api/nginx/certificates.js b/src/backend/routes/api/nginx/certificates.js index a6328a2..2525ea7 100644 --- a/src/backend/routes/api/nginx/certificates.js +++ b/src/backend/routes/api/nginx/certificates.js @@ -163,7 +163,7 @@ router * POST /api/nginx/certificates/123/upload * * Upload certificates - */validate + */ .post((req, res, next) => { if (!req.files) { res.status(400) diff --git a/src/frontend/js/app/api.js b/src/frontend/js/app/api.js index 4f27144..76ec5c9 100644 --- a/src/frontend/js/app/api.js +++ b/src/frontend/js/app/api.js @@ -535,6 +535,14 @@ module.exports = { */ upload: function (id, form_data) { return FileUpload('nginx/certificates/' + id + '/upload', form_data); + }, + + /** + * @param {FormData} form_data + * @params {Promise} + */ + validate: function (form_data) { + return FileUpload('nginx/certificates/validate', form_data); } } }, diff --git a/src/frontend/js/app/nginx/certificates/form.ejs b/src/frontend/js/app/nginx/certificates/form.ejs index ef20f69..32edb6b 100644 --- a/src/frontend/js/app/nginx/certificates/form.ejs +++ b/src/frontend/js/app/nginx/certificates/form.ejs @@ -39,22 +39,32 @@
-
<%- i18n('all-hosts', 'other-certificate') %>
+
<%- i18n('certificates', 'other-certificate-key') %>*
- +
-
<%- i18n('all-hosts', 'other-certificate-key') %>
+
<%- i18n('certificates', 'other-certificate') %>*
- +
+
+
+
<%- i18n('certificates', 'other-intermediate-certificate') %>
+
+ + +
+
+
+ <% } %> diff --git a/src/frontend/js/app/nginx/certificates/form.js b/src/frontend/js/app/nginx/certificates/form.js index 7b5fa2b..9a7fa54 100644 --- a/src/frontend/js/app/nginx/certificates/form.js +++ b/src/frontend/js/app/nginx/certificates/form.js @@ -15,13 +15,14 @@ module.exports = Mn.View.extend({ max_file_size: 5120, ui: { - form: 'form', - domain_names: 'input[name="domain_names"]', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - other_ssl_certificate: '#other_ssl_certificate', - other_ssl_certificate_key: '#other_ssl_certificate_key' + form: 'form', + domain_names: 'input[name="domain_names"]', + buttons: '.modal-footer button', + cancel: 'button.cancel', + save: 'button.save', + other_certificate: '#other_certificate', + other_certificate_key: '#other_certificate_key', + other_intermediate_certificate: '#other_intermediate_certificate' }, events: { @@ -33,8 +34,8 @@ module.exports = Mn.View.extend({ return; } - let view = this; - let data = this.ui.form.serializeJSON(); + let view = this; + let data = this.ui.form.serializeJSON(); data.provider = this.model.get('provider'); // Manipulate @@ -46,55 +47,66 @@ module.exports = Mn.View.extend({ data.domain_names = data.domain_names.split(','); } - let method = App.Api.Nginx.Certificates.create; - let is_new = true; let ssl_files = []; - if (this.model.get('id')) { - // edit - is_new = false; - method = App.Api.Nginx.Certificates.update; - data.id = this.model.get('id'); - } - // check files are attached if (this.model.get('provider') === 'other' && !this.model.hasSslFiles()) { - if (!this.ui.other_ssl_certificate[0].files.length || !this.ui.other_ssl_certificate[0].files[0].size) { - alert('certificate file is not attached'); + if (!this.ui.other_certificate[0].files.length || !this.ui.other_certificate[0].files[0].size) { + alert('Certificate file is not attached'); return; } else { - if (this.ui.other_ssl_certificate[0].files[0].size > this.max_file_size) { - alert('certificate file is too large (> 5kb)'); + if (this.ui.other_certificate[0].files[0].size > this.max_file_size) { + alert('Certificate file is too large (> 5kb)'); return; } - ssl_files.push({name: 'certificate', file: this.ui.other_ssl_certificate[0].files[0]}); + ssl_files.push({name: 'certificate', file: this.ui.other_certificate[0].files[0]}); } - if (!this.ui.other_ssl_certificate_key[0].files.length || !this.ui.other_ssl_certificate_key[0].files[0].size) { - alert('certificate key file is not attached'); + if (!this.ui.other_certificate_key[0].files.length || !this.ui.other_certificate_key[0].files[0].size) { + alert('Certificate key file is not attached'); return; } else { - if (this.ui.other_ssl_certificate_key[0].files[0].size > this.max_file_size) { - alert('certificate key file is too large (> 5kb)'); + if (this.ui.other_certificate_key[0].files[0].size > this.max_file_size) { + alert('Certificate key file is too large (> 5kb)'); return; } - ssl_files.push({name: 'certificate_key', file: this.ui.other_ssl_certificate_key[0].files[0]}); + ssl_files.push({name: 'certificate_key', file: this.ui.other_certificate_key[0].files[0]}); + } + + if (this.ui.other_intermediate_certificate[0].files.length && this.ui.other_intermediate_certificate[0].files[0].size) { + if (this.ui.other_intermediate_certificate[0].files[0].size > this.max_file_size) { + alert('Intermediate Certificate file is too large (> 5kb)'); + return; + } + ssl_files.push({name: 'intermediate_certificate', file: this.ui.other_intermediate_certificate[0].files[0]}); } } this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - method(data) + + // compile file data + let form_data = new FormData(); + if (view.model.get('provider') && ssl_files.length) { + ssl_files.map(function (file) { + form_data.append(file.name, file.file); + }); + } + + new Promise(resolve => { + if (view.model.get('provider') === 'other') { + resolve(App.Api.Nginx.Certificates.validate(form_data)); + } else { + resolve(); + } + }) + .then(() => { + return App.Api.Nginx.Certificates.create(data); + }) .then(result => { view.model.set(result); // Now upload the certs if we need to - if (ssl_files.length) { - let form_data = new FormData(); - - ssl_files.map(function (file) { - form_data.append(file.name, file.file); - }); - + if (view.model.get('provider') === 'other') { return App.Api.Nginx.Certificates.upload(view.model.get('id'), form_data) .then(result => { view.model.set('meta', _.assign({}, view.model.get('meta'), result)); @@ -103,9 +115,7 @@ module.exports = Mn.View.extend({ }) .then(() => { App.UI.closeModal(function () { - if (is_new) { - App.Controller.showNginxCertificates(); - } + App.Controller.showNginxCertificates(); }); }) .catch(err => { diff --git a/src/frontend/js/i18n/messages.json b/src/frontend/js/i18n/messages.json index 2b2bb3a..232b716 100644 --- a/src/frontend/js/i18n/messages.json +++ b/src/frontend/js/i18n/messages.json @@ -66,8 +66,6 @@ "force-ssl": "Force SSL", "domain-names": "Domain Names", "cert-provider": "Certificate Provider", - "other-certificate": "Certificate", - "other-certificate-key": "Certificate Key", "block-exploits": "Block Common Exploits", "caching-enabled": "Cache Assets" }, @@ -141,7 +139,10 @@ "delete": "Delete SSL Certificate", "delete-confirm": "Are you sure you want to delete this SSL Certificate? Any hosts using it will need to be updated later.", "help-title": "SSL Certificates", - "help-content": "TODO" + "help-content": "TODO", + "other-certificate": "Certificate", + "other-certificate-key": "Certificate Key", + "other-intermediate-certificate": "Intermediate Certificate" }, "access-lists": { "title": "Access Lists",