diff options
author | Clément Lassieur <clement@lassieur.org> | 2023-11-09 14:15:44 +0100 |
---|---|---|
committer | Mark H Weaver <mhw@netris.org> | 2023-12-30 09:24:46 -0500 |
commit | ba0d2ab758143b9fe2ca14f6eed07d9a6a350c2b (patch) | |
tree | 0ecd8a30389110b49a8a1c1513332dbacfcb46f9 /data/extensions/https-everywhere@eff.org/background-scripts/update.js | |
parent | f889514426e512e5602c71e1b411ae0332a33366 (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.js | 480 |
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); |