Add per-tab processing of multiple certs

This commit is contained in:
Darryl Nixon 2023-06-16 13:25:55 -07:00
parent 359c35a04e
commit 396ba90c7e

View file

@ -10,6 +10,8 @@
// const API_BASE = "https://crowdtls.mips.uk/api/v1"; // const API_BASE = "https://crowdtls.mips.uk/api/v1";
const API_BASE = "http://127.0.0.1:8000/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. * 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 }); let securityInfo = await browser.webRequest.getSecurityInfo(details.requestId, { certificateChain: true, rawDER: true });
if (securityInfo.state !== "insecure") { if (securityInfo.state !== "insecure") {
const fingerprint = securityInfo.certificates[0].fingerprint.sha256; const fingerprint = securityInfo.certificates[0].fingerprint.sha256;
let fpData = localStorage.getItem(fingerprint); let fpData = localStorage.getItem(fingerprint);
const currentTime = Date.now(); const currentTime = Date.now();
@ -46,7 +49,17 @@ async function process_request(details) {
fpData.lastCheck = currentTime; fpData.lastCheck = currentTime;
localStorage.setItem(fingerprint, JSON.stringify(fpData)); 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) { } catch (error) {
console.error(error); console.error(error);
@ -59,17 +72,19 @@ async function process_request(details) {
* *
* @async * @async
* @function * @function
* @param {string} hostname - The hostname of the website. * @param {Object} buffer - The buffer containing info for a specific tab
* @param {Array} certificates - The list of certificates.
*/ */
async function check_fingerprint(hostname, certificates) { async function do_checks(buffer) {
try { let fingerprint_buffer = {};
const fingerprints = certificates.map(cert => cert.fingerprint.sha256); Object.keys(buffer).forEach(hostname => {
fingerprint_buffer[hostname] = Object.keys(buffer[hostname]);
});
try {
const response = await fetch(`${API_BASE}/check`, { const response = await fetch(`${API_BASE}/check`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ host: hostname, fps: fingerprints }), body: JSON.stringify(fingerprint_buffer),
timeout: 5000 timeout: 5000
}); });
@ -80,11 +95,12 @@ async function check_fingerprint(hostname, certificates) {
const data = await response.json(); const data = await response.json();
if (data) { Object.keys(buffer).forEach(hostname => {
if (data["send"] === true) { if (!data[hostname]) {
send_certificate_chain(hostname, certificates); delete buffer[hostname];
} }
} });
send_certificate_chain(buffer);
} }
catch (error) { catch (error) {
console.error(error); console.error(error);
@ -96,23 +112,17 @@ async function check_fingerprint(hostname, certificates) {
* *
* @async * @async
* @function * @function
* @param {string} hostname - The hostname of the website. * @param {Object} chain - The buffer containing info for a specific tab
* @param {Array} certificates - The list of certificates.
* @throws Will log an error to the console if the request fails. * @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 { try {
let chain = {};
certificates.forEach(cert => {
chain[cert.fingerprint.sha256] = cert.rawDER;
});
const response = await fetch(`${API_BASE}/new`, { const response = await fetch(`${API_BASE}/new`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ host: hostname, certs: chain }), body: JSON.stringify(chain),
timeout: 10000 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 * @function
* @description Performs garbage collection on localStorage by removing objects older than 12 hours based on their "lastCheck" property.
* @returns {void} * @returns {void}
*/ */
function garbage_collection() { async function garbage_collection() {
const now = new Date().getTime(); const now = new Date().getTime();
const twelveHoursInMillis = 12 * 60 * 60 * 1000; const twelveHoursInMillis = 12 * 60 * 60 * 1000;
for (let i = 0; i < localStorage.length; i++) { 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); setInterval(garbage_collection, 3600000);
@ -157,4 +178,12 @@ browser.webRequest.onHeadersReceived.addListener(
process_request, process_request,
{ urls: ["https://*/*"] }, { urls: ["https://*/*"] },
["blocking"] ["blocking"]
); );
// 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];
}
});