Project

General

Profile

Download (4.29 KB) Statistics
| Branch: | Tag: | Revision:

haketilo / background / policy_injector.js @ 263d03d5

1
/**
2
 * This file is part of Haketilo.
3
 *
4
 * Function: Injecting policy to page by modifying HTTP headers.
5
 *
6
 * Copyright (C) 2021, Wojtek Kosior
7
 * Copyright (C) 2021, jahoti
8
 *
9
 * This program is free software: you can redistribute it and/or modify
10
 * it under the terms of the GNU General Public License as published by
11
 * the Free Software Foundation, either version 3 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU General Public License for more details.
18
 *
19
 * As additional permission under GNU GPL version 3 section 7, you
20
 * may distribute forms of that code without the copy of the GNU
21
 * GPL normally required by section 4, provided you include this
22
 * license notice and, in case of non-source distribution, a URL
23
 * through which recipients can access the Corresponding Source.
24
 * If you modify file(s) with this exception, you may extend this
25
 * exception to your version of the file(s), but you are not
26
 * obligated to do so. If you do not wish to do so, delete this
27
 * exception statement from your version.
28
 *
29
 * As a special exception to the GPL, any HTML file which merely
30
 * makes function calls to this code, and for that purpose
31
 * includes it by reference shall be deemed a separate work for
32
 * copyright law purposes. If you modify this code, you may extend
33
 * this exception to your version of the code, but you are not
34
 * obligated to do so. If you do not wish to do so, delete this
35
 * exception statement from your version.
36
 *
37
 * You should have received a copy of the GNU General Public License
38
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
39
 *
40
 *
41
 * I, Wojtek Kosior, thereby promise not to sue for violation of this file's
42
 * license. Although I request that you do not make use this code in a
43
 * proprietary program, I am not going to enforce this in court.
44
 */
45

    
46
/*
47
 * IMPORTS_START
48
 * IMPORT sign_data
49
 * IMPORT extract_signed
50
 * IMPORT make_csp_rule
51
 * IMPORT csp_header_regex
52
 * IMPORTS_END
53
 */
54

    
55
function inject_csp_headers(headers, policy)
56
{
57
    let csp_headers;
58
    let old_signature;
59
    let haketilo_header;
60

    
61
    for (const header of headers.filter(h => h.name === "x-haketilo")) {
62
	/* x-haketilo header has format: <signature>_0_<data> */
63
	const match = /^([^_]+)_(0_.*)$/.exec(header.value);
64
	if (!match)
65
	    continue;
66

    
67
	const result = extract_signed(...match.slice(1, 3));
68
	if (result.fail)
69
	    continue;
70

    
71
	/* This should succeed - it's our self-produced valid JSON. */
72
	const old_data = JSON.parse(decodeURIComponent(result.data));
73

    
74
	/* Confirmed- it's the originals, smuggled in! */
75
	csp_headers = old_data.csp_headers;
76
	old_signature = old_data.policy_sig;
77

    
78
	haketilo_header = header;
79
	break;
80
    }
81

    
82
    if (policy.has_payload) {
83
	csp_headers = [];
84
	const non_csp_headers = [];
85
	const header_list =
86
	      h => csp_header_regex.test(h) ? csp_headers : non_csp_headers;
87
	headers.forEach(h => header_list(h.name).push(h));
88
	headers = non_csp_headers;
89
    } else {
90
	headers.push(...csp_headers || []);
91
    }
92

    
93
    if (!haketilo_header) {
94
	haketilo_header = {name: "x-haketilo"};
95
	headers.push(haketilo_header);
96
    }
97

    
98
    if (old_signature)
99
	headers = headers.filter(h => h.value.search(old_signature) === -1);
100

    
101
    const policy_str = encodeURIComponent(JSON.stringify(policy));
102
    const signed_policy = sign_data(policy_str, new Date().getTime());
103
    const later_30sec = new Date(new Date().getTime() + 30000).toGMTString();
104
    headers.push({
105
	name: "Set-Cookie",
106
	value: `haketilo-${signed_policy.join("=")}; Expires=${later_30sec};`
107
    });
108

    
109
    /*
110
     * Smuggle in the signature and the original CSP headers for future use.
111
     * These are signed with a time of 0, as it's not clear there is a limit on
112
     * how long Firefox might retain headers in the cache.
113
     */
114
    let haketilo_data = {csp_headers, policy_sig: signed_policy[0]};
115
    haketilo_data = encodeURIComponent(JSON.stringify(haketilo_data));
116
    haketilo_header.value = sign_data(haketilo_data, 0).join("_");
117

    
118
    if (!policy.allow) {
119
	headers.push({
120
	    name: "content-security-policy",
121
	    value: make_csp_rule(policy)
122
	});
123
    }
124

    
125
    return headers;
126
}
127

    
128
/*
129
 * EXPORTS_START
130
 * EXPORT inject_csp_headers
131
 * EXPORTS_END
132
 */
(4-4/7)