@@ -25,6 +25,9 @@ export function toJA4(bytes) {
2525 let tlsr = { } ;
2626 try {
2727 tlsr = parseTLSRecord ( bytes ) ;
28+ if ( tlsr . handshake . value . handshakeType . value !== 0x01 ) {
29+ throw new Error ( ) ;
30+ }
2831 } catch ( err ) {
2932 throw new OperationError ( "Data is not a valid TLS Client Hello. QUIC is not yet supported.\n" + err ) ;
3033 }
@@ -48,16 +51,7 @@ export function toJA4(bytes) {
4851 break ;
4952 }
5053 }
51- switch ( version ) {
52- case 0x0304 : version = "13" ; break ; // TLS 1.3
53- case 0x0303 : version = "12" ; break ; // TLS 1.2
54- case 0x0302 : version = "11" ; break ; // TLS 1.1
55- case 0x0301 : version = "10" ; break ; // TLS 1.0
56- case 0x0300 : version = "s3" ; break ; // SSL 3.0
57- case 0x0200 : version = "s2" ; break ; // SSL 2.0
58- case 0x0100 : version = "s1" ; break ; // SSL 1.0
59- default : version = "00" ; // Unknown
60- }
54+ version = tlsVersionMapper ( version ) ;
6155
6256 /* SNI
6357 If the SNI extension (0x0000) exists, then the destination of the connection is a domain, or “d” in the fingerprint.
@@ -99,6 +93,7 @@ export function toJA4(bytes) {
9993 if ( ext . type . value === "application_layer_protocol_negotiation" ) {
10094 alpn = parseFirstALPNValue ( ext . value . data ) ;
10195 alpn = alpn . charAt ( 0 ) + alpn . charAt ( alpn . length - 1 ) ;
96+ if ( alpn . charCodeAt ( 0 ) > 127 ) alpn = "99" ;
10297 break ;
10398 }
10499 }
@@ -164,3 +159,106 @@ export function toJA4(bytes) {
164159 "JA4_ro" : `${ ptype } ${ version } ${ sni } ${ cipherLen } ${ extLen } ${ alpn } _${ originalCiphersRaw } _${ originalExtensionsRaw } ` ,
165160 } ;
166161}
162+
163+
164+ /**
165+ * Calculate the JA4Server from a given TLS Server Hello Stream
166+ * @param {Uint8Array } bytes
167+ * @returns {string }
168+ */
169+ export function toJA4S ( bytes ) {
170+ let tlsr = { } ;
171+ try {
172+ tlsr = parseTLSRecord ( bytes ) ;
173+ if ( tlsr . handshake . value . handshakeType . value !== 0x02 ) {
174+ throw new Error ( ) ;
175+ }
176+ } catch ( err ) {
177+ throw new OperationError ( "Data is not a valid TLS Server Hello. QUIC is not yet supported.\n" + err ) ;
178+ }
179+
180+ /* QUIC
181+ “q” or “t”, which denotes whether the hello packet is for QUIC or TCP.
182+ TODO: Implement QUIC
183+ */
184+ const ptype = "t" ;
185+
186+ /* TLS Version
187+ TLS version is shown in 3 different places. If extension 0x002b exists (supported_versions), then the version
188+ is the highest value in the extension. Remember to ignore GREASE values. If the extension doesn’t exist, then
189+ the TLS version is the value of the Protocol Version. Handshake version (located at the top of the packet)
190+ should be ignored.
191+ */
192+ let version = tlsr . version . value ;
193+ for ( const ext of tlsr . handshake . value . extensions . value ) {
194+ if ( ext . type . value === "supported_versions" ) {
195+ version = parseHighestSupportedVersion ( ext . value . data ) ;
196+ break ;
197+ }
198+ }
199+ version = tlsVersionMapper ( version ) ;
200+
201+ /* Number of Extensions
202+ 2 character number of cipher suites, so if there’s 6 cipher suites in the hello packet, then the value should be “06”.
203+ If there’s > 99, which there should never be, then output “99”.
204+ */
205+ let extLen = tlsr . handshake . value . extensions . value . length ;
206+ extLen = extLen > 99 ? "99" : extLen . toString ( ) . padStart ( 2 , "0" ) ;
207+
208+ /* ALPN Extension Chosen Value
209+ The first and last characters of the ALPN (Application-Layer Protocol Negotiation) first value.
210+ If there are no ALPN values or no ALPN extension then we print “00” as the value in the fingerprint.
211+ */
212+ let alpn = "00" ;
213+ for ( const ext of tlsr . handshake . value . extensions . value ) {
214+ if ( ext . type . value === "application_layer_protocol_negotiation" ) {
215+ alpn = parseFirstALPNValue ( ext . value . data ) ;
216+ alpn = alpn . charAt ( 0 ) + alpn . charAt ( alpn . length - 1 ) ;
217+ if ( alpn . charCodeAt ( 0 ) > 127 ) alpn = "99" ;
218+ break ;
219+ }
220+ }
221+
222+ /* Chosen Cipher
223+ The hex value of the chosen cipher suite
224+ */
225+ const cipher = toHexFast ( tlsr . handshake . value . cipherSuite . data ) ;
226+
227+ /* Extension hash
228+ A 12 character truncated sha256 hash of the list of extensions.
229+ The extension list is created using the 4 character hex values of the extensions, lower case, comma delimited.
230+ */
231+ const extensionsList = [ ] ;
232+ for ( const ext of tlsr . handshake . value . extensions . value ) {
233+ extensionsList . push ( toHexFast ( ext . type . data ) ) ;
234+ }
235+ const extensionsRaw = extensionsList . join ( "," ) ;
236+ const extensionsHash = runHash (
237+ "sha256" ,
238+ Utils . strToArrayBuffer ( extensionsRaw )
239+ ) . substring ( 0 , 12 ) ;
240+
241+ return {
242+ "JA4S" : `${ ptype } ${ version } ${ extLen } ${ alpn } _${ cipher } _${ extensionsHash } ` ,
243+ "JA4S_r" : `${ ptype } ${ version } ${ extLen } ${ alpn } _${ cipher } _${ extensionsRaw } ` ,
244+ } ;
245+ }
246+
247+
248+ /**
249+ * Takes a TLS version value and returns a JA4 TLS version string
250+ * @param {Uint8Array } version - Two byte array of version number
251+ * @returns {string }
252+ */
253+ function tlsVersionMapper ( version ) {
254+ switch ( version ) {
255+ case 0x0304 : return "13" ; // TLS 1.3
256+ case 0x0303 : return "12" ; // TLS 1.2
257+ case 0x0302 : return "11" ; // TLS 1.1
258+ case 0x0301 : return "10" ; // TLS 1.0
259+ case 0x0300 : return "s3" ; // SSL 3.0
260+ case 0x0200 : return "s2" ; // SSL 2.0
261+ case 0x0100 : return "s1" ; // SSL 1.0
262+ default : return "00" ; // Unknown
263+ }
264+ }
0 commit comments