Project

General

Profile

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

haketilo / background / policy_injector.js @ 2875397f

1
/**
2
 * Hachette injecting policy to page using webRequest
3
 *
4
 * Copyright (C) 2021 Wojtek Kosior
5
 * Copyright (C) 2021 jahoti
6
 * Redistribution terms are gathered in the `copyright' file.
7
 */
8

    
9
/*
10
 * IMPORTS_START
11
 * IMPORT sign_data
12
 * IMPORT extract_signed
13
 * IMPORT sanitize_csp_header
14
 * IMPORT csp_rule
15
 * IMPORT is_csp_header_name
16
 * IMPORTS_END
17
 */
18

    
19
function inject_csp_headers(headers, policy)
20
{
21
    let csp_headers;
22
    let old_signature;
23
    let hachette_header;
24

    
25
    for (const header of headers.filter(h => h.name === "x-hachette")) {
26
	/* x-hachette header has format: <signature>_0_<data> */
27
	const match = /^([^_]+)_(0_.*)$/.exec(header.value);
28
	if (!match)
29
	    continue;
30

    
31
	const result = extract_signed(...match.slice(1, 3));
32
	if (result.fail)
33
	    continue;
34

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

    
38
	/* Confirmed- it's the originals, smuggled in! */
39
	csp_headers = old_data.csp_headers;
40
	old_signature = old_data.policy_sig;
41

    
42
	hachette_header = header;
43
	break;
44
    }
45

    
46
    if (!hachette_header) {
47
	hachette_header = {name: "x-hachette"};
48
	headers.push(hachette_header);
49
    }
50

    
51
    csp_headers = csp_headers ||
52
	headers.filter(h => is_csp_header_name(h.name));
53

    
54
    /* When blocking remove report-only CSP headers that snitch on us. */
55
    headers = headers.filter(h => !is_csp_header_name(h.name, !policy.allow));
56

    
57
    if (old_signature)
58
	headers = headers.filter(h => h.value.search(old_signature) === -1);
59

    
60
    headers.push(...csp_headers.map(h => sanitize_csp_header(h, policy)));
61

    
62
    const policy_str = encodeURIComponent(JSON.stringify(policy));
63
    const signed_policy = sign_data(policy_str, new Date().getTime());
64
    const later_30sec = new Date(new Date().getTime() + 30000).toGMTString();
65
    headers.push({
66
	name: "Set-Cookie",
67
	value: `hachette-${signed_policy.join("=")}; Expires=${later_30sec};`
68
    });
69

    
70
    /*
71
     * Smuggle in the signature and the original CSP headers for future use.
72
     * These are signed with a time of 0, as it's not clear there is a limit on
73
     * how long Firefox might retain headers in the cache.
74
     */
75
    let hachette_data = {csp_headers, policy_sig: signed_policy[0]};
76
    hachette_data = encodeURIComponent(JSON.stringify(hachette_data));
77
    hachette_header.value = sign_data(hachette_data, 0).join("_");
78

    
79
    /* To ensure there is a CSP header if required */
80
    if (!policy.allow)
81
	headers.push({
82
	    name: "content-security-policy",
83
	    value: csp_rule(policy.nonce)
84
	});
85

    
86
    return headers;
87
}
88

    
89
/*
90
 * EXPORTS_START
91
 * EXPORT inject_csp_headers
92
 * EXPORTS_END
93
 */
(3-3/6)