diff --git a/worker.js b/worker.js index ffb6dd9..c47caad 100644 --- a/worker.js +++ b/worker.js @@ -10,6 +10,8 @@ // const API_BASE = "https://crowdtls.mips.uk/api/v1"; const API_BASE = "http://127.0.0.1:8000/api/v1"; +let send_buffer = {}; + /** * Processes the given request details to extract and log security information. * @@ -26,6 +28,7 @@ async function process_request(details) { let securityInfo = await browser.webRequest.getSecurityInfo(details.requestId, { certificateChain: true, rawDER: true }); if (securityInfo.state !== "insecure") { const fingerprint = securityInfo.certificates[0].fingerprint.sha256; + let fpData = localStorage.getItem(fingerprint); const currentTime = Date.now(); @@ -46,7 +49,17 @@ async function process_request(details) { fpData.lastCheck = currentTime; localStorage.setItem(fingerprint, JSON.stringify(fpData)); - await check_fingerprint(hostname, securityInfo.certificates); + const tab = details.tabId; + if (!send_buffer[tab]) { + send_buffer[tab] = {}; + } + if (!send_buffer[tab][hostname]) { + send_buffer[tab][hostname] = {}; + } + + securityInfo.certificates.forEach(cert => { + send_buffer[tab][hostname][cert.fingerprint.sha256] = cert.rawDER; + }); } } catch (error) { console.error(error); @@ -59,17 +72,19 @@ async function process_request(details) { * * @async * @function - * @param {string} hostname - The hostname of the website. - * @param {Array} certificates - The list of certificates. + * @param {Object} buffer - The buffer containing info for a specific tab */ -async function check_fingerprint(hostname, certificates) { - try { - const fingerprints = certificates.map(cert => cert.fingerprint.sha256); +async function do_checks(buffer) { + let fingerprint_buffer = {}; + Object.keys(buffer).forEach(hostname => { + fingerprint_buffer[hostname] = Object.keys(buffer[hostname]); + }); + try { const response = await fetch(`${API_BASE}/check`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ host: hostname, fps: fingerprints }), + body: JSON.stringify(fingerprint_buffer), timeout: 5000 }); @@ -80,11 +95,12 @@ async function check_fingerprint(hostname, certificates) { const data = await response.json(); - if (data) { - if (data["send"] === true) { - send_certificate_chain(hostname, certificates); + Object.keys(buffer).forEach(hostname => { + if (!data[hostname]) { + delete buffer[hostname]; } - } + }); + send_certificate_chain(buffer); } catch (error) { console.error(error); @@ -96,23 +112,17 @@ async function check_fingerprint(hostname, certificates) { * * @async * @function - * @param {string} hostname - The hostname of the website. - * @param {Array} certificates - The list of certificates. + * @param {Object} chain - The buffer containing info for a specific tab * @throws Will log an error to the console if the request fails. */ -async function send_certificate_chain(hostname, certificates) { +async function send_certificate_chain(chain) { try { - let chain = {}; - certificates.forEach(cert => { - chain[cert.fingerprint.sha256] = cert.rawDER; - }); - const response = await fetch(`${API_BASE}/new`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ host: hostname, certs: chain }), + body: JSON.stringify(chain), timeout: 10000 }); @@ -125,11 +135,13 @@ async function send_certificate_chain(hostname, certificates) { } /** + * Performs garbage collection on localStorage by removing objects older than 12 hours based on their "lastCheck" property, + * and removes entries from send_buffer if that tab ID is no longer open. + * * @function - * @description Performs garbage collection on localStorage by removing objects older than 12 hours based on their "lastCheck" property. * @returns {void} */ -function garbage_collection() { +async function garbage_collection() { const now = new Date().getTime(); const twelveHoursInMillis = 12 * 60 * 60 * 1000; for (let i = 0; i < localStorage.length; i++) { @@ -142,6 +154,15 @@ function garbage_collection() { } } } + + const tabs = await browser.tabs.query({}); + const open_tabs = tabs.map(tab => tab.id); + + for (const tabId in send_buffer) { + if (!open_tabs.includes(Number(tabId))) { + delete send_buffer[tabId]; + } + } } setInterval(garbage_collection, 3600000); @@ -157,4 +178,12 @@ browser.webRequest.onHeadersReceived.addListener( process_request, { urls: ["https://*/*"] }, ["blocking"] -); \ No newline at end of file +); + +// When a tab update happens (i.e., page load complete), send the buffered data +browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => { + if (changeInfo.status === "complete" && send_buffer[tabId]) { + await do_checks(send_buffer[tabId]); + delete send_buffer[tabId]; + } +}); \ No newline at end of file