diff --git a/src/routes/api/internal/diagnostics/tls-handshake/+server.ts b/src/routes/api/internal/diagnostics/tls-handshake/+server.ts index e8e90764..68de94e3 100644 --- a/src/routes/api/internal/diagnostics/tls-handshake/+server.ts +++ b/src/routes/api/internal/diagnostics/tls-handshake/+server.ts @@ -6,6 +6,7 @@ import { errorManager } from '$lib/utils/error-manager'; interface TLSHandshakeRequest { hostname: string; + servername?: string; port?: number; } @@ -18,6 +19,7 @@ interface HandshakePhase { interface TLSHandshakeResponse { hostname: string; + servername?: string; port: number; success: boolean; totalTime: number; @@ -38,7 +40,7 @@ interface TLSHandshakeResponse { export const POST: RequestHandler = async ({ request }) => { try { const body: TLSHandshakeRequest = await request.json(); - const { hostname, port = 443 } = body; + const { hostname, port = 443, servername } = body; if (!hostname || typeof hostname !== 'string' || !hostname.trim()) { throw error(400, 'Hostname is required'); @@ -46,7 +48,7 @@ export const POST: RequestHandler = async ({ request }) => { const trimmedHostname = hostname.trim(); - const result = await performTLSHandshake(trimmedHostname, port); + const result = await performTLSHandshake(trimmedHostname, port, servername); return json(result); } catch (err) { errorManager.captureException(err, 'error', { component: 'TLS Handshake API' }); @@ -57,7 +59,7 @@ export const POST: RequestHandler = async ({ request }) => { } }; -function performTLSHandshake(hostname: string, port: number): Promise { +function performTLSHandshake(hostname: string, port: number, servername?: string): Promise { return new Promise((resolve, reject) => { const phases: HandshakePhase[] = []; const startTime = Date.now(); @@ -101,7 +103,7 @@ function performTLSHandshake(hostname: string, port: number): Promise { +async function testCipherPresets(hostname: string, port: number = 443, servername?: string): Promise { // First verify the host is reachable by attempting a basic TLS connection try { await new Promise((resolve, reject) => { @@ -396,6 +397,7 @@ async function testCipherPresets(hostname: string, port: number = 443): Promise< { host: hostname, port, + servername: servername || hostname, // SECURITY: rejectUnauthorized must be false to test cipher presets (see above) rejectUnauthorized: false, }, @@ -874,7 +876,7 @@ export const POST: RequestHandler = async ({ request }) => { } case 'cipher-presets': { - const { hostname, port = 443 } = body as CipherPresetsReq; + const { hostname, port = 443, servername } = body as CipherPresetsReq; // Validate hostname if (!hostname || typeof hostname !== 'string' || hostname.trim() === '') { @@ -886,8 +888,8 @@ export const POST: RequestHandler = async ({ request }) => { throw error(400, 'Invalid port number'); } - const result = await testCipherPresets(hostname, port); - return json({ ...result, hostname, port }); + const result = await testCipherPresets(hostname, port, servername); + return json({ ...result, hostname, port, servername }); } case 'banner': { diff --git a/src/routes/diagnostics/tls/banner/+page.svelte b/src/routes/diagnostics/tls/banner/+page.svelte index a0c68eec..0f170fc2 100644 --- a/src/routes/diagnostics/tls/banner/+page.svelte +++ b/src/routes/diagnostics/tls/banner/+page.svelte @@ -7,6 +7,8 @@ let host = $state(''); let port = $state(null); + let servername = $state(''); + let useCustomServername = $state(false); let service = $state('custom'); const diagnosticState = useDiagnosticState(); @@ -79,6 +81,7 @@ body: JSON.stringify({ action: 'banner', host: host.trim(), + servername: useCustomServername && servername ? servername.trim() : undefined, port: port, }), }); @@ -210,15 +213,45 @@ - +
+
+ + {#if useCustomServername} + { + examples.clear(); + if (isInputValid()) grabBanner(); + }} + /> + {/if} +
+
+ +
+ +
diff --git a/src/routes/diagnostics/tls/cipher-presets/+page.svelte b/src/routes/diagnostics/tls/cipher-presets/+page.svelte index 2cb1e4e6..e74bcc33 100644 --- a/src/routes/diagnostics/tls/cipher-presets/+page.svelte +++ b/src/routes/diagnostics/tls/cipher-presets/+page.svelte @@ -7,6 +7,8 @@ let hostname = $state('example.com'); let port = $state('443'); + let servername = $state(''); + let useCustomServername = $state(false); const diagnosticState = useDiagnosticState(); const examplesList = [ { host: 'github.com', port: '443', description: 'GitHub cipher support' }, @@ -14,6 +16,17 @@ { host: 'google.com', port: '443', description: 'Google cipher support' }, ]; const examples = useExamples(examplesList); + + // Reactive validation + const isInputValid = $derived(() => { + const trimmedHost = host.trim(); + if (!trimmedHost) return false; + if (port === null || port < 1 || port > 65535) return false; + // Basic hostname/IP validation + const hostPattern = + /^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$|^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$|^\[?[a-fA-F0-9:]+\]?$/; + return hostPattern.test(trimmedHost); + }); async function testCiphers() { if (!hostname?.trim()) { @@ -30,6 +43,7 @@ body: JSON.stringify({ action: 'cipher-presets', hostname: hostname.trim().toLowerCase(), + servername: useCustomServername && servername ? servername.trim() : undefined, port: parseInt(port) || 443, }), }); @@ -94,40 +108,73 @@

Cipher Presets Configuration

-
- -
- examples.clear()} - onkeydown={(e) => e.key === 'Enter' && testCiphers()} - class="flex-grow" - /> - examples.clear()} - onkeydown={(e) => e.key === 'Enter' && testCiphers()} - class="port-input" - /> - +
+
+ +
+ examples.clear()} + onkeydown={(e) => e.key === 'Enter' && testCiphers()} + class="flex-grow" + /> + examples.clear()} + onkeydown={(e) => e.key === 'Enter' && testCiphers()} + class="port-input" + /> +
+
+
+ +
+
+ + {#if useCustomServername} + { + examples.clear(); + if (isInputValid()) testCiphers(); + }} + /> + {/if}
+ +
+ +
diff --git a/src/routes/diagnostics/tls/handshake-analyzer/+page.svelte b/src/routes/diagnostics/tls/handshake-analyzer/+page.svelte index f8d93094..04935900 100644 --- a/src/routes/diagnostics/tls/handshake-analyzer/+page.svelte +++ b/src/routes/diagnostics/tls/handshake-analyzer/+page.svelte @@ -34,6 +34,8 @@ let hostname = $state('google.com'); let port = $state(443); + let servername = $state(''); + let useCustomServername = $state(false); const diagnosticState = useDiagnosticState(); const clipboard = useClipboard(); const examplesList = [ @@ -45,6 +47,17 @@ { hostname: 'zoom.us', port: 443, description: 'Zoom' }, ]; const examples = useExamples(examplesList); + + // Reactive validation + const isInputValid = $derived(() => { + const trimmedHost = host.trim(); + if (!trimmedHost) return false; + if (port === null || port < 1 || port > 65535) return false; + // Basic hostname/IP validation + const hostPattern = + /^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$|^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$|^\[?[a-fA-F0-9:]+\]?$/; + return hostPattern.test(trimmedHost); + }); async function analyzeHandshake() { diagnosticState.startOperation(); @@ -53,7 +66,11 @@ const response = await fetch('/api/internal/diagnostics/tls-handshake', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ hostname: hostname.trim(), port }), + body: JSON.stringify({ + hostname: hostname.trim(), + port, + servername: useCustomServername && servername ? servername.trim() : undefined + }), }); if (!response.ok) { @@ -124,8 +141,8 @@

Handshake Analysis

-
-
+
+
Port
+
+ +
+
+ + {#if useCustomServername} + { + examples.clear(); + if (isInputValid()) analyzeHandshake(); + }} + /> + {/if} +
+
+ +