@earendil-works/gondolin 0.0.1 → 0.1.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/dist/qemu-net.js DELETED
@@ -1,756 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.QemuNetworkBackend = void 0;
7
- const events_1 = require("events");
8
- const net_1 = __importDefault(require("net"));
9
- const fs_1 = __importDefault(require("fs"));
10
- const path_1 = __importDefault(require("path"));
11
- const dgram_1 = __importDefault(require("dgram"));
12
- const tls_1 = __importDefault(require("tls"));
13
- const crypto_1 = __importDefault(require("crypto"));
14
- const stream_1 = require("stream");
15
- const child_process_1 = require("child_process");
16
- const network_stack_1 = require("./network-stack");
17
- const HOP_BY_HOP_HEADERS = new Set([
18
- "connection",
19
- "keep-alive",
20
- "proxy-connection",
21
- "transfer-encoding",
22
- "te",
23
- "trailer",
24
- "upgrade",
25
- ]);
26
- class GuestTlsStream extends stream_1.Duplex {
27
- constructor(onEncryptedWrite) {
28
- super();
29
- this.onEncryptedWrite = onEncryptedWrite;
30
- }
31
- pushEncrypted(data) {
32
- this.push(data);
33
- }
34
- _read() {
35
- // data is pushed via pushEncrypted
36
- }
37
- _write(chunk, _encoding, callback) {
38
- this.onEncryptedWrite(Buffer.from(chunk));
39
- callback();
40
- }
41
- }
42
- class QemuNetworkBackend extends events_1.EventEmitter {
43
- constructor(options) {
44
- super();
45
- this.options = options;
46
- this.server = null;
47
- this.socket = null;
48
- this.waitingDrain = false;
49
- this.stack = null;
50
- this.udpSessions = new Map();
51
- this.tcpSessions = new Map();
52
- this.caCertPath = null;
53
- this.caKeyPath = null;
54
- this.tlsContexts = new Map();
55
- this.policy = null;
56
- this.policy = options.policy ?? null;
57
- }
58
- start() {
59
- if (this.server)
60
- return;
61
- if (!fs_1.default.existsSync(path_1.default.dirname(this.options.socketPath))) {
62
- fs_1.default.mkdirSync(path_1.default.dirname(this.options.socketPath), { recursive: true });
63
- }
64
- fs_1.default.rmSync(this.options.socketPath, { force: true });
65
- this.server = net_1.default.createServer((socket) => this.attachSocket(socket));
66
- this.server.on("error", (err) => this.emit("error", err));
67
- this.server.listen(this.options.socketPath);
68
- }
69
- stop() {
70
- this.detachSocket();
71
- if (this.server) {
72
- this.server.close();
73
- this.server = null;
74
- }
75
- }
76
- setPolicy(policy) {
77
- this.policy = policy;
78
- this.emit("policy", policy);
79
- }
80
- getPolicy() {
81
- return this.policy;
82
- }
83
- attachSocket(socket) {
84
- if (this.socket)
85
- this.socket.destroy();
86
- this.socket = socket;
87
- this.waitingDrain = false;
88
- this.resetStack();
89
- socket.on("data", (chunk) => {
90
- this.stack?.writeToNetwork(chunk);
91
- this.flush();
92
- });
93
- socket.on("drain", () => {
94
- this.waitingDrain = false;
95
- this.flush();
96
- });
97
- socket.on("error", (err) => {
98
- this.emit("error", err);
99
- this.detachSocket();
100
- });
101
- socket.on("close", () => {
102
- this.detachSocket();
103
- });
104
- }
105
- detachSocket() {
106
- if (this.socket) {
107
- this.socket.destroy();
108
- this.socket = null;
109
- }
110
- this.waitingDrain = false;
111
- this.cleanupSessions();
112
- this.stack?.reset();
113
- }
114
- resetStack() {
115
- this.cleanupSessions();
116
- this.stack = new network_stack_1.NetworkStack({
117
- gatewayIP: this.options.gatewayIP,
118
- vmIP: this.options.vmIP,
119
- gatewayMac: this.options.gatewayMac,
120
- vmMac: this.options.vmMac,
121
- callbacks: {
122
- onUdpSend: (message) => this.handleUdpSend(message),
123
- onTcpConnect: (message) => this.handleTcpConnect(message),
124
- onTcpSend: (message) => this.handleTcpSend(message),
125
- onTcpClose: (message) => this.handleTcpClose(message),
126
- onTcpPause: (message) => this.handleTcpPause(message),
127
- onTcpResume: (message) => this.handleTcpResume(message),
128
- },
129
- allowTcpFlow: (info) => {
130
- if (info.protocol !== "http" && info.protocol !== "tls") {
131
- if (this.options.debug) {
132
- this.emit("log", `[net] tcp blocked ${info.srcIP}:${info.srcPort} -> ${info.dstIP}:${info.dstPort} (${info.protocol})`);
133
- }
134
- return false;
135
- }
136
- const session = this.tcpSessions.get(info.key);
137
- if (session) {
138
- session.protocol = info.protocol;
139
- if (info.protocol === "http" || info.protocol === "tls") {
140
- session.http = session.http ?? {
141
- buffer: Buffer.alloc(0),
142
- processing: false,
143
- closed: false,
144
- };
145
- }
146
- }
147
- return true;
148
- },
149
- });
150
- this.stack.on("network-activity", () => this.flush());
151
- this.stack.on("error", (err) => this.emit("error", err));
152
- if (this.options.debug) {
153
- this.stack.on("dhcp", (state, ip) => {
154
- this.emit("log", `[net] dhcp ${state} ${ip}`);
155
- });
156
- }
157
- }
158
- flush() {
159
- if (!this.socket || this.waitingDrain || !this.stack)
160
- return;
161
- while (this.stack.hasPendingData()) {
162
- const chunk = this.stack.readFromNetwork(64 * 1024);
163
- if (!chunk || chunk.length === 0)
164
- break;
165
- if (this.options.debug) {
166
- this.emit("log", `[net] tx ${chunk.length} bytes to qemu`);
167
- }
168
- const ok = this.socket.write(chunk);
169
- if (!ok) {
170
- this.waitingDrain = true;
171
- return;
172
- }
173
- }
174
- }
175
- cleanupSessions() {
176
- for (const session of this.udpSessions.values()) {
177
- try {
178
- session.socket.close();
179
- }
180
- catch {
181
- // ignore
182
- }
183
- }
184
- this.udpSessions.clear();
185
- for (const session of this.tcpSessions.values()) {
186
- try {
187
- session.socket?.destroy();
188
- }
189
- catch {
190
- // ignore
191
- }
192
- }
193
- this.tcpSessions.clear();
194
- }
195
- handleUdpSend(message) {
196
- if (message.dstPort !== 53) {
197
- if (this.options.debug) {
198
- this.emit("log", `[net] udp blocked ${message.srcIP}:${message.srcPort} -> ${message.dstIP}:${message.dstPort}`);
199
- }
200
- return;
201
- }
202
- let session = this.udpSessions.get(message.key);
203
- if (!session) {
204
- const socket = dgram_1.default.createSocket("udp4");
205
- session = {
206
- socket,
207
- srcIP: message.srcIP,
208
- srcPort: message.srcPort,
209
- dstIP: message.dstIP,
210
- dstPort: message.dstPort,
211
- };
212
- this.udpSessions.set(message.key, session);
213
- socket.on("message", (data, rinfo) => {
214
- if (this.options.debug) {
215
- this.emit("log", `[net] udp recv ${rinfo.address}:${rinfo.port} -> ${session.srcIP}:${session.srcPort} (${data.length} bytes)`);
216
- }
217
- this.stack?.handleUdpResponse({
218
- data: Buffer.from(data),
219
- srcIP: session.srcIP,
220
- srcPort: session.srcPort,
221
- dstIP: session.dstIP,
222
- dstPort: session.dstPort,
223
- });
224
- this.flush();
225
- });
226
- socket.on("error", (err) => {
227
- this.emit("error", err);
228
- });
229
- }
230
- if (this.options.debug) {
231
- this.emit("log", `[net] udp send ${message.srcIP}:${message.srcPort} -> ${message.dstIP}:${message.dstPort} (${message.payload.length} bytes)`);
232
- }
233
- session.socket.send(message.payload, message.dstPort, message.dstIP);
234
- }
235
- handleTcpConnect(message) {
236
- const connectIP = message.dstIP === (this.options.gatewayIP ?? "192.168.127.1") ? "127.0.0.1" : message.dstIP;
237
- const session = {
238
- socket: null,
239
- srcIP: message.srcIP,
240
- srcPort: message.srcPort,
241
- dstIP: message.dstIP,
242
- dstPort: message.dstPort,
243
- connectIP,
244
- flowControlPaused: false,
245
- protocol: null,
246
- connected: false,
247
- pendingWrites: [],
248
- };
249
- this.tcpSessions.set(message.key, session);
250
- this.stack?.handleTcpConnected({ key: message.key });
251
- this.flush();
252
- }
253
- handleTcpSend(message) {
254
- const session = this.tcpSessions.get(message.key);
255
- if (!session)
256
- return;
257
- if (session.protocol === "http") {
258
- this.handlePlainHttpData(message.key, session, message.data);
259
- return;
260
- }
261
- if (session.protocol === "tls") {
262
- this.handleTlsData(message.key, session, message.data);
263
- return;
264
- }
265
- this.ensureTcpSocket(message.key, session);
266
- if (session.socket && session.connected && session.socket.writable) {
267
- session.socket.write(message.data);
268
- }
269
- else {
270
- session.pendingWrites.push(message.data);
271
- }
272
- }
273
- handleTcpClose(message) {
274
- const session = this.tcpSessions.get(message.key);
275
- if (session) {
276
- session.http = undefined;
277
- if (session.tls) {
278
- if (message.destroy) {
279
- session.tls.socket.destroy();
280
- }
281
- else {
282
- session.tls.socket.end();
283
- }
284
- session.tls = undefined;
285
- }
286
- if (session.socket) {
287
- if (message.destroy) {
288
- session.socket.destroy();
289
- }
290
- else {
291
- session.socket.end();
292
- }
293
- }
294
- else {
295
- this.tcpSessions.delete(message.key);
296
- }
297
- }
298
- }
299
- handleTcpPause(message) {
300
- const session = this.tcpSessions.get(message.key);
301
- if (session && session.socket) {
302
- session.flowControlPaused = true;
303
- session.socket.pause();
304
- }
305
- }
306
- handleTcpResume(message) {
307
- const session = this.tcpSessions.get(message.key);
308
- if (session && session.socket) {
309
- session.flowControlPaused = false;
310
- session.socket.resume();
311
- }
312
- }
313
- ensureTcpSocket(key, session) {
314
- if (session.socket)
315
- return;
316
- const socket = new net_1.default.Socket();
317
- session.socket = socket;
318
- socket.connect(session.dstPort, session.connectIP, () => {
319
- session.connected = true;
320
- for (const pending of session.pendingWrites) {
321
- socket.write(pending);
322
- }
323
- session.pendingWrites = [];
324
- });
325
- socket.on("data", (data) => {
326
- this.stack?.handleTcpData({ key, data: Buffer.from(data) });
327
- this.flush();
328
- });
329
- socket.on("end", () => {
330
- this.stack?.handleTcpEnd({ key });
331
- this.flush();
332
- });
333
- socket.on("close", () => {
334
- this.stack?.handleTcpClosed({ key });
335
- this.tcpSessions.delete(key);
336
- });
337
- socket.on("error", () => {
338
- this.stack?.handleTcpError({ key });
339
- this.tcpSessions.delete(key);
340
- });
341
- }
342
- ensureTlsSession(key, session) {
343
- if (session.tls)
344
- return session.tls;
345
- const stream = new GuestTlsStream((chunk) => {
346
- this.stack?.handleTcpData({ key, data: chunk });
347
- this.flush();
348
- });
349
- const tlsSocket = new tls_1.default.TLSSocket(stream, {
350
- isServer: true,
351
- secureContext: this.getTlsContext(session.dstIP),
352
- SNICallback: (servername, callback) => {
353
- const sni = servername || session.dstIP;
354
- try {
355
- const context = this.getTlsContext(sni);
356
- if (this.options.debug) {
357
- this.emit("log", `[net] tls sni ${sni}`);
358
- }
359
- callback(null, context);
360
- }
361
- catch (err) {
362
- callback(err);
363
- }
364
- },
365
- });
366
- tlsSocket.on("data", (data) => {
367
- this.handleTlsHttpData(key, session, Buffer.from(data));
368
- });
369
- tlsSocket.on("error", (err) => {
370
- this.emit("error", err);
371
- this.stack?.handleTcpError({ key });
372
- });
373
- tlsSocket.on("close", () => {
374
- this.stack?.handleTcpClosed({ key });
375
- this.tcpSessions.delete(key);
376
- });
377
- session.tls = {
378
- stream,
379
- socket: tlsSocket,
380
- servername: null,
381
- };
382
- if (this.options.debug) {
383
- this.emit("log", `[net] tls mitm start ${session.dstIP}:${session.dstPort}`);
384
- }
385
- return session.tls;
386
- }
387
- async handlePlainHttpData(key, session, data) {
388
- await this.handleHttpDataWithWriter(key, session, data, {
389
- scheme: "http",
390
- write: (chunk) => {
391
- this.stack?.handleTcpData({ key, data: chunk });
392
- },
393
- finish: () => {
394
- this.stack?.handleTcpEnd({ key });
395
- this.flush();
396
- },
397
- });
398
- }
399
- async handleTlsHttpData(key, session, data) {
400
- const tlsSession = session.tls;
401
- if (!tlsSession)
402
- return;
403
- await this.handleHttpDataWithWriter(key, session, data, {
404
- scheme: "https",
405
- write: (chunk) => {
406
- tlsSession.socket.write(chunk);
407
- },
408
- finish: () => {
409
- tlsSession.socket.end(() => {
410
- this.stack?.handleTcpEnd({ key });
411
- this.flush();
412
- });
413
- },
414
- });
415
- }
416
- async handleHttpDataWithWriter(key, session, data, options) {
417
- const httpSession = session.http ?? {
418
- buffer: Buffer.alloc(0),
419
- processing: false,
420
- closed: false,
421
- };
422
- session.http = httpSession;
423
- if (httpSession.closed)
424
- return;
425
- httpSession.buffer = Buffer.concat([httpSession.buffer, data]);
426
- if (httpSession.processing)
427
- return;
428
- const parsed = this.parseHttpRequest(httpSession.buffer);
429
- if (!parsed)
430
- return;
431
- httpSession.processing = true;
432
- httpSession.buffer = parsed.remaining;
433
- try {
434
- await this.fetchAndRespond(parsed.request, options.scheme, options.write);
435
- }
436
- catch (err) {
437
- this.emit("error", err instanceof Error ? err : new Error(String(err)));
438
- this.respondWithError(options.write, 502, "Bad Gateway");
439
- }
440
- finally {
441
- httpSession.closed = true;
442
- options.finish();
443
- this.flush();
444
- }
445
- }
446
- handleTlsData(key, session, data) {
447
- const tlsSession = this.ensureTlsSession(key, session);
448
- if (!tlsSession)
449
- return;
450
- tlsSession.stream.pushEncrypted(data);
451
- }
452
- parseHttpRequest(buffer) {
453
- const headerEnd = buffer.indexOf("\r\n\r\n");
454
- if (headerEnd === -1)
455
- return null;
456
- const headerBlock = buffer.subarray(0, headerEnd).toString("utf8");
457
- const lines = headerBlock.split("\r\n");
458
- if (lines.length === 0)
459
- return null;
460
- const [method, target, version] = lines[0].split(" ");
461
- if (!method || !target || !version)
462
- return null;
463
- const headers = {};
464
- for (let i = 1; i < lines.length; i += 1) {
465
- const line = lines[i];
466
- const idx = line.indexOf(":");
467
- if (idx === -1)
468
- continue;
469
- const key = line.slice(0, idx).trim().toLowerCase();
470
- const value = line.slice(idx + 1).trim();
471
- if (!key)
472
- continue;
473
- if (headers[key]) {
474
- headers[key] = `${headers[key]}, ${value}`;
475
- }
476
- else {
477
- headers[key] = value;
478
- }
479
- }
480
- const bodyOffset = headerEnd + 4;
481
- const bodyBuffer = buffer.subarray(bodyOffset);
482
- const transferEncoding = headers["transfer-encoding"]?.toLowerCase();
483
- if (transferEncoding === "chunked") {
484
- const chunked = this.decodeChunkedBody(bodyBuffer);
485
- if (!chunked.complete)
486
- return null;
487
- return {
488
- request: {
489
- method,
490
- target,
491
- version,
492
- headers,
493
- body: chunked.body,
494
- },
495
- remaining: bodyBuffer.subarray(chunked.bytesConsumed),
496
- };
497
- }
498
- const contentLength = headers["content-length"] ? Number(headers["content-length"]) : 0;
499
- if (!Number.isFinite(contentLength) || contentLength < 0)
500
- return null;
501
- if (bodyBuffer.length < contentLength)
502
- return null;
503
- return {
504
- request: {
505
- method,
506
- target,
507
- version,
508
- headers,
509
- body: bodyBuffer.subarray(0, contentLength),
510
- },
511
- remaining: bodyBuffer.subarray(contentLength),
512
- };
513
- }
514
- decodeChunkedBody(buffer) {
515
- let offset = 0;
516
- const chunks = [];
517
- while (true) {
518
- const lineEnd = buffer.indexOf("\r\n", offset);
519
- if (lineEnd === -1)
520
- return { complete: false, body: Buffer.alloc(0), bytesConsumed: 0 };
521
- const sizeLine = buffer.subarray(offset, lineEnd).toString("ascii").split(";")[0].trim();
522
- const size = parseInt(sizeLine, 16);
523
- if (!Number.isFinite(size))
524
- return { complete: false, body: Buffer.alloc(0), bytesConsumed: 0 };
525
- const chunkStart = lineEnd + 2;
526
- const chunkEnd = chunkStart + size;
527
- if (buffer.length < chunkEnd + 2)
528
- return { complete: false, body: Buffer.alloc(0), bytesConsumed: 0 };
529
- if (size > 0) {
530
- chunks.push(buffer.subarray(chunkStart, chunkEnd));
531
- }
532
- if (buffer[chunkEnd] !== 0x0d || buffer[chunkEnd + 1] !== 0x0a) {
533
- return { complete: false, body: Buffer.alloc(0), bytesConsumed: 0 };
534
- }
535
- offset = chunkEnd + 2;
536
- if (size === 0) {
537
- return { complete: true, body: Buffer.concat(chunks), bytesConsumed: offset };
538
- }
539
- }
540
- }
541
- async fetchAndRespond(request, defaultScheme, write) {
542
- const url = this.buildFetchUrl(request, defaultScheme);
543
- if (!url) {
544
- this.respondWithError(write, 400, "Bad Request");
545
- return;
546
- }
547
- if (this.options.debug) {
548
- this.emit("log", `[net] http bridge ${request.method} ${url}`);
549
- }
550
- let hookRequest = {
551
- method: request.method,
552
- url,
553
- headers: this.stripHopByHopHeaders(request.headers),
554
- body: request.body.length > 0 ? request.body : null,
555
- };
556
- if (this.options.httpHooks?.onRequest) {
557
- const updated = await this.options.httpHooks.onRequest(hookRequest);
558
- if (updated)
559
- hookRequest = updated;
560
- }
561
- const response = await fetch(hookRequest.url, {
562
- method: hookRequest.method,
563
- headers: hookRequest.headers,
564
- body: hookRequest.body ? new Uint8Array(hookRequest.body) : undefined,
565
- });
566
- if (this.options.debug) {
567
- this.emit("log", `[net] http bridge response ${response.status} ${response.statusText}`);
568
- }
569
- const responseBody = Buffer.from(await response.arrayBuffer());
570
- let responseHeaders = this.stripHopByHopHeaders(this.headersToRecord(response.headers));
571
- responseHeaders["content-length"] = responseBody.length.toString();
572
- responseHeaders["connection"] = "close";
573
- let hookResponse = {
574
- status: response.status,
575
- statusText: response.statusText || "OK",
576
- headers: responseHeaders,
577
- body: responseBody,
578
- };
579
- if (this.options.httpHooks?.onResponse) {
580
- const updated = await this.options.httpHooks.onResponse(hookResponse, hookRequest);
581
- if (updated)
582
- hookResponse = updated;
583
- }
584
- this.sendHttpResponse(write, hookResponse);
585
- }
586
- sendHttpResponse(write, response) {
587
- const statusLine = `HTTP/1.1 ${response.status} ${response.statusText}\r\n`;
588
- const headers = Object.entries(response.headers)
589
- .map(([name, value]) => `${name}: ${value}`)
590
- .join("\r\n");
591
- const headerBlock = `${statusLine}${headers}\r\n\r\n`;
592
- write(Buffer.from(headerBlock));
593
- if (response.body.length > 0) {
594
- write(response.body);
595
- }
596
- }
597
- respondWithError(write, status, statusText) {
598
- const body = Buffer.from(`${status} ${statusText}\n`);
599
- this.sendHttpResponse(write, {
600
- status,
601
- statusText,
602
- headers: {
603
- "content-length": body.length.toString(),
604
- "content-type": "text/plain",
605
- connection: "close",
606
- },
607
- body,
608
- });
609
- }
610
- buildFetchUrl(request, defaultScheme) {
611
- if (request.target.startsWith("http://") || request.target.startsWith("https://")) {
612
- return request.target;
613
- }
614
- const host = request.headers["host"];
615
- if (!host)
616
- return null;
617
- return `${defaultScheme}://${host}${request.target}`;
618
- }
619
- getMitmDir() {
620
- return this.options.mitmCertDir ?? path_1.default.join(process.cwd(), "var", "mitm");
621
- }
622
- ensureCa() {
623
- if (this.caCertPath && this.caKeyPath)
624
- return;
625
- const mitmDir = this.getMitmDir();
626
- fs_1.default.mkdirSync(mitmDir, { recursive: true });
627
- const caKeyPath = path_1.default.join(mitmDir, "ca.key");
628
- const caCertPath = path_1.default.join(mitmDir, "ca.crt");
629
- if (!fs_1.default.existsSync(caKeyPath) || !fs_1.default.existsSync(caCertPath)) {
630
- (0, child_process_1.execFileSync)("openssl", [
631
- "req",
632
- "-x509",
633
- "-newkey",
634
- "rsa:2048",
635
- "-sha256",
636
- "-days",
637
- "3650",
638
- "-nodes",
639
- "-subj",
640
- "/CN=eregion-mitm-ca",
641
- "-keyout",
642
- caKeyPath,
643
- "-out",
644
- caCertPath,
645
- ]);
646
- if (this.options.debug) {
647
- this.emit("log", `[net] generated mitm CA at ${caCertPath}`);
648
- }
649
- }
650
- this.caKeyPath = caKeyPath;
651
- this.caCertPath = caCertPath;
652
- }
653
- getTlsContext(servername) {
654
- const normalized = servername.trim() || "unknown";
655
- const cached = this.tlsContexts.get(normalized);
656
- if (cached)
657
- return cached;
658
- this.ensureCa();
659
- if (!this.caCertPath || !this.caKeyPath) {
660
- throw new Error("MITM CA is not initialized");
661
- }
662
- const { keyPath, certPath } = this.ensureLeafCertificate(normalized);
663
- const leafCert = fs_1.default.readFileSync(certPath, "utf8");
664
- const caCert = fs_1.default.readFileSync(this.caCertPath, "utf8");
665
- const context = tls_1.default.createSecureContext({
666
- key: fs_1.default.readFileSync(keyPath),
667
- cert: `${leafCert}\n${caCert}`,
668
- });
669
- this.tlsContexts.set(normalized, context);
670
- return context;
671
- }
672
- ensureLeafCertificate(servername) {
673
- if (!this.caCertPath || !this.caKeyPath) {
674
- throw new Error("MITM CA is not initialized");
675
- }
676
- const hostsDir = path_1.default.join(this.getMitmDir(), "hosts");
677
- fs_1.default.mkdirSync(hostsDir, { recursive: true });
678
- const hash = crypto_1.default.createHash("sha256").update(servername).digest("hex").slice(0, 12);
679
- const slug = servername.replace(/[^a-zA-Z0-9.-]/g, "_");
680
- const baseName = `${slug || "host"}-${hash}`;
681
- const keyPath = path_1.default.join(hostsDir, `${baseName}.key`);
682
- const certPath = path_1.default.join(hostsDir, `${baseName}.crt`);
683
- if (fs_1.default.existsSync(keyPath) && fs_1.default.existsSync(certPath)) {
684
- return { keyPath, certPath };
685
- }
686
- const csrPath = path_1.default.join(hostsDir, `${baseName}.csr`);
687
- const configPath = path_1.default.join(hostsDir, `${baseName}.cnf`);
688
- const safeName = servername.replace(/[\r\n]/g, "");
689
- const san = net_1.default.isIP(servername) ? `IP:${servername}` : `DNS:${servername}`;
690
- const config = [
691
- "[req]",
692
- "distinguished_name=req_dist",
693
- "prompt=no",
694
- "req_extensions=v3_req",
695
- "[req_dist]",
696
- `CN=${safeName}`,
697
- "[v3_req]",
698
- `subjectAltName=${san}`,
699
- "keyUsage=digitalSignature,keyEncipherment",
700
- "extendedKeyUsage=serverAuth",
701
- "",
702
- ].join("\n");
703
- fs_1.default.writeFileSync(configPath, config);
704
- (0, child_process_1.execFileSync)("openssl", ["genrsa", "-out", keyPath, "2048"]);
705
- (0, child_process_1.execFileSync)("openssl", [
706
- "req",
707
- "-new",
708
- "-key",
709
- keyPath,
710
- "-out",
711
- csrPath,
712
- "-config",
713
- configPath,
714
- ]);
715
- (0, child_process_1.execFileSync)("openssl", [
716
- "x509",
717
- "-req",
718
- "-in",
719
- csrPath,
720
- "-CA",
721
- this.caCertPath,
722
- "-CAkey",
723
- this.caKeyPath,
724
- "-CAcreateserial",
725
- "-out",
726
- certPath,
727
- "-days",
728
- "825",
729
- "-sha256",
730
- "-extfile",
731
- configPath,
732
- "-extensions",
733
- "v3_req",
734
- ]);
735
- fs_1.default.rmSync(csrPath, { force: true });
736
- fs_1.default.rmSync(configPath, { force: true });
737
- return { keyPath, certPath };
738
- }
739
- stripHopByHopHeaders(headers) {
740
- const output = {};
741
- for (const [name, value] of Object.entries(headers)) {
742
- if (!HOP_BY_HOP_HEADERS.has(name.toLowerCase())) {
743
- output[name.toLowerCase()] = value;
744
- }
745
- }
746
- return output;
747
- }
748
- headersToRecord(headers) {
749
- const record = {};
750
- headers.forEach((value, key) => {
751
- record[key.toLowerCase()] = value;
752
- });
753
- return record;
754
- }
755
- }
756
- exports.QemuNetworkBackend = QemuNetworkBackend;