diff --git a/app.py b/app.py index 595bb3c..9621125 100644 --- a/app.py +++ b/app.py @@ -3234,6 +3234,109 @@ def infer_tls_terminator(process_name, local_port, protocol, tls_listener_names) return "unknown" return "upstream-or-external-lb" +def parse_proc_crypto_entries(): + """Parse /proc/crypto into a list of dict entries.""" + entries = [] + try: + with open("/proc/crypto", "r", encoding="utf-8", errors="ignore") as f: + raw = f.read() + except Exception: + return entries + + blocks = [block.strip() for block in raw.split("\n\n") if block.strip()] + for block in blocks: + item = {} + for line in block.splitlines(): + if ":" not in line: + continue + key, value = line.split(":", 1) + item[key.strip().lower()] = value.strip() + if item: + entries.append(item) + return entries + +def collect_algorithm_competition(requested_algorithm="aes"): + """ + Build algorithm implementation competition using kernel crypto registry. + The winner is the implementation with highest priority. + """ + entries = parse_proc_crypto_entries() + requested = (requested_algorithm or "aes").lower() + req_type_allow = { + "aes": {"skcipher", "aead", "cipher"}, + "sha": {"shash", "ahash", "hash"}, + "chacha20": {"skcipher", "aead", "cipher"} + } + req_tokens = { + "aes": ["aes"], + "sha": ["sha"], + "chacha20": ["chacha20", "xchacha20", "chacha"] + } + allowed_types = req_type_allow.get(requested, {"skcipher", "aead", "cipher", "shash", "ahash", "hash"}) + tokens = req_tokens.get(requested, [requested]) + candidates = [] + + for entry in entries: + name = str(entry.get("name", "")).lower() + driver = str(entry.get("driver", "")).lower() + alg_type = str(entry.get("type", "")).lower() + + if not any(token in name or token in driver for token in tokens): + continue + if alg_type and alg_type not in allowed_types: + continue + + try: + priority = int(entry.get("priority", "0") or 0) + except ValueError: + priority = 0 + + impl_name = driver or name or "unknown-impl" + candidates.append({ + "name": impl_name, + "priority": priority, + "type": alg_type or "unknown", + "source": "kernel" + }) + + # Deduplicate by implementation name, keep the highest priority variant. + dedup = {} + for item in candidates: + existing = dedup.get(item["name"]) + if existing is None or item["priority"] > existing["priority"]: + dedup[item["name"]] = item + candidates = list(dedup.values()) + candidates.sort(key=lambda x: x["priority"], reverse=True) + + if not candidates: + # Fallback keeps the UX informative on hosts without readable /proc/crypto. + fallback_map = { + "aes": [ + {"name": "aesni-intel", "priority": 300, "type": "skcipher", "source": "mock"}, + {"name": "aes-avx", "priority": 200, "type": "skcipher", "source": "mock"}, + {"name": "aes-generic", "priority": 100, "type": "skcipher", "source": "mock"} + ], + "sha": [ + {"name": "sha256-avx2", "priority": 240, "type": "shash", "source": "mock"}, + {"name": "sha256-ssse3", "priority": 180, "type": "shash", "source": "mock"}, + {"name": "sha256-generic", "priority": 100, "type": "shash", "source": "mock"} + ], + "chacha20": [ + {"name": "chacha20-neon", "priority": 260, "type": "skcipher", "source": "mock"}, + {"name": "chacha20-simd", "priority": 220, "type": "skcipher", "source": "mock"}, + {"name": "chacha20-generic", "priority": 100, "type": "skcipher", "source": "mock"} + ] + } + candidates = fallback_map.get(requested, fallback_map["aes"]) + + selected = candidates[0] if candidates else None + return { + "request": requested.upper(), + "implementations": candidates[:8], + "selected": selected, + "selection_policy": "max-priority" + } + def collect_crypto_realtime(): """ Build a near-realtime list of processes likely interacting with kernel crypto. @@ -3390,6 +3493,12 @@ def collect_crypto_realtime(): if p not in unique_processes: unique_processes.append(p) + algorithm_competitions = { + "aes": collect_algorithm_competition("aes"), + "sha": collect_algorithm_competition("sha"), + "chacha20": collect_algorithm_competition("chacha20") + } + return { "items": items[:24], "processes": unique_processes[:16], @@ -3399,6 +3508,8 @@ def collect_crypto_realtime(): "active_flows": active_flows, "unknown_pid_flows": int(unknown_pid_flows), "tls_terminators": sorted(list(tls_listener_names))[:8], + "algorithm_competition": algorithm_competitions["aes"], + "algorithm_competitions": algorithm_competitions, "source": "live-heuristic-v2", "timestamp": datetime.utcnow().isoformat() + "Z" } diff --git a/index.html b/index.html index 4d85e3e..fb3efdb 100755 --- a/index.html +++ b/index.html @@ -100,7 +100,7 @@

