├── .gitattributes ├── LICENSE.txt ├── README.md ├── StoreUI ├── 1280x800.png ├── 440x280.png ├── AllSecure.png ├── SomeInsecure.png ├── icon128b.png ├── icon64.png └── nonsecure.png ├── TODO.txt ├── background.js ├── images ├── HSTSyes.png ├── HTTPSno.png ├── HTTPSyes.png ├── IsHSTS.png ├── IsHTTPS.png ├── icon128.png ├── icon16.png ├── icon19.png ├── icon32.png ├── icon38.png └── icon48.png ├── injected.css ├── injected.js ├── manifest.json ├── options.css ├── options.html ├── options.js ├── popup.css ├── popup.html └── popup.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 by the moarTLS Authors. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # moartls 2 | moarTLS is a Browser Extension that flags non-secure links and downloads. 3 | 4 | Learn more about the extension in the [Announcement Blog Post](https://textplain.wordpress.com/2016/03/17/seek-and-destroy-non-secure-references-using-the-moartls-analyzer/) 5 | 6 | Install the Edge / Chrome extension from the [Chrome WebStore](https://chrome.google.com/webstore/detail/moartls-analyzer/ldfbacdbackkjhclmhnjabngnppnkagh) 7 | 8 | Install the Firefox extension from the [Mozilla Addons site](https://addons.mozilla.org/en-US/firefox/addon/moartls/?src=cb-dl-rating) 9 | 10 | Install the Edge Legacy version from the [Microsoft Store page](https://www.microsoft.com/en-us/p/moartls-analyzer/9nw480sp5d2c#activetab=pivot:overviewtab) 11 | 12 | The Extension works with Opera but their store rejected the extension because they don't like mention of "Chrome" and I was too lazy to fix it. 13 | -------------------------------------------------------------------------------- /StoreUI/1280x800.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/StoreUI/1280x800.png -------------------------------------------------------------------------------- /StoreUI/440x280.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/StoreUI/440x280.png -------------------------------------------------------------------------------- /StoreUI/AllSecure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/StoreUI/AllSecure.png -------------------------------------------------------------------------------- /StoreUI/SomeInsecure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/StoreUI/SomeInsecure.png -------------------------------------------------------------------------------- /StoreUI/icon128b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/StoreUI/icon128b.png -------------------------------------------------------------------------------- /StoreUI/icon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/StoreUI/icon64.png -------------------------------------------------------------------------------- /StoreUI/nonsecure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/StoreUI/nonsecure.png -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | TODO: Reintroduce support for poking into ShadowDOMs; see https://bugs.chromium.org/p/chromium/issues/detail?id=778816#c25 2 | 3 | 4 | 5 | HTTP BAD - Detect CC and Password 6 | 7 | 8 | NOTE: DEEP SELECTOR NO LONGER WORKS. :( 9 | https://bugs.chromium.org/p/chromium/issues/detail?id=937746#c99 10 | 11 | Password is easy: 12 | document.querySelectorAll("* /deep/ input[type='password']"); 13 | 14 | Credit card is complex: 15 | https://cs.chromium.org/chromium/src/components/autofill/core/browser/autofill_regex_constants.cc?sq=package:chromium&dr=CSs&rcl=1473923811&l=140 16 | 17 | 18 | 19 | chrome.management.getSelf( (o) => { 20 | lnkVersion.textContent = lnkVersion.textContent + o.installType; <-- "development" if self-installed 21 | } ); 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ================= 30 | 31 | if (typeof msBrowser !== 'undefined') { chrome = msBrowser; } else if (typeof browser !== 'undefined') { chrome = browser;} 32 | 33 | scroll link into view 34 | run CSS / HTML / Script lint 35 | 36 | 0. Inject NOSY image? 37 | 1. Hash file downloads (https://developer.chrome.com/extensions/nativeMessaging#native-messaging-host) 38 | 39 | You should always provide a 128x128 icon; it's used during installation and by the Chrome Web Store. 40 | Extensions should also provide a 48x48 icon, which is used in the extensions management page (chrome://extensions). 41 | You can also specify a 16x16 icon to be used as the favicon for an extension's pages. 42 | 43 | Use PNG format, transparency is good. 44 | 45 | "icons": { "16": "icon16.png", 46 | "48": "icon48.png", 47 | "128": "icon128.png" }, 48 | 49 | For example, Windows often requires 32-pixel icons, and if the app includes a 32-pixel icon, Chrome will choose that instead of shrinking a 48-pixel icon down. However, you should ensure that all of your icons are square, or unexpected behavior may result. 50 | If the icon is square, the main image should be 96x96px with a 16px transparent padding. 51 | 52 | Include a 38x38 for retina macs: http://stackoverflow.com/questions/18555085/how-do-i-specify-an-chrome-extension-icon-popup-icon-for-retina-macs 53 | 54 | https://developer.chrome.com/webstore/images 55 | 56 | https://developer.chrome.com/webstore/branding 57 | 58 | https://developer.chrome.com/webstore/publish?hl=en-US#step2 59 | 60 | Applications and themes require at least one screenshot. Extensions may have no screenshots, but such extensions won't be shown in the gallery. 61 | Provide preferably 4 or 5 screenshots of your app (up to a maximum of 5). If your app supports multiple locales, you can provide locale-specific screenshots. Your screenshot should have square corners and no padding (full bleed). 62 | We prefer screenshots to be 1280x800 pixels in size, as they will be used in future high dpi displays. Currently, the Chrome Web Store will downscale all screenshots to 640x400, so if your screenshots will not look good downscaled (eg. have a lot of text) or if 1280x800 is too big for your app (screenshot of low resolution game), we also support 640x400 pixels screenshots. 63 | -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | // Background serviceworker 4 | // https://developer.chrome.com/extensions/event_pages 5 | 6 | // Our background sw isn't persistent. 7 | chrome.runtime.onStartup.addListener(()=> { init(); }); 8 | // onInstalled when user uses chrome://extensions page to reload 9 | chrome.runtime.onInstalled.addListener(() => { init(); }); 10 | 11 | function init() 12 | { 13 | // Add Badge notification if this is a dev-install 14 | chrome.management.getSelf( (o)=>{ 15 | if (o.installType === "development") { 16 | chrome.action.setBadgeText( {text: "dev"} ); 17 | } 18 | }); 19 | } 20 | 21 | chrome.downloads.onCreated.addListener(function(item) { 22 | // https://developer.chrome.com/extensions/downloads#type-DownloadItem 23 | 24 | // Note: The download manager "creates" downloads for previously-downloaded items 25 | // So we need to look for "in_progress" downloads only. 26 | // 27 | // TODO: Can we do anything useful with the item.mime property? 28 | // 29 | if (item.state == "in_progress") { 30 | const storage = (chrome.storage.sync ? chrome.storage.sync : chrome.storage.local); 31 | storage.get(["bWarnOnNonSecureDownloads"], function(bWarnOnNonSecureDownloads) { 32 | if (!bWarnOnNonSecureDownloads) return; 33 | 34 | if ((item.url.substring(0, 5) == "http:") || 35 | (item.referrer && item.referrer.substring(0, 5) == "http:")) 36 | { 37 | var sReferer = (item.referrer) ? ("\nvia\n" + item.referrer) : ""; 38 | 39 | // Manifest v3 does not support alert(), so we need to use a notification. 40 | var options = { 41 | type: 'basic', 42 | title: 'Non-Secure Download was detected', 43 | message: `Download of:\n${item.url}${sReferer}`, 44 | iconUrl: 'images/icon128.png' 45 | }; 46 | chrome.notifications.create(options); 47 | } 48 | }); 49 | } 50 | }); -------------------------------------------------------------------------------- /images/HSTSyes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/images/HSTSyes.png -------------------------------------------------------------------------------- /images/HTTPSno.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/images/HTTPSno.png -------------------------------------------------------------------------------- /images/HTTPSyes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/images/HTTPSyes.png -------------------------------------------------------------------------------- /images/IsHSTS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/images/IsHSTS.png -------------------------------------------------------------------------------- /images/IsHTTPS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/images/IsHTTPS.png -------------------------------------------------------------------------------- /images/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/images/icon128.png -------------------------------------------------------------------------------- /images/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/images/icon16.png -------------------------------------------------------------------------------- /images/icon19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/images/icon19.png -------------------------------------------------------------------------------- /images/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/images/icon32.png -------------------------------------------------------------------------------- /images/icon38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/images/icon38.png -------------------------------------------------------------------------------- /images/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericlaw1979/moartls/cc3e8a5d30fd9e2dfcec7d24ce4d42041abd7e13/images/icon48.png -------------------------------------------------------------------------------- /injected.css: -------------------------------------------------------------------------------- 1 | a.moarTLSUnsecure { 2 | background: rgba(222, 106, 106, 0.6) !important; 3 | border-radius: 6px; 4 | border: 2px solid #DC0000; 5 | padding: 4px 4px 4px 4px; 6 | margin: 2px 2px 2px 2px; 7 | transition: background 1s ease; 8 | } 9 | 10 | a.moarTLSUnsecure img { 11 | -webkit-filter:sepia(1) hue-rotate(290deg); 12 | } 13 | 14 | body.moarTLSUnsecure { 15 | background: rgba(222, 106, 106, 0.5) !important; 16 | transition: background 1s ease; 17 | } 18 | 19 | form.moarTLSUnsecure { 20 | background: rgba(222, 106, 106, 0.6) !important; 21 | border-radius: 4px; 22 | border: 2px dashed #B93737; 23 | padding: 3px; 24 | margin: 3px; 25 | transition: 0.5s ease; 26 | } 27 | 28 | input.moarTLSUnsecure, 29 | img.moarTLSUnsecure { 30 | transform:rotate(180deg); 31 | transition: transform 0.5s ease; 32 | } -------------------------------------------------------------------------------- /injected.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | { 4 | if (typeof chrome === "undefined") var chrome = browser; 5 | 6 | // Remove URI-wrappers added by e.g. hotmail 7 | function unwrapUri(str) { 8 | let sl = /https:\/\/\w+\.safelinks.protection.outlook.com.*/ 9 | if (sl.test(str)) { 10 | return decodeURIComponent(str.substring(str.indexOf('url=')+4)); 11 | } 12 | return str; 13 | } 14 | 15 | function isNonsecure(str) { 16 | str = str.toLowerCase(); 17 | if (str.startsWith("http:") || str.startsWith("ftp:")) return true; 18 | return false; 19 | } 20 | 21 | function findUnsecureImages() 22 | { 23 | const imgs = document.querySelectorAll("img"); 24 | for (let i = 0; i < imgs.length; i++) { 25 | if (isNonsecure(unwrapUri(imgs[i].src))) { 26 | arrNonSecureImages.push(imgs[i]); 27 | } 28 | } 29 | 30 | const imginputs = document.querySelectorAll("input[type='image']"); 31 | for (let i = 0; i < imginputs.length; i++) { 32 | if (isNonsecure(unwrapUri(imginputs[i].src))) { 33 | arrNonSecureImages.push(imginputs[i]); 34 | } 35 | } 36 | } 37 | 38 | function markUnsecureImages() 39 | { 40 | for (let i = 0; i < arrNonSecureImages.length; i++) { 41 | arrNonSecureImages[i].classList.add("moarTLSUnsecure"); 42 | } 43 | } 44 | 45 | function markUnsecureDocument() 46 | { 47 | // Entire frame is unsecure? 48 | const sProt = document.location.protocol.toLowerCase(); 49 | if ((document.body) && 50 | ((sProt === "http:") || (sProt === "ftp:"))) { 51 | document.body.classList.add("moarTLSUnsecure"); 52 | } 53 | } 54 | 55 | const arrUnsecure = []; 56 | const arrNonSecureImages = []; 57 | let cLinks = 0; 58 | 59 | findUnsecureImages(); 60 | 61 | { 62 | if (chrome.storage) 63 | { 64 | const storage = (chrome.storage.sync ? chrome.storage.sync : chrome.storage.local); 65 | storage.get("bRotateNonSecureImages", function(obj) { 66 | if (obj && (false === obj.bRotateNonSecureImages)) return; 67 | markUnsecureImages(); 68 | }); 69 | 70 | storage.get("bWarnOnNonSecureDocument", function(obj) { 71 | if (obj && (false === obj.bWarnOnNonSecureDocument)) return; 72 | markUnsecureDocument(); 73 | }); 74 | } 75 | else 76 | { 77 | markUnsecureImages(); 78 | } 79 | } 80 | 81 | { 82 | const forms = document.querySelectorAll("form[action]"); 83 | for (let i = 0; i < forms.length; i++) { 84 | const thisForm = forms[i]; 85 | if (thisForm.getAttribute("action")[0] === "#") continue; // Not a cross-page 'action' 86 | cLinks++; 87 | const sUri = unwrapUri((typeof thisForm.action === "string") ? 88 | thisForm.action.toLowerCase() 89 | : thisForm.getAttribute("action").toLowerCase()); 90 | if (isNonsecure(sUri)) { 91 | arrUnsecure.push(sUri); 92 | thisForm.title = "Form target is: " + sUri; 93 | thisForm.classList.add("moarTLSUnsecure"); 94 | } 95 | } 96 | } 97 | 98 | { 99 | const lnks = document.querySelectorAll("a[href]"); 100 | for (let i = 0; i < lnks.length; i++) { 101 | const thisLink = lnks[i]; 102 | let sUnwrapped; 103 | if (thisLink.getAttribute("href")[0] === "#") { 104 | // Some funky page framework leaves all hrefs at # 105 | // and stores the true URL in a |data-url| attribute. 106 | if (thisLink.getAttribute("href").length === 1 && 107 | thisLink.getAttribute("data-url")) { 108 | sUnwrapped = unwrapUri(thisLink.getAttribute("data-url")); 109 | } 110 | else { 111 | continue; // Not a cross-page 'link' 112 | } 113 | } 114 | else { 115 | sUnwrapped = unwrapUri(thisLink.href); 116 | } 117 | 118 | cLinks++; 119 | if (isNonsecure(sUnwrapped)) { 120 | arrUnsecure.push(sUnwrapped); 121 | const oUnwrapped = document.createElement("a"); 122 | oUnwrapped.href = sUnwrapped; 123 | thisLink.title = oUnwrapped.protocol + "//" + oUnwrapped.hostname; 124 | thisLink.classList.add("moarTLSUnsecure"); 125 | } 126 | } 127 | } 128 | 129 | // We always need to send a report or else popup.js 130 | // can't know when analysis is complete. 131 | const obj = {LinkCount: cLinks, NonSecureImages: arrNonSecureImages.map(o=>{return o.src}), unsecure: arrUnsecure }; 132 | chrome.runtime.sendMessage(obj); 133 | } 134 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "moarTLS Analyzer", 4 | "short_name": "moarTLS", 5 | "description": "Analyze webpages for non-secure link references.", 6 | "version": "1.1.0.1", 7 | "author": "Eric Lawrence (@ericlaw)", 8 | "options_ui": { 9 | "page": "options.html" 10 | }, 11 | 12 | "minimum_chrome_version": "120", 13 | 14 | "commands": { 15 | "_execute_action": { 16 | "description": "Check the current page for non-secure references", 17 | "suggested_key": { 18 | "default": "Alt+M" 19 | } 20 | } 21 | }, 22 | 23 | "icons": { 24 | "16": "images/icon16.png", 25 | "32": "images/icon32.png", 26 | "48": "images/icon48.png", 27 | "128": "images/icon128.png" 28 | }, 29 | 30 | "action": { 31 | "default_icon": { 32 | "19": "images/icon19.png", 33 | "38": "images/icon38.png" 34 | }, 35 | "default_popup": "popup.html", 36 | "default_title": "Mark non-secure links" 37 | }, 38 | 39 | "background": { 40 | "service_worker": "background.js" 41 | }, 42 | 43 | "host_permissions": ["https://*/*"], 44 | 45 | "permissions": [ 46 | "activeTab", 47 | "notifications", 48 | "storage", 49 | "scripting", 50 | "downloads" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /options.css: -------------------------------------------------------------------------------- 1 | body { padding: 5px; } 2 | a { text-decoration: none; } 3 | label { 4 | -webkit-user-select: none; 5 | cursor: pointer; 6 | padding-bottom: 7px; 7 | padding-top: 7px; 8 | } 9 | input[type=checkbox] { 10 | margin-right:5px; 11 | } -------------------------------------------------------------------------------- /options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | moarTLS Options 5 | 6 | 7 | 8 | 9 | 10 | 11 |

