// DNSSEC Check - Background Service Worker // Uses Google DNS-over-HTTPS API to check DNSSEC (AD flag) const dnssecCache = new Map(); const CACHE_TTL = 5 * 60 * 1000; // 5 minutes // --- Icon Generation (OffscreenCanvas) --- function drawShield(ctx, size, fillColor) { const p = size * 0.08; // padding const w = size - p * 2; const h = size - p * 2; const cx = size / 2; const top = p; ctx.beginPath(); ctx.moveTo(cx, top); ctx.lineTo(p + w, top + h * 0.22); ctx.lineTo(p + w, top + h * 0.55); ctx.quadraticCurveTo(p + w * 0.95, top + h * 0.82, cx, top + h); ctx.quadraticCurveTo(p + w * 0.05, top + h * 0.82, p, top + h * 0.55); ctx.lineTo(p, top + h * 0.22); ctx.closePath(); ctx.fillStyle = fillColor; ctx.fill(); // Subtle border ctx.strokeStyle = 'rgba(0,0,0,0.15)'; ctx.lineWidth = Math.max(1, size * 0.03); ctx.stroke(); } function drawCheck(ctx, size) { const cx = size / 2; const cy = size * 0.52; const s = size * 0.18; ctx.beginPath(); ctx.moveTo(cx - s, cy); ctx.lineTo(cx - s * 0.25, cy + s * 0.7); ctx.lineTo(cx + s, cy - s * 0.6); ctx.strokeStyle = '#fff'; ctx.lineWidth = Math.max(2, size * 0.1); ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.stroke(); } function drawX(ctx, size) { const cx = size / 2; const cy = size * 0.5; const s = size * 0.14; ctx.strokeStyle = '#fff'; ctx.lineWidth = Math.max(2, size * 0.1); ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(cx - s, cy - s); ctx.lineTo(cx + s, cy + s); ctx.stroke(); ctx.beginPath(); ctx.moveTo(cx + s, cy - s); ctx.lineTo(cx - s, cy + s); ctx.stroke(); } function drawQuestion(ctx, size) { ctx.fillStyle = '#fff'; ctx.font = `bold ${size * 0.4}px sans-serif`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('?', size / 2, size * 0.52); } function createIcon(color, symbol, size) { const canvas = new OffscreenCanvas(size, size); const ctx = canvas.getContext('2d'); drawShield(ctx, size, color); if (symbol === 'check') drawCheck(ctx, size); else if (symbol === 'x') drawX(ctx, size); else if (symbol === '?') drawQuestion(ctx, size); return ctx.getImageData(0, 0, size, size); } // Pre-generate all icon states const STATES = { green: { color: '#27ae60', symbol: 'check' }, red: { color: '#e74c3c', symbol: 'x' }, gray: { color: '#95a5a6', symbol: '?' } }; const SIZES = [16, 32, 48, 128]; const icons = {}; for (const [name, { color, symbol }] of Object.entries(STATES)) { icons[name] = {}; for (const size of SIZES) { icons[name][size] = createIcon(color, symbol, size); } } function setIcon(tabId, state) { chrome.action.setIcon({ tabId, imageData: { 16: icons[state][16], 32: icons[state][32], 48: icons[state][48], 128: icons[state][128] } }); } function setTitle(tabId, text) { chrome.action.setTitle({ tabId, title: text }); } // --- DNSSEC Check --- async function checkDNSSEC(domain) { const cached = dnssecCache.get(domain); if (cached && Date.now() - cached.timestamp < CACHE_TTL) { return cached; } try { const url = `https://dns.google/resolve?name=${encodeURIComponent(domain)}&type=A&do=1`; const response = await fetch(url); if (!response.ok) { throw new Error(`DNS API returned ${response.status}`); } const data = await response.json(); const result = { domain, dnssec: data.AD === true, status: data.Status, timestamp: Date.now(), error: null }; dnssecCache.set(domain, result); // Auto-clean cache entry after TTL setTimeout(() => { const entry = dnssecCache.get(domain); if (entry && Date.now() - entry.timestamp >= CACHE_TTL) { dnssecCache.delete(domain); } }, CACHE_TTL + 1000); return result; } catch (error) { const result = { domain, dnssec: false, status: -1, timestamp: Date.now(), error: error.message }; return result; } } // --- Tab Handling --- function extractDomain(url) { try { const parsed = new URL(url); if (parsed.protocol === 'http:' || parsed.protocol === 'https:') { return parsed.hostname; } } catch {} return null; } async function checkTab(tabId, url) { const domain = extractDomain(url); if (!domain) { setIcon(tabId, 'gray'); setTitle(tabId, 'DNSSEC Check - Geen webpagina'); chrome.storage.session.set({ [String(tabId)]: null }); return; } setIcon(tabId, 'gray'); setTitle(tabId, `DNSSEC Check - Controleren: ${domain}...`); const result = await checkDNSSEC(domain); if (result.error) { setIcon(tabId, 'gray'); setTitle(tabId, `DNSSEC Check - Fout bij controleren`); } else if (result.dnssec) { setIcon(tabId, 'green'); setTitle(tabId, `DNSSEC Check - ${domain} is DNSSEC beveiligd`); } else { setIcon(tabId, 'red'); setTitle(tabId, `DNSSEC Check - ${domain} heeft geen DNSSEC`); } chrome.storage.session.set({ [String(tabId)]: result }); } // Check on page load chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { if (changeInfo.status === 'complete' && tab.url) { checkTab(tabId, tab.url); } }); // Check on tab switch chrome.tabs.onActivated.addListener(async ({ tabId }) => { try { const tab = await chrome.tabs.get(tabId); if (tab.url) { checkTab(tabId, tab.url); } } catch {} }); // Clean up on tab close chrome.tabs.onRemoved.addListener((tabId) => { chrome.storage.session.remove(String(tabId)); }); // Message handler for popup chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.type === 'getStatus') { chrome.storage.session.get([String(message.tabId)], (data) => { sendResponse(data[String(message.tabId)] || null); }); return true; } if (message.type === 'recheck') { chrome.tabs.get(message.tabId, (tab) => { if (tab && tab.url) { const domain = extractDomain(tab.url); if (domain) dnssecCache.delete(domain); checkTab(message.tabId, tab.url); setTimeout(() => { chrome.storage.session.get([String(message.tabId)], (data) => { sendResponse(data[String(message.tabId)] || null); }); }, 1500); } else { sendResponse(null); } }); return true; } });