/** * TLS Dashboard by Craine Runton * Source: https://github.com/cmrunton/tls-dashboard * * See /LICENSE for licensing details */ var https = require('https'); const fs = require('fs'); const config = require('./config'); const monitored_hosts = require('./monitored_hosts'); const run_date = new Date().toDateString(); var output = {}, iteration = 1, errors = 0; // Run the module monitored_hosts.forEach(get_cert_parameters) /** * Creates a connection to the host, and then reads the resulting peer certificate to extract the desired info * * @param {string} element The * @param {int} index * @param {array} array The */ function get_cert_parameters(element, index, array) { console.log(element); var options = { hostname: element.hostname, port: (element.port ? element.port : 443), method: 'GET' }; var req = https.request(options, function(res) { var cert = res.connection.getPeerCertificate(); var parsed = { 'server': element, 'subject': { 'org': cert.subject.O, 'common_name': cert.subject.CN, 'sans': cert.subjectaltname }, 'issuer': { 'org': cert.issuer.O, 'common_name': cert.issuer.CN }, 'info': { 'valid_from': parse_date(cert.valid_from), 'valid_to': parse_date(cert.valid_to), 'days_left': get_days_left(cert.valid_to), 'sort_order': get_days_left(cert.valid_to), 'background_class': '' } }; if (parsed.info.days_left <= 30 ){ parsed.info.background_class = 'danger'; } else if (parsed.info.days_left > 30 && parsed.info.days_left <= 60 ) { parsed.info.background_class = 'warning'; } else { parsed.info.background_class = 'success'; }; add_cert_details(parsed, iteration); check_iterations(); }); // Abort the request when a timeout event is emitted req.on('timeout', function () { this.abort(); }); // Handle errors generated by failed requests req.on('error', function(e) { // Increment the error count for the final output errors++; var parsed = { 'server': element, 'subject': { 'org': 'Unknown', 'common_name': '', 'sans': 'Unknown' }, 'issuer': { 'org': 'Unknown', 'common_name': '' }, 'info': { 'days_left': '', 'sort_order': 100000, 'background_class': '' } }; if (e.hasOwnProperty('code')) { switch (e.code) { case 'CERT_HAS_EXPIRED': assert(false, element+' certificate expired.'); parsed.subject.common_name = 'The certificate has expired'; parsed.issuer.common_name = e.code; parsed.info.days_left = '0'; parsed.info.sort_order = 0; parsed.info.background_class = 'danger'; break; case 'ECONNRESET': assert(false, element+' connection timed out or was reset.'); parsed.subject.common_name = 'The connection was reset by the server or timed out'; parsed.issuer.common_name = e.code; parsed.info.days_left = '--'; parsed.info.background_class = 'error'; break; case 'ECONNREFUSED': assert(false, element+' connection refused by server.'); parsed.subject.common_name = 'The connection was refused by the remote server'; parsed.issuer.common_name = e.code; parsed.info.days_left = '--'; parsed.info.background_class = 'error'; break; case 'UNABLE_TO_VERIFY_LEAF_SIGNATURE': assert(false, element+' self-signed or incomplete certificate chain.'); parsed.subject.common_name = 'The server provided a self-signed certificate or the provided certificate chain was incomplete'; parsed.issuer.common_name = e.code; parsed.info.days_left = '--'; parsed.info.background_class = 'error'; break; default: assert(false, element+' unspecified error.'); parsed.subject.common_name = 'An unspecified error occured'; parsed.issuer.common_name = e.code; parsed.info.days_left = '--'; parsed.info.background_class = 'error'; break; }; } else if (e.hasOwnProperty('reason')) { switch (e.reason) { default: assert(false, element+' hostname mismatch.'); parsed.subject.common_name = 'There was mismatch between the requested hostname and the certificate presented by the server'; parsed.issuer.common_name = 'HOSTNAME_MISMATCH'; parsed.info.days_left = '--'; parsed.info.background_class = 'error'; break; } } add_cert_details(parsed, iteration); check_iterations(); }) // Set the timeout threshold for the https connection. Set in config.js, default 5000ms req.setTimeout(config.connection_timeout); // End the request req.end(); }; /** * Parses the date string passed to it and returs a new date object * * @param {string} date_string The human readble date string that needs to be parsed */ function parse_date(date_string) { var date = new Date(Date.parse(date_string)); return date; }; /** * Takes a date string and returns the nuumber of days between now and the future date * * @param {string} date_string The human readble date string that needs to be parsed */ function get_days_left(date_string) { var now = Date.now(); var then = new Date(Date.parse(date_string)); var days_left = Math.round((then - now)/86400000); return days_left; }; /** * Helper function to put the resolved/parsed cert info into the module output object * * @param {object} object Contains the parsed certificate info * @param {string} host The name of the host that the certificate info is taken from */ function add_cert_details(object, host) { output[host] = object; }; /** * Checks the iteration count. If the forEach has iterated over all the hosts, then call the write_results function, * otherwise log the iteration to the console and increment the count */ function check_iterations() { if (iteration === monitored_hosts.length) { assert(true, 'Scanned '+iteration+' of '+monitored_hosts.length+' urls, with '+errors+' errors'); write_results(); } else { iteration++; } }; /** * Writes out the final object to a file, along with the run date to be used by the HTML page later */ function write_results() { fs.writeFile(__dirname+'/'+config.output_file.path+config.output_file.name, 'var run_date = \''+run_date+'\'; \nvar cert_info = '+JSON.stringify(output, null, 2), function(err) { // If the write errored out, notify if (err) { assert(false, 'Error writing file to the specified location.'); } }) }; /** * Test function and used to write out the final iteration in green */ function assert(value, desc) { if (value) { console.log("\033[32m "+desc+"\033[0m"); } else { console.log("\033[31m "+desc+"\033[0m"); } };