summaryrefslogtreecommitdiff
path: root/data/extensions/https-everywhere@eff.org/background-scripts/update.js
diff options
context:
space:
mode:
authorClément Lassieur <clement@lassieur.org>2023-11-09 14:15:44 +0100
committerMark H Weaver <mhw@netris.org>2023-12-30 09:24:46 -0500
commitba0d2ab758143b9fe2ca14f6eed07d9a6a350c2b (patch)
tree0ecd8a30389110b49a8a1c1513332dbacfcb46f9 /data/extensions/https-everywhere@eff.org/background-scripts/update.js
parentf889514426e512e5602c71e1b411ae0332a33366 (diff)
Migrate from HTTPS-Everywhere to Icecat's own HTTPS-Only Mode.
See <https://www.eff.org/https-everywhere>. Modified-By: Mark H Weaver <mhw@netris.org>.
Diffstat (limited to 'data/extensions/https-everywhere@eff.org/background-scripts/update.js')
-rw-r--r--data/extensions/https-everywhere@eff.org/background-scripts/update.js480
1 files changed, 0 insertions, 480 deletions
diff --git a/data/extensions/https-everywhere@eff.org/background-scripts/update.js b/data/extensions/https-everywhere@eff.org/background-scripts/update.js
deleted file mode 100644
index 4767f38..0000000
--- a/data/extensions/https-everywhere@eff.org/background-scripts/update.js
+++ /dev/null
@@ -1,480 +0,0 @@
-/* global pako */
-
-"use strict";
-
-let combined_update_channels, extension_version;
-const { update_channels } = require('./update_channels');
-const wasm = require('./wasm');
-
-// Determine if we're in the tests. If so, define some necessary components.
-if (typeof window === "undefined") {
- var WebCrypto = require("node-webcrypto-ossl"),
- crypto = new WebCrypto(),
- atob = require("atob"),
- btoa = require("btoa"),
- pako = require('../external/pako-1.0.5/pako_inflate.min.js'),
- { TextDecoder } = require('text-encoding'),
- chrome = require("sinon-chrome"),
- window = { atob, btoa, chrome, crypto, pako, TextDecoder },
- fs = require('fs');
-
- extension_version = JSON.parse(fs.readFileSync('./manifest.json')).version;
-
- combined_update_channels = update_channels;
-} else {
- extension_version = chrome.runtime.getManifest().version;
-}
-
-(function(exports) {
-
-const util = require('./util');
-
-let store,
- background_callback;
-
-// how often we should check for new rulesets
-const periodicity = 86400;
-
-const extension_date = new Date(extension_version.split('.').slice(0,3).join('-'));
-const extension_timestamp = extension_date.getTime() / 1000;
-
-let imported_keys;
-
-// update channels are loaded from `background-scripts/update_channels.js` as well as the storage api
-async function loadUpdateChannelsKeys() {
- util.log(util.NOTE, 'Loading update channels and importing associated public keys.');
-
- const stored_update_channels = await store.get_promise('update_channels', []);
- const combined_update_channels_preflight = update_channels.concat(stored_update_channels);
-
- imported_keys = {};
- combined_update_channels = [];
-
- for(let update_channel of combined_update_channels_preflight) {
-
- try{
- imported_keys[update_channel.name] = await window.crypto.subtle.importKey(
- "jwk",
- update_channel.jwk,
- {
- name: "RSA-PSS",
- hash: {name: "SHA-256"},
- },
- false,
- ["verify"]
- );
- combined_update_channels.push(update_channel);
- util.log(util.NOTE, update_channel.name + ': Update channel key loaded.');
- } catch(err) {
- util.log(util.WARN, update_channel.name + ': Could not import key. Aborting.');
- }
- }
-}
-
-
-// Determine the time until we should check for new rulesets
-async function timeToNextCheck() {
- const last_checked = await store.local.get_promise('last-checked', false);
- if(last_checked) {
- const current_timestamp = Date.now() / 1000;
- const secs_since_last_checked = current_timestamp - last_checked;
- return Math.max(0, periodicity - secs_since_last_checked);
- } else {
- return 0;
- }
-}
-
-// Check for new rulesets immediately
-async function resetTimer() {
- await store.local.set_promise('last-checked', false);
- destroyTimer();
- await createTimer();
-}
-
-// Check for new updates. If found, return the timestamp. If not, return false
-async function checkForNewUpdates(update_channel) {
- let timestamp_result = await fetch(update_channel.update_path_prefix + (update_channel.format == "bloom" ? "/latest-bloom-timestamp" : "/latest-rulesets-timestamp"));
- if(timestamp_result.status == 200) {
- let uc_timestamp = Number(await timestamp_result.text());
-
- if((await store.local.get_promise('uc-timestamp: ' + update_channel.name, 0)) < uc_timestamp) {
- return uc_timestamp;
- }
- }
- return false;
-}
-
-// Retrieve the timestamp for when an update channel was published
-async function getUpdateChannelTimestamps() {
- let timestamp_promises = [];
- for(let update_channel of combined_update_channels) {
- timestamp_promises.push(new Promise(async resolve => {
- let timestamp = await store.local.get_promise('uc-stored-timestamp: ' + update_channel.name, 0);
- resolve([update_channel, timestamp]);
- }));
- }
- let timestamps = await Promise.all(timestamp_promises);
- return timestamps;
-}
-
-// Download and return new rulesets
-async function getNewRulesets(rulesets_timestamp, update_channel) {
-
- store.local.set_promise('uc-timestamp: ' + update_channel.name, rulesets_timestamp);
-
- let signature_promise = fetch(update_channel.update_path_prefix + "/rulesets-signature." + rulesets_timestamp + ".sha256");
- let rulesets_promise = fetch(update_channel.update_path_prefix + "/default.rulesets." + rulesets_timestamp + ".gz");
-
- let responses = await Promise.all([
- signature_promise,
- rulesets_promise
- ]);
-
- let resolutions = await Promise.all([
- responses[0].arrayBuffer(),
- responses[1].arrayBuffer()
- ]);
-
- return {
- signature_array_buffer: resolutions[0],
- rulesets_array_buffer: resolutions[1]
- };
-}
-
-// Download and return new bloom
-async function getNewBloom(bloom_timestamp, update_channel) {
- store.local.set_promise('uc-timestamp: ' + update_channel.name, bloom_timestamp);
-
- let signature_promise = fetch(update_channel.update_path_prefix + "/bloom-signature." + bloom_timestamp + ".sha256");
- let bloom_metadata_promise = fetch(update_channel.update_path_prefix + "/bloom-metadata." + bloom_timestamp + ".json");
- let bloom_promise = fetch(update_channel.update_path_prefix + "/bloom." + bloom_timestamp + ".bin");
-
- let responses = await Promise.all([
- signature_promise,
- bloom_metadata_promise,
- bloom_promise
- ]);
-
- let resolutions = await Promise.all([
- responses[0].arrayBuffer(),
- responses[1].arrayBuffer(),
- responses[2].arrayBuffer()
- ]);
-
- return {
- signature_array_buffer: resolutions[0],
- bloom_metadata_array_buffer: resolutions[1],
- bloom_array_buffer: resolutions[2],
- };
-
-}
-
-// Returns a promise which verifies that the rulesets have a valid EFF
-// signature, and if so, stores them and returns true.
-// Otherwise, it throws an exception.
-function verifyAndStoreNewRulesets(new_rulesets, rulesets_timestamp, update_channel) {
- return new Promise((resolve, reject) => {
- window.crypto.subtle.verify(
- {
- name: "RSA-PSS",
- saltLength: 32
- },
- imported_keys[update_channel.name],
- new_rulesets.signature_array_buffer,
- new_rulesets.rulesets_array_buffer
- ).then(async isvalid => {
- if(isvalid) {
- util.log(util.NOTE, update_channel.name + ': Downloaded ruleset signature checks out. Storing rulesets.');
-
- const rulesets_gz = util.ArrayBufferToString(new_rulesets.rulesets_array_buffer);
- const rulesets_byte_array = pako.inflate(rulesets_gz);
- const rulesets = new TextDecoder("utf-8").decode(rulesets_byte_array);
- const rulesets_json = JSON.parse(rulesets);
-
- if(rulesets_json.timestamp != rulesets_timestamp) {
- reject(update_channel.name + ': Downloaded ruleset had an incorrect timestamp. This may be an attempted downgrade attack. Aborting.');
- } else {
- await store.local.set_promise('rulesets: ' + update_channel.name, window.btoa(rulesets_gz));
- resolve(true);
- }
- } else {
- reject(update_channel.name + ': Downloaded ruleset signature is invalid. Aborting.');
- }
- }).catch(() => {
- reject(update_channel.name + ': Downloaded ruleset signature could not be verified. Aborting.');
- });
- });
-}
-
-// Returns a promise which verifies that the bloom has a valid EFF
-// signature, and if so, stores it and returns true.
-// Otherwise, it throws an exception.
-function verifyAndStoreNewBloom(new_bloom, bloom_timestamp, update_channel) {
- return new Promise((resolve, reject) => {
- window.crypto.subtle.verify(
- {
- name: "RSA-PSS",
- saltLength: 32
- },
- imported_keys[update_channel.name],
- new_bloom.signature_array_buffer,
- new_bloom.bloom_metadata_array_buffer
- ).then(async isvalid => {
- if(isvalid) {
- util.log(util.NOTE, update_channel.name + ': Bloom filter metadata signature checks out.');
-
- const bloom_metadata = JSON.parse(util.ArrayBufferToString(new_bloom.bloom_metadata_array_buffer));
- const bloom_str = util.ArrayBufferToString(new_bloom.bloom_array_buffer);
-
- if(bloom_metadata.timestamp != bloom_timestamp) {
- reject(update_channel.name + ': Downloaded bloom filter had an incorrect timestamp. This may be an attempted downgrade attack. Aborting.');
- } else if(await sha256sum(new_bloom.bloom_array_buffer) != bloom_metadata.sha256sum) {
- reject(update_channel.name + ': sha256sum of the bloom filter is invalid. Aborting.');
- } else {
- await store.local.set_promise('bloom: ' + update_channel.name, window.btoa(bloom_str));
- await store.local.set_promise('bloom_bitmap_bits: ' + update_channel.name, bloom_metadata.bitmap_bits);
- await store.local.set_promise('bloom_k_num: ' + update_channel.name, bloom_metadata.k_num);
- await store.local.set_promise('bloom_sip_keys_0_0: ' + update_channel.name, bloom_metadata.sip_keys[0][0]);
- await store.local.set_promise('bloom_sip_keys_0_1: ' + update_channel.name, bloom_metadata.sip_keys[0][1]);
- await store.local.set_promise('bloom_sip_keys_1_0: ' + update_channel.name, bloom_metadata.sip_keys[1][0]);
- await store.local.set_promise('bloom_sip_keys_1_1: ' + update_channel.name, bloom_metadata.sip_keys[1][1]);
- resolve(true);
- }
- } else {
- reject(update_channel.name + ': Downloaded bloom filter metadata signature is invalid. Aborting.');
- }
- }).catch(() => {
- reject(update_channel.name + ': Downloaded bloom signature could not be verified. Aborting.');
- });
- });
-}
-
-async function sha256sum(buffer) {
- const hashBuffer = await window.crypto.subtle.digest('SHA-256', buffer);
- const hashArray = Array.from(new Uint8Array(hashBuffer));
- const hashHex = hashArray.map(b => ('00' + b.toString(16)).slice(-2)).join('');
- return hashHex;
-}
-
-function isNotUndefined(subject) {
- return (typeof subject != 'undefined');
-}
-
-// Apply the rulesets we have stored.
-async function applyStoredRulesets(rulesets_obj) {
- let rulesets_promises = [];
- for(let update_channel of combined_update_channels) {
- if(update_channel.format == "rulesets" || !update_channel.format) {
- rulesets_promises.push(new Promise(resolve => {
- const key = 'rulesets: ' + update_channel.name;
- chrome.storage.local.get(key, root => {
- if(root[key]) {
- util.log(util.NOTE, update_channel.name + ': Applying stored rulesets.');
-
- const rulesets_gz = window.atob(root[key]);
- const rulesets_byte_array = pako.inflate(rulesets_gz);
- const rulesets_string = new TextDecoder("utf-8").decode(rulesets_byte_array);
- const rulesets_json = JSON.parse(rulesets_string);
-
- resolve({json: rulesets_json, scope: update_channel.scope, replaces: update_channel.replaces_default_rulesets});
- } else {
- resolve();
- }
- });
- }));
- }
- }
-
- const rulesets_results = (await Promise.all(rulesets_promises)).filter(isNotUndefined);
-
- let replaces = false;
- for(const rulesets_result of rulesets_results) {
- if(rulesets_result.replaces === true) {
- replaces = true;
- }
- rulesets_obj.addFromJson(rulesets_result.json.rulesets, rulesets_result.scope);
- }
-
- if(!replaces) {
- rulesets_obj.addFromJson(util.loadExtensionFile('rules/default.rulesets', 'json'), '');
- }
-}
-
-// Apply the blooms we have stored.
-async function applyStoredBlooms(bloom_arr) {
- let bloom_promises = [];
- for(let update_channel of combined_update_channels) {
- if(update_channel.format == "bloom") {
- bloom_promises.push(new Promise(resolve => {
- const key = 'bloom: ' + update_channel.name;
- chrome.storage.local.get(key, async root => {
- if(root[key]) {
- util.log(util.NOTE, update_channel.name + ': Applying stored bloom filter.');
- const bloom = util.StringToArrayBuffer(window.atob(root[key]));
- const bloom_bitmap_bits = await store.local.get_promise('bloom_bitmap_bits: ' + update_channel.name, "");
- const bloom_k_num = await store.local.get_promise('bloom_k_num: ' + update_channel.name, "");
- const bloom_sip_keys_0_0 = await store.local.get_promise('bloom_sip_keys_0_0: ' + update_channel.name, "");
- const bloom_sip_keys_0_1 = await store.local.get_promise('bloom_sip_keys_0_1: ' + update_channel.name, "");
- const bloom_sip_keys_1_0 = await store.local.get_promise('bloom_sip_keys_1_0: ' + update_channel.name, "");
- const bloom_sip_keys_1_1 = await store.local.get_promise('bloom_sip_keys_1_1: ' + update_channel.name, "");
-
- try{
- resolve(wasm.Bloom.from_existing(bloom, bloom_bitmap_bits, bloom_k_num, [[bloom_sip_keys_0_0, bloom_sip_keys_0_1], [bloom_sip_keys_1_0, bloom_sip_keys_1_1]]));
- } catch(_) {
- resolve();
- }
- } else {
- resolve();
- }
- });
- }));
- }
- }
-
- bloom_arr.length = 0;
- const bloom_results = (await Promise.all(bloom_promises)).filter(isNotUndefined);
- for(const bloom_result of bloom_results) {
- bloom_arr.push(bloom_result);
- }
-}
-
-
-// basic workflow for periodic checks
-async function performCheck() {
- util.log(util.NOTE, 'Checking for new updates.');
-
- const current_timestamp = Date.now() / 1000;
- store.local.set_promise('last-checked', current_timestamp);
-
- let num_updates = 0;
- for(let update_channel of combined_update_channels) {
- if(update_channel.format == "bloom") {
- let new_bloom_timestamp = await checkForNewUpdates(update_channel);
- if(new_bloom_timestamp) {
- util.log(util.NOTE, update_channel.name + ': A new bloom filter has been released. Downloading now.');
- let new_bloom = await getNewBloom(new_bloom_timestamp, update_channel);
- try{
- await verifyAndStoreNewBloom(new_bloom, new_bloom_timestamp, update_channel);
- store.local.set_promise('uc-stored-timestamp: ' + update_channel.name, new_bloom_timestamp);
- num_updates++;
- } catch(err) {
- util.log(util.WARN, update_channel.name + ': ' + err);
- }
- }
- } else {
- let new_rulesets_timestamp = await checkForNewUpdates(update_channel);
- if(new_rulesets_timestamp) {
-
- if(update_channel.replaces_default_rulesets && extension_timestamp > new_rulesets_timestamp) {
- util.log(util.NOTE, update_channel.name + ': A new ruleset bundle has been released, but it is older than the extension-bundled rulesets it replaces. Skipping.');
- continue;
- }
-
- util.log(util.NOTE, update_channel.name + ': A new ruleset bundle has been released. Downloading now.');
- let new_rulesets = await getNewRulesets(new_rulesets_timestamp, update_channel);
- try{
- await verifyAndStoreNewRulesets(new_rulesets, new_rulesets_timestamp, update_channel);
- store.local.set_promise('uc-stored-timestamp: ' + update_channel.name, new_rulesets_timestamp);
- num_updates++;
- } catch(err) {
- util.log(util.WARN, update_channel.name + ': ' + err);
- }
- }
- }
- }
- if(num_updates > 0) {
- background_callback();
- }
-};
-
-async function storageListener(changes, areaName) {
- if (areaName === 'sync' || areaName === 'local') {
- if ('autoUpdateRulesets' in changes) {
- if (changes.autoUpdateRulesets.newValue) {
- await createTimer();
- } else {
- destroyTimer();
- }
- }
- }
-
- if ('update_channels' in changes) {
- await loadUpdateChannelsKeys();
- }
-};
-
-function addStorageListener() {
- chrome.storage.onChanged.addListener(storageListener);
-}
-
-function removeStorageListener() {
- chrome.storage.onChanged.removeListener(storageListener);
-}
-
-addStorageListener();
-
-let initialCheck,
- subsequentChecks;
-
-async function createTimer() {
- const time_to_next_check = await timeToNextCheck();
-
- initialCheck = setTimeout(() => {
- performCheck();
- subsequentChecks = setInterval(performCheck, periodicity * 1000);
- }, time_to_next_check * 1000);
-}
-
-function destroyTimer() {
- if (initialCheck) {
- clearTimeout(initialCheck);
- }
- if (subsequentChecks) {
- clearInterval(subsequentChecks);
- }
-}
-
-function clear_replacement_update_channels() {
- let keys = [];
- for (const update_channel of combined_update_channels) {
- if(update_channel.replaces_default_rulesets) {
- util.log(util.NOTE, update_channel.name + ': You have a new version of the extension. Clearing any stored rulesets, which replace the new extension-bundled ones.');
- keys.push('uc-timestamp: ' + update_channel.name);
- keys.push('uc-stored-timestamp: ' + update_channel.name);
- keys.push('rulesets: ' + update_channel.name);
- }
- }
-
- return new Promise(resolve => {
- chrome.storage.local.remove(keys, resolve);
- });
-}
-
-async function initialize(store_param, cb) {
- store = store_param;
- background_callback = cb;
-
- await loadUpdateChannelsKeys();
-
- if (await store.local.get_promise('extensionTimestamp', 0) !== extension_timestamp) {
- await clear_replacement_update_channels();
- await store.local.set_promise('extensionTimestamp', extension_timestamp);
- }
-
- if (await store.get_promise('autoUpdateRulesets', false)) {
- await createTimer();
- }
-}
-
-Object.assign(exports, {
- applyStoredRulesets,
- applyStoredBlooms,
- initialize,
- getUpdateChannelTimestamps,
- resetTimer,
- loadUpdateChannelsKeys,
- addStorageListener,
- removeStorageListener,
-});
-
-})(typeof exports == 'undefined' ? require.scopes.update = {} : exports);