From 6115e56171410c68966134120092641fc7dad344 Mon Sep 17 00:00:00 2001 From: Darryl Nixon Date: Tue, 6 Jun 2023 15:33:17 -0700 Subject: [PATCH] Add API interaction code and garbage collection --- PRIVACY.md | 2 + README.md | 11 ++-- manifest.json | 18 ++++-- worker.js | 157 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 178 insertions(+), 10 deletions(-) diff --git a/PRIVACY.md b/PRIVACY.md index 75b158d..330bc8d 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -11,6 +11,8 @@ The addon collects collect the following information: ### SSL/TLS Certificate Data I collect SSL/TLS certificate data associated with the fully qualified domain names (FQDNs) of the websites you visit. This data includes information exposed by security information APIs, such as `webRequest.getSecurityInfo()` for Mozilla Firefox. Please note that these APIs are managed by the browser developer (e.g., Mozilla, Microsoft, Apple, Opera) and are subject to their security and privacy practices. This explicitly does not include data associated with requests you've made to these services such as URL paths, query parameters, authentication information, cookies, or anything unrelated to the certificate data the server sends you to verify they own the domain you are visiting. +If you are browsing sites which use internal, self-signed certificates, any information included in those certificates will be sent to CrowdTLS. The content of the certificates will not be accessible by CrowdTLS end users. + **TL;DR: I collect ONLY certificate data sent to you by the web domains you visit.** ### Metadata diff --git a/README.md b/README.md index 03b4c4f..de8766f 100644 --- a/README.md +++ b/README.md @@ -37,16 +37,19 @@ You can see which permissions CrowdTLS requires in [manifest.json](manifest.json | WebExtension Permission | CrowdTLS Usage | |---|---| -| `webRequest` | Enables access and analyze web requests, allowing access to SSL/TLS certificate data. | -| `webRequestBlocking` | Required to handle and process HTTPS requests in a blocking manner. | | `https://*/*` | Enables filtering specifically for HTTPS websites contacted, but not HTTP. | -| `notifications` | For displaying notifications for important updates or alerts. | -| `storage` | Caches request/response content on your system so CrowdTLS functions more efficiently. | +| `webRequest` | Enables access and analyze web requests from above filter (i.e., SSL/TLS security data). | +| `webRequestBlocking` | Required to handle and process HTTPS requests in a blocking manner. | +| `storage` | Stores local configuration and any ephemeral caching/optimization data. | +| `notifications` | Optional, for displaying notifications for important updates or alerts. | + +Because CrowdTLS is written optimistically using [Manifest v3](https://developer.chrome.com/docs/extensions/mv3/intro/), local storage is used to store persistent and ephemeral data accessed through events rather than the traditional background page. **What's your roadmap?** The roadmap for CrowdTLS, if maintained, includes several exciting features and improvements. Here are some highlights: +- Add alternative API support for browsers that don't support webRequest.getSecurityInfo(). - Enhancing the crowd-source validation process to provide more accurate and comprehensive certificate analysis. - Introducing user customization options to allow fine-tuning of validation criteria and feedback preferences. - Collaborating with browser developers to streamline the integration of CrowdTLS functionalities natively into popular browsers. diff --git a/manifest.json b/manifest.json index dd9a624..973f9cf 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 3, "name": "CrowdTLS", "version": "0.0.1", - "description": "Crowd-sourced validation of TLS/SSL certificates received.", + "description": "Crowd-sourced validation for TLS/SSL certificates", "icons": { "16": "icons/crowdtls-16.png", "24": "icons/crowdtls-24.png", @@ -21,21 +21,27 @@ "default_title": "CrowdTLS", "default_popup": "crowdtls.html" }, - "default_locale": "en", "host_permissions": [ - "https://*" + "https://crowdtls.mips.uk/api/*", + "https://*/*" ], "permissions": [ "webRequest", "webRequestBlocking", - "notifications", - "https://*/*", "storage" ], + "optional_permissions": [ + "notifications" + ], "background": { "scripts": [ "worker.js" ] }, - "offline_enabled": false + "browser_specific_settings": { + "gecko": { + "id": "ext@crowdtls", + "strict_min_version": "101.0" + } + } } \ No newline at end of file diff --git a/worker.js b/worker.js index e69de29..69c83e8 100644 --- a/worker.js +++ b/worker.js @@ -0,0 +1,157 @@ +/** + * @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"; + +/** + * 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 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(securityInfo.certificates); + } + } catch (error) { + console.error(error); + } +} + + +/** + * Checks the fingerprint and sends a REST GET request to the API. + * + * @async + * @function + * @param {Array} certificates - The list of certificates. + */ +async function check_fingerprint(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({ 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(certificates); + } + } + } + catch (error) { + console.error(error); + } +} + +/** + * Sends the certificate chain to the CrowdTLS service. + * + * @async + * @function + * @param {Array} certificates - The list of certificates. + * @throws Will log an error to the console if the request fails. + */ +async function send_certificate_chain(certificates) { + try { + let chain = {}; + certificates.forEach(cert => { + chain[cert.fingerprint.sha256] = cert.rawDER; + }); + console.log(JSON.stringify(chain)); + + const response = await fetch(`${API_BASE} /new`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(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"] +); \ No newline at end of file