/** * MITM Certificate Search Interface * Provides search functionality for SSL MITM certificate data */ (function() { 'use strict'; // Theme toggle (shared with dashboard) var themes = ['dark', 'muted-dark', 'light']; function getTheme() { if (document.documentElement.classList.contains('light')) return 'light'; if (document.documentElement.classList.contains('muted-dark')) return 'muted-dark'; return 'dark'; } function setTheme(theme) { document.documentElement.classList.remove('light', 'muted-dark'); if (theme === 'light') document.documentElement.classList.add('light'); else if (theme === 'muted-dark') document.documentElement.classList.add('muted-dark'); try { localStorage.setItem('ppf-theme', theme); } catch(e) {} } function initTheme() { var saved = null; try { saved = localStorage.getItem('ppf-theme'); } catch(e) {} if (saved && themes.indexOf(saved) !== -1) { setTheme(saved); } else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) { setTheme('light'); } var btn = document.getElementById('themeToggle'); if (btn) { btn.addEventListener('click', function() { var current = getTheme(); var idx = themes.indexOf(current); var next = themes[(idx + 1) % themes.length]; setTheme(next); }); } } document.addEventListener('DOMContentLoaded', initTheme); // DOM elements var searchInput = document.getElementById('searchInput'); var searchClear = document.getElementById('searchClear'); var suggestions = document.getElementById('suggestions'); var resultsContainer = document.getElementById('resultsContainer'); var resultsEmpty = document.getElementById('resultsEmpty'); var resultsLoading = document.getElementById('resultsLoading'); var resultsList = document.getElementById('resultsList'); var resultsContent = document.getElementById('resultsContent'); var resultsCount = document.getElementById('resultsCount'); var noDataState = document.getElementById('noDataState'); // Stats elements var totalDetections = document.getElementById('totalDetections'); var uniqueCerts = document.getElementById('uniqueCerts'); var uniqueOrgs = document.getElementById('uniqueOrgs'); var uniqueProxies = document.getElementById('uniqueProxies'); // State var searchTimeout = null; var currentData = null; var suggestionIndex = -1; // Search field definitions for autocomplete var searchFields = [ { prefix: 'org:', desc: 'organization name', icon: '🏢' }, { prefix: 'issuer:', desc: 'certificate issuer', icon: '📄' }, { prefix: 'cn:', desc: 'common name', icon: '🔗' }, { prefix: 'proxy:', desc: 'proxy IP address', icon: '🖥' }, { prefix: 'fp:', desc: 'fingerprint', icon: '🔑' }, { prefix: 'serial:', desc: 'serial number', icon: '🔢' }, { prefix: 'expires:', desc: 'expiration year', icon: '📅' }, { prefix: 'expired:', desc: 'yes/no', icon: '⚠' } ]; /** * Escape HTML to prevent XSS */ function escapeHtml(str) { if (str === null || str === undefined) return ''; var div = document.createElement('div'); div.textContent = String(str); return div.innerHTML; } /** * Sanitize search query - remove potentially dangerous characters */ function sanitizeQuery(query) { if (!query) return ''; // Allow alphanumeric, spaces, common punctuation for searches // Remove control characters and dangerous patterns return String(query) .replace(/[\x00-\x1f\x7f]/g, '') // Control characters .replace(/<[^>]*>/g, '') // HTML tags .trim() .substring(0, 200); // Limit length } /** * Format number with comma separators */ function formatNumber(n) { if (n === null || n === undefined || n === '-') return '-'; return String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ','); } /** * Format timestamp to readable date */ function formatDate(ts) { if (!ts) return '-'; // Handle ASN.1 time format (YYYYMMDDhhmmssZ) if (typeof ts === 'string' && ts.length >= 14) { var y = ts.substring(0, 4); var m = ts.substring(4, 6); var d = ts.substring(6, 8); return y + '-' + m + '-' + d; } // Handle Unix timestamp if (typeof ts === 'number') { var date = new Date(ts * 1000); return date.toISOString().split('T')[0]; } return String(ts); } /** * Check if certificate is expired */ function isExpired(notAfter) { if (!notAfter || notAfter.length < 14) return false; var expDate = new Date( parseInt(notAfter.substring(0, 4)), parseInt(notAfter.substring(4, 6)) - 1, parseInt(notAfter.substring(6, 8)) ); return expDate < new Date(); } /** * Load initial stats */ function loadStats() { fetch('/api/mitm') .then(function(r) { return r.json(); }) .then(function(data) { currentData = data; // Update summary stats totalDetections.textContent = formatNumber(data.total_detections || 0); uniqueCerts.textContent = formatNumber(data.unique_certs || 0); uniqueProxies.textContent = formatNumber(data.unique_proxies || 0); // Count unique orgs from top_organizations var orgCount = (data.top_organizations || []).length; uniqueOrgs.textContent = formatNumber(orgCount); // Show no data state if empty if (!data.certificates || data.certificates.length === 0) { document.getElementById('statsSummary').style.display = 'none'; document.querySelector('.search-box').style.display = 'none'; document.getElementById('helpBox').style.display = 'none'; resultsContainer.style.display = 'none'; noDataState.style.display = 'block'; } }) .catch(function(err) { console.error('Failed to load MITM stats:', err); }); } /** * Parse search query into structured filters */ function parseQuery(query) { var filters = { org: null, issuer: null, cn: null, proxy: null, fp: null, serial: null, expires: null, expired: null, text: [] }; if (!query) return filters; // Match field:value patterns and quoted strings var parts = query.match(/(\w+:"[^"]+"|"[^"]+"|[\w:.]+)/g) || []; for (var i = 0; i < parts.length; i++) { var part = parts[i]; var colonIdx = part.indexOf(':'); if (colonIdx > 0 && colonIdx < part.length - 1) { var field = part.substring(0, colonIdx).toLowerCase(); var value = part.substring(colonIdx + 1).replace(/^"|"$/g, ''); if (filters.hasOwnProperty(field)) { filters[field] = value; } else { filters.text.push(part); } } else { // Plain text search filters.text.push(part.replace(/^"|"$/g, '')); } } return filters; } /** * Check if certificate matches filters */ function matchesCert(cert, filters) { // Organization filter if (filters.org) { var org = (cert.subject_o || '').toLowerCase(); if (org.indexOf(filters.org.toLowerCase()) === -1) return false; } // Issuer filter if (filters.issuer) { var issuer = (cert.issuer_cn || cert.issuer_o || '').toLowerCase(); if (issuer.indexOf(filters.issuer.toLowerCase()) === -1) return false; } // Common name filter if (filters.cn) { var cn = (cert.subject_cn || '').toLowerCase(); if (cn.indexOf(filters.cn.toLowerCase()) === -1) return false; } // Proxy filter if (filters.proxy) { var proxies = cert.proxies || []; var found = false; for (var i = 0; i < proxies.length; i++) { if (proxies[i].indexOf(filters.proxy) !== -1) { found = true; break; } } if (!found) return false; } // Fingerprint filter if (filters.fp) { var fp = (cert.fingerprint || cert.fingerprint_full || '').toLowerCase(); if (fp.indexOf(filters.fp.toLowerCase()) === -1) return false; } // Serial filter if (filters.serial) { var serial = (cert.serial || '').toLowerCase(); if (serial.indexOf(filters.serial.toLowerCase()) === -1) return false; } // Expiration year filter if (filters.expires) { var notAfter = cert.not_after || ''; if (notAfter.indexOf(filters.expires) === -1) return false; } // Expired filter if (filters.expired) { var wantExpired = filters.expired.toLowerCase() === 'yes'; var certExpired = isExpired(cert.not_after); if (wantExpired !== certExpired) return false; } // Text search (searches all fields) if (filters.text.length > 0) { var searchable = [ cert.subject_cn || '', cert.subject_o || '', cert.issuer_cn || '', cert.issuer_o || '', cert.fingerprint || '', cert.serial || '' ].join(' ').toLowerCase(); for (var j = 0; j < filters.text.length; j++) { if (searchable.indexOf(filters.text[j].toLowerCase()) === -1) { return false; } } } return true; } /** * Perform search on current data */ function performSearch(query) { query = sanitizeQuery(query); if (!query) { showEmpty(); return; } if (!currentData || !currentData.certificates) { showEmpty(); return; } showLoading(); // Use setTimeout to not block UI setTimeout(function() { var filters = parseQuery(query); var results = []; var certs = currentData.certificates || []; for (var i = 0; i < certs.length; i++) { if (matchesCert(certs[i], filters)) { results.push(certs[i]); } } showResults(results, query); }, 50); } /** * Show empty state */ function showEmpty() { resultsEmpty.style.display = 'block'; resultsLoading.style.display = 'none'; resultsList.style.display = 'none'; } /** * Show loading state */ function showLoading() { resultsEmpty.style.display = 'none'; resultsLoading.style.display = 'block'; resultsList.style.display = 'none'; } /** * Render search results */ function showResults(results, query) { resultsEmpty.style.display = 'none'; resultsLoading.style.display = 'none'; resultsList.style.display = 'block'; resultsCount.textContent = formatNumber(results.length); if (results.length === 0) { resultsContent.innerHTML = '
' + escapeHtml(field.prefix) + '';
html += '' + escapeHtml(field.desc) + '';
html += '