Project

General

Profile

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

haketilo / common / misc.js @ 792fbe18

1
/**
2
 * Hachette miscellaneous operations refactored to a separate file
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 sha256
12
 * IMPORT browser
13
 * IMPORT is_chrome
14
 * IMPORT TYPE_NAME
15
 * IMPORT TYPE_PREFIX
16
 * IMPORTS_END
17
 */
18

    
19
/* Generate a random base64-encoded 128-bit sequence */
20
function gen_nonce()
21
{
22
    let randomData = new Uint8Array(16);
23
    crypto.getRandomValues(randomData);
24
    return btoa(String.fromCharCode.apply(null, randomData));
25
}
26

    
27
/*
28
 * generating unique, per-site value that can be computed synchronously
29
 * and is impossible to guess for a malicious website
30
 */
31

    
32
/* Uint8toHex is a separate function not exported as (a) it's useful and (b) it will be used in crypto.subtle-based digests */
33
function Uint8toHex(data)
34
{
35
    let returnValue = '';
36
    for (let byte of data)
37
	returnValue += ('00' + byte.toString(16)).slice(-2);
38
    return returnValue;
39
}
40

    
41
function gen_nonce(length) // Default 16
42
{
43
    let randomData = new Uint8Array(length || 16);
44
    crypto.getRandomValues(randomData);
45
    return Uint8toHex(randomData);
46
}
47

    
48
function gen_unique(url)
49
{
50
    return sha256(get_secure_salt() + url);
51
}
52

    
53
function get_secure_salt()
54
{
55
    if (is_chrome)
56
	return browser.runtime.getManifest().key.substring(0, 50);
57
    else
58
	return browser.runtime.getURL("dummy");
59
}
60

    
61
/*
62
 * stripping url from query and target (everything after `#' or `?'
63
 * gets removed)
64
 */
65
function url_item(url)
66
{
67
    let url_re = /^([^?#]*).*$/;
68
    let match = url_re.exec(url);
69
    return match[1];
70
}
71

    
72
/*
73
 * Assume a url like:
74
 *     https://example.com/green?illuminati=confirmed#<injected-policy>#winky
75
 * This function will make it into an object like:
76
 * {
77
 *     "base_url": "https://example.com/green?illuminati=confirmed",
78
 *     "target":   "#<injected-policy>",
79
 *     "target2":  "#winky",
80
 *     "policy":   <injected-policy-as-js-object>,
81
 *     "current":  <boolean-indicating-whether-policy-url-matches>
82
 * }
83
 * In case url doesn't have 2 #'s, target2 and target can be set to undefined.
84
 */
85
function url_extract_target(url)
86
{
87
    const url_re = /^([^#]*)((#[^#]*)(#.*)?)?$/;
88
    const match = url_re.exec(url);
89
    const targets  = {
90
	base_url: match[1],
91
	target:   match[3] || "",
92
	target2:  match[4] || ""
93
    };
94
    if (!targets.target)
95
	return targets;
96

    
97
    /* %7B -> { */
98
    const index = targets.target.indexOf('%7B');
99
    if (index === -1)
100
	return targets;
101

    
102
    const now = new Date();
103
    const sig = targets.target.substring(1, index);
104
    const policy = targets.target.substring(index);
105
    if (sig !== sign_policy(policy, now) &&
106
	sig !== sign_policy(policy, now, -1))
107
	return targets;
108

    
109
    try {
110
	targets.policy = JSON.parse(decodeURIComponent(policy));
111
	targets.current = targets.policy.base_url === targets.base_url;
112
    } catch (e) {
113
	/* This should not be reached - it's our self-produced valid JSON. */
114
	console.log("Unexpected internal error - invalid JSON smuggled!", e);
115
    }
116

    
117
    return targets;
118
}
119

    
120
/* csp rule that blocks all scripts except for those injected by us */
121
function csp_rule(nonce)
122
{
123
    let rule = `script-src 'nonce-${nonce}';`;
124
    if (is_chrome)
125
	rule += `script-src-elem 'nonce-${nonce}';`;
126
    return rule;
127
}
128

    
129
/*
130
 * Print item together with type, e.g.
131
 * nice_name("s", "hello") → "hello (script)"
132
 */
133
function nice_name(prefix, name)
134
{
135
    return `${name} (${TYPE_NAME[prefix]})`;
136
}
137

    
138
/* Open settings tab with given item's editing already on. */
139
function open_in_settings(prefix, name)
140
{
141
    name = encodeURIComponent(name);
142
    const url = browser.runtime.getURL("html/options.html#" + prefix + name);
143
    window.open(url, "_blank");
144
}
145

    
146
/* Check if url corresponds to a browser's special page */
147
function is_privileged_url(url)
148
{
149
    return !!/^(chrome(-extension)?|moz-extension):\/\/|^about:/i.exec(url);
150
}
151

    
152
/* Sign a given policy for a given time */
153
function sign_policy(policy, now, hours_offset) {
154
    let time = Math.floor(now / 3600000) + (hours_offset || 0);
155
    return gen_unique(time + policy);
156
}
157

    
158
/* Regexes and objest to use as/in schemas for parse_json_with_schema(). */
159
const nonempty_string_matcher = /.+/;
160

    
161
const matchers = {
162
    sha256: /^[0-9a-f]{64}$/,
163
    nonempty_string: nonempty_string_matcher,
164
    component: [
165
	new RegExp(`^[${TYPE_PREFIX.SCRIPT}${TYPE_PREFIX.BAG}]$`),
166
	nonempty_string_matcher
167
    ]
168
};
169

    
170
/*
171
 * EXPORTS_START
172
 * EXPORT gen_nonce
173
 * EXPORT gen_unique
174
 * EXPORT url_item
175
 * EXPORT url_extract_target
176
 * EXPORT sign_policy
177
 * EXPORT csp_rule
178
 * EXPORT nice_name
179
 * EXPORT open_in_settings
180
 * EXPORT is_privileged_url
181
 * EXPORT matchers
182
 * EXPORTS_END
183
 */
(5-5/13)