react-native-nitro-net 0.1.5 → 0.2.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.
Files changed (48) hide show
  1. package/README.md +56 -4
  2. package/android/libs/arm64-v8a/librust_c_net.so +0 -0
  3. package/android/libs/armeabi-v7a/librust_c_net.so +0 -0
  4. package/android/libs/x86/librust_c_net.so +0 -0
  5. package/android/libs/x86_64/librust_c_net.so +0 -0
  6. package/cpp/HybridNetDriver.hpp +68 -0
  7. package/cpp/HybridNetServerDriver.hpp +9 -0
  8. package/cpp/HybridNetSocketDriver.hpp +149 -0
  9. package/cpp/NetBindings.hpp +52 -1
  10. package/ios/Frameworks/RustCNet.xcframework/Info.plist +5 -5
  11. package/ios/Frameworks/RustCNet.xcframework/ios-arm64/RustCNet.framework/RustCNet +0 -0
  12. package/ios/Frameworks/RustCNet.xcframework/ios-arm64_x86_64-simulator/RustCNet.framework/RustCNet +0 -0
  13. package/lib/Net.nitro.d.ts +27 -1
  14. package/lib/Net.nitro.js +3 -1
  15. package/lib/index.d.ts +4 -3
  16. package/lib/index.js +47 -6
  17. package/lib/tls.d.ts +124 -0
  18. package/lib/tls.js +451 -0
  19. package/nitrogen/generated/android/c++/JHybridNetDriverSpec.cpp +38 -1
  20. package/nitrogen/generated/android/c++/JHybridNetDriverSpec.hpp +8 -0
  21. package/nitrogen/generated/android/c++/JHybridNetServerDriverSpec.cpp +4 -0
  22. package/nitrogen/generated/android/c++/JHybridNetServerDriverSpec.hpp +1 -0
  23. package/nitrogen/generated/android/c++/JHybridNetSocketDriverSpec.cpp +70 -0
  24. package/nitrogen/generated/android/c++/JHybridNetSocketDriverSpec.hpp +15 -0
  25. package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/HybridNetDriverSpec.kt +33 -0
  26. package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/HybridNetServerDriverSpec.kt +4 -0
  27. package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/HybridNetSocketDriverSpec.kt +60 -0
  28. package/nitrogen/generated/ios/RustCNet-Swift-Cxx-Bridge.hpp +95 -44
  29. package/nitrogen/generated/ios/c++/HybridNetDriverSpecSwift.hpp +58 -0
  30. package/nitrogen/generated/ios/c++/HybridNetServerDriverSpecSwift.hpp +6 -0
  31. package/nitrogen/generated/ios/c++/HybridNetSocketDriverSpecSwift.hpp +109 -0
  32. package/nitrogen/generated/ios/swift/HybridNetDriverSpec.swift +8 -0
  33. package/nitrogen/generated/ios/swift/HybridNetDriverSpec_cxx.swift +118 -0
  34. package/nitrogen/generated/ios/swift/HybridNetServerDriverSpec.swift +1 -0
  35. package/nitrogen/generated/ios/swift/HybridNetServerDriverSpec_cxx.swift +25 -0
  36. package/nitrogen/generated/ios/swift/HybridNetSocketDriverSpec.swift +15 -0
  37. package/nitrogen/generated/ios/swift/HybridNetSocketDriverSpec_cxx.swift +278 -0
  38. package/nitrogen/generated/shared/c++/HybridNetDriverSpec.cpp +8 -0
  39. package/nitrogen/generated/shared/c++/HybridNetDriverSpec.hpp +9 -0
  40. package/nitrogen/generated/shared/c++/HybridNetServerDriverSpec.cpp +1 -0
  41. package/nitrogen/generated/shared/c++/HybridNetServerDriverSpec.hpp +1 -0
  42. package/nitrogen/generated/shared/c++/HybridNetSocketDriverSpec.cpp +15 -0
  43. package/nitrogen/generated/shared/c++/HybridNetSocketDriverSpec.hpp +16 -0
  44. package/package.json +5 -3
  45. package/react-native-nitro-net.podspec +1 -3
  46. package/src/Net.nitro.ts +27 -1
  47. package/src/index.ts +18 -9
  48. package/src/tls.ts +532 -0
