(function () { 'use strict'; var modeClass = { landmark: 'lm', standalone: 'sa', x509: 'x509' }; var modeColor = { landmark: '--lm', standalone: '--sa', x509: '--x509' }; function valueAfter(label, value) { var dt = document.querySelector('dt[data-i18n="' + label + '"]'); if (dt && dt.nextElementSibling) { dt.nextElementSibling.textContent = value == null || value === '' ? 'Unavailable' : String(value); } } function installLiveBadge() { var sub = document.querySelector('.sub'); if (!sub || document.querySelector('.mtc-live-state')) return null; var badge = document.createElement('span'); badge.className = 'mtc-live-state'; badge.textContent = 'Reading this handshake'; badge.style.cssText = 'display:block;margin:14px auto 0;width:max-content;max-width:100%;padding:4px 12px;border:1px solid var(--line);border-radius:999px;color:var(--mut);font:11px/1.4 var(--mono);letter-spacing:.06em'; sub.insertAdjacentElement('afterend', badge); return badge; } function selectLane(mode) { var selectedClass = modeClass[mode] || 'x509'; document.querySelectorAll('.lane').forEach(function (lane) { lane.classList.toggle('active', lane.classList.contains(selectedClass)); }); document.querySelectorAll('.rails .rail').forEach(function (rail, index) { var railClass = ['lm', 'sa', 'x509'][index]; rail.classList.toggle('lit', railClass === selectedClass); }); var info = document.querySelector('.info'); if (info) info.style.setProperty('--mode-c', 'var(' + (modeColor[mode] || '--x509') + ')'); } function hex(bytes) { return bytes.map(function (b) { return ('0' + b.toString(16)).slice(-2); }).join(''); } function hex4(value) { return '0x' + ('000' + value.toString(16)).slice(-4); } function isGrease(value) { return (value & 0x0f0f) === 0x0a0a; } function readU16(bytes, offset) { return (bytes[offset] << 8) | bytes[offset + 1]; } function groupName(value) { var names = { 0x0017: 'secp256r1', 0x0018: 'secp384r1', 0x0019: 'secp521r1', 0x001d: 'x25519', 0x001e: 'x448', 0x11ec: 'X25519MLKEM768' }; return isGrease(value) ? 'GREASE' : (names[value] || hex4(value)); } function versionName(value) { var names = { 0x0301: 'TLSv1.0', 0x0302: 'TLSv1.1', 0x0303: 'TLSv1.2', 0x0304: 'TLSv1.3' }; return isGrease(value) ? 'GREASE' : (names[value] || hex4(value)); } function extensionName(type) { var names = { 0x0000: 'server_name', 0x0005: 'status_request', 0x000a: 'supported_groups', 0x000b: 'ec_point_formats', 0x000d: 'signature_algorithms', 0x0010: 'application_layer_protocol_negotiation', 0x0012: 'signed_certificate_timestamp', 0x0015: 'padding', 0x0017: 'extended_master_secret', 0x001b: 'compress_certificate', 0x0023: 'session_ticket', 0x0029: 'pre_shared_key', 0x002a: 'early_data', 0x002b: 'supported_versions', 0x002c: 'cookie', 0x002d: 'psk_key_exchange_modes', 0x0031: 'post_handshake_auth', 0x0032: 'signature_algorithms_cert', 0x0033: 'key_share', 0xca34: 'trust_anchors (MTC)', 0xfe0d: 'encrypted_client_hello' }; return isGrease(type) ? 'GREASE' : (names[type] || 'unknown'); } function vector16(data, prefixBytes) { var values = []; var offset = prefixBytes; while (offset + 1 < data.length) { values.push(readU16(data, offset)); offset += 2; } return values; } function decodeRelativeOid(bytes) { if (!bytes || bytes.length === 0) return null; var parts = []; var value = 0n; var atStart = true; for (var i = 0; i < bytes.length; i++) { if (atStart && bytes[i] === 0x80) return null; value = (value << 7n) | BigInt(bytes[i] & 0x7f); if ((bytes[i] & 0x80) !== 0) { atStart = false; continue; } parts.push(value.toString()); value = 0n; atStart = true; } return atStart && parts.length ? parts.join('.') : null; } function decodeTrustAnchors(data) { var decoded = { valid: false, ids: [] }; if (!data || data.length < 2) return decoded; var listLength = readU16(data, 0); if (listLength !== data.length - 2) return decoded; var p = 2; while (p < data.length) { var idLength = data[p++]; if (idLength === 0 || p + idLength > data.length) return decoded; var id = data.slice(p, p + idLength); decoded.ids.push({ hex: hex(id), oid: decodeRelativeOid(id) }); p += idLength; } decoded.valid = p === data.length; return decoded; } function extensionDetail(extension, status) { var type = extension.type; var data = extension.data; if (type === 0xca34) { var decodedAnchors = decodeTrustAnchors(data); if (!decodedAnchors.valid) return 'malformed trust_anchors: ' + hex(data); return 'IDs: ' + decodedAnchors.ids.map(function (id) { return (id.oid || 'invalid Relative OID') + ' (' + id.hex + ')'; }).join(', '); } if (type === 0x000a && data.length >= 2) { return vector16(data, 2).map(groupName).join(', '); } if (type === 0x002b && data.length >= 1) { return vector16(data, 1).map(versionName).join(', '); } if (type === 0x002d && data.length >= 1) { return data.slice(1).map(function (mode) { return mode === 1 ? 'psk_dhe_ke' : (mode === 0 ? 'psk_ke' : String(mode)); }).join(', '); } if (type === 0x001b && data.length >= 3) { return vector16(data, 1).map(function (algorithm) { return ({ 1: 'zlib', 2: 'brotli', 3: 'zstd' })[algorithm] || String(algorithm); }).join(', '); } if (type === 0x000b && data.length >= 1) { return data.slice(1).map(function (format) { return format === 0 ? 'uncompressed' : String(format); }).join(', '); } if (type === 0xfe0d || type === 0x0033 || type === 0x000d || type === 0x0032) { return data.length + ' bytes'; } return data.length === 0 ? '' : hex(data); } function parseExtensions(raw) { var parsed = { valid: false, extensions: [] }; if (!raw || raw.length < 8 || raw.length % 2 !== 0 || !/^[0-9a-f]+$/i.test(raw)) return parsed; var bytes = []; for (var i = 0; i < raw.length; i += 2) bytes.push(parseInt(raw.slice(i, i + 2), 16)); var p = 0; if (bytes[p++] !== 1 || p + 3 > bytes.length) return parsed; var bodyLength = (bytes[p] << 16) | (bytes[p + 1] << 8) | bytes[p + 2]; p += 3; var end = bodyLength + 4; if (end !== bytes.length || p + 34 > end) return parsed; p += 2 + 32; if (p >= end) return parsed; var sessionLength = bytes[p++]; if (p + sessionLength + 2 > end) return parsed; p += sessionLength; var cipherLength = readU16(bytes, p); p += 2; if (cipherLength < 2 || cipherLength % 2 !== 0 || p + cipherLength >= end) return parsed; p += cipherLength; var compressionLength = bytes[p++]; if (compressionLength < 1 || p + compressionLength > end) return parsed; p += compressionLength; if (p === end) { parsed.valid = true; return parsed; } if (p + 2 > end) return parsed; var extensionsLength = readU16(bytes, p); p += 2; if (p + extensionsLength !== end) return parsed; while (p < end) { if (p + 4 > end) return parsed; var type = readU16(bytes, p); var length = readU16(bytes, p + 2); p += 4; if (p + length > end) return parsed; parsed.extensions.push({ type: type, length: length, data: bytes.slice(p, p + length) }); p += length; } parsed.valid = p === end; return parsed; } window.__MTC_PARSE_CLIENT_HELLO__ = parseExtensions; window.__MTC_DECODE_TRUST_ANCHORS__ = decodeTrustAnchors; function renderExtensions(status, container) { var hello = status.client_hello || {}; var parsed = parseExtensions(hello.raw || ''); var extensions = parsed.extensions; container.textContent = ''; if (!hello.present || hello.truncated || hello.capture_failed) { var unavailable = document.createElement('div'); unavailable.className = 'info'; unavailable.textContent = hello.truncated ? 'ClientHello capture exceeded the display limit.' : 'Raw ClientHello unavailable.'; container.appendChild(unavailable); return; } if (!parsed.valid) { var malformed = document.createElement('div'); malformed.className = 'info'; malformed.textContent = 'Captured ClientHello framing is malformed.'; container.appendChild(malformed); return; } if (extensions.length === 0) { var empty = document.createElement('div'); empty.className = 'info'; empty.textContent = 'ClientHello contains no extensions.'; container.appendChild(empty); return; } var table = document.createElement('table'); table.className = 'anchors'; var thead = document.createElement('thead'); var header = document.createElement('tr'); ['Type', 'Extension', 'Len', 'Details'].forEach(function (label) { var th = document.createElement('th'); th.textContent = label; header.appendChild(th); }); thead.appendChild(header); table.appendChild(thead); var tbody = document.createElement('tbody'); extensions.forEach(function (extension) { var name = extensionName(extension.type); if (name === 'unknown') return; var tr = document.createElement('tr'); if (extension.type === 0xca34) tr.className = 'ext-hl'; var values = [hex4(extension.type), name, String(extension.length), extensionDetail(extension, status)]; values.forEach(function (value, index) { var td = document.createElement('td'); td.textContent = value; if (index === 0) td.className = 'oid'; if (index === 3) td.classList.add('wr'); tr.appendChild(td); }); tbody.appendChild(tr); }); table.appendChild(tbody); container.appendChild(table); } function renderClientHello(status) { valueAfter('chLegacyVer', status.legacy_version); valueAfter('chRandom', status.client_random); valueAfter('chGroup', status.negotiated_group); valueAfter('chCiphers', status.offered_cipher_count == null ? 'Unavailable' : status.offered_cipher_count); var parse = document.getElementById('chparse'); if (!parse) return; renderExtensions(status, parse); } var badge = installLiveBadge(); fetch('/__mtc_status', { cache: 'no-store', credentials: 'same-origin' }) .then(function (response) { if (!response.ok) throw new Error('HTTP ' + response.status); return response.json(); }) .then(function (status) { selectLane(status.mode); valueAfter('certMode', status.certificate); valueAfter('proto', status.protocol); valueAfter('cipher', status.cipher); renderClientHello(status); if (badge) { badge.textContent = 'Live handshake · ' + status.protocol + ' · ' + status.certificate; badge.style.color = 'var(' + (modeColor[status.mode] || '--x509') + ')'; } window.__MTC_LIVE_STATUS__ = status; document.documentElement.classList.remove('mtc-live-pending'); }) .catch(function (error) { if (badge) { badge.textContent = 'Live handshake data unavailable'; badge.style.color = '#ff7b7b'; } document.documentElement.classList.remove('mtc-live-pending'); console.error('MTC live status:', error); }); }());