/** * @file worker.js * A background script for the CrowdTLS project. * This script is used to process web requests and retrieve security information * for each HTTPS response. */ "use strict"; // const API_BASE = "https://crowdtls.mips.uk/api/v1"; const API_BASE = "http://127.0.0.1:8000/api/v1"; /** * Processes the given request details to extract and log security information. * * @async * @function * @param {Object} details - The details of the request. * @param {string} details.requestId - The unique identifier for the request. * @throws Will throw an error if the request fails or if the security state is insecure. * @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/getSecurityInfo} */ async function process_request(details) { try { let hostname = (new URL(details.url)).hostname; 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(); if (fpData) { fpData = JSON.parse(fpData); // If the last request was less than 10 minutes ago, don't spam the API if (fpData.lastCheck && currentTime - fpData.lastCheck < 10 * 60 * 1000) { return; } // If the last response was less than 12 hours ago, don't check again if (fpData.lastResponse && currentTime - fpData.lastResponse < 12 * 60 * 60 * 1000) { return; } } else { fpData = {}; } fpData.lastCheck = currentTime; localStorage.setItem(fingerprint, JSON.stringify(fpData)); await check_fingerprint(hostname, securityInfo.certificates); } } catch (error) { console.error(error); } } /** * Checks the fingerprint and sends a REST GET request to the API. * * @async * @function * @param {string} hostname - The hostname of the website. * @param {Array} certificates - The list of certificates. */ async function check_fingerprint(hostname, certificates) { try { const fingerprints = certificates.map(cert => cert.fingerprint.sha256); const response = await fetch(`${API_BASE}/check`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ host: hostname, fps: fingerprints }), timeout: 5000 }); if (!response.ok) { console.error(`Error: ${response.status}`); return; } const data = await response.json(); if (data) { if (data["send"] === true) { send_certificate_chain(hostname, certificates); } } } catch (error) { console.error(error); } } /** * Sends the certificate chain to the CrowdTLS service. * * @async * @function * @param {string} hostname - The hostname of the website. * @param {Array} certificates - The list of certificates. * @throws Will log an error to the console if the request fails. */ async function send_certificate_chain(hostname, certificates) { 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 }), timeout: 10000 }); if (!response.ok) { console.error(`Request returned with status ${response.status}`); } } catch (error) { console.error(error); } } /** * @function * @description Performs garbage collection on localStorage by removing objects older than 12 hours based on their "lastCheck" property. * @returns {void} */ function garbage_collection() { const now = new Date().getTime(); const twelveHoursInMillis = 12 * 60 * 60 * 1000; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); const value = localStorage.getItem(key); if (value) { const entry = JSON.parse(value); if (entry.lastCheck && now - entry.lastCheck > twelveHoursInMillis) { localStorage.removeItem(key); } } } } setInterval(garbage_collection, 3600000); /** * The listener for HTTPS responses. Schedules the process_request() function for every response. * Blocking is necessary to ensure that the response is not delivered before the function is completed. * * @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/onHeadersReceived} */ browser.webRequest.onHeadersReceived.addListener( process_request, { urls: ["https://*/*"] }, ["blocking"] );