1
1
import * as stream from 'stream' ;
2
2
import * as crypto from 'crypto' ;
3
+ import * as tls from 'tls' ;
4
+ import * as net from 'net' ;
3
5
4
6
const collectBytes = ( stream : stream . Readable , byteLength : number ) => {
5
7
if ( byteLength === 0 ) return Buffer . from ( [ ] ) ;
@@ -164,4 +166,76 @@ export function calculateJa3FromFingerprintData(fingerprintData: TlsFingerprintD
164
166
165
167
export async function getTlsFingerprintAsJa3 ( rawStream : stream . Readable ) {
166
168
return calculateJa3FromFingerprintData ( await getTlsFingerprintData ( rawStream ) ) ;
169
+ }
170
+
171
+ interface FingerprintedSocket extends net . Socket {
172
+ tlsFingerprint ?: {
173
+ data : TlsFingerprintData ;
174
+ ja3 : string ;
175
+ }
176
+ }
177
+
178
+ declare module 'tls' {
179
+ interface TLSSocket {
180
+ /**
181
+ * This module extends the global TLS types so that all TLS sockets may include
182
+ * TLS fingerprint data.
183
+ *
184
+ * This is only set if the socket came from a TLS server where fingerprinting
185
+ * has been enabled with `enableFingerprinting`.
186
+ */
187
+ tlsFingerprint ?: {
188
+ data : TlsFingerprintData ;
189
+ ja3 : string ;
190
+ }
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Modify a TLS server, so that the TLS fingerprint is always parsed and attached to all
196
+ * sockets at the point when the 'secureConnection' event fires.
197
+ *
198
+ * This method mutates and returns the TLS server provided. TLS fingerprint data is
199
+ * available from all TLS sockets afterwards in the `socket.tlsFingerprint` property.
200
+ *
201
+ * This will work for all standard uses of a TLS server or similar (e.g. an HTTPS server)
202
+ * but may behave unpredictably for advanced use cases, e.g. if you are already
203
+ * manually injecting connections, hooking methods or events or otherwise doing something
204
+ * funky & complicated. In those cases you probably want to use the fingerprint
205
+ * calculation methods directly inside your funky logic instead.
206
+ */
207
+ export function enableFingerprinting ( tlsServer : tls . Server ) {
208
+ // Disable the normal TLS 'connection' event listener that triggers TLS setup:
209
+ const tlsConnectionListener = tlsServer . listeners ( 'connection' ) [ 0 ] as ( socket : net . Socket ) => { } ;
210
+ if ( ! tlsConnectionListener ) throw new Error ( 'TLS server is not listening for connection events' ) ;
211
+ tlsServer . removeListener ( 'connection' , tlsConnectionListener ) ;
212
+
213
+ // Listen ourselves for connections, get the fingerprint first, then let TLS setup resume:
214
+ tlsServer . on ( 'connection' , async ( socket : FingerprintedSocket ) => {
215
+ try {
216
+ const fingerprintData = await getTlsFingerprintData ( socket ) ;
217
+
218
+ socket . tlsFingerprint = {
219
+ data : fingerprintData ,
220
+ ja3 : calculateJa3FromFingerprintData ( fingerprintData )
221
+ } ;
222
+ } catch ( e ) {
223
+ console . warn ( `TLS fingerprint not available for TLS connection from ${
224
+ socket . remoteAddress ?? 'unknown address'
225
+ } : ${ ( e as Error ) . message ?? e } `) ;
226
+ }
227
+
228
+ // Once we have a fingerprint, TLS handshakes can continue as normal:
229
+ tlsConnectionListener . call ( tlsServer , socket ) ;
230
+ } ) ;
231
+
232
+ tlsServer . prependListener ( 'secureConnection' , ( tlsSocket : tls . TLSSocket ) => {
233
+ const fingerprint = ( tlsSocket as unknown as {
234
+ _parent ?: FingerprintedSocket , // Private TLS socket field which points to the source
235
+ } ) . _parent ?. tlsFingerprint ;
236
+
237
+ tlsSocket . tlsFingerprint = fingerprint ;
238
+ } ) ;
239
+
240
+ return tlsServer ;
167
241
}
0 commit comments