@gjsify/tls 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/README.md +27 -0
- package/lib/esm/index.js +377 -0
- package/lib/types/index.d.ts +121 -0
- package/package.json +44 -0
- package/src/index.spec.ts +674 -0
- package/src/index.ts +510 -0
- package/src/test.mts +6 -0
- package/tsconfig.json +31 -0
- package/tsconfig.tsbuildinfo +1 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
// Reference: Node.js lib/tls.js
|
|
2
|
+
// Reimplemented for GJS using Gio.TlsClientConnection / Gio.TlsServerConnection
|
|
3
|
+
|
|
4
|
+
import Gio from '@girs/gio-2.0';
|
|
5
|
+
import GLib from '@girs/glib-2.0';
|
|
6
|
+
import { Socket, Server } from 'node:net';
|
|
7
|
+
import { createNodeError, deferEmit } from '@gjsify/utils';
|
|
8
|
+
|
|
9
|
+
export const DEFAULT_MIN_VERSION = 'TLSv1.2';
|
|
10
|
+
export const DEFAULT_MAX_VERSION = 'TLSv1.3';
|
|
11
|
+
export const DEFAULT_CIPHERS = 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384';
|
|
12
|
+
|
|
13
|
+
/** Returns a list of supported TLS cipher names (subset; implementation-defined). */
|
|
14
|
+
export function getCiphers(): string[] {
|
|
15
|
+
return [
|
|
16
|
+
'aes-128-gcm', 'aes-256-gcm', 'chacha20-poly1305',
|
|
17
|
+
'aes-128-cbc', 'aes-256-cbc',
|
|
18
|
+
];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface PeerCertificate {
|
|
22
|
+
subject?: { CN?: string | string[]; [key: string]: unknown };
|
|
23
|
+
subjectaltname?: string;
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Removes a trailing dot from a fully-qualified domain name. */
|
|
28
|
+
function unfqdn(host: string): string {
|
|
29
|
+
return host.endsWith('.') ? host.slice(0, -1) : host;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Splits a hostname into parts, lower-cased, after removing trailing dots. */
|
|
33
|
+
function splitHost(host: string): string[] {
|
|
34
|
+
return unfqdn(host).toLowerCase().split('.');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Wildcard-match a hostname part list against a pattern (supports leading *). */
|
|
38
|
+
function checkWildcard(hostParts: string[], pattern: string): boolean {
|
|
39
|
+
const patParts = splitHost(pattern);
|
|
40
|
+
if (patParts.length !== hostParts.length) return false;
|
|
41
|
+
// Wildcard only valid in the leftmost label
|
|
42
|
+
if (patParts[0] === '*') {
|
|
43
|
+
// e.g. *.example.com — match any single label against *
|
|
44
|
+
return patParts.slice(1).join('.') === hostParts.slice(1).join('.');
|
|
45
|
+
}
|
|
46
|
+
return patParts.every((p, i) => p === hostParts[i]);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Verifies that the certificate `cert` is valid for `hostname`.
|
|
51
|
+
* Returns an Error if the check fails, or undefined on success.
|
|
52
|
+
*
|
|
53
|
+
* Reference: Node.js lib/tls.js exports.checkServerIdentity
|
|
54
|
+
*/
|
|
55
|
+
export function checkServerIdentity(hostname: string, cert: PeerCertificate): Error | undefined {
|
|
56
|
+
const subject = cert.subject;
|
|
57
|
+
const altNames = cert.subjectaltname as string | undefined;
|
|
58
|
+
const dnsNames: string[] = [];
|
|
59
|
+
const ips: string[] = [];
|
|
60
|
+
|
|
61
|
+
hostname = String(hostname);
|
|
62
|
+
|
|
63
|
+
if (altNames) {
|
|
64
|
+
const parts = altNames.split(', ');
|
|
65
|
+
for (const name of parts) {
|
|
66
|
+
if (name.startsWith('DNS:')) {
|
|
67
|
+
dnsNames.push(name.slice(4));
|
|
68
|
+
} else if (name.startsWith('IP Address:')) {
|
|
69
|
+
ips.push(name.slice(11).trim());
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let valid = false;
|
|
75
|
+
let reason = 'Unknown reason';
|
|
76
|
+
|
|
77
|
+
hostname = unfqdn(hostname);
|
|
78
|
+
|
|
79
|
+
// Check numeric IP addresses
|
|
80
|
+
const isIPv4 = /^(\d{1,3}\.){3}\d{1,3}$/.test(hostname);
|
|
81
|
+
const isIPv6 = hostname.includes(':');
|
|
82
|
+
if (isIPv4 || isIPv6) {
|
|
83
|
+
valid = ips.some(ip => ip.toLowerCase() === hostname.toLowerCase());
|
|
84
|
+
if (!valid)
|
|
85
|
+
reason = `IP: ${hostname} is not in the cert's list: ${ips.join(', ')}`;
|
|
86
|
+
} else if (dnsNames.length > 0 || subject?.CN) {
|
|
87
|
+
const hostParts = splitHost(hostname);
|
|
88
|
+
|
|
89
|
+
if (dnsNames.length > 0) {
|
|
90
|
+
valid = dnsNames.some(pattern => checkWildcard(hostParts, pattern));
|
|
91
|
+
if (!valid)
|
|
92
|
+
reason = `Host: ${hostname}. is not in the cert's altnames: ${altNames}`;
|
|
93
|
+
} else {
|
|
94
|
+
const cn = subject!.CN as string | string[];
|
|
95
|
+
if (Array.isArray(cn)) {
|
|
96
|
+
valid = cn.some(c => checkWildcard(hostParts, c));
|
|
97
|
+
} else if (cn) {
|
|
98
|
+
valid = checkWildcard(hostParts, cn);
|
|
99
|
+
}
|
|
100
|
+
if (!valid)
|
|
101
|
+
reason = `Host: ${hostname}. is not cert's CN: ${cn}`;
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
reason = 'Cert does not contain a DNS name';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!valid) {
|
|
108
|
+
const err = new Error(reason) as NodeJS.ErrnoException & { reason: string; host: string; cert: PeerCertificate };
|
|
109
|
+
err.reason = reason;
|
|
110
|
+
err.host = hostname;
|
|
111
|
+
err.cert = cert;
|
|
112
|
+
return err;
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface SecureContextOptions {
|
|
118
|
+
ca?: string | Buffer | Array<string | Buffer>;
|
|
119
|
+
cert?: string | Buffer | Array<string | Buffer>;
|
|
120
|
+
key?: string | Buffer | Array<string | Buffer>;
|
|
121
|
+
rejectUnauthorized?: boolean;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface TlsConnectOptions extends SecureContextOptions {
|
|
125
|
+
host?: string;
|
|
126
|
+
port?: number;
|
|
127
|
+
socket?: Socket;
|
|
128
|
+
servername?: string;
|
|
129
|
+
ALPNProtocols?: string[];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* TLSSocket wraps a net.Socket with TLS via Gio.TlsConnection.
|
|
134
|
+
*/
|
|
135
|
+
export class TLSSocket extends Socket {
|
|
136
|
+
encrypted = true;
|
|
137
|
+
authorized = false;
|
|
138
|
+
authorizationError?: string;
|
|
139
|
+
alpnProtocol: string | false = false;
|
|
140
|
+
|
|
141
|
+
/** @internal */
|
|
142
|
+
_tlsConnection: Gio.TlsConnection | null = null;
|
|
143
|
+
|
|
144
|
+
constructor(socket?: Socket, options?: SecureContextOptions) {
|
|
145
|
+
super();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* @internal Wire the TLS connection's I/O streams into this socket
|
|
150
|
+
* so that read/write operations go through the encrypted channel.
|
|
151
|
+
*/
|
|
152
|
+
_setupTlsStreams(tlsConn: Gio.TlsConnection): void {
|
|
153
|
+
this._tlsConnection = tlsConn;
|
|
154
|
+
// Replace the underlying I/O streams with the TLS connection's streams
|
|
155
|
+
(this as any)._inputStream = tlsConn.get_input_stream();
|
|
156
|
+
(this as any)._outputStream = tlsConn.get_output_stream();
|
|
157
|
+
// Store connection for teardown
|
|
158
|
+
(this as any)._connection = tlsConn as unknown as Gio.SocketConnection;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Get the peer certificate info. */
|
|
162
|
+
getPeerCertificate(_detailed?: boolean): any {
|
|
163
|
+
if (!this._tlsConnection) return {};
|
|
164
|
+
try {
|
|
165
|
+
const cert = this._tlsConnection.get_peer_certificate();
|
|
166
|
+
if (!cert) return {};
|
|
167
|
+
return {
|
|
168
|
+
subject: {},
|
|
169
|
+
issuer: {},
|
|
170
|
+
valid_from: '',
|
|
171
|
+
valid_to: '',
|
|
172
|
+
};
|
|
173
|
+
} catch {
|
|
174
|
+
return {};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Get the negotiated TLS protocol version. */
|
|
179
|
+
getProtocol(): string | null {
|
|
180
|
+
if (!this._tlsConnection) return null;
|
|
181
|
+
try {
|
|
182
|
+
const proto = this._tlsConnection.get_protocol_version();
|
|
183
|
+
switch (proto) {
|
|
184
|
+
case Gio.TlsProtocolVersion.TLS_1_0: return 'TLSv1';
|
|
185
|
+
case Gio.TlsProtocolVersion.TLS_1_1: return 'TLSv1.1';
|
|
186
|
+
case Gio.TlsProtocolVersion.TLS_1_2: return 'TLSv1.2';
|
|
187
|
+
case Gio.TlsProtocolVersion.TLS_1_3: return 'TLSv1.3';
|
|
188
|
+
default: return null;
|
|
189
|
+
}
|
|
190
|
+
} catch {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Get the cipher info. */
|
|
196
|
+
getCipher(): { name: string; version: string } | null {
|
|
197
|
+
if (!this._tlsConnection) return null;
|
|
198
|
+
try {
|
|
199
|
+
const name = this._tlsConnection.get_ciphersuite_name();
|
|
200
|
+
return { name: name || 'unknown', version: this.getProtocol() || 'unknown' };
|
|
201
|
+
} catch {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Get the negotiated ALPN protocol. */
|
|
207
|
+
getAlpnProtocol(): string | false {
|
|
208
|
+
if (!this._tlsConnection) return false;
|
|
209
|
+
try {
|
|
210
|
+
const proto = this._tlsConnection.get_negotiated_protocol();
|
|
211
|
+
return proto || false;
|
|
212
|
+
} catch {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Create a TLS client connection.
|
|
220
|
+
*
|
|
221
|
+
* Connects via TCP first (using net.Socket.connect), then upgrades
|
|
222
|
+
* the connection to TLS using Gio.TlsClientConnection.
|
|
223
|
+
*/
|
|
224
|
+
export function connect(options: TlsConnectOptions, callback?: () => void): TLSSocket {
|
|
225
|
+
const socket = new TLSSocket(undefined, options);
|
|
226
|
+
|
|
227
|
+
if (callback) {
|
|
228
|
+
socket.once('secureConnect', callback);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const port = options.port || 443;
|
|
232
|
+
const host = options.host || 'localhost';
|
|
233
|
+
const servername = options.servername || host;
|
|
234
|
+
const rejectUnauthorized = options.rejectUnauthorized !== false;
|
|
235
|
+
|
|
236
|
+
// Listen for TCP connect, then upgrade to TLS
|
|
237
|
+
socket.once('connect', () => {
|
|
238
|
+
const rawConnection: Gio.SocketConnection | null = (socket as any)._connection;
|
|
239
|
+
if (!rawConnection) {
|
|
240
|
+
socket.destroy(new Error('No underlying connection for TLS upgrade'));
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
// Create TLS client connection wrapping the raw TCP connection
|
|
246
|
+
const connectable = Gio.NetworkAddress.new(servername, port);
|
|
247
|
+
const tlsConn = Gio.TlsClientConnection.new(
|
|
248
|
+
rawConnection as Gio.IOStream,
|
|
249
|
+
connectable,
|
|
250
|
+
) as Gio.TlsClientConnection;
|
|
251
|
+
|
|
252
|
+
// Set server identity for certificate validation
|
|
253
|
+
tlsConn.set_server_identity(connectable);
|
|
254
|
+
|
|
255
|
+
// Set ALPN protocols if provided
|
|
256
|
+
if (options.ALPNProtocols && options.ALPNProtocols.length > 0) {
|
|
257
|
+
try {
|
|
258
|
+
(tlsConn as Gio.TlsClientConnection).set_advertised_protocols(options.ALPNProtocols);
|
|
259
|
+
} catch {
|
|
260
|
+
// ALPN may not be supported on all GnuTLS versions
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Handle certificate validation
|
|
265
|
+
if (!rejectUnauthorized) {
|
|
266
|
+
(tlsConn as Gio.TlsConnection).connect(
|
|
267
|
+
'accept-certificate',
|
|
268
|
+
() => true,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Perform TLS handshake asynchronously
|
|
273
|
+
const cancellable = new Gio.Cancellable();
|
|
274
|
+
(tlsConn as Gio.TlsConnection).handshake_async(
|
|
275
|
+
GLib.PRIORITY_DEFAULT,
|
|
276
|
+
cancellable,
|
|
277
|
+
(_source: Gio.TlsConnection | null, asyncResult: Gio.AsyncResult) => {
|
|
278
|
+
try {
|
|
279
|
+
(tlsConn as Gio.TlsConnection).handshake_finish(asyncResult);
|
|
280
|
+
socket.authorized = true;
|
|
281
|
+
socket._setupTlsStreams(tlsConn as Gio.TlsConnection);
|
|
282
|
+
|
|
283
|
+
// Get ALPN result
|
|
284
|
+
socket.alpnProtocol = socket.getAlpnProtocol();
|
|
285
|
+
|
|
286
|
+
// Restart reading with TLS streams
|
|
287
|
+
(socket as any)._reading = false;
|
|
288
|
+
(socket as any)._startReading();
|
|
289
|
+
|
|
290
|
+
socket.emit('secureConnect');
|
|
291
|
+
} catch (err: unknown) {
|
|
292
|
+
socket.authorized = false;
|
|
293
|
+
socket.authorizationError = err instanceof Error ? err.message : String(err);
|
|
294
|
+
if (rejectUnauthorized) {
|
|
295
|
+
socket.destroy(err instanceof Error ? err : new Error(String(err)));
|
|
296
|
+
} else {
|
|
297
|
+
// Still emit secureConnect but with authorized=false
|
|
298
|
+
socket._setupTlsStreams(tlsConn as Gio.TlsConnection);
|
|
299
|
+
socket.emit('secureConnect');
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
);
|
|
304
|
+
} catch (err: unknown) {
|
|
305
|
+
socket.destroy(err instanceof Error ? err : new Error(String(err)));
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Initiate TCP connection
|
|
310
|
+
socket.connect({ port, host });
|
|
311
|
+
return socket;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Create a TLS secure context.
|
|
316
|
+
*/
|
|
317
|
+
export function createSecureContext(options?: SecureContextOptions): { context: any } {
|
|
318
|
+
return { context: options || {} };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export const rootCertificates: string[] = [];
|
|
322
|
+
|
|
323
|
+
export interface TlsServerOptions extends SecureContextOptions {
|
|
324
|
+
requestCert?: boolean;
|
|
325
|
+
rejectUnauthorized?: boolean;
|
|
326
|
+
ALPNProtocols?: string[];
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Build a Gio.TlsCertificate from PEM cert+key strings.
|
|
331
|
+
*/
|
|
332
|
+
function buildGioCertificate(cert: string | Buffer | Array<string | Buffer>, key?: string | Buffer | Array<string | Buffer>): Gio.TlsCertificate {
|
|
333
|
+
const certStr = Array.isArray(cert)
|
|
334
|
+
? cert.map((c) => (typeof c === 'string' ? c : c.toString('utf-8'))).join('\n')
|
|
335
|
+
: typeof cert === 'string' ? cert : cert.toString('utf-8');
|
|
336
|
+
|
|
337
|
+
const keyStr = key
|
|
338
|
+
? Array.isArray(key)
|
|
339
|
+
? key.map((k) => (typeof k === 'string' ? k : k.toString('utf-8'))).join('\n')
|
|
340
|
+
: typeof key === 'string' ? key : key.toString('utf-8')
|
|
341
|
+
: '';
|
|
342
|
+
|
|
343
|
+
const pem = keyStr ? `${certStr}\n${keyStr}` : certStr;
|
|
344
|
+
return Gio.TlsCertificate.new_from_pem(pem, pem.length);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* TLSServer wraps a net.Server to accept TLS connections.
|
|
349
|
+
*/
|
|
350
|
+
export class TLSServer extends Server {
|
|
351
|
+
private _tlsCertificate: Gio.TlsCertificate | null = null;
|
|
352
|
+
private _tlsOptions: TlsServerOptions;
|
|
353
|
+
private _sniContexts = new Map<string, Gio.TlsCertificate>();
|
|
354
|
+
|
|
355
|
+
constructor(options?: TlsServerOptions, secureConnectionListener?: (socket: TLSSocket) => void) {
|
|
356
|
+
super();
|
|
357
|
+
this._tlsOptions = options || {};
|
|
358
|
+
|
|
359
|
+
if (secureConnectionListener) {
|
|
360
|
+
this.on('secureConnection', secureConnectionListener);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (this._tlsOptions.cert) {
|
|
364
|
+
try {
|
|
365
|
+
this._tlsCertificate = buildGioCertificate(this._tlsOptions.cert, this._tlsOptions.key);
|
|
366
|
+
} catch (err: unknown) {
|
|
367
|
+
deferEmit(this, 'error', createNodeError(err, 'createServer', {}));
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Add a context for SNI (Server Name Indication).
|
|
374
|
+
*/
|
|
375
|
+
addContext(hostname: string, context: SecureContextOptions): void {
|
|
376
|
+
if (context.cert) {
|
|
377
|
+
try {
|
|
378
|
+
const cert = buildGioCertificate(context.cert, context.key);
|
|
379
|
+
this._sniContexts.set(hostname, cert);
|
|
380
|
+
} catch (err: unknown) {
|
|
381
|
+
this.emit('error', createNodeError(err, 'addContext', {}));
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
listen(...args: unknown[]): this {
|
|
387
|
+
this.on('connection', (socket: Socket) => {
|
|
388
|
+
this._upgradeTls(socket);
|
|
389
|
+
});
|
|
390
|
+
return super.listen(...(args as [any]));
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Upgrade a raw TCP socket to TLS using Gio.TlsServerConnection.
|
|
395
|
+
*/
|
|
396
|
+
private _upgradeTls(socket: Socket): void {
|
|
397
|
+
const rawConnection: Gio.SocketConnection | null = (socket as any)._connection;
|
|
398
|
+
if (!rawConnection) {
|
|
399
|
+
const err = new Error('Cannot upgrade socket: no underlying connection');
|
|
400
|
+
this.emit('tlsClientError', err, socket);
|
|
401
|
+
socket.destroy();
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (!this._tlsCertificate) {
|
|
406
|
+
const err = new Error('TLS server has no certificate configured');
|
|
407
|
+
this.emit('tlsClientError', err, socket);
|
|
408
|
+
socket.destroy();
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
try {
|
|
413
|
+
const tlsConn = Gio.TlsServerConnection.new(
|
|
414
|
+
rawConnection as Gio.IOStream,
|
|
415
|
+
this._tlsCertificate,
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
// Configure client authentication
|
|
419
|
+
if (this._tlsOptions.requestCert) {
|
|
420
|
+
tlsConn.authenticationMode = this._tlsOptions.rejectUnauthorized !== false
|
|
421
|
+
? Gio.TlsAuthenticationMode.REQUIRED
|
|
422
|
+
: Gio.TlsAuthenticationMode.REQUESTED;
|
|
423
|
+
} else {
|
|
424
|
+
tlsConn.authenticationMode = Gio.TlsAuthenticationMode.NONE;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (this._tlsOptions.rejectUnauthorized === false) {
|
|
428
|
+
(tlsConn as Gio.TlsConnection).connect(
|
|
429
|
+
'accept-certificate',
|
|
430
|
+
() => true,
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Set ALPN protocols
|
|
435
|
+
if (this._tlsOptions.ALPNProtocols && this._tlsOptions.ALPNProtocols.length > 0) {
|
|
436
|
+
try {
|
|
437
|
+
(tlsConn as any).set_advertised_protocols(this._tlsOptions.ALPNProtocols);
|
|
438
|
+
} catch {
|
|
439
|
+
// ALPN may not be supported
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Perform TLS handshake
|
|
444
|
+
const cancellable = new Gio.Cancellable();
|
|
445
|
+
(tlsConn as Gio.TlsConnection).handshake_async(
|
|
446
|
+
GLib.PRIORITY_DEFAULT,
|
|
447
|
+
cancellable,
|
|
448
|
+
(_source: Gio.TlsConnection | null, asyncResult: Gio.AsyncResult) => {
|
|
449
|
+
try {
|
|
450
|
+
(tlsConn as Gio.TlsConnection).handshake_finish(asyncResult);
|
|
451
|
+
|
|
452
|
+
// Create TLSSocket with TLS I/O streams wired up
|
|
453
|
+
const tlsSocket = new TLSSocket();
|
|
454
|
+
tlsSocket.encrypted = true;
|
|
455
|
+
tlsSocket.authorized = true;
|
|
456
|
+
tlsSocket._setupTlsStreams(tlsConn as Gio.TlsConnection);
|
|
457
|
+
|
|
458
|
+
// Get ALPN result
|
|
459
|
+
tlsSocket.alpnProtocol = tlsSocket.getAlpnProtocol();
|
|
460
|
+
|
|
461
|
+
// Start reading on the TLS streams
|
|
462
|
+
(tlsSocket as any)._startReading();
|
|
463
|
+
|
|
464
|
+
this.emit('secureConnection', tlsSocket);
|
|
465
|
+
} catch (err: unknown) {
|
|
466
|
+
const nodeErr = createNodeError(err, 'handshake', {});
|
|
467
|
+
this.emit('tlsClientError', nodeErr, socket);
|
|
468
|
+
socket.destroy();
|
|
469
|
+
}
|
|
470
|
+
},
|
|
471
|
+
);
|
|
472
|
+
} catch (err: unknown) {
|
|
473
|
+
const nodeErr = createNodeError(err, 'tls_wrap', {});
|
|
474
|
+
this.emit('tlsClientError', nodeErr, socket);
|
|
475
|
+
socket.destroy();
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Create a TLS server.
|
|
482
|
+
*/
|
|
483
|
+
export function createServer(options?: TlsServerOptions, secureConnectionListener?: (socket: TLSSocket) => void): TLSServer;
|
|
484
|
+
export function createServer(secureConnectionListener?: (socket: TLSSocket) => void): TLSServer;
|
|
485
|
+
export function createServer(
|
|
486
|
+
optionsOrListener?: TlsServerOptions | ((socket: TLSSocket) => void),
|
|
487
|
+
secureConnectionListener?: (socket: TLSSocket) => void,
|
|
488
|
+
): TLSServer {
|
|
489
|
+
if (typeof optionsOrListener === 'function') {
|
|
490
|
+
return new TLSServer(undefined, optionsOrListener);
|
|
491
|
+
}
|
|
492
|
+
return new TLSServer(optionsOrListener, secureConnectionListener);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
export { TLSServer as Server };
|
|
496
|
+
|
|
497
|
+
export default {
|
|
498
|
+
TLSSocket,
|
|
499
|
+
TLSServer,
|
|
500
|
+
Server: TLSServer,
|
|
501
|
+
connect,
|
|
502
|
+
createServer,
|
|
503
|
+
createSecureContext,
|
|
504
|
+
checkServerIdentity,
|
|
505
|
+
getCiphers,
|
|
506
|
+
rootCertificates,
|
|
507
|
+
DEFAULT_MIN_VERSION,
|
|
508
|
+
DEFAULT_MAX_VERSION,
|
|
509
|
+
DEFAULT_CIPHERS,
|
|
510
|
+
};
|
package/src/test.mts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "ESNext",
|
|
4
|
+
"target": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"types": [
|
|
7
|
+
"node"
|
|
8
|
+
],
|
|
9
|
+
"experimentalDecorators": true,
|
|
10
|
+
"emitDeclarationOnly": true,
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"outDir": "lib",
|
|
14
|
+
"rootDir": "src",
|
|
15
|
+
"declarationDir": "lib/types",
|
|
16
|
+
"composite": true,
|
|
17
|
+
"skipLibCheck": true,
|
|
18
|
+
"allowJs": true,
|
|
19
|
+
"checkJs": false,
|
|
20
|
+
"strict": false
|
|
21
|
+
},
|
|
22
|
+
"include": [
|
|
23
|
+
"src/**/*.ts"
|
|
24
|
+
],
|
|
25
|
+
"exclude": [
|
|
26
|
+
"src/test.ts",
|
|
27
|
+
"src/test.mts",
|
|
28
|
+
"src/**/*.spec.ts",
|
|
29
|
+
"src/**/*.spec.mts"
|
|
30
|
+
]
|
|
31
|
+
}
|