From 2dd43064390994e024d9f3ecde75d7b83cc1c61e Mon Sep 17 00:00:00 2001 From: mario Date: Mon, 9 Mar 2026 10:26:37 +0100 Subject: [PATCH] First commit --- background.js | 262 ++++++++++++++++++++++++++++++++++++++++++++++++++ manifest.json | 20 ++++ popup.css | 122 +++++++++++++++++++++++ popup.html | 32 ++++++ popup.js | 134 ++++++++++++++++++++++++++ 5 files changed, 570 insertions(+) create mode 100644 background.js create mode 100644 manifest.json create mode 100644 popup.css create mode 100644 popup.html create mode 100644 popup.js diff --git a/background.js b/background.js new file mode 100644 index 0000000..b0e8da1 --- /dev/null +++ b/background.js @@ -0,0 +1,262 @@ +// 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; + } +}); diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..2b23591 --- /dev/null +++ b/manifest.json @@ -0,0 +1,20 @@ +{ + "manifest_version": 3, + "name": "DNSSEC Check", + "version": "1.0", + "description": "Controleer of een website DNSSEC gebruikt. Groen schild = DNSSEC beveiligd, Rood schild = Geen DNSSEC.", + "permissions": [ + "tabs", + "storage" + ], + "host_permissions": [ + "https://dns.google/*" + ], + "background": { + "service_worker": "background.js" + }, + "action": { + "default_popup": "popup.html", + "default_title": "DNSSEC Check" + } +} diff --git a/popup.css b/popup.css new file mode 100644 index 0000000..29982b8 --- /dev/null +++ b/popup.css @@ -0,0 +1,122 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + width: 300px; + background: #fff; + color: #333; +} + +.container { + padding: 20px; + text-align: center; +} + +.header { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + margin-bottom: 16px; +} + +.shield { + filter: drop-shadow(0 2px 4px rgba(0,0,0,0.15)); +} + +h1 { + font-size: 16px; + font-weight: 600; + color: #222; +} + +.domain { + font-size: 14px; + font-weight: 500; + color: #555; + padding: 8px 12px; + background: #f5f6fa; + border-radius: 6px; + margin-bottom: 12px; + word-break: break-all; +} + +.status { + font-size: 15px; + font-weight: 600; + margin-bottom: 8px; +} + +.status.secure { + color: #27ae60; +} + +.status.insecure { + color: #e74c3c; +} + +.status.unknown { + color: #95a5a6; +} + +.description { + font-size: 12px; + color: #777; + line-height: 1.5; + margin-bottom: 16px; +} + +.recheck-btn { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 8px 16px; + font-size: 12px; + font-weight: 500; + color: #555; + background: #f0f0f0; + border: 1px solid #ddd; + border-radius: 6px; + cursor: pointer; + transition: all 0.15s; +} + +.recheck-btn:hover { + background: #e4e4e4; + color: #333; +} + +.recheck-btn:active { + transform: scale(0.97); +} + +.recheck-btn.loading svg { + animation: spin 0.8s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +/* Shield colors */ +.shield-green #shield-path { + fill: #27ae60; + stroke: #1e8449; + stroke-width: 1.5; +} + +.shield-red #shield-path { + fill: #e74c3c; + stroke: #c0392b; + stroke-width: 1.5; +} + +.shield-gray #shield-path { + fill: #95a5a6; + stroke: #7f8c8d; + stroke-width: 1.5; +} diff --git a/popup.html b/popup.html new file mode 100644 index 0000000..4ac63cc --- /dev/null +++ b/popup.html @@ -0,0 +1,32 @@ + + + + + + + +
+
+ + + + +

DNSSEC Check

+
+ +
Laden...
+
+
+ + +
+ + + + diff --git a/popup.js b/popup.js new file mode 100644 index 0000000..e0a0362 --- /dev/null +++ b/popup.js @@ -0,0 +1,134 @@ +// DNSSEC Check - Popup Logic + +const container = document.getElementById('container'); +const domainEl = document.getElementById('domain'); +const statusEl = document.getElementById('status'); +const descriptionEl = document.getElementById('description'); +const shieldSymbol = document.getElementById('shield-symbol'); +const recheckBtn = document.getElementById('recheck-btn'); + +const SVG_CHECK = ` + +`; + +const SVG_X = ` + + +`; + +const SVG_QUESTION = ` + ? +`; + +function setShield(state) { + container.className = 'container'; + container.classList.add(`shield-${state}`); + + if (state === 'green') shieldSymbol.innerHTML = SVG_CHECK; + else if (state === 'red') shieldSymbol.innerHTML = SVG_X; + else shieldSymbol.innerHTML = SVG_QUESTION; +} + +function showStatus(result) { + if (!result || result.error) { + setShield('gray'); + domainEl.textContent = result?.domain || 'Onbekend'; + statusEl.textContent = 'Kon niet controleren'; + statusEl.className = 'status unknown'; + descriptionEl.textContent = result?.error + ? `Fout: ${result.error}` + : 'Geen informatie beschikbaar voor deze pagina.'; + return; + } + + domainEl.textContent = result.domain; + + if (result.dnssec) { + setShield('green'); + statusEl.textContent = 'DNSSEC Beveiligd'; + statusEl.className = 'status secure'; + descriptionEl.textContent = + 'Deze website gebruikt DNSSEC. DNS-antwoorden zijn cryptografisch geverifieerd, wat beschermt tegen DNS-spoofing en manipulatie.'; + } else { + setShield('red'); + statusEl.textContent = 'Geen DNSSEC'; + statusEl.className = 'status insecure'; + descriptionEl.textContent = + 'Deze website gebruikt geen DNSSEC. DNS-antwoorden worden niet cryptografisch geverifieerd, waardoor ze kwetsbaar kunnen zijn voor manipulatie.'; + } +} + +function showNoWeb() { + setShield('gray'); + domainEl.textContent = 'Geen website'; + statusEl.textContent = 'Niet beschikbaar'; + statusEl.className = 'status unknown'; + descriptionEl.textContent = 'DNSSEC-controle is alleen beschikbaar op HTTP/HTTPS websites.'; + recheckBtn.style.display = 'none'; +} + +// Get current tab and fetch status +chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { + const tab = tabs[0]; + if (!tab) { + showNoWeb(); + return; + } + + try { + const url = new URL(tab.url); + if (url.protocol !== 'http:' && url.protocol !== 'https:') { + showNoWeb(); + return; + } + } catch { + showNoWeb(); + return; + } + + chrome.runtime.sendMessage( + { type: 'getStatus', tabId: tab.id }, + (result) => { + if (result) { + showStatus(result); + } else { + domainEl.textContent = new URL(tab.url).hostname; + setShield('gray'); + statusEl.textContent = 'Controleren...'; + statusEl.className = 'status unknown'; + descriptionEl.textContent = 'Even geduld, DNSSEC wordt gecontroleerd.'; + + // Wait and retry + setTimeout(() => { + chrome.runtime.sendMessage( + { type: 'getStatus', tabId: tab.id }, + (r) => showStatus(r) + ); + }, 2000); + } + } + ); + + // Recheck button + recheckBtn.addEventListener('click', () => { + recheckBtn.classList.add('loading'); + recheckBtn.disabled = true; + + setShield('gray'); + statusEl.textContent = 'Controleren...'; + statusEl.className = 'status unknown'; + descriptionEl.textContent = 'Opnieuw controleren...'; + + chrome.runtime.sendMessage( + { type: 'recheck', tabId: tab.id }, + (result) => { + recheckBtn.classList.remove('loading'); + recheckBtn.disabled = false; + showStatus(result); + } + ); + }); +});