226 lines
6.8 KiB
JavaScript
226 lines
6.8 KiB
JavaScript
/**
|
|
* 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) {
|
|
var options = {
|
|
hostname: 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 = 'info';
|
|
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 = 'info';
|
|
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 = 'info';
|
|
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 = 'info';
|
|
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 = 'info';
|
|
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(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) {
|
|
console.log('Error writing file. \n');
|
|
}
|
|
})
|
|
};
|
|
|
|
/**
|
|
* 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");
|
|
}
|
|
};
|