12 |

16 | 17 | 18 |
 
19 | 20 |
21 |

22 | In the report, Alt+Click non-secure links to check whether HTTPS is available. 23 |

24 | -------------------------------------------------------------------------------- /options.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | document.addEventListener('DOMContentLoaded', function() { 4 | const storage = (chrome.storage.sync ? chrome.storage.sync : chrome.storage.local); 5 | storage.get(null, function(prefs) 6 | { 7 | document.getElementById("cbRotateImages").checked = !(prefs && (false === prefs["bRotateNonSecureImages"])); 8 | document.getElementById("cbWarnOnNonSecureDownloads").checked = (prefs && (true === prefs["bWarnOnNonSecureDownloads"])); 9 | document.getElementById("cbWarnOnNonSecureDocument").checked = !(prefs && (false === prefs["bWarnOnNonSecureDocument"])); 10 | }); 11 | 12 | var checkboxes = document.querySelectorAll("input[type=checkbox]"); 13 | for (let i=0; i 2 | 3 | 4 | 5 | moarTLS Analyzer 6 | 7 | 8 | 9 | 10 |

moarTLS Analyzer

11 |
12 |
Analyzing page...
13 |
14 |
15 | 16 |
Alt+Click items to check if HTTPS would've worked 17 | CopyClear Page 18 |
19 | 20 | -------------------------------------------------------------------------------- /popup.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | if (typeof chrome.runtime === "undefined") chrome = browser; 4 | 5 | document.addEventListener('DOMContentLoaded', function() { 6 | { 7 | const lnkVersion = document.getElementById("lblVersion"); 8 | lnkVersion.textContent = "v"+chrome.runtime.getManifest().version; 9 | lnkVersion.addEventListener("click", function() { chrome.runtime.openOptionsPage(); }, false); 10 | 11 | const lnkCopyForBug = document.getElementById("lnkCopyForBug"); 12 | lnkCopyForBug.addEventListener("click", function() { copyForBug(); }, false); 13 | 14 | const lnkUnmark = document.getElementById("lnkUnmark"); 15 | lnkUnmark.addEventListener("click", function() { 16 | lnkUnmark.textContent = ""; 17 | chrome.tabs.executeScript(null, {code:"{const u = document.querySelectorAll('.moarTLSUnsecure');for (let i = 0; i < u.length; i++) u[i].classList.remove('moarTLSUnsecure');}", allFrames: true, runAt:"document_idle"}, null); 18 | }, false); 19 | } 20 | 21 | chrome.tabs.query({active: true, currentWindow: true }, function(activeTabs) { 22 | if (activeTabs.length < 1) return; // impossible? 23 | const activeTab = activeTabs[0]; 24 | 25 | const oUri = document.createElement("a"); 26 | oUri.href = activeTab.url; 27 | const sOrigin = "https://" + oUri.host +"/"; 28 | 29 | const sProt = oUri.protocol.toLowerCase(); 30 | 31 | if ((sProt === "http:") || (sProt === "ftp:")) 32 | { 33 | document.getElementById("lnkUnmark").style.display="inline"; 34 | } 35 | 36 | if (sProt.indexOf("chrome") == 0) { 37 | if (oUri.host === "newtab") { 38 | // injecting into newtab is permitted 39 | document.getElementById("lnkDomain").style.display = "none"; 40 | } 41 | else { 42 | // otherwise, bail out. 43 | document.getElementById("txtStatus").textContent = "Unfortunately, Chrome's internal pages cannot be analyzed."; 44 | return; 45 | } 46 | } 47 | 48 | // http://stackoverflow.com/questions/11613371/chrome-extension-content-script-on-https-chrome-google-com-webstore 49 | if (oUri.href.toLowerCase().indexOf("https://chrome.google.com/webstore/") == 0) 50 | { 51 | document.getElementById("txtStatus").textContent = "Unfortunately, Chrome's Web Store pages cannot be analyzed."; 52 | // Bail out. 53 | return; 54 | } 55 | 56 | const lnkDomain = document.getElementById("lnkDomain"); 57 | lnkDomain.href = "https://dev.ssllabs.com/ssltest/analyze.html?d=" + escape(oUri.hostname); 58 | lnkDomain.textContent = (((sProt == "http:") || (sProt =="ftp:")) ? (sProt.slice(0,-1)+"/") : "") + oUri.hostname; 59 | 60 | { 61 | document.getElementById("txtStatus").textContent = "Analyzing top-level page"; 62 | 63 | // Mark top-level Page 64 | if (sProt == "https:") 65 | { 66 | document.getElementById("lnkDomain").classList.add("pageIsHTTPS"); 67 | } 68 | 69 | // If HTTP/HTTPS, use XHR to check for HSTS 70 | if ((sProt == "http:") || (sProt == "https:")) 71 | { 72 | // TODO: Switch XHR over to fetch() so that we're fully buzzword compliant. 73 | const oReq = new XMLHttpRequest(); 74 | oReq.addEventListener("load", function() { 75 | const sHSTS = oReq.getResponseHeader("Strict-Transport-Security"); 76 | const bHSTS = (sHSTS && sHSTS.includes("max-age=") && !sHSTS.includes("max-age=0")); 77 | const l = document.getElementById("lnkDomain"); 78 | if (sProt != "https:") { l.classList.add("pageCanUpgrade"); } 79 | if (bHSTS) { l.classList.add("pageIsHSTS"); l.classList.remove("pageIsHTTPS"); } 80 | 81 | const arrLI = htLinks[sOrigin]; 82 | markLIs(arrLI, true, bHSTS); 83 | }, false); 84 | const fnErr = function() { 85 | document.getElementById("lnkDomain").classList.add("pageCannotUpgrade"); 86 | const arrLI = htLinks[sOrigin]; 87 | markLIs(arrLI, false, false); 88 | }; 89 | oReq.addEventListener("error", fnErr, false); 90 | oReq.addEventListener("timeout", fnErr, false); 91 | oReq.open("HEAD", sOrigin, true); 92 | oReq.setRequestHeader("Cache-Control", "no-cache"); 93 | oReq.timeout = 5000; 94 | oReq.send(); 95 | } 96 | } 97 | 98 | document.getElementById("txtStatus").textContent = "Analyzing page elements"; 99 | 100 | 101 | chrome.scripting.insertCSS({ 102 | target: {tabId: activeTab.id, allFrames: true}, files: ["injected.css"] 103 | }); 104 | 105 | 106 | /* TODO REstore error handling after we learn how 107 | 108 | // If you try to inject into an extensions page or the webstore/NTP you'll get an error 109 | if (chrome.runtime.lastError) { 110 | console.log('moarTLS error injecting css : \n' + chrome.runtime.lastError.message); 111 | chrome.runtime.sendMessage(null, {"error": chrome.runtime.lastError.message, "context": "insertCSS"}); 112 | } 113 | });*/ 114 | 115 | 116 | // https://developer.chrome.com/docs/extensions/mv3/intro/mv3-migration/#cs-static-file 117 | chrome.scripting.executeScript({ 118 | target: {tabId: activeTab.id, allFrames: true}, files: ['injected.js'] 119 | }); 120 | 121 | /* TODO: Restore error handling after we learn how 122 | chrome.tabs.executeScript(null, {file:"injected.js", allFrames: true, runAt:"document_idle"}, function() { 123 | // If you try to inject into an extensions page or the webstore/NTP you'll get an error 124 | if (chrome.runtime.lastError) { 125 | console.log('moarTLS error injecting script : \n' + chrome.runtime.lastError.message); 126 | chrome.runtime.sendMessage(null, {"error": chrome.runtime.lastError.message, "context": "executeScript"}); 127 | } 128 | });*/ 129 | 130 | }); 131 | }, false); 132 | 133 | function copyForBug() 134 | { 135 | const copyFrom = document.createElement("textarea"); 136 | 137 | // TODO: Generate a proper report 138 | copyFrom.textContent = document.body.textContent; 139 | document.body.appendChild(copyFrom); 140 | copyFrom.focus(); 141 | copyFrom.select(); 142 | document.execCommand('Copy', false, null); 143 | copyFrom.remove(); 144 | const lnkCopyForBug = document.getElementById("lnkCopyForBug"); 145 | lnkCopyForBug.textContent = "copied!"; 146 | setTimeout(function() { lnkCopyForBug.innerHTML = "Copy"; }, 450); 147 | } 148 | 149 | function computeLinksDisplayString(cInsecure, cTotal) 150 | { 151 | if (cTotal < 1) return "This page does not contain any links."; 152 | if (cInsecure < 1) { 153 | if (cTotal == 1) return "The only link on this page is secure."; 154 | if (cTotal == 2) return "Both links on this page are secure."; 155 | return "All " + cTotal + " links on this page are secure."; 156 | } 157 | if (cInsecure == cTotal) { 158 | if (cTotal == 1) return "The only link on this page is non-secure."; 159 | if (cTotal == 2) return "Both links on this page are non-secure."; 160 | return "All " + cTotal + " links on this page are non-secure."; 161 | } 162 | let sResult = ("\n"+cInsecure + " of " + cTotal + " links " + ((cInsecure == 1) ? "is" : "are") + " non-secure."); 163 | return sResult; 164 | } 165 | 166 | function computeImagesDisplayString(cInsecureImages) 167 | { 168 | if (cInsecureImages < 1) return ""; //"This page does not contain any images."; 169 | let sResult = ((cInsecureImages == 1) ? "One image is" : (cInsecureImages + " images are")) + " non-secure."; 170 | return sResult; 171 | } 172 | 173 | // Update list UI based on HTTPS/HSTS availability 174 | function markLIs(arrLI, bHTTPS, bHSTS) 175 | { 176 | // No links yet 177 | if (!arrLI) { return; } 178 | for (let i=0; i < arrLI.length; i++) { 179 | if (arrLI[i].textContent.substring(0, 11) == "[Checking] ") { 180 | arrLI[i].textContent = arrLI[i].textContent.substring(11); 181 | } 182 | 183 | if (bHTTPS) { 184 | arrLI[i].classList.add("isHTTPSyes"); 185 | if (bHSTS) arrLI[i].classList.add("isHSTS"); 186 | arrLI[i].title = "This URL is available via HTTPS" + ((bHSTS) ? " + HSTS!" : "."); 187 | } 188 | else { 189 | arrLI[i].classList.add("isHTTPSno"); 190 | arrLI[i].title = "This URL is NOT available by simply changing the protocol to HTTPS."; 191 | } 192 | } 193 | } 194 | 195 | // Check a target for HTTPS/HSTS availability using XHR 196 | // TODO: Switch to FETCH and handle cases of redirections 197 | function checkForHTTPS(lnk) 198 | { 199 | if ((lnk.title.substring(0,11) == "This URL is") || 200 | (lnk.title.substring(0,11) == "[Checking] ")) return; 201 | 202 | const oUri = document.createElement("a"); 203 | oUri.href = lnk.textContent; 204 | // Wipe path entirely to prevent cases where e.g. a HEAD example.com/buy 205 | // isn't idempotent // if (oUri.pathname.includes("logout")) 206 | // TODO if we ever remove this: ensure proper Path encoding when calling oReq.open 207 | const sOrigin = "https://" + oUri.host +"/"; 208 | 209 | const arrLI = htLinks[sOrigin]; 210 | for (let i=0; i < arrLI.length; i++) 211 | { 212 | arrLI[i].textContent = "[Checking] " + arrLI[i].textContent; 213 | arrLI[i].title = "[Checking] Using XmlHttpRequest to check for a HTTPS version of this url..."; 214 | } 215 | 216 | const oReq = new XMLHttpRequest(); 217 | oReq.addEventListener("load", function() { 218 | const sHSTS = oReq.getResponseHeader("Strict-Transport-Security"); 219 | const bHSTS = (sHSTS && sHSTS.includes("max-age=") && !sHSTS.includes("max-age=0")); 220 | 221 | markLIs(arrLI, true, bHSTS); 222 | }, false); 223 | 224 | const fnErr = function() { markLIs(arrLI, false, false); }; 225 | 226 | oReq.addEventListener("error", fnErr, false); 227 | oReq.addEventListener("timeout", fnErr, false); 228 | 229 | oReq.open("HEAD", sOrigin, true); 230 | oReq.setRequestHeader("Cache-Control", "no-cache"); 231 | oReq.timeout = 5000; 232 | oReq.send(); 233 | } 234 | 235 | // Total number of elements evaluated in the page 236 | var cTotalLinks = 0; 237 | // Total number of non-secure elements in the page 238 | var cLinksUnsecure = 0; 239 | // Total number of non-secure images in the page 240 | var cImagesUnsecure = 0; 241 | // Hashtable mapping Origin->ListItem[] 242 | var htLinks = {}; 243 | 244 | chrome.runtime.onMessage.addListener( function(request, sender, sendResponse) { 245 | 246 | if (request.error) 247 | { 248 | let divError = document.createElement("div"); 249 | divError.className = "scanErrorMessage"; 250 | divError.textContent = "Error in " + request.context + ": " + request.error; 251 | document.body.appendChild(divError); 252 | return; 253 | } 254 | 255 | cTotalLinks += request.LinkCount || 0; 256 | cLinksUnsecure += (request.unsecure) ? request.unsecure.length : 0; 257 | cImagesUnsecure += (request.NonSecureImages) ? request.NonSecureImages.length : 0; 258 | 259 | const bAnyInsecure = (cLinksUnsecure + cImagesUnsecure > 0); 260 | 261 | document.getElementById("txtStatus").textContent = computeLinksDisplayString(cLinksUnsecure, cTotalLinks, cImagesUnsecure); 262 | document.getElementById("txtStatus2").textContent = computeImagesDisplayString(cImagesUnsecure); 263 | 264 | if (bAnyInsecure) { 265 | document.body.style.backgroundColor = "#FFFF40"; 266 | document.getElementById("lnkUnmark").style.display="inline"; 267 | document.getElementById("lnkCopyForBug").style.display="inline"; 268 | document.getElementById("lnkTips").style.display="inline"; 269 | } 270 | else 271 | { 272 | if (document.getElementById("lnkDomain").classList.contains("pageIsHTTPS") || 273 | document.getElementById("lnkDomain").classList.contains("pageIsHSTS")) 274 | { 275 | document.body.style.backgroundColor = "#68FF68"; 276 | } 277 | } 278 | 279 | let listUnsecure = document.getElementById("olUnsecureList"); 280 | if (!listUnsecure && bAnyInsecure) 281 | { 282 | listUnsecure = document.createElement("ol"); 283 | listUnsecure.id = "olUnsecureList"; 284 | document.getElementById("divUnsecureList").appendChild(listUnsecure); 285 | } 286 | 287 | for (let i=0; i < request.unsecure.length; i++) { 288 | const listItem = document.createElement("li"); 289 | const text = document.createTextNode(request.unsecure[i]); 290 | listItem.appendChild(text); 291 | 292 | const oUri = document.createElement("a"); 293 | oUri.href = request.unsecure[i]; 294 | const sOrigin = "https://" + oUri.host +"/"; 295 | if (undefined === htLinks[sOrigin]) 296 | { 297 | htLinks[sOrigin] = []; 298 | } 299 | htLinks[sOrigin].push(listItem); 300 | 301 | listItem.addEventListener('click', function(e) { 302 | 303 | if ((e.altKey || e.ctrlKey) || (1 == e.button)) 304 | { 305 | document.getElementById("lnkTips").style.display = "none"; 306 | checkForHTTPS(this); 307 | return; 308 | } 309 | 310 | }, false); 311 | listUnsecure.appendChild(listItem); 312 | } 313 | 314 | 315 | for (let i=0; i < request.NonSecureImages.length; i++) { 316 | const listItem = document.createElement("li"); 317 | const text = document.createTextNode(request.NonSecureImages[i]); 318 | listItem.appendChild(text); 319 | 320 | const oUri = document.createElement("a"); 321 | oUri.href = request.NonSecureImages[i]; 322 | const sOrigin = "https://" + oUri.host +"/"; 323 | if (undefined === htLinks[sOrigin]) 324 | { 325 | htLinks[sOrigin] = []; 326 | } 327 | htLinks[sOrigin].push(listItem); 328 | 329 | listItem.addEventListener('click', function(e) { 330 | 331 | if ((e.altKey || e.ctrlKey) || (1 == e.button)) 332 | { 333 | document.getElementById("lnkTips").style.display = "none"; 334 | checkForHTTPS(this); 335 | return; 336 | } 337 | 338 | }, false); 339 | listUnsecure.appendChild(listItem); 340 | } 341 | }); 342 | 343 | window.addEventListener('click', function(e) { 344 | if ((e.target.nodeName == "A") && (e.target.href !== undefined)) { 345 | chrome.tabs.create({ url: e.target.href }); 346 | } 347 | }); --------------------------------------------------------------------------------