diff --git a/README.md b/README.md index 1b70d09..33c499b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # tls-dashboard A dashboard written in JavaScript & HTML to check the remaining time before a TLS certificate expires. A combination of a Node module and an HTML/CSS/JS webpage to display the info. -**Version:** 1.1.0 +![version](https://img.shields.io/badge/version-1.2.0-brightgreen.svg?style=flat-square) ## Node Setup ### `node_app/config.js` @@ -34,8 +34,7 @@ To get the web service started on a remote server, you'll need to either move th ## Example Take a look at a live example page [here on GitLab][1]. Screenshots below. -![Example dashboard](https://raw.githubusercontent.com/cmrunton/tls-dashboard/master/screenshot.png) -![Example dashboard](https://raw.githubusercontent.com/cmrunton/tls-dashboard/master/screenshot_2.png) +![Example dashboard](https://raw.githubusercontent.com/cmrunton/tls-dashboard/master/tls-dashboard.png) ## TODO 1. Database integration? diff --git a/node_app/error_codes.md b/node_app/error_codes.md new file mode 100644 index 0000000..502ac17 --- /dev/null +++ b/node_app/error_codes.md @@ -0,0 +1,101 @@ + +# Expired Certificate +## Object keys + [ 'code' ] + e.code => CERT_HAS_EXPIRED +## Console output + { Error: certificate has expired + at Error (native) + at TLSSocket. (_tls_wrap.js:1055:38) + at emitNone (events.js:86:13) + at TLSSocket.emit (events.js:185:7) + at TLSSocket._finishInit (_tls_wrap.js:580:8) + at TLSWrap.ssl.onhandshakedone (_tls_wrap.js:412:38) code: 'CERT_HAS_EXPIRED' } + +# Host Timeout +## Object keys + [ 'code' ] + e.code => ECONNRESET +## Console output + { Error: socket hang up + at createHangUpError (_http_client.js:250:15) + at TLSSocket.socketCloseListener (_http_client.js:282:23) + at emitOne (events.js:101:20) + at TLSSocket.emit (events.js:188:7) + at TCP._handle.close [as _onclose] (net.js:492:12) code: 'ECONNRESET' } + +# Connection refused +## Object keys + [ 'code', 'errno', 'syscall', 'address', 'port' ] + e.code => ECONNREFUSED +## Console output + { Error: connect ECONNREFUSED 10.7.29.195:443 + at Object.exports._errnoException (util.js:949:11) + at exports._exceptionWithHostPort (util.js:972:20) + at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1080:14) + code: 'ECONNREFUSED', + errno: 'ECONNREFUSED', + syscall: 'connect', + address: '10.7.29.195', + port: 443 } + +# Self-signed Certificate +## Object keys + [ 'code' ] + e.code => UNABLE_TO_VERIFY_LEAF_SIGNATURE +## Console output + { Error: unable to verify the first certificate + at Error (native) + at TLSSocket. (_tls_wrap.js:1055:38) + at emitNone (events.js:86:13) + at TLSSocket.emit (events.js:185:7) + at TLSSocket._finishInit (_tls_wrap.js:580:8) + at TLSWrap.ssl.onhandshakedone (_tls_wrap.js:412:38) code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE' } + +# Incomplete Certificate Chain +## Object Keys + [ 'code' ] + e.code => UNABLE_TO_VERIFY_LEAF_SIGNATURE +## Console output + { Error: unable to verify the first certificate + at Error (native) + at TLSSocket. (_tls_wrap.js:1055:38) + at emitNone (events.js:86:13) + at TLSSocket.emit (events.js:185:7) + at TLSSocket._finishInit (_tls_wrap.js:580:8) + at TLSWrap.ssl.onhandshakedone (_tls_wrap.js:412:38) code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE' } + + +# Hostname Mismatch +## Object keys + [ 'reason', 'host', 'cert' ] + e.reason => Host: wrong.host.badssl.com. is not in the cert's altnames: DNS:*.badssl.com, DNS:badssl.com +## Console output + { Error: Hostname/IP doesn't match certificate's altnames: "Host: wrong.host.badssl.com. is not in the cert's altnames: DNS:*.badssl.com, DNS:badssl.com" + at Object.checkServerIdentity (tls.js:203:15) + at TLSSocket. (_tls_wrap.js:1061:29) + at emitNone (events.js:86:13) + at TLSSocket.emit (events.js:185:7) + at TLSSocket._finishInit (_tls_wrap.js:580:8) + at TLSWrap.ssl.onhandshakedone (_tls_wrap.js:412:38) + reason: 'Host: wrong.host.badssl.com. is not in the cert\'s altnames: DNS:*.badssl.com, DNS:badssl.com', + host: 'wrong.host.badssl.com.', + cert: + { subject: { OU: [Object], CN: '*.badssl.com' }, + issuer: + { C: 'GB', + ST: 'Greater Manchester', + L: 'Salford', + O: 'COMODO CA Limited', + CN: 'COMODO RSA Domain Validation Secure Server CA' }, + subjectaltname: 'DNS:*.badssl.com, DNS:badssl.com', + infoAccess: { 'CA Issuers - URI': [Object], 'OCSP - URI': [Object] }, + modulus: 'C204ECF88CEE04C2B3D850D57058CC9318EB5CA86849B022B5F9959EB12B2C763E6CC04B604C4CEAB2B4C00F80B6B0F972C98602F95C415D132B7F71C44BBCE9942E5037A6671C618CF64142C546D31687279F74EB0A9D11522621736C844C7955E4D16BE8063D481552ADB328DBAAFF6EFF60954A776B39F124D131B6DD4DC0C4FC53B96D42ADB57CFEAEF515D23348E72271C7C2147A6C28EA374ADFEA6CB572B47E5AA216DC69B15744DB0A12ABDEC30F47745C4122E19AF91B93E6AD2206292EB1BA491C0C279EA3FB8BF7407200AC9208D98C5784538105CBE6FE6B5498402785C710BB7370EF6918410745557CF9643F3D2CC3A97CEB931A4C86D1CA85', + exponent: '0x10001', + valid_from: 'Apr 9 00:00:00 2015 GMT', + valid_to: 'Jul 7 23:59:59 2016 GMT', + fingerprint: 'C8:67:8E:DB:FD:BB:30:B5:3F:2D:7B:F9:66:B8:14:C6:2E:95:92:CE', + ext_key_usage: [ '1.3.6.1.5.5.7.3.1', '1.3.6.1.5.5.7.3.2' ], + serialNumber: '2FEB1825187C1A5086407B44E5B785A5', + raw: } } + diff --git a/node_app/get_cert_info.js b/node_app/get_cert_info.js index 49c0360..1f7c6c9 100644 --- a/node_app/get_cert_info.js +++ b/node_app/get_cert_info.js @@ -64,91 +64,78 @@ function get_cert_parameters(element, index, array) { 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.code ==='ECONNREFUSED') { - // The connection was refused by the server (ex. 443 not open, not resonding, etc.) - assert(false, 'Connection to '+element+' refused'); - var parsed = { - 'server': element, - 'subject': { - 'org': 'Unknown', - 'common_name': 'Unknown', - 'sans': 'Unknown' - }, - 'issuer': { - 'org': 'Unknown', - 'common_name': 'Last check connection refused' - }, - 'info': { - 'days_left': '??' - } + 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; }; - add_cert_details(parsed, iteration); - check_iterations(); - } else if (e.code ==='ECONNRESET') { - // The connection to the server timed out - assert(false, 'Connection to '+element+' timed out'); - var parsed = { - 'server': element, - 'subject': { - 'org': 'Unknown', - 'common_name': 'Unknown', - 'sans': 'Unknown' - }, - 'issuer': { - 'org': 'Unknown', - 'common_name': 'Last check timed out' - }, - 'info': { - 'days_left': '??' - } - }; - add_cert_details(parsed, iteration); - check_iterations(); - } else if (e.reason.startsWith('Host: '+element+'. is not in the cert\'s altnames')) { - // There is a hostname mismatch between the cert and the server - assert(false, element+' had a hostname mismatch'); - var parsed = { - 'server': element, - 'subject': { - 'org': 'Unknown', - 'common_name': 'Unknown', - 'sans': 'Unknown' - }, - 'issuer': { - 'org': 'Unknown', - 'common_name': 'Hostname mismatch' - }, - 'info': { - 'days_left': '??' - } - }; - add_cert_details(parsed, iteration); - check_iterations(); - } else { - var err = e; - // Catchall for all other errors to prevent the script bombing out - assert(false, 'Connection to '+element+' errored out'); - var parsed = { - 'server': element, - 'subject': { - 'org': 'Unknown', - 'common_name': 'Unknown', - 'sans': 'Unknown' - }, - 'issuer': { - 'org': 'Unknown', - 'common_name': '' - }, - 'info': { - 'days_left': '??', - 'common_name': 'Unspecified error' - } - }; - add_cert_details(parsed, iteration); - check_iterations(); + } 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 diff --git a/screenshot.png b/screenshot.png deleted file mode 100644 index 0a2741a..0000000 Binary files a/screenshot.png and /dev/null differ diff --git a/screenshot_2.png b/screenshot_2.png deleted file mode 100644 index 54ed555..0000000 Binary files a/screenshot_2.png and /dev/null differ diff --git a/tls-dashboard.png b/tls-dashboard.png new file mode 100644 index 0000000..3bc8930 Binary files /dev/null and b/tls-dashboard.png differ diff --git a/web_service/js/tls-dashboard/certificates.js b/web_service/js/tls-dashboard/certificates.js index 067a958..1bedda1 100644 --- a/web_service/js/tls-dashboard/certificates.js +++ b/web_service/js/tls-dashboard/certificates.js @@ -1,6 +1,23 @@ -var run_date = 'Tue May 24 2016'; +var run_date = 'Fri Jun 17 2016'; var cert_info = { "1": { + "server": "http-only.runtondev.com", + "subject": { + "org": "Unknown", + "common_name": "The connection was refused by the remote server", + "sans": "Unknown" + }, + "issuer": { + "org": "Unknown", + "common_name": "ECONNREFUSED" + }, + "info": { + "days_left": "--", + "sort_order": 100000, + "background_class": "info" + } + }, + "2": { "server": "www.google.com", "subject": { "org": "Google Inc", @@ -12,12 +29,100 @@ var cert_info = { "common_name": "Google Internet Authority G2" }, "info": { - "valid_from": "2016-05-18T10:59:02.000Z", - "valid_to": "2016-08-10T10:46:00.000Z", - "days_left": 77 + "valid_from": "2016-06-08T12:37:29.000Z", + "valid_to": "2016-08-31T12:30:00.000Z", + "days_left": 75, + "sort_order": 75, + "background_class": "success" } }, - "2": { + "3": { + "server": "expired.badssl.com", + "subject": { + "org": "Unknown", + "common_name": "The certificate has expired", + "sans": "Unknown" + }, + "issuer": { + "org": "Unknown", + "common_name": "CERT_HAS_EXPIRED" + }, + "info": { + "days_left": "0", + "sort_order": 0, + "background_class": "danger" + } + }, + "4": { + "server": "incomplete-chain.badssl.com", + "subject": { + "org": "Unknown", + "common_name": "The server provided a self-signed certificate or the provided certificate chain was incomplete", + "sans": "Unknown" + }, + "issuer": { + "org": "Unknown", + "common_name": "UNABLE_TO_VERIFY_LEAF_SIGNATURE" + }, + "info": { + "days_left": "--", + "sort_order": 100000, + "background_class": "info" + } + }, + "5": { + "server": "wrong.host.badssl.com", + "subject": { + "org": "Unknown", + "common_name": "There was mismatch between the requested hostname and the certificate presented by the server", + "sans": "Unknown" + }, + "issuer": { + "org": "Unknown", + "common_name": "HOSTNAME_MISMATCH" + }, + "info": { + "days_left": "--", + "sort_order": 100000, + "background_class": "info" + } + }, + "6": { + "server": "self-signed.badssl.com", + "subject": { + "org": "Unknown", + "common_name": "The server provided a self-signed certificate or the provided certificate chain was incomplete", + "sans": "Unknown" + }, + "issuer": { + "org": "Unknown", + "common_name": "UNABLE_TO_VERIFY_LEAF_SIGNATURE" + }, + "info": { + "days_left": "--", + "sort_order": 100000, + "background_class": "info" + } + }, + "7": { + "server": "sha256.badssl.com", + "subject": { + "common_name": "*.badssl.com", + "sans": "DNS:*.badssl.com, DNS:badssl.com" + }, + "issuer": { + "org": "COMODO CA Limited", + "common_name": "COMODO RSA Domain Validation Secure Server CA" + }, + "info": { + "valid_from": "2015-04-09T00:00:00.000Z", + "valid_to": "2016-07-07T23:59:59.000Z", + "days_left": 20, + "sort_order": 20, + "background_class": "danger" + } + }, + "8": { "server": "www.twitter.com", "subject": { "org": "Twitter, Inc.", @@ -31,117 +136,62 @@ var cert_info = { "info": { "valid_from": "2016-03-09T00:00:00.000Z", "valid_to": "2018-03-14T12:00:00.000Z", - "days_left": 659 - } - }, - "3": { - "server": "news.ycombinator.com", - "subject": { - "common_name": "*.ycombinator.com", - "sans": "DNS:*.ycombinator.com, DNS:ycombinator.com" - }, - "issuer": { - "org": "COMODO CA Limited", - "common_name": "COMODO RSA Domain Validation Secure Server CA" - }, - "info": { - "valid_from": "2014-08-22T00:00:00.000Z", - "valid_to": "2019-08-21T23:59:59.000Z", - "days_left": 1184 - } - }, - "4": { - "server": "www.github.com", - "subject": { - "org": "GitHub, Inc.", - "common_name": "github.com", - "sans": "DNS:github.com, DNS:www.github.com" - }, - "issuer": { - "org": "DigiCert Inc", - "common_name": "DigiCert SHA2 Extended Validation Server CA" - }, - "info": { - "valid_from": "2016-03-10T00:00:00.000Z", - "valid_to": "2018-05-17T12:00:00.000Z", - "days_left": 723 - } - }, - "5": { - "server": "example.dev", - "subject": { - "org": "Unknown", - "common_name": "Unknown", - "sans": "Unknown" - }, - "issuer": { - "org": "Unknown", - "common_name": "Last check timed out" - }, - "info": { - "days_left": "??" - } - }, - "6": { - "server": "example.dev", - "subject": { - "org": "Unknown", - "common_name": "Unknown", - "sans": "Unknown" - }, - "issuer": { - "org": "Unknown", - "common_name": "Last check connection refused" - }, - "info": { - "days_left": "??" - } - }, - "7": { - "server": "danger.example.com", - "subject": { - "common_name": "danger.example.com", - "sans": "DNS:pma.logthecrux.com" - }, - "issuer": { - "org": "Let's Encrypt", - "common_name": "Let's Encrypt Authority X3" - }, - "info": { - "valid_from": "2016-05-17T18:15:00.000Z", - "valid_to": "2016-08-15T18:15:00.000Z", - "days_left": 15 - } - }, - "8": { - "server": "warning.example.com", - "subject": { - "common_name": "warning.example.com", - "sans": "DNS:pma.logthecrux.com" - }, - "issuer": { - "org": "Let's Encrypt", - "common_name": "Let's Encrypt Authority X3" - }, - "info": { - "valid_from": "2016-05-17T18:15:00.000Z", - "valid_to": "2016-08-15T18:15:00.000Z", - "days_left": 40 + "days_left": 635, + "sort_order": 635, + "background_class": "success" } }, "9": { - "server": "mismatch.example.com", + "server": "nonexistent.runtondev.com", "subject": { "org": "Unknown", - "common_name": "Unknown", + "common_name": "The connection was reset by the server or timed out", "sans": "Unknown" }, "issuer": { "org": "Unknown", - "common_name": "Hostname mismatch" + "common_name": "ECONNRESET" }, "info": { - "days_left": "??" + "days_left": "--", + "sort_order": 100000, + "background_class": "info" } - } + }, + "10": { + "server": "warning.runtondev.com", + "subject": { + "common_name": "warning.runtondev.com", + "sans": "" + }, + "issuer": { + "org": "Madeup CA", + "common_name": "CA that doesn't exist" + }, + "info": { + "valid_from": "2015-04-09T00:00:00.000Z", + "valid_to": "2016-07-07T23:59:59.000Z", + "days_left": 45, + "sort_order": 45, + "background_class": "warning" + } + }, + "11": { + "server": "danger.runtondev.com", + "subject": { + "common_name": "danger.runtondev.com", + "sans": "" + }, + "issuer": { + "org": "Madeup CA", + "common_name": "CA that doesn't exist" + }, + "info": { + "valid_from": "2015-04-09T00:00:00.000Z", + "valid_to": "2016-07-07T23:59:59.000Z", + "days_left": 1, + "sort_order": 1, + "background_class": "danger" + } + }, } \ No newline at end of file