From dcfc78b0d175bee7b3b7e273282078d50bd4ca09 Mon Sep 17 00:00:00 2001 From: jahoti Date: Mon, 12 Jul 2021 00:00:00 +0000 Subject: Stop using the nonce consistently for a URL Nonces are now randomly generated, either in the page (for non-HTTP(S) pages) or by a background module which stores them by tab and frame IDs. In order to support the increased variance in nonce-generating methods and allow them to be loaded from the background, handle_page_actions is now invoked separately according to (non-)blocking mechanism. --- background/nonce_store.js | 30 ++++++++++++++++++++++++++++++ background/page_actions_server.js | 2 ++ background/policy_injector.js | 4 ++-- common/misc.js | 19 +++++++++++++++++++ content/main.js | 21 +++++++++++++++++---- content/page_actions.js | 4 +--- copyright | 6 +++++- 7 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 background/nonce_store.js diff --git a/background/nonce_store.js b/background/nonce_store.js new file mode 100644 index 0000000..9370876 --- /dev/null +++ b/background/nonce_store.js @@ -0,0 +1,30 @@ +/** + * Central management of HTTP(S) nonces + * + * Copyright (C) 2021 jahoti + * Redistribution terms are gathered in the `copyright' file. + */ + +/* + * IMPORTS_START + * IMPORT gen_nonce + * IMPORTS_END + */ + +var nonces = {}; + +function retrieve_nonce(tabId, frameId, update) +{ + let code = tabId + '.' + frameId; + console.log('Nonce for ' + code + ' ' + (update ? 'created/updated' : 'requested')); + if (update) + nonces[code] = gen_nonce(); + + return nonces[code]; +} + +/* + * EXPORTS_START + * EXPORT retrieve_nonce + * EXPORTS_END + */ diff --git a/background/page_actions_server.js b/background/page_actions_server.js index 2d9c333..d92b870 100644 --- a/background/page_actions_server.js +++ b/background/page_actions_server.js @@ -11,6 +11,7 @@ * IMPORT TYPE_PREFIX * IMPORT CONNECTION_TYPE * IMPORT browser + * IMPORT retrieve_nonce * IMPORT listen_for_connection * IMPORT sha256 * IMPORT get_query_best @@ -137,6 +138,7 @@ function handle_message(port, message, handler) function new_connection(port) { console.log("new page actions connection!"); + port.postMessage(['nonce', retrieve_nonce((port.sender.tab || '').id, port.sender.frameId)]); let handler = []; handler.push(m => handle_message(port, m, handler)); port.onMessage.addListener(handler[0]); diff --git a/background/policy_injector.js b/background/policy_injector.js index eb67963..9f79425 100644 --- a/background/policy_injector.js +++ b/background/policy_injector.js @@ -11,7 +11,7 @@ * IMPORT get_storage * IMPORT browser * IMPORT is_chrome - * IMPORT gen_unique + * IMPORT retrieve_nonce * IMPORT url_item * IMPORT get_query_best * IMPORT csp_rule @@ -45,7 +45,7 @@ function inject(details) const [pattern, settings] = query_best(url); - const nonce = gen_unique(url); + const nonce = retrieve_nonce(details.tabId, details.frameId, true); const rule = csp_rule(nonce); var headers; diff --git a/common/misc.js b/common/misc.js index 8b56e79..7e48059 100644 --- a/common/misc.js +++ b/common/misc.js @@ -2,6 +2,7 @@ * Myext miscellaneous operations refactored to a separate file * * Copyright (C) 2021 Wojtek Kosior + * Copyright (C) 2021 jahoti * Redistribution terms are gathered in the `copyright' file. */ @@ -18,6 +19,23 @@ * generating unique, per-site value that can be computed synchronously * and is impossible to guess for a malicious website */ + +/* Uint8toHex is a separate function not exported as (a) it's useful and (b) it will be used in crypto.subtle-based digests */ +function Uint8toHex(data) +{ + let returnValue = ''; + for (let byte of data) + returnValue += ('00' + byte.toString(16)).slice(-2); + return returnValue; +} + +function gen_nonce(length) // Default 16 +{ + let randomData = new Uint8Array(length || 16); + crypto.getRandomValues(randomData); + return Uint8toHex(randomData); +} + function gen_unique(url) { return sha256(get_secure_salt() + url); @@ -98,6 +116,7 @@ function is_privileged_url(url) /* * EXPORTS_START * EXPORT gen_unique + * EXPORT gen_nonce * EXPORT url_item * EXPORT url_extract_target * EXPORT csp_rule diff --git a/content/main.js b/content/main.js index 9acf749..3204a8a 100644 --- a/content/main.js +++ b/content/main.js @@ -2,15 +2,18 @@ * Myext main content script run in all frames * * Copyright (C) 2021 Wojtek Kosior + * Copyright (C) 2021 jahoti * Redistribution terms are gathered in the `copyright' file. */ /* * IMPORTS_START + * IMPORT CONNECTION_TYPE * IMPORT handle_page_actions * IMPORT url_item * IMPORT url_extract_target * IMPORT gen_unique + * IMPORT gen_nonce * IMPORT csp_rule * IMPORT is_privileged_url * IMPORT sanitize_attributes @@ -113,7 +116,7 @@ function inject_csp(head) let meta = document.createElement("meta"); meta.setAttribute("http-equiv", "Content-Security-Policy"); - meta.setAttribute("content", csp_rule(unique)); + meta.setAttribute("content", csp_rule(nonce)); if (head.firstElementChild === null) head.appendChild(meta); @@ -123,13 +126,23 @@ function inject_csp(head) if (!is_privileged_url(document.URL)) { start_activity_info_server(); - handle_page_actions(unique); + var nonce, port = browser.runtime.connect({name : CONNECTION_TYPE.PAGE_ACTIONS}); if (is_http()) { - /* rely on CSP injected through webRequest */ + /* rely on CSP injected through webRequest, at the cost of having to fetch a nonce via messaging */ + const nonce_capturer = msg => { + port.onMessage.removeListener(nonce_capturer); + handle_page_actions(msg[1], port); + }; + + port.onMessage.addListener(nonce_capturer); + } else if (is_whitelisted()) { - /* do not block scripts at all */ + /* do not block scripts at all; as a result, there is no need for a green-lighted nonce */ + handle_page_actions(null, port); } else { + nonce = gen_nonce(); + handle_page_actions(nonce, port); block_nodes_recursively(document.documentElement); if (is_chrome) { diff --git a/content/page_actions.js b/content/page_actions.js index fd405fe..dff5f71 100644 --- a/content/page_actions.js +++ b/content/page_actions.js @@ -7,7 +7,6 @@ /* * IMPORTS_START - * IMPORT CONNECTION_TYPE * IMPORT browser * IMPORT report_script * IMPORT report_settings @@ -55,9 +54,8 @@ function add_script(script_text) report_script(script_text); } -function handle_page_actions(script_nonce) { +function handle_page_actions(script_nonce, port) { // Add port as an argument so we can "pre-receive" a nonce in main.js document.addEventListener("DOMContentLoaded", document_loaded); - port = browser.runtime.connect({name : CONNECTION_TYPE.PAGE_ACTIONS}); port.onMessage.addListener(handle_message); port.postMessage({url: document.URL}); diff --git a/copyright b/copyright index a01c1fe..93e241b 100644 --- a/copyright +++ b/copyright @@ -10,7 +10,7 @@ Files: build.sh Copyright: 2021 Wojtek Kosior License: CC0 -Files: manifest.json +Files: manifest.json common/misc.js content/main.js Copyright: 2021 Wojtek Kosior 2021 jahoti License: GPL-3+-javascript or Alicense-1.0 @@ -46,6 +46,10 @@ License: Expat OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Files: background/nonce_store.js +Copyright: 2021 jahoti +License: GPL-3+-javascript or Alicense-1.0 + Files: content/freezer.js Copyright: 2005-2021 Giorgio Maone - https://maone.net 2021 jahoti -- cgit v1.2.3