package/src/tls.ts ADDED
@@ -0,0 +1,532 @@
1
+ import { Socket, Server as NetServer, SocketOptions } from './index'
2
+ import { Driver } from './Driver'
3
+ import { NetSocketDriver } from './Net.nitro'
4
+
5
+ export interface PeerCertificate {
6
+ subject: { [key: string]: string }
7
+ issuer: { [key: string]: string }
8
+ valid_from: string
9
+ valid_to: string
10
+ fingerprint: string
11
+ fingerprint256: string
12
+ serialNumber: string
13
+ }
14
+
15
+ export interface ConnectionOptions extends SocketOptions {
16
+ host?: string
17
+ port?: number
18
+ path?: string
19
+ servername?: string // SNI
20
+ rejectUnauthorized?: boolean
21
+ session?: ArrayBuffer // TLS Session ticket for resumption
22
+ secureContext?: SecureContext
23
+ ca?: string | string[]
24
+ cert?: string | string[]
25
+ key?: string | string[]
26
+ pfx?: string | ArrayBuffer
27
+ passphrase?: string
28
+ keylog?: boolean // Enable keylogging (SSLKEYLOGFILE format)
29
+ }
30
+
31
+ export interface SecureContextOptions {
32
+ pfx?: string | ArrayBuffer
33
+ passphrase?: string
34
+ cert?: string | string[]
35
+ key?: string | string[]
36
+ ca?: string | string[]
37
+ }
38
+
39
+ export const DEFAULT_MIN_VERSION = 'TLSv1.2';
40
+ export const DEFAULT_MAX_VERSION = 'TLSv1.3';
41
+ export const rootCertificates: string[] = [];
42
+ export const DEFAULT_ECDH_CURVE = 'auto'; // Managed by rustls
43
+ export const SLAB_BUFFER_SIZE = 10 * 1024 * 1024; // 10MB default
44
+
45
+ export class SecureContext {
46
+ private _id: number;
47
+
48
+ constructor(options?: SecureContextOptions) {
49
+ if (options && options.pfx) {
50
+ this._id = Driver.createEmptySecureContext();
51
+ const pfx = typeof options.pfx === 'string' ? Buffer.from(options.pfx).buffer : options.pfx;
52
+ Driver.setPFXToSecureContext(this._id, pfx, options.passphrase);
53
+ } else if (options && options.cert && options.key) {
54
+ const cert = Array.isArray(options.cert) ? options.cert[0] : options.cert;
55
+ const key = Array.isArray(options.key) ? options.key[0] : options.key;
56
+ this._id = Driver.createSecureContext(cert, key, options.passphrase);
57
+ } else {
58
+ this._id = Driver.createEmptySecureContext();
59
+ }
60
+
61
+ if (options && options.ca) {
62
+ const cas = Array.isArray(options.ca) ? options.ca : [options.ca];
63
+ for (const ca of cas) {
64
+ Driver.addCACertToSecureContext(this._id, ca);
65
+ }
66
+ }
67
+ }
68
+
69
+ setOCSPResponse(ocsp: ArrayBuffer): void {
70
+ Driver.setOCSPResponseToSecureContext(this._id, ocsp);
71
+ }
72
+
73
+ getTicketKeys(): ArrayBuffer | undefined {
74
+ return Driver.getTicketKeys(this._id);
75
+ }
76
+
77
+ setTicketKeys(keys: ArrayBuffer): void {
78
+ Driver.setTicketKeys(this._id, keys);
79
+ }
80
+
81
+ get id(): number {
82
+ return this._id;
83
+ }
84
+
85
+ // Node.js doesn't have these on SecureContext but we might need them
86
+ addCACert(ca: string): void {
87
+ Driver.addCACertToSecureContext(this._id, ca);
88
+ }
89
+ }
90
+
91
+ export function createSecureContext(options?: SecureContextOptions): SecureContext {
92
+ return new SecureContext(options);
93
+ }
94
+
95
+ export class TLSSocket extends Socket {
96
+ private _servername?: string
97
+
98
+ get encrypted(): boolean {
99
+ return true
100
+ }
101
+
102
+ get servername(): string | undefined {
103
+ return this._servername
104
+ }
105
+
106
+ get authorized(): boolean {
107
+ const driver = (this as any)._driver as NetSocketDriver
108
+ return driver.getAuthorizationError() === undefined
109
+ }
110
+
111
+ get authorizationError(): string | undefined {
112
+ const driver = (this as any)._driver as NetSocketDriver
113
+ return driver.getAuthorizationError()
114
+ }
115
+
116
+ get alpnProtocol(): string | undefined {
117
+ const driver = (this as any)._driver as NetSocketDriver
118
+ return driver.getALPN()
119
+ }
120
+
121
+ getProtocol(): string | undefined {
122
+ const driver = (this as any)._driver as NetSocketDriver
123
+ return driver.getProtocol()
124
+ }
125
+
126
+ getCipher(): { name: string, version: string } | undefined {
127
+ const driver = (this as any)._driver as NetSocketDriver
128
+ const cipher = driver.getCipher()
129
+ const protocol = driver.getProtocol()
130
+ if (cipher) {
131
+ return {
132
+ name: cipher,
133
+ version: protocol || 'Unknown'
134
+ }
135
+ }
136
+ return undefined
137
+ }
138
+
139
+ getPeerCertificate(detailed?: boolean): PeerCertificate | {} {
140
+ const driver = (this as any)._driver as NetSocketDriver
141
+ const json = driver.getPeerCertificateJSON()
142
+ if (json) {
143
+ try {
144
+ return JSON.parse(json) as PeerCertificate
145
+ } catch (e) {
146
+ console.error('Failed to parse peer certificate JSON', e)
147
+ }
148
+ }
149
+ return {}
150
+ }
151
+
152
+ isSessionReused(): boolean {
153
+ const driver = (this as any)._driver as NetSocketDriver
154
+ return driver.isSessionReused()
155
+ }
156
+
157
+ getSession(): ArrayBuffer | undefined {
158
+ const driver = (this as any)._driver as NetSocketDriver
159
+ return driver.getSession()
160
+ }
161
+
162
+ getEphemeralKeyInfo(): string | undefined {
163
+ const driver = (this as any)._driver as NetSocketDriver
164
+ return driver.getEphemeralKeyInfo()
165
+ }
166
+
167
+ getFinished(): Buffer | undefined {
168
+ throw new Error('getFinished is not supported by rustls');
169
+ }
170
+
171
+ getPeerFinished(): Buffer | undefined {
172
+ throw new Error('getPeerFinished is not supported by rustls');
173
+ }
174
+
175
+ getSharedSigalgs(): string | undefined {
176
+ const driver = (this as any)._driver as NetSocketDriver
177
+ return driver.getSharedSigalgs()
178
+ }
179
+
180
+ renegotiate(options: any, callback: (err: Error | null) => void): boolean {
181
+ if (callback) {
182
+ process.nextTick(() => callback(new Error('Renegotiation is not supported by rustls')));
183
+ }
184
+ return false;
185
+ }
186
+
187
+ disableRenegotiation(): void {
188
+ // No-op, already effectively disabled
189
+ }
190
+
191
+ constructor(socket: Socket, options?: ConnectionOptions)
192
+ constructor(options: ConnectionOptions)
193
+ constructor(socketOrOptions: Socket | ConnectionOptions, options?: ConnectionOptions) {
194
+ let opts: ConnectionOptions = {}
195
+ if (socketOrOptions instanceof Socket) {
196
+ opts = { ...options, socketDriver: (socketOrOptions as any)._driver }
197
+ } else {
198
+ opts = socketOrOptions || {}
199
+ }
200
+
201
+ super(opts)
202
+
203
+ if (socketOrOptions instanceof Socket) {
204
+ this._servername = (socketOrOptions as any)._servername
205
+ }
206
+
207
+ this.on('event', (event: number, data?: ArrayBuffer) => {
208
+ if (event === 10 && data) { // KEYLOG
209
+ this.emit('keylog', Buffer.from(data))
210
+ } else if (event === 11 && data) { // OCSP
211
+ this.emit('OCSPResponse', Buffer.from(data))
212
+ }
213
+ })
214
+ }
215
+
216
+ override connect(options: any, connectionListener?: () => void): this {
217
+ // Override connect to use connectTLS
218
+ const port = typeof options === 'number' ? options : options.port
219
+ const host = (typeof options === 'object' && options.host) ? options.host : (typeof options === 'string' ? arguments[1] : 'localhost')
220
+ const path = (typeof options === 'object' && options.path) ? options.path : undefined
221
+ const servername = (typeof options === 'object' && options.servername) ? options.servername : (path ? 'localhost' : host)
222
+ this._servername = servername
223
+ const rejectUnauthorized = (typeof options === 'object' && options.rejectUnauthorized !== undefined) ? options.rejectUnauthorized : true
224
+ const session = (typeof options === 'object' && options.session) ? options.session : undefined
225
+
226
+ const driver = (this as any)._driver as NetSocketDriver
227
+
228
+ if (driver) {
229
+ this.connecting = true;
230
+ if (connectionListener) this.once('secureConnect', connectionListener);
231
+
232
+ this.once('connect', () => {
233
+ this.emit('secureConnect')
234
+ })
235
+
236
+ if (session) {
237
+ driver.setSession(session)
238
+ }
239
+
240
+ const secureContext = (typeof options === 'object' && options.secureContext) ? options.secureContext : undefined;
241
+ let secureContextId: number | undefined = secureContext ? secureContext.id : undefined;
242
+
243
+ // If cert/key/ca provided directly, create a temporary secure context
244
+ if (!secureContextId && typeof options === 'object' && (options.cert || options.key || options.ca)) {
245
+ secureContextId = createSecureContext({
246
+ cert: options.cert,
247
+ key: options.key,
248
+ ca: options.ca
249
+ }).id;
250
+ }
251
+
252
+ if (options && options.keylog) {
253
+ driver.enableKeylog()
254
+ }
255
+
256
+ if (path) {
257
+ if (secureContextId !== undefined) {
258
+ driver.connectUnixTLSWithContext(path, servername, rejectUnauthorized, secureContextId)
259
+ } else {
260
+ driver.connectUnixTLS(path, servername, rejectUnauthorized)
261
+ }
262
+ } else {
263
+ if (secureContextId !== undefined) {
264
+ driver.connectTLSWithContext(host, port, servername, rejectUnauthorized, secureContextId)
265
+ } else {
266
+ driver.connectTLS(host, port, servername, rejectUnauthorized)
267
+ }
268
+ }
269
+ }
270
+
271
+ return this
272
+ }
273
+ }
274
+
275
+ export function connect(options: ConnectionOptions, connectionListener?: () => void): TLSSocket
276
+ export function connect(port: number, host?: string, options?: ConnectionOptions, connectionListener?: () => void): TLSSocket
277
+ export function connect(port: number, options?: ConnectionOptions, connectionListener?: () => void): TLSSocket
278
+ export function connect(...args: any[]): TLSSocket {
279
+ let port: number
280
+ let host: string = 'localhost'
281
+ let options: ConnectionOptions = {}
282
+ let listener: (() => void) | undefined
283
+
284
+ if (typeof args[0] === 'object') {
285
+ options = args[0]
286
+ port = options.port || 443
287
+ host = options.host || 'localhost'
288
+ listener = args[1]
289
+ } else {
290
+ port = args[0]
291
+ if (typeof args[1] === 'string') {
292
+ host = args[1]
293
+ options = args[2] || {}
294
+ listener = args[3]
295
+ } else if (typeof args[1] === 'object') {
296
+ options = args[1]
297
+ listener = args[2]
298
+ } else if (typeof args[1] === 'function') {
299
+ listener = args[1]
300
+ }
301
+ }
302
+
303
+ const socket = new TLSSocket(options)
304
+ socket.connect({
305
+ port,
306
+ host,
307
+ ...options
308
+ }, listener)
309
+ return socket
310
+ }
311
+
312
+ export class Server extends NetServer {
313
+ private _secureContextId: number = 0;
314
+
315
+ constructor(options?: any, connectionListener?: (socket: Socket) => void) {
316
+ super(options);
317
+
318
+ if (options && options.secureContext) {
319
+ this._secureContextId = (options.secureContext as SecureContext).id;
320
+ } else if (options && (options.key || options.cert || options.ca)) {
321
+ this._secureContextId = createSecureContext({
322
+ cert: options.cert,
323
+ key: options.key,
324
+ ca: options.ca
325
+ }).id;
326
+ }
327
+
328
+ this.on('connection', (socket: Socket) => {
329
+ const tlsSocket = new TLSSocket(socket);
330
+ this.emit('secureConnection', tlsSocket);
331
+ });
332
+
333
+ this.on('session', (data: ArrayBuffer) => {
334
+ this.emit('newSession', data);
335
+ });
336
+
337
+ if (options && options.SNICallback) {
338
+ console.warn("SNICallback is not supported yet, use addContext() instead");
339
+ }
340
+
341
+ if (connectionListener) {
342
+ this.on('secureConnection', connectionListener);
343
+ }
344
+ }
345
+
346
+ addContext(hostname: string, context: { key: string, cert: string }): void {
347
+ if (!this._secureContextId) {
348
+ throw new Error("Cannot addContext to a non-TLS server. Provide initial cert/key in constructor.");
349
+ }
350
+ Driver.addContextToSecureContext(this._secureContextId, hostname, context.cert, context.key);
351
+ }
352
+
353
+ setSecureContext(options: { key: string, cert: string, ca?: string | string[] }): void {
354
+ this._secureContextId = createSecureContext(options).id;
355
+ }
356
+
357
+ getTicketKeys(): ArrayBuffer | undefined {
358
+ return this._secureContextId ? Driver.getTicketKeys(this._secureContextId) : undefined;
359
+ }
360
+
361
+ setTicketKeys(keys: ArrayBuffer): void {
362
+ if (!this._secureContextId) throw new Error("Not a TLS server");
363
+ Driver.setTicketKeys(this._secureContextId, keys);
364
+ }
365
+
366
+ override listen(port?: any, host?: any, backlog?: any, callback?: any): this {
367
+ if (!this._secureContextId) {
368
+ return super.listen(port, host, backlog, callback);
369
+ }
370
+
371
+ let _port = 0;
372
+ let _host: string | undefined;
373
+ let _backlog: number | undefined;
374
+ let _path: string | undefined;
375
+ let _callback: (() => void) | undefined;
376
+ let ipv6Only = false;
377
+ let reusePort = false;
378
+ let handle: { fd?: number } | undefined;
379
+
380
+ if (typeof port === 'object' && port !== null) {
381
+ if (typeof port.fd === 'number') {
382
+ handle = port;
383
+ _backlog = port.backlog;
384
+ _callback = host;
385
+ } else {
386
+ _port = port.port;
387
+ _host = port.host;
388
+ _backlog = port.backlog;
389
+ _path = port.path;
390
+ ipv6Only = port.ipv6Only === true;
391
+ reusePort = port.reusePort === true;
392
+ _callback = host;
393
+ }
394
+ } else {
395
+ _port = typeof port === 'number' ? port : (typeof port === 'string' && !isNaN(Number(port)) ? Number(port) : 0);
396
+ if (typeof port === 'string' && isNaN(Number(port))) _path = port;
397
+
398
+ if (typeof host === 'string') _host = host;
399
+ else if (typeof host === 'function') _callback = host;
400
+
401
+ if (typeof backlog === 'number') _backlog = backlog;
402
+ else if (typeof backlog === 'function') _callback = backlog;
403
+
404
+ if (typeof callback === 'function') _callback = callback;
405
+ }
406
+
407
+ if (_callback) this.once('listening', _callback);
408
+
409
+ const driver = (this as any)._driver;
410
+
411
+ if (handle || _path) {
412
+ console.warn("TLS over Unix sockets/handles not fully implemented yet");
413
+ }
414
+
415
+ driver.listenTLS(_port || 0, this._secureContextId, _backlog, ipv6Only, reusePort);
416
+
417
+ return this;
418
+ }
419
+ }
420
+
421
+ export function createServer(options?: any, connectionListener?: (socket: Socket) => void): Server {
422
+ return new Server(options, connectionListener);
423
+ }
424
+
425
+ export function getCiphers(): string[] {
426
+ return [
427
+ 'TLS_AES_128_GCM_SHA256',
428
+ 'TLS_AES_256_GCM_SHA384',
429
+ 'TLS_CHACHA20_POLY1305_SHA256',
430
+ 'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256',
431
+ 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384',
432
+ 'TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256',
433
+ 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256',
434
+ 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384',
435
+ 'TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256'
436
+ ];
437
+ }
438
+
439
+ export function checkServerIdentity(hostname: string, cert: PeerCertificate): Error | undefined {
440
+ const subject = cert.subject;
441
+ const dnsNames: string[] = [];
442
+
443
+ // In a real implementation we'd extract SANs from the cert object if available.
444
+ // Our PeerCertificate already has subject.CN.
445
+ if (subject && subject.CN) {
446
+ dnsNames.push(subject.CN);
447
+ }
448
+
449
+ // SANs are preferred over CN but our current peer_cert JSON might not have them exploded yet
450
+ // unless x509-parser logic is updated. For now, we match against CN.
451
+ // Wildcard matching logic:
452
+ const matchHash = (host: string, pattern: string) => {
453
+ const parts = host.split('.');
454
+ const patternParts = pattern.split('.');
455
+ if (parts.length !== patternParts.length) return false;
456
+ for (let i = 0; i < parts.length; i++) {
457
+ if (patternParts[i] === '*') continue;
458
+ if (parts[i].toLowerCase() !== patternParts[i].toLowerCase()) return false;
459
+ }
460
+ return true;
461
+ };
462
+
463
+ const matches = dnsNames.some(name => {
464
+ if (name.includes('*')) {
465
+ return matchHash(hostname, name);
466
+ }
467
+ return name.toLowerCase() === hostname.toLowerCase();
468
+ });
469
+
470
+ if (!matches) {
471
+ const err = new Error(`Hostname/IP does not match certificate's altnames: Host: ${hostname}. is not in cert's altnames: ${dnsNames.join(', ')}`);
472
+ (err as any).reason = 'Host name mismatch';
473
+ (err as any).host = hostname;
474
+ (err as any).cert = cert;
475
+ return err;
476
+ }
477
+
478
+ return undefined;
479
+ }
480
+
481
+ // -----------------------------------------------------------------------------
482
+ // Legacy Classes & Utils
483
+ // -----------------------------------------------------------------------------
484
+
485
+ /**
486
+ * Legacy CryptoStream for Node.js compatibility.
487
+ * In this implementation, it's a simple wrapper around TLSSocket.
488
+ */
489
+ export class CryptoStream extends TLSSocket {
490
+ constructor(options?: ConnectionOptions) {
491
+ super(options || {});
492
+ }
493
+ }
494
+
495
+ /**
496
+ * Legacy SecurePair for Node.js compatibility.
497
+ */
498
+ export class SecurePair {
499
+ public cleartext: CryptoStream;
500
+ public encrypted: CryptoStream;
501
+
502
+ constructor(secureContext?: SecureContext, isServer?: boolean, requestCert?: boolean, rejectUnauthorized?: boolean) {
503
+ this.cleartext = new CryptoStream();
504
+ this.encrypted = this.cleartext; // Logically the same in our simplified model
505
+ }
506
+ }
507
+
508
+ export function createSecurePair(secureContext?: SecureContext, isServer?: boolean, requestCert?: boolean, rejectUnauthorized?: boolean): SecurePair {
509
+ return new SecurePair(secureContext, isServer, requestCert, rejectUnauthorized);
510
+ }
511
+
512
+ /**
513
+ * Legacy certificate string parser.
514
+ */
515
+ export function parseCertString(certString: string): { [key: string]: string } {
516
+ const out: { [key: string]: string } = {};
517
+ const parts = certString.split('/');
518
+ for (const part of parts) {
519
+ const [key, value] = part.split('=');
520
+ if (key && value) out[key] = value;
521
+ }
522
+ return out;
523
+ }
524
+
525
+ /**
526
+ * Mock implementation of convertTLSV1CertToPEM.
527
+ */
528
+ export function convertTLSV1CertToPEM(cert: string | Buffer): string {
529
+ if (typeof cert === 'string' && cert.includes('BEGIN CERTIFICATE')) return cert;
530
+ const body = (cert instanceof Buffer) ? cert.toString('base64') : Buffer.from(cert).toString('base64');
531
+ return `-----BEGIN CERTIFICATE-----\n${body}\n-----END CERTIFICATE-----`;
532
+ }