From 3a9048cd26fb08df67797f05214d9af92a4e2dcd Mon Sep 17 00:00:00 2001 From: James Edington Date: Sun, 30 May 2021 16:32:43 -0500 Subject: [PATCH] Giant overhaul --- src/js/constants.js | 14 ++-- src/js/oop.js | 111 ++++++++++++++++++++++++++++++++ src/js/util.js | 153 +++----------------------------------------- src/manifest.json | 2 +- 4 files changed, 130 insertions(+), 150 deletions(-) create mode 100644 src/js/oop.js diff --git a/src/js/constants.js b/src/js/constants.js index 3db2aa3..020ffec 100644 --- a/src/js/constants.js +++ b/src/js/constants.js @@ -31,11 +31,13 @@ const fp_host_alt = new Map(); //TODO TODO TODO TODO TODO TODO TODO fp_host_alt.set('07:ED:BD:82:4A:49:88:CF:EF:42:15:DA:20:D4:8C:2B:41:D7:15:29:D7:C9:00:F5:70:92:6F:27:7C:C2:30:C5', 'cacert.org'); -fp_host_alt.set('89:4E:BC:0B:23:DA:2A:50:C0:18:6B:7F:8F:25:EF:1F:6B:29:35:AF:32:A9:45:84:EF:80:AA:F8:77:A3:A0:6E', 'us'); //fpki.gov=us -fp_host_alt.set('B1:07:B3:3F:45:3E:55:10:F6:8E:51:31:10:C6:F6:94:4B:AC:C2:63:DF:01:37:F8:21:C1:B3:C2:F8:F8:63:D2', 'us'); // whytf does the DoD have so many roots, and why was this one never cross-signed? +fp_host_alt.set('89:4E:BC:0B:23:DA:2A:50:C0:18:6B:7F:8F:25:EF:1F:6B:29:35:AF:32:A9:45:84:EF:80:AA:F8:77:A3:A0:6E', 'gov'); // fpki.gov +fp_host_alt.set('B1:07:B3:3F:45:3E:55:10:F6:8E:51:31:10:C6:F6:94:4B:AC:C2:63:DF:01:37:F8:21:C1:B3:C2:F8:F8:63:D2', 'gov'); // whytf does the DoD have so many roots, and why was this one never cross-signed? +host_country.set('cacert.org', 'nl'); +host_country.set('gov', 'us'); -//Record the SHA-256 Fingerprint -> (reduced) hostname mappings -//As well as non-idiopathic SHA-256 Fingerprint -> country mappings +// Record the Fingerprint -> (reduced) hostname mappings +// As well as non-idiopathic Fingerprint -> country mappings getAsset("db/IncludedCACertificateReport.json", "json") .filter(ca => new Set(ca['Trust Bits'].split(';')).has('Websites')) .forEach(ca => { @@ -48,13 +50,13 @@ getAsset("db/IncludedCACertificateReport.json", "json") fp_host.set(fp, host); let country = identifyCountry(host); - if( country ) host_country.get(host) = country; + if( country ) host_country.set(host, country); }); //Apply idiopathic country mappings Object.entries(getAsset("db/host_cityStateCountry.json","json")) .forEach(([host, [city, state, country]]) => { - if( host in host_country ) { + if( host_country.has(host) ) { console.warn("Overwriting country! Database may need to be checked.\nIf you're seeing this, please open an issue on GitHub; include the following:", { host: host, diff --git a/src/js/oop.js b/src/js/oop.js new file mode 100644 index 0000000..955be7c --- /dev/null +++ b/src/js/oop.js @@ -0,0 +1,111 @@ +class SecurityDetails { + constructor(requestDetails,securityInfo) { + this.securityInfo = securityInfo; + this.requestDetails = requestDetails; + + Object.freeze(this.securityInfo); + Object.freeze(this.requestDetails); + + //lazily-evaluated: + this._secType=undefined; + this._caId=undefined; + } + + get secType() { + if( this._secType === undefined ) { + this._secType = SecurityDetails.identifySecType(this.securityInfo); + } + return this._secType; + } + + get caId() { + if( this._caId === undefined ) { + this._caId = SecurityDetails.identifyCaId(this.securityInfo); + } + return this._caId; + } + + static identifySecType(securityInfo){ + //Takes in a browser.webRequest.getSecurityInfo object + //and returns an integer from secTypes corresponding to the + try { + switch(securityInfo.state) { + case 'insecure': + //genuinely not HTTPS + + return secTypes.insecure; + + case 'secure': + // ANY HTTPS-secured connection // + + const certChain=securityInfo.certificates; + + //If it's length-0 -OR- somehow undefined/null/etc + if(!( certChain.length >= 1 )) { + //TODO/FIXME: Mozilla doesn't provide + //any access whatsoever to self-signed + //or otherwise nominally-invalid certs + // https://bugzilla.mozilla.org/show_bug.cgi?id=1678492 + return secTypes.unknown; + } else if( certChain.length == 1 ) { + + return secTypes.selfSigned; + } + + const rootCert = certChain[certChain.length-1]; + + //Now, this connection is... + if(rootCert.isBuiltInRoot){ + //...Mozilla-supported + return secTypes.Mozilla; + } + + if(!securityInfo.isUntrusted){//why didn't they use .isTrusted lol + //...supported by a Non-Mozilla cert,... + if(isItMitM(rootCert)){ //TODO + //...a TLS MITM proxy + return secTypes.MitM; + } else { + //...an alternative Root CA + if(fp_host_alt.has(rootCert.fingerprint[fp_alg])) { + return secTypes.aRootKnown; + } else { + return secTypes.aRootUnknown; + } + } + } + default: + throw {status:'thisShouldNeverHappen',securityInfo:securityInfo}; + } + } catch(e) { + console.error(e.status||e,{securityInfo:securityInfo}); + return secTypes.unknown; + } + } + + static identifyCaId(securityInfo) { + try { + const certChain = securityInfo.certificates; + if( ! certChain.length ) { + return null; + } else if( certChain.length == 1 ) { + return 'self'; + } else { + const rootCert = certChain[certChain.length-1]; + const fp = rootCert.fingerprint[fp_alg]; + if( fp_host.has(fp) ){ + return fp_host.get(fp); + } else if( fp_host_alt.has(fp) ){ + return fp_host_alt.get(fp); + } else { + console.warn('Unknown CA',certChain); + return fp; + } + } + throw {status:'thisShouldNeverHappen',securityInfo:securityInfo}; + } catch(e) { + console.error(e.status||e,{securityInfo:securityInfo}); + return null; + } + } +} diff --git a/src/js/util.js b/src/js/util.js index 5db51c8..c5ceb93 100644 --- a/src/js/util.js +++ b/src/js/util.js @@ -1,117 +1,5 @@ browser.browserAction.disable();//This should be greyed-out by default -class SecurityDetails { - constructor(requestDetails,securityInfo) { - this.securityInfo = securityInfo; - this.requestDetails = requestDetails; - - Object.freeze(this.securityInfo); - Object.freeze(this.requestDetails); - - //lazily-evaluated: - this._secType=undefined; - this._caId=undefined; - } - - get secType() { - if( this._secType === undefined ) { - this._secType = SecurityDetails.identifySecType(this.securityInfo); - } - return this._secType; - } - - get caId() { - if( this._caId === undefined ) { - this._caId = SecurityDetails.identifyCaId(this.securityInfo); - } - return this._caId; - } - - static identifySecType(securityInfo){ - //Takes in a browser.webRequest.getSecurityInfo object - //and returns an integer from secTypes corresponding to the - try { - switch(securityInfo.state) { - case 'insecure': - //genuinely not HTTPS - - return secTypes.insecure; - - case 'secure': - // ANY HTTPS-secured connection // - - const certChain=securityInfo.certificates; - - //If it's length-0 -OR- somehow undefined/null/etc - if(!( certChain.length >= 1 )) { - //TODO/FIXME: Mozilla doesn't provide - //any access whatsoever to self-signed - //or otherwise nominally-invalid certs - // https://bugzilla.mozilla.org/show_bug.cgi?id=1678492 - return secTypes.unknown; - } else if( certChain.length == 1 ) { - - return secTypes.selfSigned; - } - - const rootCert = certChain[certChain.length-1]; - - //Now, this connection is... - if(rootCert.isBuiltInRoot){ - //...Mozilla-supported - return secTypes.Mozilla; - } - - if(!securityInfo.isUntrusted){//why didn't they use .isTrusted lol - //...supported by a Non-Mozilla cert,... - if(isItMitM(rootCert)){ //TODO - //...a TLS MITM proxy - return secTypes.MitM; - } else { - //...an alternative Root CA - if(certChain[certChain.length-1].fingerprint[fp_alg] in fp_host_alt) { - return secTypes.aRootKnown; - } else { - return secTypes.aRootUnknown; - } - } - } - default: - throw {status:'thisShouldNeverHappen',securityInfo:securityInfo}; - } - } catch(e) { - console.error(e.status||e,{securityInfo:securityInfo}); - return secTypes.unknown; - } - } - - static identifyCaId(securityInfo) { - try { - const certChain = securityInfo.certificates; - if( ! certChain.length ) { - return null; - } else if( certChain.length == 1 ) { - return 'self'; - } else { - const rootCert = certChain[certChain.length-1]; - const fp = rootCert.fingerprint[fp_alg]; - if( fp in fp_host){ - return fp_host.get(fp); - } else if( fp_host_alt.has(fp) ){ - return fp_host_alt.get(fp); - } else { - console.warn('Unknown CA',certChain); - return fp; - } - } - throw {status:'thisShouldNeverHappen',securityInfo:securityInfo}; - } catch(e) { - console.error(e.status||e,{securityInfo:securityInfo}); - return null; - } - } -} - function getAsset(path,type=null){ const assetURL = browser.runtime.getURL(path); const xhr = new XMLHttpRequest(); @@ -132,12 +20,12 @@ function flag(s){ return String.fromCodePoint(...s.split('').map(u=>u.codePointAt()+127365)); } -function removeFragment(url){ +function urldefrag(url){ //Removes the fragment from a URL //TODO? iff profiler indicts this function: // return url.match(/^[^#]*/)[0]; - let u=new URL(url); - u.hash=""; + let u = new URL(url); + u.hash = ""; return u.toString(); } @@ -152,7 +40,7 @@ function genBrowserActionSpec(secType=null, caId=null){ case secTypes.Mozilla: return { Icon: {path: `images/root_icons/${caId}.ico`}, - Title: {title: `${flag(host_country.get(caId)||'XX')} ${caId}\n(Mozilla-trusted Root CA)`}, + Title: {title: `${flag(identifyCountry(caId)||'XX')} ${caId}\n(Mozilla-trusted Root CA)`}, BadgeText: {text: '\uD83E\uDD8A'}, BadgeBackgroundColor: {color: 'LimeGreen'} }; @@ -166,8 +54,8 @@ function genBrowserActionSpec(secType=null, caId=null){ case secTypes.aRootKnown: return { Icon: {path: `images/alt_root_icons/${caId}.ico`}, - Title: {title: `${caId}\n(Alternative Root CA)`}, - BadgeText: {text: rootHost[0].toUpperCase()}, //TODO + Title: {title: `${flag(identifyCountry(caId)||'XX')} ${caId}\n(Alternative Root CA)`}, + BadgeText: {text: caId[0]}, //TODO BadgeBackgroundColor: {color: 'Teal'} }; case secTypes.aRootUnknown: @@ -269,36 +157,15 @@ function match0(s,r){ function identifyCountry(hostname, only_gov=false){ const h = hostname.split ? hostname.split('.') : hostname; + if( host_country.has(h.join('.')) ) return host_country.get(h.join('.')); const len = h.length; const tld = len >= 1 ? h[len-1] : null; const sld = len >= 2 ? h[len-2] : null; + if( only_gov && sld != 'gov' && tld != 'gov' ) return null; + if( country_tld_exceptions.has(tld) ) return country_tld_exceptions.get(tld); - else return null; - if( only_gov && sld != 'gov' ) return null; - if - switch( match0(tld,exceptionRe) ) { - case 'uk': - - return 'gb'; - case 'ac': - - return 'sh'; - case 'gov': - - return 'us'; - case null: - //2-letter TLD *not* in the exception list; - //it's a valid ccTLD corresponding to its country - return tld; - default: - //2-letter TLD *in* the exception list (e.g.: .eu); - //it's not a valid ccTLD and we don't know the country - return null; - } - } else { - return null; - } + else if( tld.length == 2 ) return tld; } function reduceHostname(hostname){ diff --git a/src/manifest.json b/src/manifest.json index 5585e0f..ff38b2f 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "cerdicator", - "version": "0.0.12", + "version": "0.1.0", "description": "Enhanced TLS indicator with an emphasis on information about the Root Certificate Authority from which the connection's authenticity is derived.",