@jshookmcp/jshook 0.2.9 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/README.zh.md +2 -2
- package/dist/{AntiCheatDetector-BNk-EoBt.mjs → AntiCheatDetector-CqGDXmfc.mjs} +159 -53
- package/dist/{CodeInjector-Cq8q01kp.mjs → CodeInjector-BdjRfNx7.mjs} +5 -5
- package/dist/{ConsoleMonitor-CPVQW1Y-.mjs → ConsoleMonitor-DykL3IAw.mjs} +85 -17
- package/dist/{DetailedDataManager-BQQcxh64.mjs → DetailedDataManager-HT49OrvF.mjs} +1 -1
- package/dist/{ExtensionManager-CWYgw0YW.mjs → ExtensionManager-BDMsY2Dz.mjs} +15 -8
- package/dist/{HardwareBreakpoint-B9gZCdFP.mjs → HardwareBreakpoint-Cc2AFq1Y.mjs} +3 -3
- package/dist/{HeapAnalyzer-BLDH0dCv.mjs → HeapAnalyzer-DruMgsgj.mjs} +20 -20
- package/dist/{HookGeneratorBuilders.core.generators.storage-CtcdK78Q.mjs → HookGeneratorBuilders.core.generators.storage-CTbB4Lcx.mjs} +1 -74
- package/dist/{InstrumentationSession-CvPC7Jwy.mjs → InstrumentationSession-DLH0vd-z.mjs} +2 -2
- package/dist/{MemoryController-CbVdCIJF.mjs → MemoryController-CMtviNW_.mjs} +3 -3
- package/dist/{MemoryScanSession-BsDZbLYm.mjs → MemoryScanSession-ITgb_NMi.mjs} +2 -2
- package/dist/{MemoryScanner-Bcpml6II.mjs → MemoryScanner-CiL7Z3ey.mjs} +12 -9
- package/dist/{NativeMemoryManager.impl-dZtA1ZGn.mjs → NativeMemoryManager.impl-D9Lkovvn.mjs} +13 -10
- package/dist/{NativeMemoryManager.utils-B-FjA2mJ.mjs → NativeMemoryManager.utils-BBlAixF5.mjs} +1 -1
- package/dist/{PEAnalyzer-D1lzJ_VG.mjs → PEAnalyzer-DMQ44gen.mjs} +15 -15
- package/dist/{PageController-Bqm2kZ_X.mjs → PageController-BPJNqqBN.mjs} +18 -4
- package/dist/{PointerChainEngine-BOhyVsjx.mjs → PointerChainEngine-K7wN8Z-w.mjs} +10 -7
- package/dist/ProcessRegistry-zGg12QbE.mjs +74 -0
- package/dist/{ResponseBuilder-D3iFYx2N.mjs → ResponseBuilder-CJXWmWNw.mjs} +10 -10
- package/dist/{ScriptManager-aHHq0X7U.mjs → ScriptManager-ZuWD-0Jg.mjs} +195 -192
- package/dist/{Speedhack-CqdIFlQl.mjs → Speedhack-D-z0umeT.mjs} +2 -2
- package/dist/{StructureAnalyzer-DhFaPvRO.mjs → StructureAnalyzer-Cav5AVSL.mjs} +9 -6
- package/dist/{ToolCatalog-C0JGZoOm.mjs → ToolCatalog-5OJdMiF0.mjs} +81 -81
- package/dist/{ToolProbe-oC7aPrkv.mjs → ToolProbe-DbCFGyrg.mjs} +1 -1
- package/dist/{ToolRegistry-BjaF4oNz.mjs → ToolRegistry-B9krbTtI.mjs} +51 -2
- package/dist/{ToolRouter.policy-BWV67ZK-.mjs → ToolRouter.policy-BGDAGyeH.mjs} +60 -20
- package/dist/TraceRecorder-B41Z5XBj.mjs +1286 -0
- package/dist/{Win32API-CePkipZY.mjs → Win32API-C2kjj0ze.mjs} +18 -12
- package/dist/{Win32Debug-BvKs-gxc.mjs → Win32Debug-CKrGOTpo.mjs} +2 -2
- package/dist/{WorkflowEngine-CuvkZtWu.mjs → WorkflowEngine-DJ6M4opp.mjs} +226 -255
- package/dist/analysis-BHeJW2Nb.mjs +1234 -0
- package/dist/{antidebug-CqDTB_uk.mjs → antidebug-BRKeyt27.mjs} +3 -3
- package/dist/{artifactRetention-CFEprwPw.mjs → artifactRetention-CPXkUJXp.mjs} +13 -6
- package/dist/{artifacts-Bk2-_uPq.mjs → artifacts-DkfosXH3.mjs} +1 -1
- package/dist/authorization-schema-DRqyJMSk.mjs +31 -0
- package/dist/{binary-instrument-CXfpx6fT.mjs → binary-instrument--V3MAhJ4.mjs} +19 -27
- package/dist/bind-helpers-ClV34xdn.mjs +42 -0
- package/dist/{boringssl-inspector-BH2D3VKc.mjs → boringssl-inspector-Bo_LOLaS.mjs} +1 -1
- package/dist/{browser-BpOr5PEx.mjs → browser-Dx3_S2cG.mjs} +324 -37
- package/dist/capabilities-CcHlvWgK.mjs +33 -0
- package/dist/{constants-B0OANIBL.mjs → constants-CDZLOoVv.mjs} +18 -3
- package/dist/{coordination-qUbyF8KU.mjs → coordination-DgItD9DL.mjs} +2 -2
- package/dist/{debugger-gnKxRSN0.mjs → debugger-RS3RSAqs.mjs} +30 -13
- package/dist/definitions-BEoYofW5.mjs +47 -0
- package/dist/{definitions-bAhHQJq9.mjs → definitions-BRaefg3u.mjs} +11 -5
- package/dist/{definitions-DVGfrn7y.mjs → definitions-BbkvZkiv.mjs} +2 -2
- package/dist/definitions-BtWSHJ3o.mjs +17 -0
- package/dist/{definitions-BMfYXoNC.mjs → definitions-C1gCHO0i.mjs} +1 -1
- package/dist/{definitions-C1UvM5Iy.mjs → definitions-CDOg_b-l.mjs} +14 -2
- package/dist/definitions-CVPD9hzZ.mjs +54 -0
- package/dist/{definitions-Cke7zEb8.mjs → definitions-Cea8Lgl7.mjs} +1 -1
- package/dist/definitions-DAgIyjxM.mjs +10 -0
- package/dist/{definitions-B4rAvHNZ.mjs → definitions-DJA27nsL.mjs} +12 -9
- package/dist/{definitions-ClJLzsJQ.mjs → definitions-DKPFU3LW.mjs} +1 -1
- package/dist/{definitions-D3VsGcvz.mjs → definitions-DPRpZQ96.mjs} +7 -7
- package/dist/{definitions-B18eyf0B.mjs → definitions-DUE5gmdn.mjs} +1 -1
- package/dist/definitions-DYVjOtxa.mjs +26 -0
- package/dist/{definitions-BB_4jnmy.mjs → definitions-DcYLVLCo.mjs} +1 -1
- package/dist/{definitions-Beid2EB3.mjs → definitions-Pp5LI2H4.mjs} +1 -1
- package/dist/definitions-j9KdHVNR.mjs +14 -0
- package/dist/definitions-uzkjBwa7.mjs +258 -0
- package/dist/{definitions-Cq-zroAU.mjs → definitions-va-AnLuQ.mjs} +4 -4
- package/dist/{encoding-Bvz5jLRv.mjs → encoding-DJeqHmpd.mjs} +18 -4
- package/dist/{evidence-graph-bridge-C_fv9PuC.mjs → evidence-graph-bridge-DcYizFk2.mjs} +1 -0
- package/dist/{factory-DxlGh9Xf.mjs → factory-C90tBff6.mjs} +6 -6
- package/dist/flat-target-session-Dgax2Cy3.mjs +29 -0
- package/dist/{graphql-DYWzJ29s.mjs → graphql-CoHrhweh.mjs} +205 -34
- package/dist/{handlers-C67ktuRN.mjs → handlers-4jmR0nMs.mjs} +220 -32
- package/dist/{handlers-DlCJN4Td.mjs → handlers-BAHPxcch.mjs} +122 -90
- package/dist/{handlers-9sAbfIg-.mjs → handlers-BOs9b907.mjs} +849 -801
- package/dist/{handlers-DxGIq15_2.mjs → handlers-BWXEy6ef.mjs} +16 -16
- package/dist/{handlers-tB9Mp9ZK.mjs → handlers-Bndn6QvE.mjs} +31 -4
- package/dist/{handlers-CTsDAO6p.mjs → handlers-BqC4bD4s.mjs} +1 -1
- package/dist/{handlers-C87g8oCe.mjs → handlers-BtYq60bM2.mjs} +1 -1
- package/dist/{handlers-DeLOCd5m.mjs → handlers-BzgcB4iv.mjs} +17 -17
- package/dist/{handlers-Cgyg6c0U.mjs → handlers-CRyRWj2b.mjs} +237 -23
- package/dist/{handlers-U6L4xhuF.mjs → handlers-CVv2H1uq.mjs} +24 -17
- package/dist/{handlers-tiy7EIBp.mjs → handlers-Dl5a7JS4.mjs} +3 -3
- package/dist/{handlers-D6j6yka7.mjs → handlers-Dx2d7jt7.mjs} +1893 -1480
- package/dist/{handlers-Bl8zkwz1.mjs → handlers-Dz9PYsCa.mjs} +95 -6
- package/dist/handlers-HujRKC3b.mjs +661 -0
- package/dist/{handlers.impl-DS0d9fUw.mjs → handlers.impl-XWXkQfyi.mjs} +70 -24
- package/dist/{hooks-CzCWByww.mjs → hooks-B1B8NRHL.mjs} +3 -3
- package/dist/index.mjs +154 -144
- package/dist/{maintenance-P7ePRXQC.mjs → maintenance-PRMkLVRW.mjs} +35 -30
- package/dist/manifest-67Bok-Si.mjs +58 -0
- package/dist/{manifest-B3QVVeBS.mjs → manifest-6lNTMZAB2.mjs} +33 -28
- package/dist/manifest-B2duEHiH.mjs +90 -0
- package/dist/manifest-B6EY9Vm8.mjs +57 -0
- package/dist/{manifest-gZ4s_UtG.mjs → manifest-B6nKSbyY.mjs} +32 -33
- package/dist/{manifest-2ToTpjv8.mjs → manifest-BL8AQNPF.mjs} +31 -31
- package/dist/{manifest-DzwvxPJX.mjs → manifest-BSZvJJmV.mjs} +23 -14
- package/dist/{manifest-Sc_0JQ13.mjs → manifest-BU7qzUyX.mjs} +23 -23
- package/dist/{manifest-CT7zZBV1.mjs → manifest-Bl62e8WK.mjs} +24 -23
- package/dist/manifest-Bo5cXjdt.mjs +82 -0
- package/dist/manifest-BpS4gtUK.mjs +1347 -0
- package/dist/manifest-Bv65_e2W.mjs +101 -0
- package/dist/manifest-BytNIF4Z.mjs +117 -0
- package/dist/{manifest-BqrQ4Tpj.mjs → manifest-C-xtsjS3.mjs} +23 -23
- package/dist/{manifest-NXctwWQq.mjs → manifest-CDYl7OhA.mjs} +36 -38
- package/dist/manifest-CRZ3xmkD.mjs +61 -0
- package/dist/manifest-CoW6u4Tp.mjs +132 -0
- package/dist/manifest-Cq5zN_8A.mjs +50 -0
- package/dist/{manifest-CAhOuvSl.mjs → manifest-D7YZM_2e.mjs} +75 -85
- package/dist/{manifest-DCyjf4n2.mjs → manifest-DE_VrAeQ.mjs} +27 -7
- package/dist/manifest-DGsXSCpT.mjs +39 -0
- package/dist/{manifest-BB2J8IMJ.mjs → manifest-DJ2vfEuW.mjs} +48 -41
- package/dist/{manifest-3g71z6Bg.mjs → manifest-DPXDYhEu.mjs} +26 -25
- package/dist/manifest-Dd4fQb0a.mjs +322 -0
- package/dist/{manifest-CXsRWjjI.mjs → manifest-Deq6opGg.mjs} +95 -96
- package/dist/{manifest-C9RT5nk32.mjs → manifest-DfJTafJK.mjs} +14 -11
- package/dist/manifest-DgOdgN_j.mjs +50 -0
- package/dist/{manifest-BmtZzQiQ2.mjs → manifest-DlbMW4v4.mjs} +17 -15
- package/dist/{manifest-DrbmZcFl2.mjs → manifest-DmVfbH0w.mjs} +212 -91
- package/dist/manifest-Dog6Ddjr.mjs +109 -0
- package/dist/manifest-DvgU5FWb.mjs +58 -0
- package/dist/manifest-HsfDBs7j.mjs +50 -0
- package/dist/manifest-I8oQHvCG.mjs +186 -0
- package/dist/manifest-NvH_a-av.mjs +786 -0
- package/dist/{manifest-Dh8WBmEW.mjs → manifest-cEJU1v0Z.mjs} +24 -24
- package/dist/manifest-wOl5XLB12.mjs +112 -0
- package/dist/{modules-C184v-S9.mjs → modules-tZozf0LQ.mjs} +130 -860
- package/dist/{mojo-ipc-B_H61Afw.mjs → mojo-ipc-DXNEXEqb.mjs} +141 -26
- package/dist/{network-671Cw6hV.mjs → network-CPVvwvFg.mjs} +1329 -823
- package/dist/{outputPaths-B1uGmrWZ.mjs → outputPaths-um7lCRY3.mjs} +4 -8
- package/dist/{platform-WmNn8Sxb.mjs → platform-CYeFoTWp.mjs} +101 -10
- package/dist/{process-QcbIy5Zq.mjs → process-BTbgcVc6.mjs} +251 -346
- package/dist/{proxy-DqNs0bAd.mjs → proxy-r8YN6nP1.mjs} +30 -8
- package/dist/{registry-D-6e18lB.mjs → registry-Bl8ZQW61.mjs} +3 -3
- package/dist/{response-BQVP-xUn.mjs → response-CWhh2aLo.mjs} +7 -1
- package/dist/{shared-state-board-DV-dpHFJ.mjs → shared-state-board-BoZnSoj-.mjs} +2 -2
- package/dist/{sourcemap-Dq8ez8vS.mjs → sourcemap-BIDHUVXy.mjs} +350 -66
- package/dist/{streaming-BUQ0VJsg.mjs → streaming-Dal6utPp.mjs} +13 -13
- package/dist/{tool-builder-DCbIC5Eo.mjs → tool-builder-BHJp32mV.mjs} +1 -1
- package/dist/{transform-CiYJfNX0.mjs → transform-DRVgGG90.mjs} +18 -14
- package/dist/wasm-BYx5UOeG.mjs +1044 -0
- package/dist/webcrack-Be0_FccV.mjs +747 -0
- package/dist/{workflow-f3xJOcjx.mjs → workflow-BpuKEtvn.mjs} +8 -8
- package/package.json +76 -43
- package/dist/TraceRecorder-DgxyVbdQ.mjs +0 -519
- package/dist/analysis-CL9uACt9.mjs +0 -463
- package/dist/bind-helpers-xFfRF-qm.mjs +0 -22
- package/dist/definitions-6M-eejaT.mjs +0 -53
- package/dist/definitions-B3QdlrHv.mjs +0 -34
- package/dist/definitions-CXEI7QC72.mjs +0 -216
- package/dist/definitions-C_4r7Fo-2.mjs +0 -14
- package/dist/definitions-CkFDALoa.mjs +0 -26
- package/dist/definitions-Cy3Sl6gV.mjs +0 -34
- package/dist/definitions-LKpC3-nL.mjs +0 -9
- package/dist/handlers-DdFzXLvF.mjs +0 -446
- package/dist/manifest-82baTv4U.mjs +0 -45
- package/dist/manifest-BKbgbSiY.mjs +0 -60
- package/dist/manifest-Bcf-TJzH.mjs +0 -848
- package/dist/manifest-Bnd7kqEY.mjs +0 -55
- package/dist/manifest-BqQX6OQC2.mjs +0 -65
- package/dist/manifest-Br4RPFt5.mjs +0 -370
- package/dist/manifest-C5qDjysN.mjs +0 -107
- package/dist/manifest-CBYWCUBJ.mjs +0 -51
- package/dist/manifest-CFADCRa1.mjs +0 -37
- package/dist/manifest-CQVhavRF.mjs +0 -114
- package/dist/manifest-CV12bcrF.mjs +0 -121
- package/dist/manifest-CZLUCfG02.mjs +0 -95
- package/dist/manifest-D6phHKFd.mjs +0 -131
- package/dist/manifest-DHsnKgP6.mjs +0 -60
- package/dist/manifest-Df_dliIe.mjs +0 -55
- package/dist/manifest-DhKRAT8_.mjs +0 -92
- package/dist/manifest-DlpTj4ic2.mjs +0 -193
- package/dist/manifest-DuwHjUa5.mjs +0 -70
- package/dist/manifest-qSleDqdO.mjs +0 -1023
- package/dist/wasm-DQTnHDs4.mjs +0 -531
- /package/dist/{CacheAdapters-CDe5WPSV.mjs → CacheAdapters-jJFy20G-.mjs} +0 -0
- /package/dist/{DarwinAPI-BNPxu0RH.mjs → DarwinAPI-ETyy0xyo.mjs} +0 -0
- /package/dist/{EventBus-DgPmwpeu.mjs → EventBus-DFKvADm3.mjs} +0 -0
- /package/dist/{EvidenceGraphBridge-SFesNera.mjs → EvidenceGraphBridge-318Oi0Lf.mjs} +0 -0
- /package/dist/{FingerprintManager-gzWtkKuf.mjs → FingerprintManager-BN4UQWnX.mjs} +0 -0
- /package/dist/{PrerequisiteError-Dl33Svkz.mjs → PrerequisiteError-TuyZIs6n.mjs} +0 -0
- /package/dist/{ReverseEvidenceGraph-Dlsk94LC.mjs → ReverseEvidenceGraph-C02-gXOh.mjs} +0 -0
- /package/dist/{StealthVerifier-Bo4T3bz8.mjs → StealthVerifier-BWmPgQsv.mjs} +0 -0
- /package/dist/{VersionDetector-CwVLVdDM.mjs → VersionDetector-K3V4vGsw.mjs} +0 -0
- /package/dist/{betterSqlite3-0pqusHHH.mjs → betterSqlite3-DLSBZodi.mjs} +0 -0
- /package/dist/{concurrency-Bt0yv1kJ.mjs → concurrency-Drev_Vz9.mjs} +0 -0
- /package/dist/{formatAddress-DVkj9kpI.mjs → formatAddress-nnMvEohD.mjs} +0 -0
- /package/dist/{parse-args-BlRjqlkL.mjs → parse-args-B4cY5Vx5.mjs} +0 -0
- /package/dist/{ssrf-policy-ZaUfvhq7.mjs → ssrf-policy-Dsqd-DTX.mjs} +0 -0
- /package/dist/{types-CPhOReNX.mjs → types-DDBWs9UP.mjs} +0 -0
|
@@ -1,29 +1,39 @@
|
|
|
1
|
-
import { n as asJsonResponse } from "./response-
|
|
2
|
-
import { a as enableKeyLog, c as parseKeyLog, i as disableKeyLog, l as summarizeKeyLog, n as TLSKeyLogExtractor, o as getKeyLogFilePath, r as decryptPayload, s as lookupSecret } from "./boringssl-inspector-
|
|
3
|
-
import { a as argString, n as argEnum, o as argStringArray, r as argNumber, t as argBool } from "./parse-args-
|
|
4
|
-
import { a as isLoopbackHost, s as isPrivateHost } from "./ssrf-policy-
|
|
1
|
+
import { n as asJsonResponse } from "./response-CWhh2aLo.mjs";
|
|
2
|
+
import { a as enableKeyLog, c as parseKeyLog, i as disableKeyLog, l as summarizeKeyLog, n as TLSKeyLogExtractor, o as getKeyLogFilePath, r as decryptPayload, s as lookupSecret } from "./boringssl-inspector-Bo_LOLaS.mjs";
|
|
3
|
+
import { a as argString, n as argEnum, o as argStringArray, r as argNumber, t as argBool } from "./parse-args-B4cY5Vx5.mjs";
|
|
4
|
+
import { a as isLoopbackHost, s as isPrivateHost } from "./ssrf-policy-Dsqd-DTX.mjs";
|
|
5
5
|
import { X509Certificate, createHash, randomBytes, randomUUID } from "node:crypto";
|
|
6
6
|
import { readFile } from "node:fs/promises";
|
|
7
7
|
import { Socket, createServer, isIP } from "node:net";
|
|
8
8
|
import { createSocket } from "node:dgram";
|
|
9
9
|
import { checkServerIdentity, connect } from "node:tls";
|
|
10
|
-
//#region src/server/domains/boringssl-inspector/handlers/shared.ts
|
|
11
|
-
function validateNetworkTarget(host) {
|
|
12
|
-
if (isPrivateHost(host) && !isLoopbackHost(host)) return {
|
|
13
|
-
ok: false,
|
|
14
|
-
error: `Blocked: target host "${host}" resolves to a private/internal address. SSRF protection applies.`
|
|
15
|
-
};
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
10
|
+
//#region src/server/domains/boringssl-inspector/handlers/shared/types.ts
|
|
18
11
|
const TLS_VERSION_SET = new Set([
|
|
19
12
|
"TLSv1",
|
|
20
13
|
"TLSv1.1",
|
|
21
14
|
"TLSv1.2",
|
|
22
15
|
"TLSv1.3"
|
|
23
16
|
]);
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/server/domains/boringssl-inspector/handlers/shared/common.ts
|
|
24
19
|
function errorMessage(error) {
|
|
25
20
|
return error instanceof Error ? error.message : String(error);
|
|
26
21
|
}
|
|
22
|
+
function normalizeHex(value) {
|
|
23
|
+
return value.replace(/\s+/g, "").toUpperCase();
|
|
24
|
+
}
|
|
25
|
+
function isHex(value) {
|
|
26
|
+
return value.length > 0 && value.length % 2 === 0 && /^[0-9A-F]+$/i.test(value);
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/server/domains/boringssl-inspector/handlers/shared/tls-probe.ts
|
|
30
|
+
function validateNetworkTarget(host) {
|
|
31
|
+
if (isPrivateHost(host) && !isLoopbackHost(host)) return {
|
|
32
|
+
ok: false,
|
|
33
|
+
error: `Blocked: target host "${host}" resolves to a private/internal address. SSRF protection applies.`
|
|
34
|
+
};
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
27
37
|
function normalizeSocketServername(servername) {
|
|
28
38
|
return typeof servername === "string" && servername.length > 0 ? servername : null;
|
|
29
39
|
}
|
|
@@ -35,6 +45,45 @@ function applyTlsValidationPolicy(options, allowInvalidCertificates) {
|
|
|
35
45
|
Reflect.set(next, "rejectUnauthorized", !allowInvalidCertificates);
|
|
36
46
|
return next;
|
|
37
47
|
}
|
|
48
|
+
async function loadProbeCaBundle(args) {
|
|
49
|
+
const caPem = argString(args, "caPem") ?? null;
|
|
50
|
+
const caPath = argString(args, "caPath") ?? null;
|
|
51
|
+
if (caPem && caPath) return {
|
|
52
|
+
ok: false,
|
|
53
|
+
error: "caPem and caPath are mutually exclusive"
|
|
54
|
+
};
|
|
55
|
+
if (caPem) return {
|
|
56
|
+
ok: true,
|
|
57
|
+
ca: caPem,
|
|
58
|
+
source: "inline",
|
|
59
|
+
path: null,
|
|
60
|
+
bytes: Buffer.byteLength(caPem)
|
|
61
|
+
};
|
|
62
|
+
if (caPath) try {
|
|
63
|
+
const ca = await readFile(caPath, "utf8");
|
|
64
|
+
return {
|
|
65
|
+
ok: true,
|
|
66
|
+
ca,
|
|
67
|
+
source: "path",
|
|
68
|
+
path: caPath,
|
|
69
|
+
bytes: Buffer.byteLength(ca)
|
|
70
|
+
};
|
|
71
|
+
} catch (error) {
|
|
72
|
+
return {
|
|
73
|
+
ok: false,
|
|
74
|
+
error: `Failed to read caPath "${caPath}": ${errorMessage(error)}`
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
ok: true,
|
|
79
|
+
ca: void 0,
|
|
80
|
+
source: null,
|
|
81
|
+
path: null,
|
|
82
|
+
bytes: null
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/server/domains/boringssl-inspector/handlers/shared/certificates.ts
|
|
38
87
|
function isNonEmptyObject(value) {
|
|
39
88
|
return value !== null && typeof value === "object" && Object.keys(value).length > 0;
|
|
40
89
|
}
|
|
@@ -81,43 +130,46 @@ function buildPeerCertificateChain(peerCertificate) {
|
|
|
81
130
|
}
|
|
82
131
|
return chain;
|
|
83
132
|
}
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
ok: false,
|
|
89
|
-
error: "caPem and caPath are mutually exclusive"
|
|
90
|
-
};
|
|
91
|
-
if (caPem) return {
|
|
92
|
-
ok: true,
|
|
93
|
-
ca: caPem,
|
|
94
|
-
source: "inline",
|
|
95
|
-
path: null,
|
|
96
|
-
bytes: Buffer.byteLength(caPem)
|
|
97
|
-
};
|
|
98
|
-
if (caPath) try {
|
|
99
|
-
const ca = await readFile(caPath, "utf8");
|
|
133
|
+
function parseDerCertificate(der) {
|
|
134
|
+
const sha256 = createHash("sha256").update(der).digest("hex").toUpperCase();
|
|
135
|
+
try {
|
|
136
|
+
const cert = new X509Certificate(der);
|
|
100
137
|
return {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
138
|
+
subject: cert.subject || void 0,
|
|
139
|
+
issuer: cert.issuer || void 0,
|
|
140
|
+
serialNumber: cert.serialNumber || void 0,
|
|
141
|
+
validFrom: cert.validFrom || void 0,
|
|
142
|
+
validTo: cert.validTo || void 0,
|
|
143
|
+
sha256,
|
|
144
|
+
length: der.length
|
|
106
145
|
};
|
|
107
|
-
} catch
|
|
146
|
+
} catch {
|
|
108
147
|
return {
|
|
109
|
-
|
|
110
|
-
|
|
148
|
+
sha256,
|
|
149
|
+
length: der.length
|
|
111
150
|
};
|
|
112
151
|
}
|
|
113
|
-
return {
|
|
114
|
-
ok: true,
|
|
115
|
-
ca: void 0,
|
|
116
|
-
source: null,
|
|
117
|
-
path: null,
|
|
118
|
-
bytes: null
|
|
119
|
-
};
|
|
120
152
|
}
|
|
153
|
+
function parseCertificateChain(hexPayload) {
|
|
154
|
+
const buffer = Buffer.from(normalizeHex(hexPayload), "hex");
|
|
155
|
+
const certs = [];
|
|
156
|
+
let cursor = 0;
|
|
157
|
+
while (cursor < buffer.length - 4) if (buffer[cursor] === 48) {
|
|
158
|
+
const info = parseDerCertificate(buffer.subarray(cursor));
|
|
159
|
+
certs.push({
|
|
160
|
+
sha256: info.sha256,
|
|
161
|
+
length: info.length
|
|
162
|
+
});
|
|
163
|
+
cursor += info.length;
|
|
164
|
+
} else cursor += 1;
|
|
165
|
+
if (certs.length === 0 && buffer.length > 0) certs.push({
|
|
166
|
+
sha256: createHash("sha256").update(buffer).digest("hex").toUpperCase(),
|
|
167
|
+
length: buffer.length
|
|
168
|
+
});
|
|
169
|
+
return certs;
|
|
170
|
+
}
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/server/domains/boringssl-inspector/handlers/shared/session-buffer.ts
|
|
121
173
|
function makeSessionId(kind) {
|
|
122
174
|
return `${kind}_${randomUUID()}`;
|
|
123
175
|
}
|
|
@@ -137,6 +189,83 @@ function serializeSessionState(session) {
|
|
|
137
189
|
error: session.error
|
|
138
190
|
};
|
|
139
191
|
}
|
|
192
|
+
function wakeSessionWaiters(session) {
|
|
193
|
+
for (const waiter of session.waiters) waiter();
|
|
194
|
+
session.waiters.clear();
|
|
195
|
+
}
|
|
196
|
+
function attachBufferedSession(session) {
|
|
197
|
+
session.socket.on("data", (chunk) => {
|
|
198
|
+
session.buffer = Buffer.concat([session.buffer, chunk]);
|
|
199
|
+
wakeSessionWaiters(session);
|
|
200
|
+
});
|
|
201
|
+
session.socket.on("end", () => {
|
|
202
|
+
session.ended = true;
|
|
203
|
+
wakeSessionWaiters(session);
|
|
204
|
+
});
|
|
205
|
+
session.socket.on("close", () => {
|
|
206
|
+
session.closed = true;
|
|
207
|
+
wakeSessionWaiters(session);
|
|
208
|
+
});
|
|
209
|
+
session.socket.on("error", (error) => {
|
|
210
|
+
session.error = error.message;
|
|
211
|
+
wakeSessionWaiters(session);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
function waitForSessionActivity(session, timeoutMs) {
|
|
215
|
+
return new Promise((resolve) => {
|
|
216
|
+
const onActivity = () => {
|
|
217
|
+
clearTimeout(timer);
|
|
218
|
+
session.waiters.delete(onActivity);
|
|
219
|
+
resolve(true);
|
|
220
|
+
};
|
|
221
|
+
const timer = setTimeout(() => {
|
|
222
|
+
session.waiters.delete(onActivity);
|
|
223
|
+
resolve(false);
|
|
224
|
+
}, timeoutMs);
|
|
225
|
+
session.waiters.add(onActivity);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
function consumeSessionBuffer(session, delimiter, includeDelimiter, maxBytes) {
|
|
229
|
+
const delimiterHex = delimiter ? delimiter.toString("hex").toUpperCase() : null;
|
|
230
|
+
if (delimiter) {
|
|
231
|
+
const matchIndex = session.buffer.indexOf(delimiter);
|
|
232
|
+
if (matchIndex >= 0) {
|
|
233
|
+
const consumedBytes = matchIndex + delimiter.length;
|
|
234
|
+
const data = includeDelimiter ? session.buffer.subarray(0, consumedBytes) : session.buffer.subarray(0, matchIndex);
|
|
235
|
+
session.buffer = session.buffer.subarray(consumedBytes);
|
|
236
|
+
return {
|
|
237
|
+
data,
|
|
238
|
+
matchedDelimiter: true,
|
|
239
|
+
stopReason: "delimiter",
|
|
240
|
+
delimiterHex
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (typeof maxBytes === "number" && session.buffer.length >= maxBytes) {
|
|
245
|
+
const data = session.buffer.subarray(0, maxBytes);
|
|
246
|
+
session.buffer = session.buffer.subarray(maxBytes);
|
|
247
|
+
return {
|
|
248
|
+
data,
|
|
249
|
+
matchedDelimiter: false,
|
|
250
|
+
stopReason: "maxBytes",
|
|
251
|
+
delimiterHex
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
if ((session.error || session.ended || session.closed) && session.buffer.length > 0) {
|
|
255
|
+
const data = session.buffer;
|
|
256
|
+
session.buffer = Buffer.alloc(0);
|
|
257
|
+
return {
|
|
258
|
+
data,
|
|
259
|
+
matchedDelimiter: false,
|
|
260
|
+
stopReason: session.error ? "error" : "closed",
|
|
261
|
+
delimiterHex
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
//#endregion
|
|
267
|
+
//#region src/server/domains/boringssl-inspector/handlers/shared/websocket.ts
|
|
268
|
+
const WEBSOCKET_ACCEPT_SUFFIX = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
140
269
|
function serializeWebSocketSessionState(session) {
|
|
141
270
|
return {
|
|
142
271
|
bufferedBytes: session.parserBuffer.length,
|
|
@@ -153,32 +282,34 @@ function normalizeWebSocketPath(path) {
|
|
|
153
282
|
return path.startsWith("/") ? path : `/${path}`;
|
|
154
283
|
}
|
|
155
284
|
function websocketOpcodeName(opcode) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
285
|
+
return {
|
|
286
|
+
1: "text",
|
|
287
|
+
2: "binary",
|
|
288
|
+
8: "close",
|
|
289
|
+
9: "ping",
|
|
290
|
+
10: "pong"
|
|
291
|
+
}[opcode] ?? null;
|
|
164
292
|
}
|
|
165
293
|
function computeWebSocketAccept(requestKey) {
|
|
166
|
-
return createHash("sha1").update(`${requestKey}
|
|
294
|
+
return createHash("sha1").update(`${requestKey}${WEBSOCKET_ACCEPT_SUFFIX}`, "utf8").digest("base64");
|
|
167
295
|
}
|
|
168
296
|
function encodeWebSocketFrame(type, payload, closeCode, closeReason) {
|
|
169
|
-
|
|
297
|
+
const opcodeByType = {
|
|
298
|
+
text: 1,
|
|
299
|
+
binary: 2,
|
|
300
|
+
close: 8,
|
|
301
|
+
ping: 9,
|
|
302
|
+
pong: 10
|
|
303
|
+
};
|
|
170
304
|
let framePayload = payload;
|
|
171
|
-
if (type === "
|
|
172
|
-
else if (type === "close") {
|
|
173
|
-
opcode = 8;
|
|
305
|
+
if (type === "close") {
|
|
174
306
|
if (closeCode !== void 0 && closeCode !== null) {
|
|
175
307
|
const reasonBuffer = closeReason ? Buffer.from(closeReason, "utf8") : Buffer.alloc(0);
|
|
176
308
|
framePayload = Buffer.alloc(2 + reasonBuffer.length);
|
|
177
309
|
framePayload.writeUInt16BE(closeCode, 0);
|
|
178
310
|
reasonBuffer.copy(framePayload, 2);
|
|
179
311
|
} else if (closeReason) framePayload = Buffer.from(closeReason, "utf8");
|
|
180
|
-
}
|
|
181
|
-
else if (type === "pong") opcode = 10;
|
|
312
|
+
}
|
|
182
313
|
const maskKey = randomBytes(4);
|
|
183
314
|
const payloadLength = framePayload.length;
|
|
184
315
|
let header;
|
|
@@ -194,7 +325,7 @@ function encodeWebSocketFrame(type, payload, closeCode, closeReason) {
|
|
|
194
325
|
header[1] = 255;
|
|
195
326
|
header.writeBigUInt64BE(BigInt(payloadLength), 2);
|
|
196
327
|
}
|
|
197
|
-
header[0] = 128 |
|
|
328
|
+
header[0] = 128 | opcodeByType[type];
|
|
198
329
|
const maskedPayload = Buffer.alloc(payloadLength);
|
|
199
330
|
for (let index = 0; index < payloadLength; index += 1) maskedPayload[index] = framePayload[index] ^ maskKey[index % 4];
|
|
200
331
|
return Buffer.concat([
|
|
@@ -255,42 +386,6 @@ function tryConsumeWebSocketFrame(buffer) {
|
|
|
255
386
|
bytesConsumed: cursor + payloadLength
|
|
256
387
|
};
|
|
257
388
|
}
|
|
258
|
-
function wakeSessionWaiters(session) {
|
|
259
|
-
for (const waiter of session.waiters) waiter();
|
|
260
|
-
session.waiters.clear();
|
|
261
|
-
}
|
|
262
|
-
function attachBufferedSession(session) {
|
|
263
|
-
session.socket.on("data", (chunk) => {
|
|
264
|
-
session.buffer = Buffer.concat([session.buffer, chunk]);
|
|
265
|
-
wakeSessionWaiters(session);
|
|
266
|
-
});
|
|
267
|
-
session.socket.on("end", () => {
|
|
268
|
-
session.ended = true;
|
|
269
|
-
wakeSessionWaiters(session);
|
|
270
|
-
});
|
|
271
|
-
session.socket.on("close", () => {
|
|
272
|
-
session.closed = true;
|
|
273
|
-
wakeSessionWaiters(session);
|
|
274
|
-
});
|
|
275
|
-
session.socket.on("error", (error) => {
|
|
276
|
-
session.error = error.message;
|
|
277
|
-
wakeSessionWaiters(session);
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
function waitForSessionActivity(session, timeoutMs) {
|
|
281
|
-
return new Promise((resolve) => {
|
|
282
|
-
const onActivity = () => {
|
|
283
|
-
clearTimeout(timer);
|
|
284
|
-
session.waiters.delete(onActivity);
|
|
285
|
-
resolve(true);
|
|
286
|
-
};
|
|
287
|
-
const timer = setTimeout(() => {
|
|
288
|
-
session.waiters.delete(onActivity);
|
|
289
|
-
resolve(false);
|
|
290
|
-
}, timeoutMs);
|
|
291
|
-
session.waiters.add(onActivity);
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
389
|
function wakeWebSocketWaiters(session) {
|
|
295
390
|
for (const waiter of session.waiters) waiter();
|
|
296
391
|
session.waiters.clear();
|
|
@@ -309,70 +404,59 @@ function waitForWebSocketActivity(session, timeoutMs) {
|
|
|
309
404
|
session.waiters.add(onActivity);
|
|
310
405
|
});
|
|
311
406
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
407
|
+
//#endregion
|
|
408
|
+
//#region src/server/domains/boringssl-inspector/handlers/shared/tls-parse.ts
|
|
409
|
+
const TLS_VERSION_NAMES = {
|
|
410
|
+
"3:1": "TLS 1.0",
|
|
411
|
+
"3:2": "TLS 1.1",
|
|
412
|
+
"3:3": "TLS 1.2",
|
|
413
|
+
"3:4": "TLS 1.3"
|
|
414
|
+
};
|
|
415
|
+
const CONTENT_TYPE_NAMES = {
|
|
416
|
+
20: "change_cipher_spec",
|
|
417
|
+
21: "alert",
|
|
418
|
+
22: "handshake",
|
|
419
|
+
23: "application_data",
|
|
420
|
+
24: "heartbeat"
|
|
421
|
+
};
|
|
422
|
+
const CIPHER_SUITES_BY_ID = {
|
|
423
|
+
156: "TLS_RSA_WITH_AES_128_GCM_SHA256",
|
|
424
|
+
157: "TLS_RSA_WITH_AES_256_GCM_SHA384",
|
|
425
|
+
52392: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
|
426
|
+
52393: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
|
|
427
|
+
4865: "TLS_AES_128_GCM_SHA256",
|
|
428
|
+
4866: "TLS_AES_256_GCM_SHA384",
|
|
429
|
+
4867: "TLS_CHACHA20_POLY1305_SHA256",
|
|
430
|
+
4868: "TLS_AES_128_CCM_SHA256",
|
|
431
|
+
4869: "TLS_AES_128_CCM_8_SHA256",
|
|
432
|
+
49195: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
|
433
|
+
49196: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
|
|
434
|
+
49199: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
|
435
|
+
49200: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
|
|
436
|
+
};
|
|
437
|
+
const EXTENSION_NAMES = {
|
|
438
|
+
0: "server_name",
|
|
439
|
+
1: "max_fragment_length",
|
|
440
|
+
5: "status_request",
|
|
441
|
+
10: "supported_groups",
|
|
442
|
+
13: "signature_algorithms",
|
|
443
|
+
16: "application_layer_protocol_negotiation",
|
|
444
|
+
18: "signed_certificate_timestamp",
|
|
445
|
+
23: "record_size_limit",
|
|
446
|
+
27: "compress_certificate",
|
|
447
|
+
35: "session_ticket",
|
|
448
|
+
43: "supported_versions",
|
|
449
|
+
44: "cookie",
|
|
450
|
+
45: "psk_key_exchange_modes",
|
|
451
|
+
49: "post_handshake_auth",
|
|
452
|
+
51: "key_share"
|
|
453
|
+
};
|
|
355
454
|
function tlsVersionName(major, minor) {
|
|
356
|
-
|
|
357
|
-
if (major === 3 && minor === 2) return "TLS 1.1";
|
|
358
|
-
if (major === 3 && minor === 3) return "TLS 1.2";
|
|
359
|
-
if (major === 3 && minor === 4) return "TLS 1.3";
|
|
360
|
-
return `0x${major.toString(16).padStart(2, "0")}${minor.toString(16).padStart(2, "0")}`;
|
|
455
|
+
return TLS_VERSION_NAMES[`${major}:${minor}`] ?? `0x${major.toString(16).padStart(2, "0")}${minor.toString(16).padStart(2, "0")}`;
|
|
361
456
|
}
|
|
362
457
|
function contentTypeName(contentType) {
|
|
363
|
-
|
|
364
|
-
if (contentType === 21) return "alert";
|
|
365
|
-
if (contentType === 22) return "handshake";
|
|
366
|
-
if (contentType === 23) return "application_data";
|
|
367
|
-
if (contentType === 24) return "heartbeat";
|
|
368
|
-
return "unknown";
|
|
458
|
+
return CONTENT_TYPE_NAMES[contentType] ?? "unknown";
|
|
369
459
|
}
|
|
370
|
-
/**
|
|
371
|
-
* Parse TLS ClientHello to extract SNI, cipher suites, and extensions.
|
|
372
|
-
* Handles two input layouts:
|
|
373
|
-
* - With handshake header: [type(1) + length(3) + version(2) + random(32) + ...]
|
|
374
|
-
* - Without handshake header: [version(2) + random(32) + ...]
|
|
375
|
-
*/
|
|
376
460
|
function parseClientHello(payload) {
|
|
377
461
|
const result = {
|
|
378
462
|
cipherSuites: [],
|
|
@@ -426,88 +510,12 @@ function parseClientHello(payload) {
|
|
|
426
510
|
}
|
|
427
511
|
return result;
|
|
428
512
|
}
|
|
429
|
-
const CIPHER_SUITES_BY_ID = {
|
|
430
|
-
156: "TLS_RSA_WITH_AES_128_GCM_SHA256",
|
|
431
|
-
157: "TLS_RSA_WITH_AES_256_GCM_SHA384",
|
|
432
|
-
52392: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
|
433
|
-
52393: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
|
|
434
|
-
4865: "TLS_AES_128_GCM_SHA256",
|
|
435
|
-
4866: "TLS_AES_256_GCM_SHA384",
|
|
436
|
-
4867: "TLS_CHACHA20_POLY1305_SHA256",
|
|
437
|
-
4868: "TLS_AES_128_CCM_SHA256",
|
|
438
|
-
4869: "TLS_AES_128_CCM_8_SHA256",
|
|
439
|
-
49195: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
|
440
|
-
49196: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
|
|
441
|
-
49199: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
|
442
|
-
49200: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
|
|
443
|
-
};
|
|
444
|
-
const EXTENSION_NAMES = {
|
|
445
|
-
0: "server_name",
|
|
446
|
-
1: "max_fragment_length",
|
|
447
|
-
5: "status_request",
|
|
448
|
-
10: "supported_groups",
|
|
449
|
-
13: "signature_algorithms",
|
|
450
|
-
16: "application_layer_protocol_negotiation",
|
|
451
|
-
18: "signed_certificate_timestamp",
|
|
452
|
-
23: "record_size_limit",
|
|
453
|
-
27: "compress_certificate",
|
|
454
|
-
35: "session_ticket",
|
|
455
|
-
43: "supported_versions",
|
|
456
|
-
44: "cookie",
|
|
457
|
-
45: "psk_key_exchange_modes",
|
|
458
|
-
49: "post_handshake_auth",
|
|
459
|
-
51: "key_share"
|
|
460
|
-
};
|
|
461
|
-
/**
|
|
462
|
-
* Parse a DER-encoded certificate to extract basic info.
|
|
463
|
-
*/
|
|
464
|
-
function parseDerCertificate(der) {
|
|
465
|
-
const sha256 = createHash("sha256").update(der).digest("hex").toUpperCase();
|
|
466
|
-
try {
|
|
467
|
-
const cert = new X509Certificate(der);
|
|
468
|
-
return {
|
|
469
|
-
subject: cert.subject || void 0,
|
|
470
|
-
issuer: cert.issuer || void 0,
|
|
471
|
-
serialNumber: cert.serialNumber || void 0,
|
|
472
|
-
validFrom: cert.validFrom || void 0,
|
|
473
|
-
validTo: cert.validTo || void 0,
|
|
474
|
-
sha256,
|
|
475
|
-
length: der.length
|
|
476
|
-
};
|
|
477
|
-
} catch {
|
|
478
|
-
return {
|
|
479
|
-
sha256,
|
|
480
|
-
length: der.length
|
|
481
|
-
};
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
/**
|
|
485
|
-
* Parse a chain of DER certificates from hex.
|
|
486
|
-
*/
|
|
487
|
-
function parseCertificateChain(hexPayload) {
|
|
488
|
-
const buffer = Buffer.from(normalizeHex(hexPayload), "hex");
|
|
489
|
-
const certs = [];
|
|
490
|
-
let cursor = 0;
|
|
491
|
-
while (cursor < buffer.length - 4) if (buffer[cursor] === 48) {
|
|
492
|
-
const info = parseDerCertificate(buffer.subarray(cursor));
|
|
493
|
-
certs.push({
|
|
494
|
-
sha256: info.sha256,
|
|
495
|
-
length: info.length
|
|
496
|
-
});
|
|
497
|
-
cursor += info.length;
|
|
498
|
-
} else cursor += 1;
|
|
499
|
-
if (certs.length === 0 && buffer.length > 0) certs.push({
|
|
500
|
-
sha256: createHash("sha256").update(buffer).digest("hex").toUpperCase(),
|
|
501
|
-
length: buffer.length
|
|
502
|
-
});
|
|
503
|
-
return certs;
|
|
504
|
-
}
|
|
505
513
|
//#endregion
|
|
506
|
-
//#region src/server/domains/boringssl-inspector/handlers/
|
|
514
|
+
//#region src/server/domains/boringssl-inspector/handlers/base.ts
|
|
507
515
|
/**
|
|
508
|
-
*
|
|
516
|
+
* BoringsslInspectorBaseHandlers — shared state and transport helpers.
|
|
509
517
|
*/
|
|
510
|
-
var
|
|
518
|
+
var BoringsslInspectorBaseHandlers = class {
|
|
511
519
|
extensionInvoke;
|
|
512
520
|
eventBus;
|
|
513
521
|
tcpSessions = /* @__PURE__ */ new Map();
|
|
@@ -531,9 +539,6 @@ var BoringsslInspectorHandlers = class {
|
|
|
531
539
|
getWebSocketSession(sessionId) {
|
|
532
540
|
return this.websocketSessions.get(sessionId) ?? null;
|
|
533
541
|
}
|
|
534
|
-
emitWebSocketEvent(event, payload) {
|
|
535
|
-
this.eventBus?.emit(event, payload);
|
|
536
|
-
}
|
|
537
542
|
parseWritePayload(args) {
|
|
538
543
|
const dataHex = argString(args, "dataHex");
|
|
539
544
|
const dataText = argString(args, "dataText");
|
|
@@ -743,347 +748,14 @@ var BoringsslInspectorHandlers = class {
|
|
|
743
748
|
session.activeRead = false;
|
|
744
749
|
}
|
|
745
750
|
}
|
|
746
|
-
|
|
747
|
-
const
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
consumed = tryConsumeWebSocketFrame(session.parserBuffer);
|
|
752
|
-
} catch (error) {
|
|
753
|
-
session.error = errorMessage(error);
|
|
754
|
-
session.socket.destroy();
|
|
755
|
-
break;
|
|
756
|
-
}
|
|
757
|
-
if (!consumed) break;
|
|
758
|
-
session.parserBuffer = session.parserBuffer.subarray(consumed.bytesConsumed);
|
|
759
|
-
const frame = consumed.frame;
|
|
760
|
-
session.frames.push(frame);
|
|
761
|
-
if (frame.type === "ping" && !session.closeSent && !session.socket.destroyed) {
|
|
762
|
-
const pongFrame = encodeWebSocketFrame("pong", frame.data);
|
|
763
|
-
session.socket.write(pongFrame);
|
|
764
|
-
this.emitWebSocketEvent("websocket:session_written", {
|
|
765
|
-
sessionId: session.id,
|
|
766
|
-
frameType: "pong",
|
|
767
|
-
byteLength: frame.data.length,
|
|
768
|
-
automatic: true,
|
|
769
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
770
|
-
});
|
|
771
|
-
}
|
|
772
|
-
if (frame.type === "close") {
|
|
773
|
-
session.closeReceived = true;
|
|
774
|
-
if (!session.closeSent && !session.socket.destroyed) {
|
|
775
|
-
session.closeSent = true;
|
|
776
|
-
session.socket.write(encodeWebSocketFrame("close", frame.data, frame.closeCode, frame.closeReason));
|
|
777
|
-
this.emitWebSocketEvent("websocket:session_written", {
|
|
778
|
-
sessionId: session.id,
|
|
779
|
-
frameType: "close",
|
|
780
|
-
byteLength: frame.data.length,
|
|
781
|
-
automatic: true,
|
|
782
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
783
|
-
});
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
wakeWebSocketWaiters(session);
|
|
751
|
+
async closeBufferedSession(sessionId, sessions, kind, args) {
|
|
752
|
+
const session = sessions.get(sessionId);
|
|
753
|
+
if (!session) return {
|
|
754
|
+
ok: false,
|
|
755
|
+
error: `Unknown ${kind} sessionId "${sessionId}"`
|
|
788
756
|
};
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
parseBufferedFrames();
|
|
792
|
-
});
|
|
793
|
-
session.socket.on("end", () => {
|
|
794
|
-
session.ended = true;
|
|
795
|
-
wakeWebSocketWaiters(session);
|
|
796
|
-
});
|
|
797
|
-
session.socket.on("close", () => {
|
|
798
|
-
session.closed = true;
|
|
799
|
-
wakeWebSocketWaiters(session);
|
|
800
|
-
});
|
|
801
|
-
session.socket.on("error", (error) => {
|
|
802
|
-
session.error = error.message;
|
|
803
|
-
wakeWebSocketWaiters(session);
|
|
804
|
-
});
|
|
805
|
-
parseBufferedFrames();
|
|
806
|
-
}
|
|
807
|
-
async readWebSocketFrame(session, args) {
|
|
808
|
-
const timeoutMs = argNumber(args, "timeoutMs") ?? 5e3;
|
|
809
|
-
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return {
|
|
810
|
-
ok: false,
|
|
811
|
-
error: "timeoutMs must be a positive number"
|
|
812
|
-
};
|
|
813
|
-
if (session.activeRead) return {
|
|
814
|
-
ok: false,
|
|
815
|
-
error: `Session "${session.id}" already has a pending read`,
|
|
816
|
-
sessionId: session.id,
|
|
817
|
-
kind: session.kind,
|
|
818
|
-
state: serializeWebSocketSessionState(session)
|
|
819
|
-
};
|
|
820
|
-
session.activeRead = true;
|
|
821
|
-
const startedAt = Date.now();
|
|
822
|
-
try {
|
|
823
|
-
while (true) {
|
|
824
|
-
const frame = session.frames.shift();
|
|
825
|
-
if (frame) {
|
|
826
|
-
this.emitWebSocketEvent("websocket:frame_read", {
|
|
827
|
-
sessionId: session.id,
|
|
828
|
-
frameType: frame.type,
|
|
829
|
-
byteLength: frame.data.length,
|
|
830
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
831
|
-
});
|
|
832
|
-
return {
|
|
833
|
-
ok: true,
|
|
834
|
-
sessionId: session.id,
|
|
835
|
-
kind: session.kind,
|
|
836
|
-
scheme: session.scheme,
|
|
837
|
-
frameType: frame.type,
|
|
838
|
-
fin: frame.fin,
|
|
839
|
-
opcode: frame.opcode,
|
|
840
|
-
masked: frame.masked,
|
|
841
|
-
byteLength: frame.data.length,
|
|
842
|
-
dataHex: frame.data.toString("hex").toUpperCase(),
|
|
843
|
-
dataText: frame.type === "binary" ? null : frame.data.toString("utf8"),
|
|
844
|
-
closeCode: frame.closeCode,
|
|
845
|
-
closeReason: frame.closeReason,
|
|
846
|
-
elapsedMs: Date.now() - startedAt,
|
|
847
|
-
state: serializeWebSocketSessionState(session)
|
|
848
|
-
};
|
|
849
|
-
}
|
|
850
|
-
if (session.error) return {
|
|
851
|
-
ok: false,
|
|
852
|
-
error: session.error,
|
|
853
|
-
sessionId: session.id,
|
|
854
|
-
kind: session.kind,
|
|
855
|
-
state: serializeWebSocketSessionState(session)
|
|
856
|
-
};
|
|
857
|
-
if (session.closed || session.ended) return {
|
|
858
|
-
ok: false,
|
|
859
|
-
error: "socket closed before a WebSocket frame was available",
|
|
860
|
-
sessionId: session.id,
|
|
861
|
-
kind: session.kind,
|
|
862
|
-
state: serializeWebSocketSessionState(session)
|
|
863
|
-
};
|
|
864
|
-
const remainingMs = timeoutMs - (Date.now() - startedAt);
|
|
865
|
-
if (remainingMs <= 0) return {
|
|
866
|
-
ok: false,
|
|
867
|
-
error: "read timed out",
|
|
868
|
-
sessionId: session.id,
|
|
869
|
-
kind: session.kind,
|
|
870
|
-
state: serializeWebSocketSessionState(session)
|
|
871
|
-
};
|
|
872
|
-
if (!await waitForWebSocketActivity(session, remainingMs)) return {
|
|
873
|
-
ok: false,
|
|
874
|
-
error: "read timed out",
|
|
875
|
-
sessionId: session.id,
|
|
876
|
-
kind: session.kind,
|
|
877
|
-
state: serializeWebSocketSessionState(session)
|
|
878
|
-
};
|
|
879
|
-
}
|
|
880
|
-
} finally {
|
|
881
|
-
session.activeRead = false;
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
async sendWebSocketFrame(session, args) {
|
|
885
|
-
if (session.closed || session.socket.destroyed) return {
|
|
886
|
-
ok: false,
|
|
887
|
-
error: `Session "${session.id}" is already closed`,
|
|
888
|
-
sessionId: session.id,
|
|
889
|
-
kind: session.kind,
|
|
890
|
-
state: serializeWebSocketSessionState(session)
|
|
891
|
-
};
|
|
892
|
-
const frameType = argEnum(args, "frameType", new Set([
|
|
893
|
-
"text",
|
|
894
|
-
"binary",
|
|
895
|
-
"ping",
|
|
896
|
-
"pong",
|
|
897
|
-
"close"
|
|
898
|
-
]));
|
|
899
|
-
if (!frameType) return {
|
|
900
|
-
ok: false,
|
|
901
|
-
error: "frameType is required"
|
|
902
|
-
};
|
|
903
|
-
const dataHex = argString(args, "dataHex");
|
|
904
|
-
const dataText = argString(args, "dataText");
|
|
905
|
-
if (dataHex && dataText) return {
|
|
906
|
-
ok: false,
|
|
907
|
-
error: "dataHex and dataText are mutually exclusive"
|
|
908
|
-
};
|
|
909
|
-
const timeoutMs = argNumber(args, "timeoutMs") ?? 5e3;
|
|
910
|
-
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return {
|
|
911
|
-
ok: false,
|
|
912
|
-
error: "timeoutMs must be a positive number"
|
|
913
|
-
};
|
|
914
|
-
let payload = Buffer.alloc(0);
|
|
915
|
-
if (dataHex) {
|
|
916
|
-
const normalized = normalizeHex(dataHex);
|
|
917
|
-
if (!isHex(normalized)) return {
|
|
918
|
-
ok: false,
|
|
919
|
-
error: "dataHex must be valid even-length hexadecimal data"
|
|
920
|
-
};
|
|
921
|
-
payload = Buffer.from(normalized, "hex");
|
|
922
|
-
} else if (dataText !== void 0) payload = Buffer.from(dataText, "utf8");
|
|
923
|
-
let closeCode = null;
|
|
924
|
-
let closeReason = null;
|
|
925
|
-
if (frameType === "close") {
|
|
926
|
-
const rawCloseCode = argNumber(args, "closeCode");
|
|
927
|
-
if (rawCloseCode !== void 0) {
|
|
928
|
-
if (!Number.isInteger(rawCloseCode) || rawCloseCode < 1e3 || rawCloseCode > 4999) return {
|
|
929
|
-
ok: false,
|
|
930
|
-
error: "closeCode must be an integer between 1000 and 4999"
|
|
931
|
-
};
|
|
932
|
-
closeCode = rawCloseCode;
|
|
933
|
-
}
|
|
934
|
-
closeReason = argString(args, "closeReason") ?? null;
|
|
935
|
-
if (dataHex || dataText) return {
|
|
936
|
-
ok: false,
|
|
937
|
-
error: "close frames use closeCode/closeReason instead of dataHex/dataText"
|
|
938
|
-
};
|
|
939
|
-
session.closeSent = true;
|
|
940
|
-
}
|
|
941
|
-
if (frameType === "text" && dataHex) return {
|
|
942
|
-
ok: false,
|
|
943
|
-
error: "text frames require UTF-8 dataText instead of dataHex"
|
|
944
|
-
};
|
|
945
|
-
const frameBuffer = encodeWebSocketFrame(frameType, payload, closeCode, closeReason);
|
|
946
|
-
return new Promise((resolve) => {
|
|
947
|
-
let settled = false;
|
|
948
|
-
const finish = (result) => {
|
|
949
|
-
if (settled) return;
|
|
950
|
-
settled = true;
|
|
951
|
-
clearTimeout(timer);
|
|
952
|
-
session.socket.off("error", onError);
|
|
953
|
-
resolve(result);
|
|
954
|
-
};
|
|
955
|
-
const timer = setTimeout(() => {
|
|
956
|
-
finish({
|
|
957
|
-
ok: false,
|
|
958
|
-
error: "write timed out",
|
|
959
|
-
sessionId: session.id,
|
|
960
|
-
kind: session.kind,
|
|
961
|
-
state: serializeWebSocketSessionState(session)
|
|
962
|
-
});
|
|
963
|
-
}, timeoutMs);
|
|
964
|
-
const onError = (error) => {
|
|
965
|
-
finish({
|
|
966
|
-
ok: false,
|
|
967
|
-
error: error.message,
|
|
968
|
-
sessionId: session.id,
|
|
969
|
-
kind: session.kind,
|
|
970
|
-
state: serializeWebSocketSessionState(session)
|
|
971
|
-
});
|
|
972
|
-
};
|
|
973
|
-
session.socket.once("error", onError);
|
|
974
|
-
session.socket.write(frameBuffer, () => {
|
|
975
|
-
this.emitWebSocketEvent("websocket:session_written", {
|
|
976
|
-
sessionId: session.id,
|
|
977
|
-
frameType,
|
|
978
|
-
byteLength: frameType === "close" ? closeReason ? Buffer.byteLength(closeReason) + 2 : closeCode ? 2 : 0 : payload.length,
|
|
979
|
-
automatic: false,
|
|
980
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
981
|
-
});
|
|
982
|
-
finish({
|
|
983
|
-
ok: true,
|
|
984
|
-
sessionId: session.id,
|
|
985
|
-
kind: session.kind,
|
|
986
|
-
scheme: session.scheme,
|
|
987
|
-
frameType,
|
|
988
|
-
bytesWritten: frameBuffer.length,
|
|
989
|
-
payloadBytes: frameType === "close" ? closeReason ? Buffer.byteLength(closeReason) + 2 : closeCode ? 2 : 0 : payload.length,
|
|
990
|
-
state: serializeWebSocketSessionState(session)
|
|
991
|
-
});
|
|
992
|
-
});
|
|
993
|
-
});
|
|
994
|
-
}
|
|
995
|
-
async closeWebSocketSession(sessionId, args) {
|
|
996
|
-
const session = this.websocketSessions.get(sessionId);
|
|
997
|
-
if (!session) return {
|
|
998
|
-
ok: false,
|
|
999
|
-
error: `Unknown websocket sessionId "${sessionId}"`
|
|
1000
|
-
};
|
|
1001
|
-
const force = argBool(args, "force") ?? false;
|
|
1002
|
-
const timeoutMs = argNumber(args, "timeoutMs") ?? 1e3;
|
|
1003
|
-
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return {
|
|
1004
|
-
ok: false,
|
|
1005
|
-
error: "timeoutMs must be a positive number"
|
|
1006
|
-
};
|
|
1007
|
-
const queuedFramesDiscarded = session.frames.length;
|
|
1008
|
-
if (session.closed || session.socket.destroyed) {
|
|
1009
|
-
this.websocketSessions.delete(sessionId);
|
|
1010
|
-
return {
|
|
1011
|
-
ok: true,
|
|
1012
|
-
sessionId,
|
|
1013
|
-
kind: session.kind,
|
|
1014
|
-
force,
|
|
1015
|
-
closed: true,
|
|
1016
|
-
queuedFramesDiscarded,
|
|
1017
|
-
state: serializeWebSocketSessionState(session)
|
|
1018
|
-
};
|
|
1019
|
-
}
|
|
1020
|
-
let closeCode = null;
|
|
1021
|
-
const rawCloseCode = argNumber(args, "closeCode");
|
|
1022
|
-
if (rawCloseCode !== void 0) {
|
|
1023
|
-
if (!Number.isInteger(rawCloseCode) || rawCloseCode < 1e3 || rawCloseCode > 4999) return {
|
|
1024
|
-
ok: false,
|
|
1025
|
-
error: "closeCode must be an integer between 1000 and 4999"
|
|
1026
|
-
};
|
|
1027
|
-
closeCode = rawCloseCode;
|
|
1028
|
-
}
|
|
1029
|
-
const closeReason = argString(args, "closeReason") ?? null;
|
|
1030
|
-
return new Promise((resolve) => {
|
|
1031
|
-
let settled = false;
|
|
1032
|
-
const finish = (closed) => {
|
|
1033
|
-
if (settled) return;
|
|
1034
|
-
settled = true;
|
|
1035
|
-
clearTimeout(timer);
|
|
1036
|
-
session.socket.off("close", onClose);
|
|
1037
|
-
session.socket.off("error", onError);
|
|
1038
|
-
this.websocketSessions.delete(sessionId);
|
|
1039
|
-
this.emitWebSocketEvent("websocket:session_closed", {
|
|
1040
|
-
sessionId,
|
|
1041
|
-
reason: session.error,
|
|
1042
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1043
|
-
});
|
|
1044
|
-
resolve({
|
|
1045
|
-
ok: true,
|
|
1046
|
-
sessionId,
|
|
1047
|
-
kind: session.kind,
|
|
1048
|
-
force,
|
|
1049
|
-
closed,
|
|
1050
|
-
queuedFramesDiscarded,
|
|
1051
|
-
state: serializeWebSocketSessionState(session)
|
|
1052
|
-
});
|
|
1053
|
-
};
|
|
1054
|
-
const onClose = () => finish(true);
|
|
1055
|
-
const onError = () => finish(session.socket.destroyed || session.closed);
|
|
1056
|
-
const timer = setTimeout(() => {
|
|
1057
|
-
session.socket.destroy();
|
|
1058
|
-
finish(session.socket.destroyed || session.closed);
|
|
1059
|
-
}, timeoutMs);
|
|
1060
|
-
session.socket.once("close", onClose);
|
|
1061
|
-
session.socket.once("error", onError);
|
|
1062
|
-
if (force) {
|
|
1063
|
-
session.socket.destroy();
|
|
1064
|
-
return;
|
|
1065
|
-
}
|
|
1066
|
-
if (!session.closeSent) {
|
|
1067
|
-
session.closeSent = true;
|
|
1068
|
-
session.socket.write(encodeWebSocketFrame("close", Buffer.alloc(0), closeCode, closeReason));
|
|
1069
|
-
this.emitWebSocketEvent("websocket:session_written", {
|
|
1070
|
-
sessionId,
|
|
1071
|
-
frameType: "close",
|
|
1072
|
-
byteLength: closeReason ? Buffer.byteLength(closeReason) + 2 : closeCode ? 2 : 0,
|
|
1073
|
-
automatic: false,
|
|
1074
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1075
|
-
});
|
|
1076
|
-
}
|
|
1077
|
-
});
|
|
1078
|
-
}
|
|
1079
|
-
async closeBufferedSession(sessionId, sessions, kind, args) {
|
|
1080
|
-
const session = sessions.get(sessionId);
|
|
1081
|
-
if (!session) return {
|
|
1082
|
-
ok: false,
|
|
1083
|
-
error: `Unknown ${kind} sessionId "${sessionId}"`
|
|
1084
|
-
};
|
|
1085
|
-
const force = argBool(args, "force") ?? false;
|
|
1086
|
-
const timeoutMs = argNumber(args, "timeoutMs") ?? 1e3;
|
|
757
|
+
const force = argBool(args, "force") ?? false;
|
|
758
|
+
const timeoutMs = argNumber(args, "timeoutMs") ?? 1e3;
|
|
1087
759
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return {
|
|
1088
760
|
ok: false,
|
|
1089
761
|
error: "timeoutMs must be a positive number"
|
|
@@ -1145,6 +817,13 @@ var BoringsslInspectorHandlers = class {
|
|
|
1145
817
|
session.socket.end();
|
|
1146
818
|
});
|
|
1147
819
|
}
|
|
820
|
+
};
|
|
821
|
+
//#endregion
|
|
822
|
+
//#region src/server/domains/boringssl-inspector/handlers/tls-handlers.ts
|
|
823
|
+
/**
|
|
824
|
+
* BoringsslInspectorTlsHandlers — keylog and TLS parsing helpers.
|
|
825
|
+
*/
|
|
826
|
+
var BoringsslInspectorTlsHandlers = class extends BoringsslInspectorBaseHandlers {
|
|
1148
827
|
async handleTlsKeylogEnable(_args) {
|
|
1149
828
|
return {
|
|
1150
829
|
enabled: true,
|
|
@@ -1240,7 +919,7 @@ var BoringsslInspectorHandlers = class {
|
|
|
1240
919
|
error: "rawHex is required"
|
|
1241
920
|
});
|
|
1242
921
|
const normalizedHex = normalizeHex(rawHex);
|
|
1243
|
-
if (
|
|
922
|
+
if (!/^(?:[0-9a-f]{2})+$/i.test(normalizedHex)) return asJsonResponse({
|
|
1244
923
|
success: false,
|
|
1245
924
|
error: "Invalid hex payload"
|
|
1246
925
|
});
|
|
@@ -1249,9 +928,9 @@ var BoringsslInspectorHandlers = class {
|
|
|
1249
928
|
success: false,
|
|
1250
929
|
error: "TLS record is too short"
|
|
1251
930
|
});
|
|
1252
|
-
const contentType = record[0]
|
|
1253
|
-
const versionMajor = record[1]
|
|
1254
|
-
const versionMinor = record[2]
|
|
931
|
+
const contentType = record[0];
|
|
932
|
+
const versionMajor = record[1];
|
|
933
|
+
const versionMinor = record[2];
|
|
1255
934
|
const declaredLength = record.readUInt16BE(3);
|
|
1256
935
|
const payload = record.subarray(5);
|
|
1257
936
|
const clientHello = contentType === 22 && payload.length > 0 && payload[0] === 1 ? parseClientHello(payload) : void 0;
|
|
@@ -1331,12 +1010,25 @@ var BoringsslInspectorHandlers = class {
|
|
|
1331
1010
|
return asJsonResponse({
|
|
1332
1011
|
success: true,
|
|
1333
1012
|
certificateCount: certs.length,
|
|
1334
|
-
fingerprints: certs.map((
|
|
1335
|
-
sha256:
|
|
1336
|
-
length:
|
|
1013
|
+
fingerprints: certs.map((cert) => ({
|
|
1014
|
+
sha256: cert.sha256,
|
|
1015
|
+
length: cert.length
|
|
1337
1016
|
}))
|
|
1338
1017
|
});
|
|
1339
1018
|
}
|
|
1019
|
+
};
|
|
1020
|
+
//#endregion
|
|
1021
|
+
//#region src/server/domains/boringssl-inspector/handlers/tls-probe-handlers.ts
|
|
1022
|
+
/**
|
|
1023
|
+
* BoringsslInspectorTlsProbeHandlers — active TLS probe handler.
|
|
1024
|
+
*/
|
|
1025
|
+
const TLS_VERSION_ORDER$2 = [
|
|
1026
|
+
"TLSv1",
|
|
1027
|
+
"TLSv1.1",
|
|
1028
|
+
"TLSv1.2",
|
|
1029
|
+
"TLSv1.3"
|
|
1030
|
+
];
|
|
1031
|
+
var BoringsslInspectorTlsProbeHandlers = class extends BoringsslInspectorTlsHandlers {
|
|
1340
1032
|
async handleTlsProbeEndpoint(args) {
|
|
1341
1033
|
const host = argString(args, "host")?.trim() ?? null;
|
|
1342
1034
|
if (!host) return {
|
|
@@ -1368,13 +1060,7 @@ var BoringsslInspectorHandlers = class {
|
|
|
1368
1060
|
error: errorMessage(error)
|
|
1369
1061
|
};
|
|
1370
1062
|
}
|
|
1371
|
-
|
|
1372
|
-
"TLSv1",
|
|
1373
|
-
"TLSv1.1",
|
|
1374
|
-
"TLSv1.2",
|
|
1375
|
-
"TLSv1.3"
|
|
1376
|
-
];
|
|
1377
|
-
if (minVersion && maxVersion && versionOrder.indexOf(minVersion) > versionOrder.indexOf(maxVersion)) return {
|
|
1063
|
+
if (minVersion && maxVersion && TLS_VERSION_ORDER$2.indexOf(minVersion) > TLS_VERSION_ORDER$2.indexOf(maxVersion)) return {
|
|
1378
1064
|
ok: false,
|
|
1379
1065
|
error: "minVersion must not be greater than maxVersion"
|
|
1380
1066
|
};
|
|
@@ -1547,6 +1233,19 @@ var BoringsslInspectorHandlers = class {
|
|
|
1547
1233
|
});
|
|
1548
1234
|
});
|
|
1549
1235
|
}
|
|
1236
|
+
};
|
|
1237
|
+
//#endregion
|
|
1238
|
+
//#region src/server/domains/boringssl-inspector/handlers/session-handlers.ts
|
|
1239
|
+
/**
|
|
1240
|
+
* BoringsslInspectorSessionHandlers — stateful TCP and TLS session handlers.
|
|
1241
|
+
*/
|
|
1242
|
+
const TLS_VERSION_ORDER$1 = [
|
|
1243
|
+
"TLSv1",
|
|
1244
|
+
"TLSv1.1",
|
|
1245
|
+
"TLSv1.2",
|
|
1246
|
+
"TLSv1.3"
|
|
1247
|
+
];
|
|
1248
|
+
var BoringsslInspectorSessionHandlers = class extends BoringsslInspectorTlsProbeHandlers {
|
|
1550
1249
|
async handleTcpOpen(args) {
|
|
1551
1250
|
const host = argString(args, "host") ?? "127.0.0.1";
|
|
1552
1251
|
const port = argNumber(args, "port");
|
|
@@ -1702,13 +1401,7 @@ var BoringsslInspectorHandlers = class {
|
|
|
1702
1401
|
error: errorMessage(error)
|
|
1703
1402
|
};
|
|
1704
1403
|
}
|
|
1705
|
-
|
|
1706
|
-
"TLSv1",
|
|
1707
|
-
"TLSv1.1",
|
|
1708
|
-
"TLSv1.2",
|
|
1709
|
-
"TLSv1.3"
|
|
1710
|
-
];
|
|
1711
|
-
if (minVersion && maxVersion && versionOrder.indexOf(minVersion) > versionOrder.indexOf(maxVersion)) return {
|
|
1404
|
+
if (minVersion && maxVersion && TLS_VERSION_ORDER$1.indexOf(minVersion) > TLS_VERSION_ORDER$1.indexOf(maxVersion)) return {
|
|
1712
1405
|
ok: false,
|
|
1713
1406
|
error: "minVersion must not be greater than maxVersion"
|
|
1714
1407
|
};
|
|
@@ -1741,195 +1434,585 @@ var BoringsslInspectorHandlers = class {
|
|
|
1741
1434
|
const startedAt = Date.now();
|
|
1742
1435
|
return new Promise((resolve) => {
|
|
1743
1436
|
let settled = false;
|
|
1744
|
-
const socket = connect(applyTlsValidationPolicy({
|
|
1745
|
-
host,
|
|
1746
|
-
port,
|
|
1747
|
-
servername: target.requestedServername ?? void 0,
|
|
1748
|
-
...minVersion ? { minVersion } : {},
|
|
1749
|
-
...maxVersion ? { maxVersion } : {},
|
|
1750
|
-
...alpnProtocols.length > 0 ? { ALPNProtocols: alpnProtocols } : {},
|
|
1751
|
-
...caBundle.ca ? { ca: caBundle.ca } : {}
|
|
1752
|
-
}, allowInvalidCertificates));
|
|
1753
|
-
const finish = (payload) => {
|
|
1437
|
+
const socket = connect(applyTlsValidationPolicy({
|
|
1438
|
+
host,
|
|
1439
|
+
port,
|
|
1440
|
+
servername: target.requestedServername ?? void 0,
|
|
1441
|
+
...minVersion ? { minVersion } : {},
|
|
1442
|
+
...maxVersion ? { maxVersion } : {},
|
|
1443
|
+
...alpnProtocols.length > 0 ? { ALPNProtocols: alpnProtocols } : {},
|
|
1444
|
+
...caBundle.ca ? { ca: caBundle.ca } : {}
|
|
1445
|
+
}, allowInvalidCertificates));
|
|
1446
|
+
const finish = (payload) => {
|
|
1447
|
+
if (settled) return;
|
|
1448
|
+
settled = true;
|
|
1449
|
+
clearTimeout(timer);
|
|
1450
|
+
socket.off("error", onError);
|
|
1451
|
+
socket.off("secureConnect", onSecureConnect);
|
|
1452
|
+
resolve(payload);
|
|
1453
|
+
};
|
|
1454
|
+
const timer = setTimeout(() => {
|
|
1455
|
+
socket.destroy();
|
|
1456
|
+
this.eventBus?.emit("tls:probe_completed", {
|
|
1457
|
+
host,
|
|
1458
|
+
port,
|
|
1459
|
+
success: false,
|
|
1460
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1461
|
+
});
|
|
1462
|
+
finish({
|
|
1463
|
+
ok: false,
|
|
1464
|
+
error: "TLS open timed out",
|
|
1465
|
+
target,
|
|
1466
|
+
policy
|
|
1467
|
+
});
|
|
1468
|
+
}, timeoutMs);
|
|
1469
|
+
const onError = (error) => {
|
|
1470
|
+
this.eventBus?.emit("tls:probe_completed", {
|
|
1471
|
+
host,
|
|
1472
|
+
port,
|
|
1473
|
+
success: false,
|
|
1474
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1475
|
+
});
|
|
1476
|
+
finish({
|
|
1477
|
+
ok: false,
|
|
1478
|
+
error: error.message,
|
|
1479
|
+
errorCode: error.code ?? null,
|
|
1480
|
+
target,
|
|
1481
|
+
policy
|
|
1482
|
+
});
|
|
1483
|
+
};
|
|
1484
|
+
const onSecureConnect = () => {
|
|
1485
|
+
const handshakeMs = Date.now() - startedAt;
|
|
1486
|
+
const peerCertificate = socket.getPeerCertificate(true);
|
|
1487
|
+
const hasLeafCertificate = hasPeerCertificate(peerCertificate);
|
|
1488
|
+
const certificateChain = hasLeafCertificate ? buildPeerCertificateChain(peerCertificate) : [];
|
|
1489
|
+
const leafCertificate = certificateChain[0] ?? null;
|
|
1490
|
+
const hostnameError = skipHostnameCheck || !hasLeafCertificate ? void 0 : checkServerIdentity(target.validationTarget, peerCertificate);
|
|
1491
|
+
const hostnameValidation = {
|
|
1492
|
+
checked: !skipHostnameCheck,
|
|
1493
|
+
target: skipHostnameCheck ? null : target.validationTarget,
|
|
1494
|
+
matched: skipHostnameCheck ? null : hostnameError === void 0,
|
|
1495
|
+
error: !skipHostnameCheck && !hasLeafCertificate ? "Peer certificate was not presented by the server" : hostnameError?.message ?? null
|
|
1496
|
+
};
|
|
1497
|
+
const authorizationReasons = [
|
|
1498
|
+
socket.authorized ? "Certificate chain validated against the active trust store." : `Certificate chain validation failed: ${socket.authorizationError ?? "unknown_authority"}`,
|
|
1499
|
+
skipHostnameCheck ? "Hostname validation was skipped by request." : hostnameValidation.matched ? "Hostname validation passed." : `Hostname validation failed: ${hostnameValidation.error ?? "unknown_error"}`,
|
|
1500
|
+
!socket.authorized && allowInvalidCertificates ? "Policy allowed the session to continue despite certificate trust failure." : null
|
|
1501
|
+
].filter((reason) => Boolean(reason));
|
|
1502
|
+
const cipher = socket.getCipher();
|
|
1503
|
+
const metadata = {
|
|
1504
|
+
target,
|
|
1505
|
+
policy,
|
|
1506
|
+
transport: {
|
|
1507
|
+
protocol: socket.getProtocol() ?? null,
|
|
1508
|
+
alpnProtocol: normalizeAlpnProtocol(socket.alpnProtocol),
|
|
1509
|
+
cipher: {
|
|
1510
|
+
name: cipher.name,
|
|
1511
|
+
standardName: cipher.standardName,
|
|
1512
|
+
version: cipher.version
|
|
1513
|
+
},
|
|
1514
|
+
localAddress: socket.localAddress ?? null,
|
|
1515
|
+
localPort: socket.localPort ?? null,
|
|
1516
|
+
remoteAddress: socket.remoteAddress ?? null,
|
|
1517
|
+
remotePort: socket.remotePort ?? null,
|
|
1518
|
+
servernameSent: normalizeSocketServername(socket.servername),
|
|
1519
|
+
sessionReused: socket.isSessionReused()
|
|
1520
|
+
},
|
|
1521
|
+
authorization: {
|
|
1522
|
+
socketAuthorized: socket.authorized,
|
|
1523
|
+
authorizationError: typeof socket.authorizationError === "string" ? socket.authorizationError : socket.authorizationError?.message ?? null,
|
|
1524
|
+
hostnameValidation,
|
|
1525
|
+
policyAllowed: (socket.authorized || allowInvalidCertificates) && (skipHostnameCheck || hostnameValidation.matched === true),
|
|
1526
|
+
reasons: authorizationReasons
|
|
1527
|
+
},
|
|
1528
|
+
certificates: {
|
|
1529
|
+
leaf: leafCertificate,
|
|
1530
|
+
chain: certificateChain
|
|
1531
|
+
}
|
|
1532
|
+
};
|
|
1533
|
+
if (!metadata.authorization.policyAllowed) {
|
|
1534
|
+
socket.destroy();
|
|
1535
|
+
this.eventBus?.emit("tls:probe_completed", {
|
|
1536
|
+
host,
|
|
1537
|
+
port,
|
|
1538
|
+
success: false,
|
|
1539
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1540
|
+
});
|
|
1541
|
+
finish({
|
|
1542
|
+
ok: false,
|
|
1543
|
+
error: "TLS session authorization failed",
|
|
1544
|
+
...metadata,
|
|
1545
|
+
timing: { handshakeMs }
|
|
1546
|
+
});
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
const sessionId = makeSessionId("tls");
|
|
1550
|
+
const session = {
|
|
1551
|
+
id: sessionId,
|
|
1552
|
+
kind: "tls",
|
|
1553
|
+
socket,
|
|
1554
|
+
host,
|
|
1555
|
+
port,
|
|
1556
|
+
createdAt: Date.now(),
|
|
1557
|
+
buffer: Buffer.alloc(0),
|
|
1558
|
+
ended: false,
|
|
1559
|
+
closed: false,
|
|
1560
|
+
error: null,
|
|
1561
|
+
waiters: /* @__PURE__ */ new Set(),
|
|
1562
|
+
activeRead: false,
|
|
1563
|
+
metadata
|
|
1564
|
+
};
|
|
1565
|
+
attachBufferedSession(session);
|
|
1566
|
+
this.tlsSessions.set(sessionId, session);
|
|
1567
|
+
this.eventBus?.emit("tls:session_opened", {
|
|
1568
|
+
sessionId,
|
|
1569
|
+
host,
|
|
1570
|
+
port,
|
|
1571
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1572
|
+
});
|
|
1573
|
+
this.eventBus?.emit("tls:probe_completed", {
|
|
1574
|
+
host,
|
|
1575
|
+
port,
|
|
1576
|
+
success: true,
|
|
1577
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1578
|
+
});
|
|
1579
|
+
finish({
|
|
1580
|
+
ok: true,
|
|
1581
|
+
sessionId,
|
|
1582
|
+
kind: "tls",
|
|
1583
|
+
...metadata,
|
|
1584
|
+
timing: { handshakeMs },
|
|
1585
|
+
state: serializeSessionState(session)
|
|
1586
|
+
});
|
|
1587
|
+
};
|
|
1588
|
+
socket.once("error", onError);
|
|
1589
|
+
socket.once("secureConnect", onSecureConnect);
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
async handleTlsWrite(args) {
|
|
1593
|
+
const sessionId = argString(args, "sessionId")?.trim() ?? null;
|
|
1594
|
+
if (!sessionId) return {
|
|
1595
|
+
ok: false,
|
|
1596
|
+
error: "sessionId is required"
|
|
1597
|
+
};
|
|
1598
|
+
const session = this.getTlsSession(sessionId);
|
|
1599
|
+
if (!session) return {
|
|
1600
|
+
ok: false,
|
|
1601
|
+
error: `Unknown tls sessionId "${sessionId}"`
|
|
1602
|
+
};
|
|
1603
|
+
return this.writeBufferedSession(session, args);
|
|
1604
|
+
}
|
|
1605
|
+
async handleTlsReadUntil(args) {
|
|
1606
|
+
const sessionId = argString(args, "sessionId")?.trim() ?? null;
|
|
1607
|
+
if (!sessionId) return {
|
|
1608
|
+
ok: false,
|
|
1609
|
+
error: "sessionId is required"
|
|
1610
|
+
};
|
|
1611
|
+
const session = this.getTlsSession(sessionId);
|
|
1612
|
+
if (!session) return {
|
|
1613
|
+
ok: false,
|
|
1614
|
+
error: `Unknown tls sessionId "${sessionId}"`
|
|
1615
|
+
};
|
|
1616
|
+
return this.readBufferedSessionUntil(session, args);
|
|
1617
|
+
}
|
|
1618
|
+
async handleTlsClose(args) {
|
|
1619
|
+
const sessionId = argString(args, "sessionId")?.trim() ?? null;
|
|
1620
|
+
if (!sessionId) return {
|
|
1621
|
+
ok: false,
|
|
1622
|
+
error: "sessionId is required"
|
|
1623
|
+
};
|
|
1624
|
+
return this.closeBufferedSession(sessionId, this.tlsSessions, "tls", args);
|
|
1625
|
+
}
|
|
1626
|
+
};
|
|
1627
|
+
//#endregion
|
|
1628
|
+
//#region src/server/domains/boringssl-inspector/handlers/websocket-frame-handlers.ts
|
|
1629
|
+
/**
|
|
1630
|
+
* BoringsslInspectorWebSocketFrameHandlers — WebSocket session/frame operations.
|
|
1631
|
+
*/
|
|
1632
|
+
var BoringsslInspectorWebSocketFrameHandlers = class extends BoringsslInspectorSessionHandlers {
|
|
1633
|
+
emitWebSocketEvent(event, payload) {
|
|
1634
|
+
this.eventBus?.emit(event, payload);
|
|
1635
|
+
}
|
|
1636
|
+
attachWebSocketSession(session) {
|
|
1637
|
+
const parseBufferedFrames = () => {
|
|
1638
|
+
while (session.parserBuffer.length > 0) {
|
|
1639
|
+
let consumed;
|
|
1640
|
+
try {
|
|
1641
|
+
consumed = tryConsumeWebSocketFrame(session.parserBuffer);
|
|
1642
|
+
} catch (error) {
|
|
1643
|
+
session.error = errorMessage(error);
|
|
1644
|
+
session.socket.destroy();
|
|
1645
|
+
break;
|
|
1646
|
+
}
|
|
1647
|
+
if (!consumed) break;
|
|
1648
|
+
session.parserBuffer = session.parserBuffer.subarray(consumed.bytesConsumed);
|
|
1649
|
+
const frame = consumed.frame;
|
|
1650
|
+
session.frames.push(frame);
|
|
1651
|
+
if (frame.type === "ping" && !session.closeSent && !session.socket.destroyed) {
|
|
1652
|
+
const pongFrame = encodeWebSocketFrame("pong", frame.data);
|
|
1653
|
+
session.socket.write(pongFrame);
|
|
1654
|
+
this.emitWebSocketEvent("websocket:session_written", {
|
|
1655
|
+
sessionId: session.id,
|
|
1656
|
+
frameType: "pong",
|
|
1657
|
+
byteLength: frame.data.length,
|
|
1658
|
+
automatic: true,
|
|
1659
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
if (frame.type === "close") {
|
|
1663
|
+
session.closeReceived = true;
|
|
1664
|
+
if (!session.closeSent && !session.socket.destroyed) {
|
|
1665
|
+
session.closeSent = true;
|
|
1666
|
+
session.socket.write(encodeWebSocketFrame("close", frame.data, frame.closeCode, frame.closeReason));
|
|
1667
|
+
this.emitWebSocketEvent("websocket:session_written", {
|
|
1668
|
+
sessionId: session.id,
|
|
1669
|
+
frameType: "close",
|
|
1670
|
+
byteLength: frame.data.length,
|
|
1671
|
+
automatic: true,
|
|
1672
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
wakeWebSocketWaiters(session);
|
|
1678
|
+
};
|
|
1679
|
+
session.socket.on("data", (chunk) => {
|
|
1680
|
+
session.parserBuffer = Buffer.concat([session.parserBuffer, chunk]);
|
|
1681
|
+
parseBufferedFrames();
|
|
1682
|
+
});
|
|
1683
|
+
session.socket.on("end", () => {
|
|
1684
|
+
session.ended = true;
|
|
1685
|
+
wakeWebSocketWaiters(session);
|
|
1686
|
+
});
|
|
1687
|
+
session.socket.on("close", () => {
|
|
1688
|
+
session.closed = true;
|
|
1689
|
+
wakeWebSocketWaiters(session);
|
|
1690
|
+
});
|
|
1691
|
+
session.socket.on("error", (error) => {
|
|
1692
|
+
session.error = error.message;
|
|
1693
|
+
wakeWebSocketWaiters(session);
|
|
1694
|
+
});
|
|
1695
|
+
parseBufferedFrames();
|
|
1696
|
+
}
|
|
1697
|
+
async readWebSocketFrame(session, args) {
|
|
1698
|
+
const timeoutMs = argNumber(args, "timeoutMs") ?? 5e3;
|
|
1699
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return {
|
|
1700
|
+
ok: false,
|
|
1701
|
+
error: "timeoutMs must be a positive number"
|
|
1702
|
+
};
|
|
1703
|
+
if (session.activeRead) return {
|
|
1704
|
+
ok: false,
|
|
1705
|
+
error: `Session "${session.id}" already has a pending read`,
|
|
1706
|
+
sessionId: session.id,
|
|
1707
|
+
kind: session.kind,
|
|
1708
|
+
state: serializeWebSocketSessionState(session)
|
|
1709
|
+
};
|
|
1710
|
+
session.activeRead = true;
|
|
1711
|
+
const startedAt = Date.now();
|
|
1712
|
+
try {
|
|
1713
|
+
while (true) {
|
|
1714
|
+
const frame = session.frames.shift();
|
|
1715
|
+
if (frame) {
|
|
1716
|
+
this.emitWebSocketEvent("websocket:frame_read", {
|
|
1717
|
+
sessionId: session.id,
|
|
1718
|
+
frameType: frame.type,
|
|
1719
|
+
byteLength: frame.data.length,
|
|
1720
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1721
|
+
});
|
|
1722
|
+
return {
|
|
1723
|
+
ok: true,
|
|
1724
|
+
sessionId: session.id,
|
|
1725
|
+
kind: session.kind,
|
|
1726
|
+
scheme: session.scheme,
|
|
1727
|
+
frameType: frame.type,
|
|
1728
|
+
fin: frame.fin,
|
|
1729
|
+
opcode: frame.opcode,
|
|
1730
|
+
masked: frame.masked,
|
|
1731
|
+
byteLength: frame.data.length,
|
|
1732
|
+
dataHex: frame.data.toString("hex").toUpperCase(),
|
|
1733
|
+
dataText: frame.type === "binary" ? null : frame.data.toString("utf8"),
|
|
1734
|
+
closeCode: frame.closeCode,
|
|
1735
|
+
closeReason: frame.closeReason,
|
|
1736
|
+
elapsedMs: Date.now() - startedAt,
|
|
1737
|
+
state: serializeWebSocketSessionState(session)
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
if (session.error) return {
|
|
1741
|
+
ok: false,
|
|
1742
|
+
error: session.error,
|
|
1743
|
+
sessionId: session.id,
|
|
1744
|
+
kind: session.kind,
|
|
1745
|
+
state: serializeWebSocketSessionState(session)
|
|
1746
|
+
};
|
|
1747
|
+
if (session.closed || session.ended) return {
|
|
1748
|
+
ok: false,
|
|
1749
|
+
error: "socket closed before a WebSocket frame was available",
|
|
1750
|
+
sessionId: session.id,
|
|
1751
|
+
kind: session.kind,
|
|
1752
|
+
state: serializeWebSocketSessionState(session)
|
|
1753
|
+
};
|
|
1754
|
+
const remainingMs = timeoutMs - (Date.now() - startedAt);
|
|
1755
|
+
if (remainingMs <= 0) return {
|
|
1756
|
+
ok: false,
|
|
1757
|
+
error: "read timed out",
|
|
1758
|
+
sessionId: session.id,
|
|
1759
|
+
kind: session.kind,
|
|
1760
|
+
state: serializeWebSocketSessionState(session)
|
|
1761
|
+
};
|
|
1762
|
+
if (!await waitForWebSocketActivity(session, remainingMs)) return {
|
|
1763
|
+
ok: false,
|
|
1764
|
+
error: "read timed out",
|
|
1765
|
+
sessionId: session.id,
|
|
1766
|
+
kind: session.kind,
|
|
1767
|
+
state: serializeWebSocketSessionState(session)
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
} finally {
|
|
1771
|
+
session.activeRead = false;
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
async sendWebSocketFrame(session, args) {
|
|
1775
|
+
if (session.closed || session.socket.destroyed) return {
|
|
1776
|
+
ok: false,
|
|
1777
|
+
error: `Session "${session.id}" is already closed`,
|
|
1778
|
+
sessionId: session.id,
|
|
1779
|
+
kind: session.kind,
|
|
1780
|
+
state: serializeWebSocketSessionState(session)
|
|
1781
|
+
};
|
|
1782
|
+
const frameType = argEnum(args, "frameType", new Set([
|
|
1783
|
+
"text",
|
|
1784
|
+
"binary",
|
|
1785
|
+
"ping",
|
|
1786
|
+
"pong",
|
|
1787
|
+
"close"
|
|
1788
|
+
]));
|
|
1789
|
+
if (!frameType) return {
|
|
1790
|
+
ok: false,
|
|
1791
|
+
error: "frameType is required"
|
|
1792
|
+
};
|
|
1793
|
+
const dataHex = argString(args, "dataHex");
|
|
1794
|
+
const dataText = argString(args, "dataText");
|
|
1795
|
+
if (dataHex && dataText) return {
|
|
1796
|
+
ok: false,
|
|
1797
|
+
error: "dataHex and dataText are mutually exclusive"
|
|
1798
|
+
};
|
|
1799
|
+
const timeoutMs = argNumber(args, "timeoutMs") ?? 5e3;
|
|
1800
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return {
|
|
1801
|
+
ok: false,
|
|
1802
|
+
error: "timeoutMs must be a positive number"
|
|
1803
|
+
};
|
|
1804
|
+
let payload = Buffer.alloc(0);
|
|
1805
|
+
if (dataHex) {
|
|
1806
|
+
const normalized = normalizeHex(dataHex);
|
|
1807
|
+
if (!isHex(normalized)) return {
|
|
1808
|
+
ok: false,
|
|
1809
|
+
error: "dataHex must be valid even-length hexadecimal data"
|
|
1810
|
+
};
|
|
1811
|
+
payload = Buffer.from(normalized, "hex");
|
|
1812
|
+
} else if (dataText !== void 0) payload = Buffer.from(dataText, "utf8");
|
|
1813
|
+
let closeCode = null;
|
|
1814
|
+
let closeReason = null;
|
|
1815
|
+
if (frameType === "close") {
|
|
1816
|
+
const rawCloseCode = argNumber(args, "closeCode");
|
|
1817
|
+
if (rawCloseCode !== void 0) {
|
|
1818
|
+
if (!Number.isInteger(rawCloseCode) || rawCloseCode < 1e3 || rawCloseCode > 4999) return {
|
|
1819
|
+
ok: false,
|
|
1820
|
+
error: "closeCode must be an integer between 1000 and 4999"
|
|
1821
|
+
};
|
|
1822
|
+
closeCode = rawCloseCode;
|
|
1823
|
+
}
|
|
1824
|
+
closeReason = argString(args, "closeReason") ?? null;
|
|
1825
|
+
if (dataHex || dataText) return {
|
|
1826
|
+
ok: false,
|
|
1827
|
+
error: "close frames use closeCode/closeReason instead of dataHex/dataText"
|
|
1828
|
+
};
|
|
1829
|
+
session.closeSent = true;
|
|
1830
|
+
}
|
|
1831
|
+
if (frameType === "text" && dataHex) return {
|
|
1832
|
+
ok: false,
|
|
1833
|
+
error: "text frames require UTF-8 dataText instead of dataHex"
|
|
1834
|
+
};
|
|
1835
|
+
const frameBuffer = encodeWebSocketFrame(frameType, payload, closeCode, closeReason);
|
|
1836
|
+
return new Promise((resolve) => {
|
|
1837
|
+
let settled = false;
|
|
1838
|
+
const finish = (result) => {
|
|
1839
|
+
if (settled) return;
|
|
1840
|
+
settled = true;
|
|
1841
|
+
clearTimeout(timer);
|
|
1842
|
+
session.socket.off("error", onError);
|
|
1843
|
+
resolve(result);
|
|
1844
|
+
};
|
|
1845
|
+
const timer = setTimeout(() => {
|
|
1846
|
+
finish({
|
|
1847
|
+
ok: false,
|
|
1848
|
+
error: "write timed out",
|
|
1849
|
+
sessionId: session.id,
|
|
1850
|
+
kind: session.kind,
|
|
1851
|
+
state: serializeWebSocketSessionState(session)
|
|
1852
|
+
});
|
|
1853
|
+
}, timeoutMs);
|
|
1854
|
+
const onError = (error) => {
|
|
1855
|
+
finish({
|
|
1856
|
+
ok: false,
|
|
1857
|
+
error: error.message,
|
|
1858
|
+
sessionId: session.id,
|
|
1859
|
+
kind: session.kind,
|
|
1860
|
+
state: serializeWebSocketSessionState(session)
|
|
1861
|
+
});
|
|
1862
|
+
};
|
|
1863
|
+
session.socket.once("error", onError);
|
|
1864
|
+
session.socket.write(frameBuffer, () => {
|
|
1865
|
+
this.emitWebSocketEvent("websocket:session_written", {
|
|
1866
|
+
sessionId: session.id,
|
|
1867
|
+
frameType,
|
|
1868
|
+
byteLength: frameType === "close" ? closeReason ? Buffer.byteLength(closeReason) + 2 : closeCode ? 2 : 0 : payload.length,
|
|
1869
|
+
automatic: false,
|
|
1870
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1871
|
+
});
|
|
1872
|
+
finish({
|
|
1873
|
+
ok: true,
|
|
1874
|
+
sessionId: session.id,
|
|
1875
|
+
kind: session.kind,
|
|
1876
|
+
scheme: session.scheme,
|
|
1877
|
+
frameType,
|
|
1878
|
+
bytesWritten: frameBuffer.length,
|
|
1879
|
+
payloadBytes: frameType === "close" ? closeReason ? Buffer.byteLength(closeReason) + 2 : closeCode ? 2 : 0 : payload.length,
|
|
1880
|
+
state: serializeWebSocketSessionState(session)
|
|
1881
|
+
});
|
|
1882
|
+
});
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
async closeWebSocketSession(sessionId, args) {
|
|
1886
|
+
const session = this.websocketSessions.get(sessionId);
|
|
1887
|
+
if (!session) return {
|
|
1888
|
+
ok: false,
|
|
1889
|
+
error: `Unknown websocket sessionId "${sessionId}"`
|
|
1890
|
+
};
|
|
1891
|
+
const force = argBool(args, "force") ?? false;
|
|
1892
|
+
const timeoutMs = argNumber(args, "timeoutMs") ?? 1e3;
|
|
1893
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return {
|
|
1894
|
+
ok: false,
|
|
1895
|
+
error: "timeoutMs must be a positive number"
|
|
1896
|
+
};
|
|
1897
|
+
const queuedFramesDiscarded = session.frames.length;
|
|
1898
|
+
if (session.closed || session.socket.destroyed) {
|
|
1899
|
+
this.websocketSessions.delete(sessionId);
|
|
1900
|
+
return {
|
|
1901
|
+
ok: true,
|
|
1902
|
+
sessionId,
|
|
1903
|
+
kind: session.kind,
|
|
1904
|
+
force,
|
|
1905
|
+
closed: true,
|
|
1906
|
+
queuedFramesDiscarded,
|
|
1907
|
+
state: serializeWebSocketSessionState(session)
|
|
1908
|
+
};
|
|
1909
|
+
}
|
|
1910
|
+
let closeCode = null;
|
|
1911
|
+
const rawCloseCode = argNumber(args, "closeCode");
|
|
1912
|
+
if (rawCloseCode !== void 0) {
|
|
1913
|
+
if (!Number.isInteger(rawCloseCode) || rawCloseCode < 1e3 || rawCloseCode > 4999) return {
|
|
1914
|
+
ok: false,
|
|
1915
|
+
error: "closeCode must be an integer between 1000 and 4999"
|
|
1916
|
+
};
|
|
1917
|
+
closeCode = rawCloseCode;
|
|
1918
|
+
}
|
|
1919
|
+
const closeReason = argString(args, "closeReason") ?? null;
|
|
1920
|
+
return new Promise((resolve) => {
|
|
1921
|
+
let settled = false;
|
|
1922
|
+
const finish = (closed) => {
|
|
1754
1923
|
if (settled) return;
|
|
1755
1924
|
settled = true;
|
|
1756
1925
|
clearTimeout(timer);
|
|
1757
|
-
socket.off("
|
|
1758
|
-
socket.off("
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
const timer = setTimeout(() => {
|
|
1762
|
-
socket.destroy();
|
|
1763
|
-
this.eventBus?.emit("tls:probe_completed", {
|
|
1764
|
-
host,
|
|
1765
|
-
port,
|
|
1766
|
-
success: false,
|
|
1767
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1768
|
-
});
|
|
1769
|
-
finish({
|
|
1770
|
-
ok: false,
|
|
1771
|
-
error: "TLS open timed out",
|
|
1772
|
-
target,
|
|
1773
|
-
policy
|
|
1774
|
-
});
|
|
1775
|
-
}, timeoutMs);
|
|
1776
|
-
const onError = (error) => {
|
|
1777
|
-
this.eventBus?.emit("tls:probe_completed", {
|
|
1778
|
-
host,
|
|
1779
|
-
port,
|
|
1780
|
-
success: false,
|
|
1781
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1782
|
-
});
|
|
1783
|
-
finish({
|
|
1784
|
-
ok: false,
|
|
1785
|
-
error: error.message,
|
|
1786
|
-
errorCode: error.code ?? null,
|
|
1787
|
-
target,
|
|
1788
|
-
policy
|
|
1789
|
-
});
|
|
1790
|
-
};
|
|
1791
|
-
const onSecureConnect = () => {
|
|
1792
|
-
const handshakeMs = Date.now() - startedAt;
|
|
1793
|
-
const peerCertificate = socket.getPeerCertificate(true);
|
|
1794
|
-
const hasLeafCertificate = hasPeerCertificate(peerCertificate);
|
|
1795
|
-
const certificateChain = hasLeafCertificate ? buildPeerCertificateChain(peerCertificate) : [];
|
|
1796
|
-
const leafCertificate = certificateChain[0] ?? null;
|
|
1797
|
-
const hostnameError = skipHostnameCheck || !hasLeafCertificate ? void 0 : checkServerIdentity(target.validationTarget, peerCertificate);
|
|
1798
|
-
const hostnameValidation = {
|
|
1799
|
-
checked: !skipHostnameCheck,
|
|
1800
|
-
target: skipHostnameCheck ? null : target.validationTarget,
|
|
1801
|
-
matched: skipHostnameCheck ? null : hostnameError === void 0,
|
|
1802
|
-
error: !skipHostnameCheck && !hasLeafCertificate ? "Peer certificate was not presented by the server" : hostnameError?.message ?? null
|
|
1803
|
-
};
|
|
1804
|
-
const authorizationReasons = [
|
|
1805
|
-
socket.authorized ? "Certificate chain validated against the active trust store." : `Certificate chain validation failed: ${socket.authorizationError ?? "unknown_authority"}`,
|
|
1806
|
-
skipHostnameCheck ? "Hostname validation was skipped by request." : hostnameValidation.matched ? "Hostname validation passed." : `Hostname validation failed: ${hostnameValidation.error ?? "unknown_error"}`,
|
|
1807
|
-
!socket.authorized && allowInvalidCertificates ? "Policy allowed the session to continue despite certificate trust failure." : null
|
|
1808
|
-
].filter((reason) => Boolean(reason));
|
|
1809
|
-
const cipher = socket.getCipher();
|
|
1810
|
-
const metadata = {
|
|
1811
|
-
target,
|
|
1812
|
-
policy,
|
|
1813
|
-
transport: {
|
|
1814
|
-
protocol: socket.getProtocol() ?? null,
|
|
1815
|
-
alpnProtocol: normalizeAlpnProtocol(socket.alpnProtocol),
|
|
1816
|
-
cipher: {
|
|
1817
|
-
name: cipher.name,
|
|
1818
|
-
standardName: cipher.standardName,
|
|
1819
|
-
version: cipher.version
|
|
1820
|
-
},
|
|
1821
|
-
localAddress: socket.localAddress ?? null,
|
|
1822
|
-
localPort: socket.localPort ?? null,
|
|
1823
|
-
remoteAddress: socket.remoteAddress ?? null,
|
|
1824
|
-
remotePort: socket.remotePort ?? null,
|
|
1825
|
-
servernameSent: normalizeSocketServername(socket.servername),
|
|
1826
|
-
sessionReused: socket.isSessionReused()
|
|
1827
|
-
},
|
|
1828
|
-
authorization: {
|
|
1829
|
-
socketAuthorized: socket.authorized,
|
|
1830
|
-
authorizationError: typeof socket.authorizationError === "string" ? socket.authorizationError : socket.authorizationError?.message ?? null,
|
|
1831
|
-
hostnameValidation,
|
|
1832
|
-
policyAllowed: (socket.authorized || allowInvalidCertificates) && (skipHostnameCheck || hostnameValidation.matched === true),
|
|
1833
|
-
reasons: authorizationReasons
|
|
1834
|
-
},
|
|
1835
|
-
certificates: {
|
|
1836
|
-
leaf: leafCertificate,
|
|
1837
|
-
chain: certificateChain
|
|
1838
|
-
}
|
|
1839
|
-
};
|
|
1840
|
-
if (!metadata.authorization.policyAllowed) {
|
|
1841
|
-
socket.destroy();
|
|
1842
|
-
this.eventBus?.emit("tls:probe_completed", {
|
|
1843
|
-
host,
|
|
1844
|
-
port,
|
|
1845
|
-
success: false,
|
|
1846
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1847
|
-
});
|
|
1848
|
-
finish({
|
|
1849
|
-
ok: false,
|
|
1850
|
-
error: "TLS session authorization failed",
|
|
1851
|
-
...metadata,
|
|
1852
|
-
timing: { handshakeMs }
|
|
1853
|
-
});
|
|
1854
|
-
return;
|
|
1855
|
-
}
|
|
1856
|
-
const sessionId = makeSessionId("tls");
|
|
1857
|
-
const session = {
|
|
1858
|
-
id: sessionId,
|
|
1859
|
-
kind: "tls",
|
|
1860
|
-
socket,
|
|
1861
|
-
host,
|
|
1862
|
-
port,
|
|
1863
|
-
createdAt: Date.now(),
|
|
1864
|
-
buffer: Buffer.alloc(0),
|
|
1865
|
-
ended: false,
|
|
1866
|
-
closed: false,
|
|
1867
|
-
error: null,
|
|
1868
|
-
waiters: /* @__PURE__ */ new Set(),
|
|
1869
|
-
activeRead: false,
|
|
1870
|
-
metadata
|
|
1871
|
-
};
|
|
1872
|
-
attachBufferedSession(session);
|
|
1873
|
-
this.tlsSessions.set(sessionId, session);
|
|
1874
|
-
this.eventBus?.emit("tls:session_opened", {
|
|
1926
|
+
session.socket.off("close", onClose);
|
|
1927
|
+
session.socket.off("error", onError);
|
|
1928
|
+
this.websocketSessions.delete(sessionId);
|
|
1929
|
+
this.emitWebSocketEvent("websocket:session_closed", {
|
|
1875
1930
|
sessionId,
|
|
1876
|
-
|
|
1877
|
-
port,
|
|
1878
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1879
|
-
});
|
|
1880
|
-
this.eventBus?.emit("tls:probe_completed", {
|
|
1881
|
-
host,
|
|
1882
|
-
port,
|
|
1883
|
-
success: true,
|
|
1931
|
+
reason: session.error,
|
|
1884
1932
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1885
1933
|
});
|
|
1886
|
-
|
|
1934
|
+
resolve({
|
|
1887
1935
|
ok: true,
|
|
1888
1936
|
sessionId,
|
|
1889
|
-
kind:
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1937
|
+
kind: session.kind,
|
|
1938
|
+
force,
|
|
1939
|
+
closed,
|
|
1940
|
+
queuedFramesDiscarded,
|
|
1941
|
+
state: serializeWebSocketSessionState(session)
|
|
1893
1942
|
});
|
|
1894
1943
|
};
|
|
1895
|
-
|
|
1896
|
-
socket.
|
|
1944
|
+
const onClose = () => finish(true);
|
|
1945
|
+
const onError = () => finish(session.socket.destroyed || session.closed);
|
|
1946
|
+
const timer = setTimeout(() => {
|
|
1947
|
+
session.socket.destroy();
|
|
1948
|
+
finish(session.socket.destroyed || session.closed);
|
|
1949
|
+
}, timeoutMs);
|
|
1950
|
+
session.socket.once("close", onClose);
|
|
1951
|
+
session.socket.once("error", onError);
|
|
1952
|
+
if (force) {
|
|
1953
|
+
session.socket.destroy();
|
|
1954
|
+
return;
|
|
1955
|
+
}
|
|
1956
|
+
if (!session.closeSent) {
|
|
1957
|
+
session.closeSent = true;
|
|
1958
|
+
session.socket.write(encodeWebSocketFrame("close", Buffer.alloc(0), closeCode, closeReason));
|
|
1959
|
+
this.emitWebSocketEvent("websocket:session_written", {
|
|
1960
|
+
sessionId,
|
|
1961
|
+
frameType: "close",
|
|
1962
|
+
byteLength: closeReason ? Buffer.byteLength(closeReason) + 2 : closeCode ? 2 : 0,
|
|
1963
|
+
automatic: false,
|
|
1964
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1897
1967
|
});
|
|
1898
1968
|
}
|
|
1899
|
-
async
|
|
1969
|
+
async handleWebSocketSendFrame(args) {
|
|
1900
1970
|
const sessionId = argString(args, "sessionId")?.trim() ?? null;
|
|
1901
1971
|
if (!sessionId) return {
|
|
1902
1972
|
ok: false,
|
|
1903
1973
|
error: "sessionId is required"
|
|
1904
1974
|
};
|
|
1905
|
-
const session = this.
|
|
1975
|
+
const session = this.getWebSocketSession(sessionId);
|
|
1906
1976
|
if (!session) return {
|
|
1907
1977
|
ok: false,
|
|
1908
|
-
error: `Unknown
|
|
1978
|
+
error: `Unknown websocket sessionId "${sessionId}"`
|
|
1909
1979
|
};
|
|
1910
|
-
return this.
|
|
1980
|
+
return this.sendWebSocketFrame(session, args);
|
|
1911
1981
|
}
|
|
1912
|
-
async
|
|
1982
|
+
async handleWebSocketReadFrame(args) {
|
|
1913
1983
|
const sessionId = argString(args, "sessionId")?.trim() ?? null;
|
|
1914
1984
|
if (!sessionId) return {
|
|
1915
1985
|
ok: false,
|
|
1916
1986
|
error: "sessionId is required"
|
|
1917
1987
|
};
|
|
1918
|
-
const session = this.
|
|
1988
|
+
const session = this.getWebSocketSession(sessionId);
|
|
1919
1989
|
if (!session) return {
|
|
1920
1990
|
ok: false,
|
|
1921
|
-
error: `Unknown
|
|
1991
|
+
error: `Unknown websocket sessionId "${sessionId}"`
|
|
1922
1992
|
};
|
|
1923
|
-
return this.
|
|
1993
|
+
return this.readWebSocketFrame(session, args);
|
|
1924
1994
|
}
|
|
1925
|
-
async
|
|
1995
|
+
async handleWebSocketClose(args) {
|
|
1926
1996
|
const sessionId = argString(args, "sessionId")?.trim() ?? null;
|
|
1927
1997
|
if (!sessionId) return {
|
|
1928
1998
|
ok: false,
|
|
1929
1999
|
error: "sessionId is required"
|
|
1930
2000
|
};
|
|
1931
|
-
return this.
|
|
2001
|
+
return this.closeWebSocketSession(sessionId, args);
|
|
1932
2002
|
}
|
|
2003
|
+
};
|
|
2004
|
+
//#endregion
|
|
2005
|
+
//#region src/server/domains/boringssl-inspector/handlers/websocket-handlers.ts
|
|
2006
|
+
/**
|
|
2007
|
+
* BoringsslInspectorWebSocketHandlers — WebSocket upgrade/open handlers and bypass helpers.
|
|
2008
|
+
*/
|
|
2009
|
+
const TLS_VERSION_ORDER = [
|
|
2010
|
+
"TLSv1",
|
|
2011
|
+
"TLSv1.1",
|
|
2012
|
+
"TLSv1.2",
|
|
2013
|
+
"TLSv1.3"
|
|
2014
|
+
];
|
|
2015
|
+
var BoringsslInspectorWebSocketHandlers = class extends BoringsslInspectorWebSocketFrameHandlers {
|
|
1933
2016
|
async handleWebSocketOpen(args) {
|
|
1934
2017
|
const rawUrl = argString(args, "url")?.trim() ?? null;
|
|
1935
2018
|
const rawHost = argString(args, "host")?.trim() ?? null;
|
|
@@ -2007,13 +2090,7 @@ var BoringsslInspectorHandlers = class {
|
|
|
2007
2090
|
error: errorMessage(error)
|
|
2008
2091
|
};
|
|
2009
2092
|
}
|
|
2010
|
-
|
|
2011
|
-
"TLSv1",
|
|
2012
|
-
"TLSv1.1",
|
|
2013
|
-
"TLSv1.2",
|
|
2014
|
-
"TLSv1.3"
|
|
2015
|
-
];
|
|
2016
|
-
if (minVersion && maxVersion && versionOrder.indexOf(minVersion) > versionOrder.indexOf(maxVersion)) return {
|
|
2093
|
+
if (minVersion && maxVersion && TLS_VERSION_ORDER.indexOf(minVersion) > TLS_VERSION_ORDER.indexOf(maxVersion)) return {
|
|
2017
2094
|
ok: false,
|
|
2018
2095
|
error: "minVersion must not be greater than maxVersion"
|
|
2019
2096
|
};
|
|
@@ -2110,14 +2187,6 @@ var BoringsslInspectorHandlers = class {
|
|
|
2110
2187
|
sendHandshake();
|
|
2111
2188
|
};
|
|
2112
2189
|
const onSecureConnect = () => {
|
|
2113
|
-
if (!(socket instanceof Object) || !("getPeerCertificate" in socket)) {
|
|
2114
|
-
finish({
|
|
2115
|
-
ok: false,
|
|
2116
|
-
error: "Expected a TLS socket for wss session",
|
|
2117
|
-
target
|
|
2118
|
-
});
|
|
2119
|
-
return;
|
|
2120
|
-
}
|
|
2121
2190
|
const tlsSocket = socket;
|
|
2122
2191
|
const peerCertificate = tlsSocket.getPeerCertificate(true);
|
|
2123
2192
|
const hasLeafCertificate = hasPeerCertificate(peerCertificate);
|
|
@@ -2300,40 +2369,6 @@ var BoringsslInspectorHandlers = class {
|
|
|
2300
2369
|
}
|
|
2301
2370
|
});
|
|
2302
2371
|
}
|
|
2303
|
-
async handleWebSocketSendFrame(args) {
|
|
2304
|
-
const sessionId = argString(args, "sessionId")?.trim() ?? null;
|
|
2305
|
-
if (!sessionId) return {
|
|
2306
|
-
ok: false,
|
|
2307
|
-
error: "sessionId is required"
|
|
2308
|
-
};
|
|
2309
|
-
const session = this.getWebSocketSession(sessionId);
|
|
2310
|
-
if (!session) return {
|
|
2311
|
-
ok: false,
|
|
2312
|
-
error: `Unknown websocket sessionId "${sessionId}"`
|
|
2313
|
-
};
|
|
2314
|
-
return this.sendWebSocketFrame(session, args);
|
|
2315
|
-
}
|
|
2316
|
-
async handleWebSocketReadFrame(args) {
|
|
2317
|
-
const sessionId = argString(args, "sessionId")?.trim() ?? null;
|
|
2318
|
-
if (!sessionId) return {
|
|
2319
|
-
ok: false,
|
|
2320
|
-
error: "sessionId is required"
|
|
2321
|
-
};
|
|
2322
|
-
const session = this.getWebSocketSession(sessionId);
|
|
2323
|
-
if (!session) return {
|
|
2324
|
-
ok: false,
|
|
2325
|
-
error: `Unknown websocket sessionId "${sessionId}"`
|
|
2326
|
-
};
|
|
2327
|
-
return this.readWebSocketFrame(session, args);
|
|
2328
|
-
}
|
|
2329
|
-
async handleWebSocketClose(args) {
|
|
2330
|
-
const sessionId = argString(args, "sessionId")?.trim() ?? null;
|
|
2331
|
-
if (!sessionId) return {
|
|
2332
|
-
ok: false,
|
|
2333
|
-
error: "sessionId is required"
|
|
2334
|
-
};
|
|
2335
|
-
return this.closeWebSocketSession(sessionId, args);
|
|
2336
|
-
}
|
|
2337
2372
|
async handleBypassCertPinning(args) {
|
|
2338
2373
|
if (this.extensionInvoke) try {
|
|
2339
2374
|
const result = await this.extensionInvoke(args);
|
|
@@ -2354,6 +2389,13 @@ var BoringsslInspectorHandlers = class {
|
|
|
2354
2389
|
args
|
|
2355
2390
|
});
|
|
2356
2391
|
}
|
|
2392
|
+
};
|
|
2393
|
+
//#endregion
|
|
2394
|
+
//#region src/server/domains/boringssl-inspector/handlers/raw-socket-handlers.ts
|
|
2395
|
+
/**
|
|
2396
|
+
* BoringsslInspectorRawSocketHandlers — stateless raw TCP/UDP helpers.
|
|
2397
|
+
*/
|
|
2398
|
+
var BoringsslInspectorRawSocketHandlers = class extends BoringsslInspectorWebSocketHandlers {
|
|
2357
2399
|
async handleRawTcpSend(args) {
|
|
2358
2400
|
const host = argString(args, "host") ?? "127.0.0.1";
|
|
2359
2401
|
const port = argNumber(args, "port");
|
|
@@ -2420,7 +2462,7 @@ var BoringsslInspectorHandlers = class {
|
|
|
2420
2462
|
server.close();
|
|
2421
2463
|
resolve({
|
|
2422
2464
|
ok: false,
|
|
2423
|
-
error: "Listen timed out
|
|
2465
|
+
error: "Listen timed out - no connection received"
|
|
2424
2466
|
});
|
|
2425
2467
|
}, timeout);
|
|
2426
2468
|
server.on("connection", (socket) => {
|
|
@@ -2549,4 +2591,10 @@ var BoringsslInspectorHandlers = class {
|
|
|
2549
2591
|
}
|
|
2550
2592
|
};
|
|
2551
2593
|
//#endregion
|
|
2594
|
+
//#region src/server/domains/boringssl-inspector/handlers/handler-class.ts
|
|
2595
|
+
/**
|
|
2596
|
+
* BoringsslInspectorHandlers — thin facade over the split handler chain.
|
|
2597
|
+
*/
|
|
2598
|
+
var BoringsslInspectorHandlers = class extends BoringsslInspectorRawSocketHandlers {};
|
|
2599
|
+
//#endregion
|
|
2552
2600
|
export { BoringsslInspectorHandlers };
|