summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWojtek Kosior <wk@koszkonutek-tmp.pl.eu.org>2021-06-18 11:45:01 +0200
committerWojtek Kosior <wk@koszkonutek-tmp.pl.eu.org>2021-06-18 11:45:01 +0200
commit7ee7889ae8f1473474254553ec3b3469fb0a935b (patch)
tree153fe596bc65600e21d856f97231f8195f79b9ec
parent6bae771df7b238f8ef4e992660e911fb5808299c (diff)
downloadbrowser-extension-7ee7889ae8f1473474254553ec3b3469fb0a935b.tar.gz
browser-extension-7ee7889ae8f1473474254553ec3b3469fb0a935b.zip
when possible inject CSP as http(s) header using webRequest instead of adding a <meta> tag
-rw-r--r--TODOS.org11
-rw-r--r--background/background.html2
-rw-r--r--background/main.js4
-rw-r--r--background/policy_injector.js93
-rw-r--r--content/main.js54
5 files changed, 139 insertions, 25 deletions
diff --git a/TODOS.org b/TODOS.org
index 50a96a8..f53efbe 100644
--- a/TODOS.org
+++ b/TODOS.org
@@ -24,7 +24,7 @@ TODO:
- test with more browser forks (Abrowser, Parabola IceWeasel, LibreWolf)
- also see if browsers based on pre-quantum FF support enough of
WebExtensions for easy porting
-- make sure page's own csp doesn't block our scripts
+- make sure page's own csp in <head> doesn't block our scripts
- make blocking more torough -- CRUCIAL
- mind the data: urls -- CRUCIAL
- find out how and make it possible to whitelist non-https urls and
@@ -39,8 +39,13 @@ TODO:
- all solutions to modularize js code SUCK; come up with own simple DSL
to manage imports/exports
- perform never-ending refactoring of already-written code
-- when redirecting to target, make it possible to smartly recognize
- and remove previous added target
+- also implement support for whitelisting of non-https urls
+- validate data entered in settings
+- stop always using the same script nonce on given https(s) site (this
+ improvement seems to be unachievable in case of other protocols)
+- besides blocking scripts through csp, also block connections that needlessly
+ fetch those scripts
+- make extension's all html files proper XHTML
DONE:
- make it possible to use wildcard urls in settings -- DONE 2021-05-14
diff --git a/background/background.html b/background/background.html
index b3e8010..53a74e9 100644
--- a/background/background.html
+++ b/background/background.html
@@ -37,7 +37,7 @@
<script src="./settings_query.js"></script>
<script src="./page_actions_server.js"></script>
<script src="/common/gen_unique.js"></script>
- <script src="./policy_smuggler.js"></script>
+ <script src="./policy_injector.js"></script>
<script src="./main.js"></script>
</head>
</html>
diff --git a/background/main.js b/background/main.js
index 4af7aa0..2f35321 100644
--- a/background/main.js
+++ b/background/main.js
@@ -30,12 +30,12 @@
const get_storage = window.get_storage;
const start_storage_server = window.start_storage_server;
const start_page_actions_server = window.start_page_actions_server;
- const start_policy_smuggler = window.start_policy_smuggler;
+ const start_policy_injector = window.start_policy_injector;
const browser = window.browser;
start_storage_server();
start_page_actions_server();
- start_policy_smuggler();
+ start_policy_injector();
async function init_myext(install_details)
{
diff --git a/background/policy_injector.js b/background/policy_injector.js
new file mode 100644
index 0000000..e2d6358
--- /dev/null
+++ b/background/policy_injector.js
@@ -0,0 +1,93 @@
+/**
+ * Myext injecting policy to page using webRequest
+ *
+ * Copyright (C) 2021 Wojtek Kosior
+ *
+ * This code is dual-licensed under:
+ * - Asshole license 1.0,
+ * - GPLv3 or (at your option) any later version
+ *
+ * "dual-licensed" means you can choose the license you prefer.
+ *
+ * This code is released under a permissive license because I disapprove of
+ * copyright and wouldn't be willing to sue a violator. Despite not putting
+ * this code under copyleft (which is also kind of copyright), I do not want
+ * it to be made proprietary. Hence, the permissive alternative to GPL is the
+ * Asshole license 1.0 that allows me to call you an asshole if you use it.
+ * This means you're legally ok regardless of how you utilize this code but if
+ * you make it into something nonfree, you're an asshole.
+ *
+ * You should have received a copy of both GPLv3 and Asshole license 1.0
+ * together with this code. If not, please see:
+ * - https://www.gnu.org/licenses/gpl-3.0.en.html
+ * - https://koszko.org/asshole-license.txt
+ */
+
+"use strict";
+
+(() => {
+ const TYPE_PREFIX = window.TYPE_PREFIX;
+ const get_storage = window.get_storage;
+ const browser = window.browser;
+ const is_chrome = window.is_chrome;
+ const gen_unique = window.gen_unique;
+ const url_item = window.url_item;
+ const get_query_best = window.get_query_best;
+
+ var storage;
+ var query_best;
+
+ let csp_header_names = {
+ "content-security-policy" : true,
+ "x-webkit-csp" : true,
+ "x-content-security-policy" : true
+ };
+
+ function is_noncsp_header(header)
+ {
+ return !csp_header_names[header.name.toLowerCase()];
+ }
+
+ function inject(details)
+ {
+ let url = url_item(details.url);
+
+ let [pattern, settings] = query_best(url);
+
+ if (settings !== undefined && settings.allow) {
+ console.log("allowing", url);
+ return {cancel : false};
+ }
+
+ let nonce = gen_unique(url).substring(1);
+ let headers = details.responseHeaders.filter(is_noncsp_header);
+ headers.push({
+ name : "content-security-policy",
+ value : `script-src 'nonce-${nonce}'; script-src-elem 'nonce-${nonce}';`
+ });
+
+ console.log("modified headers", url, headers);
+
+ return {responseHeaders: headers};
+ }
+
+ async function start() {
+ storage = await get_storage();
+ query_best = await get_query_best();
+
+ let extra_opts = ["blocking", "responseHeaders"];
+ if (is_chrome)
+ extra_opts.push("extraHeaders");
+
+ browser.webRequest.onHeadersReceived.addListener(
+ inject,
+ {
+ urls: ["<all_urls>"],
+ types: ["main_frame", "sub_frame"]
+ },
+ extra_opts
+ );
+ }
+
+ window.start_policy_injector = start;
+})();
diff --git a/content/main.js b/content/main.js
index 23f7f66..eb5d0ac 100644
--- a/content/main.js
+++ b/content/main.js
@@ -30,29 +30,45 @@
const url_item = window.url_item;
const gen_unique = window.gen_unique;
- var url_re = /^([^#]*)((#[^#]*)(#.*)?)?$/;
- var match = url_re.exec(document.URL);
- var base_url = match[1];
- var first_target = match[3];
- var second_target = match[4];
+ /*
+ * Due to some technical limitations the chosen method of whitelisting sites
+ * is to smuggle whitelist indicator in page's url as a "magical" string
+ * after '#'. Right now this is not needed in HTTP(s) pages where native
+ * script blocking happens through CSP header injection but is needed for
+ * protocols like ftp:// and file://.
+ *
+ * The code that actually injects the magical string into ftp:// and file://
+ * urls has not yet been added to the extension.
+ */
- // TODO: can be refactored *a little bit* with policy_smuggler.js
let url = url_item(document.URL);
let unique = gen_unique(url);
-
let nonce = unique.substring(1);
- var block = true;
- if (first_target !== undefined &&
- first_target === unique) {
- block = false;
- console.log(["allowing", document.URL]);
- if (second_target !== undefined)
- window.location.href = base_url + second_target;
- else
- history.replaceState(null, "", base_url);
- } else {
- console.log(["not allowing", document.URL]);
+ function needs_blocking()
+ {
+ if (url.startsWith("https://") || url.startsWith("http://"))
+ return false;
+
+ let url_re = /^([^#]*)((#[^#]*)(#.*)?)?$/;
+ let match = url_re.exec(document.URL);
+ let base_url = match[1];
+ let first_target = match[3];
+ let second_target = match[4];
+
+ if (first_target !== undefined &&
+ first_target === unique) {
+ if (second_target !== undefined)
+ window.location.href = base_url + second_target;
+ else
+ history.replaceState(null, "", base_url);
+
+ console.log(["allowing whitelisted", document.URL]);
+ return false;
+ }
+
+ console.log(["disallowing", document.URL]);
+ return true;
}
function handle_mutation(mutations, observer)
@@ -129,7 +145,7 @@ script-src-elem 'nonce-${nonce}';\
}
}
- if (block) {
+ if (needs_blocking()) {
var observer = new MutationObserver(handle_mutation);
observer.observe(document.documentElement, {
attributes: true,