Linux Kernel Ring 0 Visualization

- + diff --git a/static/js/crypto-belt.js b/static/js/crypto-belt.js index 90c7b42..4284466 100644 --- a/static/js/crypto-belt.js +++ b/static/js/crypto-belt.js @@ -19,6 +19,8 @@ class CryptoSubsystemVisualization { this.prevLaneKeys = new Set(); this.laneHistory = new Map(); this.recentlyGone = []; + this.selectedCompetitionAlgorithm = 'AES'; + this.algorithmModes = ['AES', 'SHA', 'CHACHA20']; } init(containerId = 'crypto-belt-container') { @@ -535,6 +537,162 @@ class CryptoSubsystemVisualization { }); } + getCompetitionPayload(meta) { + const selected = String(this.selectedCompetitionAlgorithm || 'AES').toLowerCase(); + const groups = meta?.algorithm_competitions || null; + if (groups && groups[selected]) return groups[selected]; + return meta?.algorithm_competition || { + request: this.selectedCompetitionAlgorithm, + implementations: [], + selected: null, + selection_policy: 'max-priority' + }; + } + + drawAlgorithmCompetition(layer, meta, width) { + const comp = this.getCompetitionPayload(meta); + const request = String(comp.request || this.selectedCompetitionAlgorithm || 'AES').toUpperCase(); + const impls = Array.isArray(comp.implementations) ? comp.implementations.slice(0, 5) : []; + const selectedName = String(comp?.selected?.name || '').toLowerCase(); + + const panelX = Math.floor(width * 0.73); + const panelY = 130; + const panelW = Math.max(260, Math.floor(width * 0.24)); + const panelH = Math.max(220, 170 + impls.length * 30); + + const panel = layer.append('g').attr('class', 'crypto-algo-competition'); + panel.append('rect') + .attr('x', panelX) + .attr('y', panelY) + .attr('width', panelW) + .attr('height', panelH) + .attr('rx', 8) + .style('fill', 'rgba(8, 11, 16, 0.88)') + .style('stroke', 'rgba(165, 178, 200, 0.35)') + .style('stroke-width', 1); + + panel.append('text') + .attr('x', panelX + 14) + .attr('y', panelY + 22) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '11px') + .style('fill', '#d7ddea') + .text('ALGORITHM COMPETITION'); + + const toggleY = panelY + 38; + this.algorithmModes.forEach((mode, idx) => { + const isActive = mode === request; + const btnX = panelX + 14 + idx * 86; + const btn = panel.append('g') + .attr('class', 'algo-toggle-btn') + .style('cursor', 'pointer') + .on('click', () => { + this.selectedCompetitionAlgorithm = mode; + this.renderFlowMap(this.lastPayload || this.normalizeTelemetry(this.getFallbackTelemetry())); + }); + + btn.append('rect') + .attr('x', btnX) + .attr('y', toggleY) + .attr('width', 78) + .attr('height', 18) + .attr('rx', 4) + .style('fill', isActive ? 'rgba(32, 52, 81, 0.92)' : 'rgba(12, 16, 22, 0.85)') + .style('stroke', isActive ? 'rgba(124, 178, 255, 0.9)' : 'rgba(150, 162, 182, 0.35)') + .style('stroke-width', isActive ? 1.1 : 0.8); + + btn.append('text') + .attr('x', btnX + 39) + .attr('y', toggleY + 12) + .attr('text-anchor', 'middle') + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '9px') + .style('letter-spacing', '0.3px') + .style('fill', isActive ? '#cfe2ff' : '#a7b3c5') + .text(mode); + }); + + panel.append('text') + .attr('x', panelX + 14) + .attr('y', panelY + 67) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '10px') + .style('fill', '#99a8bd') + .text(`request ${request} -> lookup -> pick max priority`); + + const stepsY = panelY + 92; + panel.append('text') + .attr('x', panelX + 14) + .attr('y', stepsY) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '10px') + .style('fill', '#b3bece') + .text(`${request} REQUEST`); + + panel.append('line') + .attr('x1', panelX + 20) + .attr('y1', stepsY + 8) + .attr('x2', panelX + 20) + .attr('y2', stepsY + 28) + .style('stroke', '#7c8ca2') + .style('stroke-width', 1); + + panel.append('text') + .attr('x', panelX + 14) + .attr('y', stepsY + 42) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '10px') + .style('fill', '#b3bece') + .text('CRYPTO LOOKUP'); + + const baseY = stepsY + 64; + if (!impls.length) { + panel.append('text') + .attr('x', panelX + 14) + .attr('y', baseY) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '10px') + .style('fill', '#97a5ba') + .text('No implementations detected'); + return; + } + + impls.forEach((impl, idx) => { + const y = baseY + idx * 30; + const name = String(impl.name || 'unknown'); + const prio = Number(impl.priority || 0); + const isSelected = name.toLowerCase() === selectedName; + + panel.append('rect') + .attr('x', panelX + 12) + .attr('y', y - 12) + .attr('width', panelW - 24) + .attr('height', 22) + .attr('rx', 5) + .style('fill', isSelected ? 'rgba(20, 39, 29, 0.9)' : 'rgba(14, 18, 24, 0.85)') + .style('stroke', isSelected ? 'rgba(114, 242, 173, 0.8)' : 'rgba(150, 162, 182, 0.28)') + .style('stroke-width', isSelected ? 1.2 : 0.8); + + panel.append('text') + .attr('x', panelX + 20) + .attr('y', y + 2) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '10px') + .style('fill', isSelected ? '#9effca' : '#c5cedb') + .text(`${name} priority ${prio}`); + + if (isSelected) { + panel.append('text') + .attr('x', panelX + panelW - 70) + .attr('y', y + 2) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '9px') + .style('fill', '#9effca') + .text('SELECTED'); + } + }); + } + drawNode(group, x, y, label, level, intensity, palette, emphasis) { const width = Math.min(Math.max(150, String(label).length * 8 + 28), 250); const height = 34; @@ -635,6 +793,7 @@ class CryptoSubsystemVisualization { const layer = this.svg.append('g').attr('class', 'crypto-flow-layer'); this.drawGrid(layer, width, height); this.drawProtocolLegend(layer); + this.drawAlgorithmCompetition(layer, payload?.meta || {}, width); const lanes = Array.isArray(payload.items) ? payload.items : []; const topY = 150; @@ -644,7 +803,7 @@ class CryptoSubsystemVisualization { const endpointY = 520; const startX = width * 0.16; - const usableWidth = width * 0.70; + const usableWidth = width * 0.52; const laneCount = Math.max(lanes.length, 1); const laneStep = laneCount > 1 ? usableWidth / (laneCount - 1) : 0; @@ -758,6 +917,48 @@ class CryptoSubsystemVisualization { ops_per_sec: 960, tls_sessions: 2, active_flows: 3, + algorithm_competition: { + request: 'AES', + implementations: [ + { name: 'aesni-intel', priority: 300, type: 'skcipher' }, + { name: 'aes-avx', priority: 200, type: 'skcipher' }, + { name: 'aes-generic', priority: 100, type: 'skcipher' } + ], + selected: { name: 'aesni-intel', priority: 300, type: 'skcipher' }, + selection_policy: 'max-priority' + }, + algorithm_competitions: { + aes: { + request: 'AES', + implementations: [ + { name: 'aesni-intel', priority: 300, type: 'skcipher' }, + { name: 'aes-avx', priority: 200, type: 'skcipher' }, + { name: 'aes-generic', priority: 100, type: 'skcipher' } + ], + selected: { name: 'aesni-intel', priority: 300, type: 'skcipher' }, + selection_policy: 'max-priority' + }, + sha: { + request: 'SHA', + implementations: [ + { name: 'sha256-avx2', priority: 240, type: 'shash' }, + { name: 'sha256-ssse3', priority: 180, type: 'shash' }, + { name: 'sha256-generic', priority: 100, type: 'shash' } + ], + selected: { name: 'sha256-avx2', priority: 240, type: 'shash' }, + selection_policy: 'max-priority' + }, + chacha20: { + request: 'CHACHA20', + implementations: [ + { name: 'chacha20-neon', priority: 260, type: 'skcipher' }, + { name: 'chacha20-simd', priority: 220, type: 'skcipher' }, + { name: 'chacha20-generic', priority: 100, type: 'skcipher' } + ], + selected: { name: 'chacha20-neon', priority: 260, type: 'skcipher' }, + selection_policy: 'max-priority' + } + }, source: 'mock' } }; @@ -786,7 +987,10 @@ class CryptoSubsystemVisualization { const source = String(data?.meta?.source || 'api'); const unknownPid = Number(data?.meta?.unknown_pid_flows || 0); const terms = Array.isArray(data?.meta?.tls_terminators) ? data.meta.tls_terminators.join(',') : '-'; - this.telemetryNode.textContent = `ops/s: ${ops} | tls: ${tls} | active: ${flows} | unknown-pid: ${unknownPid} | terminator: ${terms || '-'} | source: ${source}`; + const selectedComp = this.getCompetitionPayload(data?.meta || {}); + const selectedImpl = String(selectedComp?.selected?.name || '-'); + const reqLabel = String(selectedComp?.request || this.selectedCompetitionAlgorithm || 'AES').toUpperCase(); + this.telemetryNode.textContent = `ops/s: ${ops} | tls: ${tls} | active: ${flows} | unknown-pid: ${unknownPid} | terminator: ${terms || '-'} | ${reqLabel}: ${selectedImpl} | source: ${source}`; } }) .catch(() => {