Project

General

Profile

« Previous | Next » 

Revision 7ee7889a

Added by Wojtek Kosior about 2 years ago

when possible inject CSP as http(s) header using webRequest instead of adding a tag

View differences:

TODOS.org
24 24
- test with more browser forks (Abrowser, Parabola IceWeasel, LibreWolf)
25 25
  - also see if browsers based on pre-quantum FF support enough of
26 26
    WebExtensions for easy porting
27
- make sure page's own csp doesn't block our scripts
27
- make sure page's own csp in <head> doesn't block our scripts
28 28
- make blocking more torough -- CRUCIAL
29 29
  - mind the data: urls -- CRUCIAL
30 30
- find out how and make it possible to whitelist non-https urls and
......
39 39
- all solutions to modularize js code SUCK; come up with own simple DSL
40 40
  to manage imports/exports
41 41
- perform never-ending refactoring of already-written code
42
- when redirecting to target, make it possible to smartly recognize
43
  and remove previous added target
42
- also implement support for whitelisting of non-https urls
43
- validate data entered in settings
44
- stop always using the same script nonce on given https(s) site (this
45
  improvement seems to be unachievable in case of other protocols)
46
- besides blocking scripts through csp, also block connections that needlessly
47
  fetch those scripts
48
- make extension's all html files proper XHTML
44 49

  
45 50
DONE:
46 51
- make it possible to use wildcard urls in settings -- DONE 2021-05-14
background/background.html
37 37
    <script src="./settings_query.js"></script>
38 38
    <script src="./page_actions_server.js"></script>
39 39
    <script src="/common/gen_unique.js"></script>
40
    <script src="./policy_smuggler.js"></script>
40
    <script src="./policy_injector.js"></script>
41 41
    <script src="./main.js"></script>
42 42
  </head>
43 43
</html>
background/main.js
30 30
    const get_storage = window.get_storage;
31 31
    const start_storage_server = window.start_storage_server;
32 32
    const start_page_actions_server = window.start_page_actions_server;
33
    const start_policy_smuggler = window.start_policy_smuggler;
33
    const start_policy_injector = window.start_policy_injector;
34 34
    const browser = window.browser;
35 35

  
36 36
    start_storage_server();
37 37
    start_page_actions_server();
38
    start_policy_smuggler();
38
    start_policy_injector();
39 39

  
40 40
    async function init_myext(install_details)
