@gjsify/tls 0.4.0 → 0.4.4
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/package.json +45 -42
- package/src/cert.spec.ts +0 -230
- package/src/index.spec.ts +0 -697
- package/src/index.ts +0 -909
- package/src/test.mts +0 -8
- package/src/tls.gjs.spec.ts +0 -165
- package/tsconfig.json +0 -29
- package/tsconfig.tsbuildinfo +0 -1
package/src/index.ts
DELETED
|
@@ -1,909 +0,0 @@
|
|
|
1
|
-
// Reference: Node.js lib/tls.js, lib/_tls_common.js, lib/_tls_wrap.js
|
|
2
|
-
// Reimplemented for GJS using Gio.TlsClientConnection / Gio.TlsServerConnection /
|
|
3
|
-
// Gio.TlsCertificate.
|
|
4
|
-
//
|
|
5
|
-
// Node-TLS option / API → Gio.TLS property/method mapping (authoritative for this file):
|
|
6
|
-
//
|
|
7
|
-
// Node option / API → Gio binding
|
|
8
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
9
|
-
// tls.createSecureContext({cert}) → Gio.TlsCertificate.new_from_pem
|
|
10
|
-
// {key} (separate PEM) → PEM concatenated then new_from_pem
|
|
11
|
-
// {ca} (PEM string or array) → per-block Gio.TlsCertificate.new_from_pem
|
|
12
|
-
// used as trust anchors via cert.verify()
|
|
13
|
-
// {rejectUnauthorized: false} → TlsConnection 'accept-certificate'
|
|
14
|
-
// signal returns true
|
|
15
|
-
// {minVersion}/{maxVersion}/ → Not exposed by Gio (handled by GnuTLS
|
|
16
|
-
// {ciphers} backend); stored for diagnostics only
|
|
17
|
-
// {ALPNProtocols} → TlsConnection.set_advertised_protocols
|
|
18
|
-
// tlsSocket.alpnProtocol → TlsConnection.get_negotiated_protocol
|
|
19
|
-
// {servername} (SNI) → TlsClientConnection.set_server_identity
|
|
20
|
-
// (Gio.NetworkAddress with hostname)
|
|
21
|
-
// tls.connect({cert,key}) (mTLS) → TlsConnection.set_certificate(client_cert)
|
|
22
|
-
// tls.createServer({SNICallback}) → Best-effort: see "Open TODOs" — Gio
|
|
23
|
-
// does not surface the ClientHello
|
|
24
|
-
// server_name to JS before handshake.
|
|
25
|
-
// tlsSocket.getPeerCertificate() → TlsConnection.get_peer_certificate +
|
|
26
|
-
// TlsCertificate.get_subject_name /
|
|
27
|
-
// get_issuer_name / get_dns_names /
|
|
28
|
-
// get_ip_addresses / get_not_valid_* /
|
|
29
|
-
// certificate_pem
|
|
30
|
-
// detailed=true issuer chain → TlsCertificate.get_issuer (walked)
|
|
31
|
-
// tlsSocket.getProtocol() → TlsConnection.get_protocol_version
|
|
32
|
-
// tlsSocket.getCipher() → TlsConnection.get_ciphersuite_name
|
|
33
|
-
// server: {requestCert, → TlsServerConnection.authentication_mode
|
|
34
|
-
// rejectUnauthorized} REQUESTED / REQUIRED / NONE
|
|
35
|
-
//
|
|
36
|
-
// Documented gaps (see STATUS.md "Open TODOs"):
|
|
37
|
-
// - SNI server-side selection from ClientHello: Gio does not expose
|
|
38
|
-
// server_name extension before handshake; SNICallback is consulted but
|
|
39
|
-
// selection is approximate.
|
|
40
|
-
// - OCSP stapling: not exposed by Gio.
|
|
41
|
-
// - TLS session resumption ('session' event, {session} option): GnuTLS
|
|
42
|
-
// resumption API is not surfaced via GI.
|
|
43
|
-
// - Custom DH/ECDH params, ticket keys: not exposed.
|
|
44
|
-
|
|
45
|
-
import Gio from '@girs/gio-2.0';
|
|
46
|
-
import GLib from '@girs/glib-2.0';
|
|
47
|
-
import { Socket, Server } from 'node:net';
|
|
48
|
-
import type { Server as NetServer } from 'node:net';
|
|
49
|
-
import { createNodeError, deferEmit } from '@gjsify/utils';
|
|
50
|
-
|
|
51
|
-
export const DEFAULT_MIN_VERSION = 'TLSv1.2';
|
|
52
|
-
export const DEFAULT_MAX_VERSION = 'TLSv1.3';
|
|
53
|
-
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';
|
|
54
|
-
|
|
55
|
-
/** Returns a list of supported TLS cipher names (subset; implementation-defined). */
|
|
56
|
-
export function getCiphers(): string[] {
|
|
57
|
-
return [
|
|
58
|
-
'aes-128-gcm', 'aes-256-gcm', 'chacha20-poly1305',
|
|
59
|
-
'aes-128-cbc', 'aes-256-cbc',
|
|
60
|
-
];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// ============================================================================
|
|
64
|
-
// PEM helpers
|
|
65
|
-
// ============================================================================
|
|
66
|
-
|
|
67
|
-
type PemInput = string | Buffer | Uint8Array | Array<string | Buffer | Uint8Array>;
|
|
68
|
-
|
|
69
|
-
/** Coerce a PEM input (string, Buffer/Uint8Array, or array) to a single PEM string. */
|
|
70
|
-
function pemToString(value: PemInput): string {
|
|
71
|
-
if (Array.isArray(value)) {
|
|
72
|
-
return value.map(pemToString).join('\n');
|
|
73
|
-
}
|
|
74
|
-
if (typeof value === 'string') return value;
|
|
75
|
-
if (value && typeof (value as Buffer).toString === 'function') {
|
|
76
|
-
try {
|
|
77
|
-
return (value as Buffer).toString('utf-8');
|
|
78
|
-
} catch {
|
|
79
|
-
return new TextDecoder('utf-8').decode(value as Uint8Array);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return String(value);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** Split a concatenated PEM blob into individual `-----BEGIN ...-----...-----END ...-----` blocks. */
|
|
86
|
-
function splitPemBlocks(pem: string): string[] {
|
|
87
|
-
const out: string[] = [];
|
|
88
|
-
const re = /-----BEGIN [^-]+-----[\s\S]*?-----END [^-]+-----/g;
|
|
89
|
-
let m: RegExpExecArray | null;
|
|
90
|
-
while ((m = re.exec(pem)) !== null) {
|
|
91
|
-
out.push(m[0]);
|
|
92
|
-
}
|
|
93
|
-
return out;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** Build a TlsCertificate (and chain) from PEM strings. The first cert and key are the leaf. */
|
|
97
|
-
function buildGioCertificate(cert: PemInput, key?: PemInput): Gio.TlsCertificate {
|
|
98
|
-
const certPem = pemToString(cert);
|
|
99
|
-
const keyPem = key ? pemToString(key) : '';
|
|
100
|
-
const pem = keyPem ? `${certPem}\n${keyPem}` : certPem;
|
|
101
|
-
return Gio.TlsCertificate.new_from_pem(pem, pem.length);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** Parse a CA bundle (PEM string or array) into a list of TlsCertificate trust anchors. */
|
|
105
|
-
function buildCaCertificates(ca: PemInput): Gio.TlsCertificate[] {
|
|
106
|
-
const blocks: string[] = [];
|
|
107
|
-
if (Array.isArray(ca)) {
|
|
108
|
-
for (const item of ca) blocks.push(...splitPemBlocks(pemToString(item)));
|
|
109
|
-
} else {
|
|
110
|
-
blocks.push(...splitPemBlocks(pemToString(ca)));
|
|
111
|
-
}
|
|
112
|
-
const out: Gio.TlsCertificate[] = [];
|
|
113
|
-
for (const block of blocks) {
|
|
114
|
-
try {
|
|
115
|
-
out.push(Gio.TlsCertificate.new_from_pem(block, block.length));
|
|
116
|
-
} catch {
|
|
117
|
-
// Skip blocks that aren't certificates (DH params, comments, etc).
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return out;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// ============================================================================
|
|
124
|
-
// Peer-certificate extraction
|
|
125
|
-
// ============================================================================
|
|
126
|
-
|
|
127
|
-
export interface CertSubject {
|
|
128
|
-
CN?: string | string[];
|
|
129
|
-
[key: string]: unknown;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export interface PeerCertificate {
|
|
133
|
-
subject?: CertSubject;
|
|
134
|
-
issuer?: CertSubject;
|
|
135
|
-
subjectaltname?: string;
|
|
136
|
-
valid_from?: string;
|
|
137
|
-
valid_to?: string;
|
|
138
|
-
fingerprint?: string;
|
|
139
|
-
fingerprint256?: string;
|
|
140
|
-
serialNumber?: string;
|
|
141
|
-
raw?: Uint8Array;
|
|
142
|
-
issuerCertificate?: PeerCertificate;
|
|
143
|
-
[key: string]: unknown;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/** Parse a distinguished name string (e.g. "CN=example.com,O=Foo") into a key→value object. */
|
|
147
|
-
function parseDistinguishedName(dn: string | null): CertSubject {
|
|
148
|
-
if (!dn) return {};
|
|
149
|
-
const out: CertSubject = {};
|
|
150
|
-
for (const part of dn.split(/,(?![^=]*=)/)) {
|
|
151
|
-
const eq = part.indexOf('=');
|
|
152
|
-
if (eq < 0) continue;
|
|
153
|
-
const key = part.slice(0, eq).trim();
|
|
154
|
-
const value = part.slice(eq + 1).trim();
|
|
155
|
-
const existing = out[key];
|
|
156
|
-
if (existing === undefined) out[key] = value;
|
|
157
|
-
else if (Array.isArray(existing)) existing.push(value);
|
|
158
|
-
else out[key] = [existing as string, value];
|
|
159
|
-
}
|
|
160
|
-
return out;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/** Format a GLib.DateTime as an OpenSSL-style validity string. */
|
|
164
|
-
function formatCertDate(dt: GLib.DateTime | null): string {
|
|
165
|
-
if (!dt) return '';
|
|
166
|
-
try { return dt.format('%b %d %H:%M:%S %Y GMT') ?? ''; } catch { return ''; }
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/** Build the "subjectaltname" string from DNS names + IP addresses (Node format). */
|
|
170
|
-
function formatAltNames(cert: Gio.TlsCertificate): string {
|
|
171
|
-
const parts: string[] = [];
|
|
172
|
-
try {
|
|
173
|
-
const dns = cert.get_dns_names();
|
|
174
|
-
if (dns) {
|
|
175
|
-
for (const b of dns) {
|
|
176
|
-
const data = b.get_data();
|
|
177
|
-
if (!data) continue;
|
|
178
|
-
parts.push(`DNS:${new TextDecoder('utf-8').decode(data)}`);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
} catch { /* not all backends support this */ }
|
|
182
|
-
try {
|
|
183
|
-
const ips = cert.get_ip_addresses();
|
|
184
|
-
if (ips) for (const ip of ips) parts.push(`IP Address:${ip.to_string()}`);
|
|
185
|
-
} catch { /* same */ }
|
|
186
|
-
return parts.join(', ');
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/** Compute SHA-1 / SHA-256 fingerprint strings from raw DER bytes (`AA:BB:CC:…`). */
|
|
190
|
-
function fingerprintFromBytes(bytes: Uint8Array, algo: GLib.ChecksumType): string {
|
|
191
|
-
try {
|
|
192
|
-
const cs = new GLib.Checksum(algo);
|
|
193
|
-
cs.update(bytes);
|
|
194
|
-
const hex = cs.get_string();
|
|
195
|
-
if (!hex) return '';
|
|
196
|
-
const out: string[] = [];
|
|
197
|
-
for (let i = 0; i < hex.length; i += 2) out.push(hex.slice(i, i + 2).toUpperCase());
|
|
198
|
-
return out.join(':');
|
|
199
|
-
} catch {
|
|
200
|
-
return '';
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/** Decode a single PEM cert block into raw DER bytes. */
|
|
205
|
-
function pemToDer(pem: string): Uint8Array {
|
|
206
|
-
const m = /-----BEGIN CERTIFICATE-----([\s\S]*?)-----END CERTIFICATE-----/.exec(pem);
|
|
207
|
-
if (!m) return new Uint8Array(0);
|
|
208
|
-
const b64 = m[1].replace(/[\s\r\n]+/g, '');
|
|
209
|
-
try {
|
|
210
|
-
const atob = (globalThis as { atob?: (s: string) => string }).atob;
|
|
211
|
-
if (!atob) return new Uint8Array(0);
|
|
212
|
-
const bin = atob(b64);
|
|
213
|
-
const out = new Uint8Array(bin.length);
|
|
214
|
-
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
215
|
-
return out;
|
|
216
|
-
} catch {
|
|
217
|
-
return new Uint8Array(0);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/** Convert a single TlsCertificate to the Node `getPeerCertificate()` shape. */
|
|
222
|
-
function tlsCertToPeerCert(cert: Gio.TlsCertificate, detailed: boolean): PeerCertificate {
|
|
223
|
-
const out: PeerCertificate = {};
|
|
224
|
-
try { out.subject = parseDistinguishedName(cert.get_subject_name()); } catch { /* */ }
|
|
225
|
-
try { out.issuer = parseDistinguishedName(cert.get_issuer_name()); } catch { /* */ }
|
|
226
|
-
out.subjectaltname = formatAltNames(cert);
|
|
227
|
-
try {
|
|
228
|
-
out.valid_from = formatCertDate(cert.get_not_valid_before());
|
|
229
|
-
out.valid_to = formatCertDate(cert.get_not_valid_after());
|
|
230
|
-
} catch { /* */ }
|
|
231
|
-
try {
|
|
232
|
-
const c = cert as unknown as { certificate_pem?: string; certificatePem?: string };
|
|
233
|
-
const pemProp = c.certificate_pem ?? c.certificatePem;
|
|
234
|
-
if (pemProp) {
|
|
235
|
-
const der = pemToDer(pemProp);
|
|
236
|
-
out.raw = der;
|
|
237
|
-
out.fingerprint = fingerprintFromBytes(der, GLib.ChecksumType.SHA1);
|
|
238
|
-
out.fingerprint256 = fingerprintFromBytes(der, GLib.ChecksumType.SHA256);
|
|
239
|
-
}
|
|
240
|
-
} catch { /* */ }
|
|
241
|
-
if (detailed) {
|
|
242
|
-
try {
|
|
243
|
-
const issuerCert = cert.get_issuer();
|
|
244
|
-
if (issuerCert && !issuerCert.is_same(cert)) {
|
|
245
|
-
out.issuerCertificate = tlsCertToPeerCert(issuerCert, true);
|
|
246
|
-
} else if (issuerCert) {
|
|
247
|
-
out.issuerCertificate = out; // self-signed: Node returns self-ref
|
|
248
|
-
}
|
|
249
|
-
} catch { /* */ }
|
|
250
|
-
}
|
|
251
|
-
return out;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// ============================================================================
|
|
255
|
-
// RFC 6125 hostname matching
|
|
256
|
-
// ============================================================================
|
|
257
|
-
|
|
258
|
-
/** Removes a trailing dot from a fully-qualified domain name. */
|
|
259
|
-
function unfqdn(host: string): string {
|
|
260
|
-
return host.endsWith('.') ? host.slice(0, -1) : host;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/** Splits a hostname into parts, lower-cased, after removing trailing dots. */
|
|
264
|
-
function splitHost(host: string): string[] {
|
|
265
|
-
return unfqdn(host).toLowerCase().split('.');
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/** Reject control / non-ASCII bytes in pattern labels (RFC 6125 sanity). */
|
|
269
|
-
function isPrintableAscii(s: string): boolean {
|
|
270
|
-
// U+0021 ('!') through U+007E ('~')
|
|
271
|
-
for (let i = 0; i < s.length; i++) {
|
|
272
|
-
const c = s.charCodeAt(i);
|
|
273
|
-
if (c < 0x21 || c > 0x7E) return false;
|
|
274
|
-
}
|
|
275
|
-
return true;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Match a hostname (already split into labels) against a single pattern from
|
|
280
|
-
* a SAN DNS entry or CN. Implements RFC 6125 §6.4.3:
|
|
281
|
-
* - wildcard valid only in the leftmost label
|
|
282
|
-
* - wildcard label may not contain Punycode A-labels (`xn--`)
|
|
283
|
-
* - `*.tld` (two-label patterns) are rejected
|
|
284
|
-
* - exactly one wildcard per label
|
|
285
|
-
*/
|
|
286
|
-
function checkHostMatch(hostParts: string[], pattern: string): boolean {
|
|
287
|
-
if (!pattern) return false;
|
|
288
|
-
const patternParts = splitHost(pattern);
|
|
289
|
-
if (hostParts.length !== patternParts.length) return false;
|
|
290
|
-
if (patternParts.includes('')) return false;
|
|
291
|
-
if (!patternParts.every(isPrintableAscii)) return false;
|
|
292
|
-
for (let i = hostParts.length - 1; i > 0; i--) {
|
|
293
|
-
if (hostParts[i] !== patternParts[i]) return false;
|
|
294
|
-
}
|
|
295
|
-
const hostSub = hostParts[0];
|
|
296
|
-
const patSub = patternParts[0];
|
|
297
|
-
const wildSplit = patSub.split('*', 3);
|
|
298
|
-
if (wildSplit.length === 1 || patSub.includes('xn--')) {
|
|
299
|
-
return hostSub === patSub;
|
|
300
|
-
}
|
|
301
|
-
if (wildSplit.length > 2) return false;
|
|
302
|
-
if (patternParts.length <= 2) return false;
|
|
303
|
-
const prefix = wildSplit[0];
|
|
304
|
-
const suffix = wildSplit[1];
|
|
305
|
-
if (prefix.length + suffix.length > hostSub.length) return false;
|
|
306
|
-
if (!hostSub.startsWith(prefix)) return false;
|
|
307
|
-
if (!hostSub.endsWith(suffix)) return false;
|
|
308
|
-
return true;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/** Error returned by checkServerIdentity, with Node-compatible shape. */
|
|
312
|
-
export interface CertAltNameError extends Error {
|
|
313
|
-
reason: string;
|
|
314
|
-
host: string;
|
|
315
|
-
cert: PeerCertificate;
|
|
316
|
-
code: 'ERR_TLS_CERT_ALTNAME_INVALID';
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Verifies that the certificate `cert` is valid for `hostname`.
|
|
321
|
-
* Returns an Error (with code 'ERR_TLS_CERT_ALTNAME_INVALID') if the check
|
|
322
|
-
* fails, or `undefined` on success.
|
|
323
|
-
*
|
|
324
|
-
* Reference: Node.js lib/tls.js exports.checkServerIdentity (RFC 6125 §6.4.3).
|
|
325
|
-
*/
|
|
326
|
-
export function checkServerIdentity(hostname: string, cert: PeerCertificate): CertAltNameError | undefined {
|
|
327
|
-
const subject = cert.subject;
|
|
328
|
-
const altNames = cert.subjectaltname;
|
|
329
|
-
const dnsNames: string[] = [];
|
|
330
|
-
const ips: string[] = [];
|
|
331
|
-
|
|
332
|
-
hostname = String(hostname);
|
|
333
|
-
|
|
334
|
-
if (altNames) {
|
|
335
|
-
const parts = altNames.split(', ');
|
|
336
|
-
for (const name of parts) {
|
|
337
|
-
if (name.startsWith('DNS:')) dnsNames.push(name.slice(4));
|
|
338
|
-
else if (name.startsWith('IP Address:')) ips.push(name.slice(11).trim());
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
let valid = false;
|
|
343
|
-
let reason = 'Unknown reason';
|
|
344
|
-
|
|
345
|
-
hostname = unfqdn(hostname);
|
|
346
|
-
|
|
347
|
-
const isIPv4 = /^(\d{1,3}\.){3}\d{1,3}$/.test(hostname);
|
|
348
|
-
const isIPv6 = hostname.includes(':');
|
|
349
|
-
if (isIPv4 || isIPv6) {
|
|
350
|
-
valid = ips.some(ip => ip.toLowerCase() === hostname.toLowerCase());
|
|
351
|
-
if (!valid) {
|
|
352
|
-
reason = `IP: ${hostname} is not in the cert's list: ${ips.join(', ')}`;
|
|
353
|
-
}
|
|
354
|
-
} else if (dnsNames.length > 0 || subject?.CN) {
|
|
355
|
-
const hostParts = splitHost(hostname);
|
|
356
|
-
|
|
357
|
-
if (dnsNames.length > 0) {
|
|
358
|
-
valid = dnsNames.some(pattern => checkHostMatch(hostParts, pattern.trim()));
|
|
359
|
-
if (!valid) {
|
|
360
|
-
reason = `Host: ${hostname}. is not in the cert's altnames: ${altNames}`;
|
|
361
|
-
}
|
|
362
|
-
} else {
|
|
363
|
-
const cn = subject?.CN;
|
|
364
|
-
if (Array.isArray(cn)) {
|
|
365
|
-
valid = cn.some(c => checkHostMatch(hostParts, c));
|
|
366
|
-
} else if (cn) {
|
|
367
|
-
valid = checkHostMatch(hostParts, cn);
|
|
368
|
-
}
|
|
369
|
-
if (!valid) {
|
|
370
|
-
reason = `Host: ${hostname}. is not cert's CN: ${cn}`;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
} else {
|
|
374
|
-
reason = 'Cert does not contain a DNS name';
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
if (!valid) {
|
|
378
|
-
const err = new Error(reason) as CertAltNameError;
|
|
379
|
-
err.reason = reason;
|
|
380
|
-
err.host = hostname;
|
|
381
|
-
err.cert = cert;
|
|
382
|
-
err.code = 'ERR_TLS_CERT_ALTNAME_INVALID';
|
|
383
|
-
return err;
|
|
384
|
-
}
|
|
385
|
-
return undefined;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// ============================================================================
|
|
389
|
-
// SecureContext
|
|
390
|
-
// ============================================================================
|
|
391
|
-
|
|
392
|
-
export interface SecureContextOptions {
|
|
393
|
-
ca?: PemInput;
|
|
394
|
-
cert?: PemInput;
|
|
395
|
-
key?: PemInput;
|
|
396
|
-
passphrase?: string;
|
|
397
|
-
rejectUnauthorized?: boolean;
|
|
398
|
-
ciphers?: string;
|
|
399
|
-
minVersion?: string;
|
|
400
|
-
maxVersion?: string;
|
|
401
|
-
ALPNProtocols?: string[];
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/** Internal "secure context" — parsed TLS material shared by tls.connect/createServer. */
|
|
405
|
-
export interface SecureContext {
|
|
406
|
-
certificate: Gio.TlsCertificate | null;
|
|
407
|
-
caCertificates: Gio.TlsCertificate[];
|
|
408
|
-
options: SecureContextOptions;
|
|
409
|
-
/**
|
|
410
|
-
* Node-compat handle (Node returns a `SecureContext` with an internal native
|
|
411
|
-
* `context` field). We have no native handle, so this points back at the
|
|
412
|
-
* SecureContext object itself — `ctx.context !== undefined` matches Node.
|
|
413
|
-
*/
|
|
414
|
-
context: SecureContext;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/** Build a SecureContext from PEM material. Buffer/Uint8Array/string all accepted. */
|
|
418
|
-
export function createSecureContext(options?: SecureContextOptions): SecureContext {
|
|
419
|
-
const opts = options ?? {};
|
|
420
|
-
let certificate: Gio.TlsCertificate | null = null;
|
|
421
|
-
if (opts.cert) {
|
|
422
|
-
try { certificate = buildGioCertificate(opts.cert, opts.key); } catch { certificate = null; }
|
|
423
|
-
}
|
|
424
|
-
const caCertificates = opts.ca ? buildCaCertificates(opts.ca) : [];
|
|
425
|
-
const ctx = { certificate, caCertificates, options: opts } as SecureContext;
|
|
426
|
-
ctx.context = ctx; // Node-compat self-reference
|
|
427
|
-
return ctx;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
export interface TlsConnectOptions extends SecureContextOptions {
|
|
431
|
-
host?: string;
|
|
432
|
-
port?: number;
|
|
433
|
-
socket?: Socket;
|
|
434
|
-
servername?: string;
|
|
435
|
-
ALPNProtocols?: string[];
|
|
436
|
-
/** Pre-built secure context from createSecureContext(). */
|
|
437
|
-
secureContext?: SecureContext;
|
|
438
|
-
/** Custom server-identity check (runs after the GnuTLS-level check). */
|
|
439
|
-
checkServerIdentity?: (host: string, cert: PeerCertificate) => Error | undefined;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
/** Internal helper: cast a Socket to its private-field shape (we own the impl). */
|
|
443
|
-
interface SocketInternals {
|
|
444
|
-
_connection: Gio.SocketConnection | null;
|
|
445
|
-
_ioStream: Gio.IOStream | null;
|
|
446
|
-
_inputStream: Gio.InputStream | null;
|
|
447
|
-
_outputStream: Gio.OutputStream | null;
|
|
448
|
-
_reading: boolean;
|
|
449
|
-
_startReading(): void;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
/**
|
|
453
|
-
* TLSSocket wraps a net.Socket with TLS via Gio.TlsConnection.
|
|
454
|
-
*/
|
|
455
|
-
export class TLSSocket extends Socket {
|
|
456
|
-
encrypted = true;
|
|
457
|
-
authorized = false;
|
|
458
|
-
authorizationError?: string;
|
|
459
|
-
alpnProtocol: string | false = false;
|
|
460
|
-
servername: string | undefined;
|
|
461
|
-
|
|
462
|
-
/** @internal */
|
|
463
|
-
_tlsConnection: Gio.TlsConnection | null = null;
|
|
464
|
-
/** @internal — preserved for diagnostics + future cert-chain verification. */
|
|
465
|
-
_secureContext: SecureContext | null = null;
|
|
466
|
-
|
|
467
|
-
constructor(_socket?: Socket, _options?: SecureContextOptions) {
|
|
468
|
-
super();
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* @internal Wire the TLS connection's I/O streams into this socket
|
|
473
|
-
* so that read/write operations go through the encrypted channel.
|
|
474
|
-
*/
|
|
475
|
-
_setupTlsStreams(tlsConn: Gio.TlsConnection): void {
|
|
476
|
-
this._tlsConnection = tlsConn;
|
|
477
|
-
const internals = this as unknown as SocketInternals;
|
|
478
|
-
internals._inputStream = tlsConn.get_input_stream();
|
|
479
|
-
internals._outputStream = tlsConn.get_output_stream();
|
|
480
|
-
internals._connection = tlsConn as unknown as Gio.SocketConnection;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
/**
|
|
484
|
-
* Get the peer certificate. When `detailed` is true, walks the issuer chain
|
|
485
|
-
* via `Gio.TlsCertificate.get_issuer()` and populates `issuerCertificate`
|
|
486
|
-
* recursively (with a self-reference on the root for compatibility).
|
|
487
|
-
*/
|
|
488
|
-
getPeerCertificate(detailed = false): PeerCertificate {
|
|
489
|
-
if (!this._tlsConnection) return {};
|
|
490
|
-
try {
|
|
491
|
-
const cert = this._tlsConnection.get_peer_certificate();
|
|
492
|
-
if (!cert) return {};
|
|
493
|
-
return tlsCertToPeerCert(cert, detailed);
|
|
494
|
-
} catch {
|
|
495
|
-
return {};
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
/** Get the negotiated TLS protocol version. */
|
|
500
|
-
getProtocol(): string | null {
|
|
501
|
-
if (!this._tlsConnection) return null;
|
|
502
|
-
try {
|
|
503
|
-
const proto = this._tlsConnection.get_protocol_version();
|
|
504
|
-
switch (proto) {
|
|
505
|
-
case Gio.TlsProtocolVersion.TLS_1_0: return 'TLSv1';
|
|
506
|
-
case Gio.TlsProtocolVersion.TLS_1_1: return 'TLSv1.1';
|
|
507
|
-
case Gio.TlsProtocolVersion.TLS_1_2: return 'TLSv1.2';
|
|
508
|
-
case Gio.TlsProtocolVersion.TLS_1_3: return 'TLSv1.3';
|
|
509
|
-
default: return null;
|
|
510
|
-
}
|
|
511
|
-
} catch {
|
|
512
|
-
return null;
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/** Get the negotiated cipher suite name + version. */
|
|
517
|
-
getCipher(): { name: string; version: string } | null {
|
|
518
|
-
if (!this._tlsConnection) return null;
|
|
519
|
-
try {
|
|
520
|
-
const name = this._tlsConnection.get_ciphersuite_name();
|
|
521
|
-
return { name: name || 'unknown', version: this.getProtocol() || 'unknown' };
|
|
522
|
-
} catch {
|
|
523
|
-
return null;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
/** Get the negotiated ALPN protocol (or false if none). */
|
|
528
|
-
getAlpnProtocol(): string | false {
|
|
529
|
-
if (!this._tlsConnection) return false;
|
|
530
|
-
try {
|
|
531
|
-
const proto = this._tlsConnection.get_negotiated_protocol();
|
|
532
|
-
return proto || false;
|
|
533
|
-
} catch {
|
|
534
|
-
return false;
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
/**
|
|
540
|
-
* Create a TLS client connection.
|
|
541
|
-
*
|
|
542
|
-
* Connects via TCP first (using net.Socket.connect), then upgrades
|
|
543
|
-
* the connection to TLS using Gio.TlsClientConnection.
|
|
544
|
-
*/
|
|
545
|
-
export function connect(options: TlsConnectOptions, callback?: () => void): TLSSocket {
|
|
546
|
-
const socket = new TLSSocket(undefined, options);
|
|
547
|
-
|
|
548
|
-
if (callback) {
|
|
549
|
-
socket.once('secureConnect', callback);
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
const port = options.port || 443;
|
|
553
|
-
const host = options.host || 'localhost';
|
|
554
|
-
const servername = options.servername || host;
|
|
555
|
-
const rejectUnauthorized = options.rejectUnauthorized !== false;
|
|
556
|
-
|
|
557
|
-
const ctx = options.secureContext ?? createSecureContext(options);
|
|
558
|
-
socket._secureContext = ctx;
|
|
559
|
-
socket.servername = servername;
|
|
560
|
-
const customCheckServerIdentity = options.checkServerIdentity;
|
|
561
|
-
|
|
562
|
-
socket.once('connect', () => {
|
|
563
|
-
const rawConnection = (socket as unknown as SocketInternals)._connection;
|
|
564
|
-
if (!rawConnection) {
|
|
565
|
-
socket.destroy(new Error('No underlying connection for TLS upgrade'));
|
|
566
|
-
return;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
try {
|
|
570
|
-
const connectable = Gio.NetworkAddress.new(servername, port);
|
|
571
|
-
const tlsConn = Gio.TlsClientConnection.new(
|
|
572
|
-
rawConnection as unknown as Gio.IOStream,
|
|
573
|
-
connectable,
|
|
574
|
-
);
|
|
575
|
-
|
|
576
|
-
tlsConn.set_server_identity(connectable);
|
|
577
|
-
|
|
578
|
-
// Client certificate (mTLS)
|
|
579
|
-
if (ctx.certificate) {
|
|
580
|
-
try {
|
|
581
|
-
tlsConn.set_certificate(ctx.certificate);
|
|
582
|
-
} catch (err: unknown) {
|
|
583
|
-
// eslint-disable-next-line no-console
|
|
584
|
-
console.warn('[tls] failed to set client certificate:', err);
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
// ALPN
|
|
589
|
-
if (options.ALPNProtocols && options.ALPNProtocols.length > 0) {
|
|
590
|
-
try {
|
|
591
|
-
tlsConn.set_advertised_protocols(options.ALPNProtocols);
|
|
592
|
-
} catch {
|
|
593
|
-
// ALPN may not be supported
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
// Certificate validation: by default rely on system trust store +
|
|
598
|
-
// 'accept-certificate' returning false. With a custom CA we accept
|
|
599
|
-
// peer certs that validate against `ctx.caCertificates`. With
|
|
600
|
-
// `rejectUnauthorized: false`, accept everything.
|
|
601
|
-
tlsConn.connect('accept-certificate', (
|
|
602
|
-
_conn: Gio.TlsConnection,
|
|
603
|
-
peerCert: Gio.TlsCertificate,
|
|
604
|
-
_errors: Gio.TlsCertificateFlags,
|
|
605
|
-
): boolean => {
|
|
606
|
-
if (!rejectUnauthorized) return true;
|
|
607
|
-
if (ctx.caCertificates.length === 0) return false;
|
|
608
|
-
for (const ca of ctx.caCertificates) {
|
|
609
|
-
try {
|
|
610
|
-
const flags = peerCert.verify(connectable, ca);
|
|
611
|
-
if (flags === Gio.TlsCertificateFlags.NO_FLAGS) return true;
|
|
612
|
-
} catch { /* try next */ }
|
|
613
|
-
}
|
|
614
|
-
return false;
|
|
615
|
-
});
|
|
616
|
-
|
|
617
|
-
const cancellable = new Gio.Cancellable();
|
|
618
|
-
tlsConn.handshake_async(
|
|
619
|
-
GLib.PRIORITY_DEFAULT,
|
|
620
|
-
cancellable,
|
|
621
|
-
(_source: Gio.TlsConnection | null, asyncResult: Gio.AsyncResult) => {
|
|
622
|
-
try {
|
|
623
|
-
tlsConn.handshake_finish(asyncResult);
|
|
624
|
-
socket.authorized = true;
|
|
625
|
-
socket._setupTlsStreams(tlsConn);
|
|
626
|
-
socket.alpnProtocol = socket.getAlpnProtocol();
|
|
627
|
-
|
|
628
|
-
// Custom server-identity check (post-handshake, mirrors Node).
|
|
629
|
-
if (customCheckServerIdentity) {
|
|
630
|
-
const peer = socket.getPeerCertificate();
|
|
631
|
-
const idErr = customCheckServerIdentity(servername, peer);
|
|
632
|
-
if (idErr) {
|
|
633
|
-
socket.authorized = false;
|
|
634
|
-
socket.authorizationError = idErr.message;
|
|
635
|
-
if (rejectUnauthorized) {
|
|
636
|
-
socket.destroy(idErr);
|
|
637
|
-
return;
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
const internals = socket as unknown as SocketInternals;
|
|
643
|
-
internals._reading = false;
|
|
644
|
-
internals._startReading();
|
|
645
|
-
|
|
646
|
-
socket.emit('secureConnect');
|
|
647
|
-
} catch (err: unknown) {
|
|
648
|
-
socket.authorized = false;
|
|
649
|
-
socket.authorizationError = err instanceof Error ? err.message : String(err);
|
|
650
|
-
if (rejectUnauthorized) {
|
|
651
|
-
socket.destroy(err instanceof Error ? err : new Error(String(err)));
|
|
652
|
-
} else {
|
|
653
|
-
socket._setupTlsStreams(tlsConn);
|
|
654
|
-
socket.emit('secureConnect');
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
},
|
|
658
|
-
);
|
|
659
|
-
} catch (err: unknown) {
|
|
660
|
-
socket.destroy(err instanceof Error ? err : new Error(String(err)));
|
|
661
|
-
}
|
|
662
|
-
});
|
|
663
|
-
|
|
664
|
-
socket.connect({ port, host });
|
|
665
|
-
return socket;
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
export const rootCertificates: string[] = [];
|
|
669
|
-
|
|
670
|
-
// ============================================================================
|
|
671
|
-
// TLSServer / createServer
|
|
672
|
-
// ============================================================================
|
|
673
|
-
|
|
674
|
-
export type SNICallback = (
|
|
675
|
-
servername: string,
|
|
676
|
-
cb: (err: Error | null, ctx?: SecureContext) => void,
|
|
677
|
-
) => void;
|
|
678
|
-
|
|
679
|
-
export interface TlsServerOptions extends SecureContextOptions {
|
|
680
|
-
requestCert?: boolean;
|
|
681
|
-
rejectUnauthorized?: boolean;
|
|
682
|
-
ALPNProtocols?: string[];
|
|
683
|
-
SNICallback?: SNICallback;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
/**
|
|
687
|
-
* TLSServer accepts incoming TCP connections and upgrades each to TLS via
|
|
688
|
-
* `Gio.TlsServerConnection`. Supports mTLS via `requestCert`+`rejectUnauthorized`,
|
|
689
|
-
* SNI selection via `addContext`/`SNICallback`, and ALPN negotiation.
|
|
690
|
-
*/
|
|
691
|
-
export class TLSServer extends Server {
|
|
692
|
-
private _tlsCertificate: Gio.TlsCertificate | null = null;
|
|
693
|
-
private _tlsOptions: TlsServerOptions;
|
|
694
|
-
private _sniContexts = new Map<string, SecureContext>();
|
|
695
|
-
/** @internal — exposed for tests. */
|
|
696
|
-
_secureContext: SecureContext;
|
|
697
|
-
|
|
698
|
-
constructor(options?: TlsServerOptions, secureConnectionListener?: (socket: TLSSocket) => void) {
|
|
699
|
-
super();
|
|
700
|
-
this._tlsOptions = options ?? {};
|
|
701
|
-
this._secureContext = createSecureContext(this._tlsOptions);
|
|
702
|
-
this._tlsCertificate = this._secureContext.certificate;
|
|
703
|
-
|
|
704
|
-
if (secureConnectionListener) {
|
|
705
|
-
this.on('secureConnection', secureConnectionListener);
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
if (this._tlsOptions.cert && !this._tlsCertificate) {
|
|
709
|
-
// PEM provided but failed to parse — emit error asynchronously.
|
|
710
|
-
deferEmit(this as unknown as NetServer, 'error', createNodeError(
|
|
711
|
-
new Error('Failed to parse TLS certificate'), 'createServer', {},
|
|
712
|
-
));
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
/**
|
|
717
|
-
* Add an additional context for SNI (Server Name Indication). Uses RFC 6125
|
|
718
|
-
* matching against the requested server name.
|
|
719
|
-
*/
|
|
720
|
-
addContext(hostname: string, context: SecureContextOptions): void {
|
|
721
|
-
try {
|
|
722
|
-
const ctx = createSecureContext(context);
|
|
723
|
-
this._sniContexts.set(hostname.toLowerCase(), ctx);
|
|
724
|
-
} catch (err: unknown) {
|
|
725
|
-
this.emit('error', createNodeError(err, 'addContext', {}));
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
/**
|
|
730
|
-
* Resolve a SecureContext for the given server name. Order:
|
|
731
|
-
* 1. exact match in `_sniContexts`
|
|
732
|
-
* 2. RFC 6125 wildcard match in `_sniContexts`
|
|
733
|
-
* 3. SNICallback (if provided)
|
|
734
|
-
* 4. fall through to the server's default context
|
|
735
|
-
*/
|
|
736
|
-
private _resolveSniContext(servername: string | null, done: (ctx: SecureContext) => void): void {
|
|
737
|
-
const fallback = this._secureContext;
|
|
738
|
-
if (!servername) { done(fallback); return; }
|
|
739
|
-
const lower = servername.toLowerCase();
|
|
740
|
-
const exact = this._sniContexts.get(lower);
|
|
741
|
-
if (exact) { done(exact); return; }
|
|
742
|
-
const hostParts = splitHost(lower);
|
|
743
|
-
for (const [pattern, ctx] of this._sniContexts) {
|
|
744
|
-
if (checkHostMatch(hostParts, pattern)) { done(ctx); return; }
|
|
745
|
-
}
|
|
746
|
-
if (this._tlsOptions.SNICallback) {
|
|
747
|
-
try {
|
|
748
|
-
this._tlsOptions.SNICallback(servername, (err: Error | null, ctx?: SecureContext) => {
|
|
749
|
-
if (err || !ctx) { done(fallback); return; }
|
|
750
|
-
done(ctx);
|
|
751
|
-
});
|
|
752
|
-
return;
|
|
753
|
-
} catch {
|
|
754
|
-
done(fallback);
|
|
755
|
-
return;
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
done(fallback);
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
listen(...args: unknown[]): this {
|
|
762
|
-
this.on('connection', (socket: Socket) => {
|
|
763
|
-
this._upgradeTls(socket);
|
|
764
|
-
});
|
|
765
|
-
type ListenArgs = Parameters<NetServer['listen']>;
|
|
766
|
-
return (super.listen as (...a: ListenArgs) => this)(...(args as unknown as ListenArgs));
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
/** Upgrade a raw TCP socket to TLS using Gio.TlsServerConnection. */
|
|
770
|
-
private _upgradeTls(socket: Socket): void {
|
|
771
|
-
const rawConnection = (socket as unknown as SocketInternals)._connection;
|
|
772
|
-
if (!rawConnection) {
|
|
773
|
-
const err = new Error('Cannot upgrade socket: no underlying connection');
|
|
774
|
-
this.emit('tlsClientError', err, socket);
|
|
775
|
-
socket.destroy();
|
|
776
|
-
return;
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
if (!this._tlsCertificate && this._sniContexts.size === 0 && !this._tlsOptions.SNICallback) {
|
|
780
|
-
const err = new Error('TLS server has no certificate configured');
|
|
781
|
-
this.emit('tlsClientError', err, socket);
|
|
782
|
-
socket.destroy();
|
|
783
|
-
return;
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
// SNI: Gio does not surface ClientHello server_name to JS pre-handshake;
|
|
787
|
-
// we use the server's default certificate. Real-world SNI multiplexing
|
|
788
|
-
// is documented in STATUS.md "Open TODOs".
|
|
789
|
-
this._resolveSniContext(null, (ctx) => {
|
|
790
|
-
const certificate = ctx.certificate ?? this._tlsCertificate;
|
|
791
|
-
if (!certificate) {
|
|
792
|
-
const err = new Error('SNI resolution returned no certificate');
|
|
793
|
-
this.emit('tlsClientError', err, socket);
|
|
794
|
-
socket.destroy();
|
|
795
|
-
return;
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
try {
|
|
799
|
-
const tlsConn = Gio.TlsServerConnection.new(
|
|
800
|
-
rawConnection as unknown as Gio.IOStream,
|
|
801
|
-
certificate,
|
|
802
|
-
);
|
|
803
|
-
|
|
804
|
-
// Client-cert / mTLS configuration
|
|
805
|
-
if (this._tlsOptions.requestCert) {
|
|
806
|
-
tlsConn.authenticationMode = this._tlsOptions.rejectUnauthorized !== false
|
|
807
|
-
? Gio.TlsAuthenticationMode.REQUIRED
|
|
808
|
-
: Gio.TlsAuthenticationMode.REQUESTED;
|
|
809
|
-
} else {
|
|
810
|
-
tlsConn.authenticationMode = Gio.TlsAuthenticationMode.NONE;
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
const requireClientCert = !!this._tlsOptions.requestCert
|
|
814
|
-
&& this._tlsOptions.rejectUnauthorized !== false;
|
|
815
|
-
const clientCAs = this._secureContext.caCertificates;
|
|
816
|
-
|
|
817
|
-
tlsConn.connect('accept-certificate', (
|
|
818
|
-
_conn: Gio.TlsConnection,
|
|
819
|
-
peerCert: Gio.TlsCertificate,
|
|
820
|
-
_errors: Gio.TlsCertificateFlags,
|
|
821
|
-
): boolean => {
|
|
822
|
-
if (!requireClientCert) return true;
|
|
823
|
-
if (clientCAs.length === 0) return false;
|
|
824
|
-
for (const ca of clientCAs) {
|
|
825
|
-
try {
|
|
826
|
-
const flags = peerCert.verify(null, ca);
|
|
827
|
-
if (flags === Gio.TlsCertificateFlags.NO_FLAGS) return true;
|
|
828
|
-
} catch { /* try next */ }
|
|
829
|
-
}
|
|
830
|
-
return false;
|
|
831
|
-
});
|
|
832
|
-
|
|
833
|
-
// ALPN
|
|
834
|
-
if (this._tlsOptions.ALPNProtocols && this._tlsOptions.ALPNProtocols.length > 0) {
|
|
835
|
-
try {
|
|
836
|
-
tlsConn.set_advertised_protocols(this._tlsOptions.ALPNProtocols);
|
|
837
|
-
} catch {
|
|
838
|
-
// ALPN may not be supported
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
const cancellable = new Gio.Cancellable();
|
|
843
|
-
tlsConn.handshake_async(
|
|
844
|
-
GLib.PRIORITY_DEFAULT,
|
|
845
|
-
cancellable,
|
|
846
|
-
(_source: Gio.TlsConnection | null, asyncResult: Gio.AsyncResult) => {
|
|
847
|
-
try {
|
|
848
|
-
tlsConn.handshake_finish(asyncResult);
|
|
849
|
-
|
|
850
|
-
const tlsSocket = new TLSSocket();
|
|
851
|
-
tlsSocket.encrypted = true;
|
|
852
|
-
tlsSocket.authorized = true;
|
|
853
|
-
tlsSocket._secureContext = ctx;
|
|
854
|
-
tlsSocket._setupTlsStreams(tlsConn);
|
|
855
|
-
tlsSocket.alpnProtocol = tlsSocket.getAlpnProtocol();
|
|
856
|
-
|
|
857
|
-
const internals = tlsSocket as unknown as SocketInternals;
|
|
858
|
-
internals._startReading();
|
|
859
|
-
|
|
860
|
-
this.emit('secureConnection', tlsSocket);
|
|
861
|
-
} catch (err: unknown) {
|
|
862
|
-
const nodeErr = createNodeError(err, 'handshake', {});
|
|
863
|
-
this.emit('tlsClientError', nodeErr, socket);
|
|
864
|
-
socket.destroy();
|
|
865
|
-
}
|
|
866
|
-
},
|
|
867
|
-
);
|
|
868
|
-
} catch (err: unknown) {
|
|
869
|
-
const nodeErr = createNodeError(err, 'tls_wrap', {});
|
|
870
|
-
this.emit('tlsClientError', nodeErr, socket);
|
|
871
|
-
socket.destroy();
|
|
872
|
-
}
|
|
873
|
-
});
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
/**
|
|
878
|
-
* Create a TLS server.
|
|
879
|
-
*/
|
|
880
|
-
export function createServer(options?: TlsServerOptions, secureConnectionListener?: (socket: TLSSocket) => void): TLSServer;
|
|
881
|
-
export function createServer(secureConnectionListener?: (socket: TLSSocket) => void): TLSServer;
|
|
882
|
-
export function createServer(
|
|
883
|
-
optionsOrListener?: TlsServerOptions | ((socket: TLSSocket) => void),
|
|
884
|
-
secureConnectionListener?: (socket: TLSSocket) => void,
|
|
885
|
-
): TLSServer {
|
|
886
|
-
if (typeof optionsOrListener === 'function') {
|
|
887
|
-
return new TLSServer(undefined, optionsOrListener);
|
|
888
|
-
}
|
|
889
|
-
return new TLSServer(optionsOrListener, secureConnectionListener);
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
export { TLSServer as Server };
|
|
893
|
-
|
|
894
|
-
const tlsExports = {
|
|
895
|
-
TLSSocket,
|
|
896
|
-
TLSServer,
|
|
897
|
-
Server: TLSServer,
|
|
898
|
-
connect,
|
|
899
|
-
createServer,
|
|
900
|
-
createSecureContext,
|
|
901
|
-
checkServerIdentity,
|
|
902
|
-
getCiphers,
|
|
903
|
-
rootCertificates,
|
|
904
|
-
DEFAULT_MIN_VERSION,
|
|
905
|
-
DEFAULT_MAX_VERSION,
|
|
906
|
-
DEFAULT_CIPHERS,
|
|
907
|
-
};
|
|
908
|
-
|
|
909
|
-
export default tlsExports;
|