41 41
    {
background/policy_injector.js
1
/**
2
 * Myext injecting policy to page using webRequest
3
 *
4
 * Copyright (C) 2021 Wojtek Kosior
5
 *
6
 * This code is dual-licensed under:
7
 * - Asshole license 1.0,
8
 * - GPLv3 or (at your option) any later version
9
 *
10
 * "dual-licensed" means you can choose the license you prefer.
11
 *
12
 * This code is released under a permissive license because I disapprove of
13
 * copyright and wouldn't be willing to sue a violator. Despite not putting
14
 * this code under copyleft (which is also kind of copyright), I do not want
15
 * it to be made proprietary. Hence, the permissive alternative to GPL is the
16
 * Asshole license 1.0 that allows me to call you an asshole if you use it.
17
 * This means you're legally ok regardless of how you utilize this code but if
18
 * you make it into something nonfree, you're an asshole.
19
 *
20
 * You should have received a copy of both GPLv3 and Asshole license 1.0
21
 * together with this code. If not, please see:
22
 * - https://www.gnu.org/licenses/gpl-3.0.en.html
23
 * - https://koszko.org/asshole-license.txt
24
 */
25

  
26
"use strict";
27

  
28
(() => {
29
    const TYPE_PREFIX = window.TYPE_PREFIX;
30
    const get_storage = window.get_storage;
31
    const browser = window.browser;
32
    const is_chrome = window.is_chrome;
33
    const gen_unique = window.gen_unique;
34
    const url_item = window.url_item;
35
    const get_query_best = window.get_query_best;
36

  
37
    var storage;
38
    var query_best;
39

  
40
    let csp_header_names = {
41
	"content-security-policy" : true,
42
	"x-webkit-csp" : true,
43
	"x-content-security-policy" : true
44
    };
45

  
46
    function is_noncsp_header(header)
47
    {
48
	return !csp_header_names[header.name.toLowerCase()];
49
    }
50

  
51
    function inject(details)
52
    {
53
	let url = url_item(details.url);
54

  
55
	let [pattern, settings] = query_best(url);
56

  
57
	if (settings !== undefined && settings.allow) {
58
	    console.log("allowing", url);
59
	    return {cancel : false};
60
	}
61

  
62
	let nonce = gen_unique(url).substring(1);
63
	let headers = details.responseHeaders.filter(is_noncsp_header);
64
	headers.push({
65
	    name : "content-security-policy",
66
	    value : `script-src 'nonce-${nonce}'; script-src-elem 'nonce-${nonce}';`
67
	});
68

  
69
	console.log("modified headers", url, headers);
70

  
71
	return {responseHeaders: headers};
72
    }
73

  
74
    async function start() {
75
	storage = await get_storage();
76
	query_best = await get_query_best();
77

  
78
	let extra_opts = ["blocking", "responseHeaders"];
79
	if (is_chrome)
80
	    extra_opts.push("extraHeaders");
81

  
82
	browser.webRequest.onHeadersReceived.addListener(
83
	    inject,
84
	    {
85
		urls: ["<all_urls>"],
86
		types: ["main_frame", "sub_frame"]
87
	    },
88
	    extra_opts
89
	);
90
    }
91

  
92
    window.start_policy_injector = start;
93
})();
content/main.js
30 30
    const url_item = window.url_item;
31 31
    const gen_unique = window.gen_unique;
32 32

  
33
    var url_re = /^([^#]*)((#[^#]*)(#.*)?)?$/;
34
    var match = url_re.exec(document.URL);
35
    var base_url = match[1];
36
    var first_target = match[3];
37
    var second_target = match[4];
33
    /*
34
     * Due to some technical limitations the chosen method of whitelisting sites
35
     * is to smuggle whitelist indicator in page's url as a "magical" string
36
     * after '#'. Right now this is not needed in HTTP(s) pages where native
37
     * script blocking happens through CSP header injection but is needed for
38
     * protocols like ftp:// and file://.
39
     *
40
     * The code that actually injects the magical string into ftp:// and file://
41
     * urls has not yet been added to the extension.
42
     */
38 43

  
39
    // TODO: can be refactored *a little bit* with policy_smuggler.js
40 44
    let url = url_item(document.URL);
41 45
    let unique = gen_unique(url);
42

  
43 46
    let nonce = unique.substring(1);
44 47

  
45
    var block = true;
46
    if (first_target !== undefined &&
47
	first_target === unique) {
48
	block = false;
49
	console.log(["allowing", document.URL]);
50
	if (second_target !== undefined)
51
	    window.location.href = base_url + second_target;
52
	else
53
	    history.replaceState(null, "", base_url);
54
    } else {
55
	console.log(["not allowing", document.URL]);
48
    function needs_blocking()
49
    {
50
	if (url.startsWith("https://") || url.startsWith("http://"))
51
	    return false;
52

  
53
	let url_re = /^([^#]*)((#[^#]*)(#.*)?)?$/;
54
	let match = url_re.exec(document.URL);
55
	let base_url = match[1];
56
	let first_target = match[3];
57
	let second_target = match[4];
58

  
59
	if (first_target !== undefined &&
60
	    first_target === unique) {
61
	    if (second_target !== undefined)
62
		window.location.href = base_url + second_target;
63
	    else
64
		history.replaceState(null, "", base_url);
65

  
66
	    console.log(["allowing whitelisted", document.URL]);
67
	    return false;
68
	}
69

  
70
	console.log(["disallowing", document.URL]);
71
	return true;
56 72
    }
57 73

  
58 74
    function handle_mutation(mutations, observer)
......
129 145
	}
130 146
    }
131 147

  
132
    if (block) {
148
    if (needs_blocking()) {
133 149
	var observer = new MutationObserver(handle_mutation);
134 150
	observer.observe(document.documentElement, {
135 151
	    attributes: true,

Also available in: Unified diff