123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357 |
- // Copyright (c) 2018, Open Systems AG. All rights reserved.
- //
- // Use of this source code is governed by a BSD-style license
- // that can be found in the LICENSE file in the root of the source
- // tree.
- package ja3
- import (
- "encoding/binary"
- "strconv"
- )
- const (
- // Constants used for parsing
- recordLayerHeaderLen int = 5
- handshakeHeaderLen int = 6
- randomDataLen int = 32
- sessionIDHeaderLen int = 1
- cipherSuiteHeaderLen int = 2
- compressMethodHeaderLen int = 1
- extensionsHeaderLen int = 2
- extensionHeaderLen int = 4
- sniExtensionHeaderLen int = 5
- ecExtensionHeaderLen int = 2
- ecpfExtensionHeaderLen int = 1
- versionExtensionHeaderLen int = 1
- signatureAlgorithmsExtensionHeaderLen int = 2
- contentType uint8 = 22
- handshakeType uint8 = 1
- sniExtensionType uint16 = 0
- sniNameDNSHostnameType uint8 = 0
- ecExtensionType uint16 = 10
- ecpfExtensionType uint16 = 11
- versionExtensionType uint16 = 43
- signatureAlgorithmsExtensionType uint16 = 13
- // Versions
- // The bitmask covers the versions SSL3.0 to TLS1.2
- tlsVersionBitmask uint16 = 0xFFFC
- tls13 uint16 = 0x0304
- // GREASE values
- // The bitmask covers all GREASE values
- GreaseBitmask uint16 = 0x0F0F
- // Constants used for marshalling
- dashByte = byte(45)
- commaByte = byte(44)
- )
- // parseSegment to populate the corresponding ClientHello object or return an error
- func (j *ClientHello) parseSegment(segment []byte) error {
- // Check if we can decode the next fields
- if len(segment) < recordLayerHeaderLen {
- return &ParseError{LengthErr, 1}
- }
- // Check if we have "Content Type: Handshake (22)"
- contType := uint8(segment[0])
- if contType != contentType {
- return &ParseError{errType: ContentTypeErr}
- }
- // Check if TLS record layer version is supported
- tlsRecordVersion := uint16(segment[1])<<8 | uint16(segment[2])
- if tlsRecordVersion&tlsVersionBitmask != 0x0300 && tlsRecordVersion != tls13 {
- return &ParseError{VersionErr, 1}
- }
- // Check that the Handshake is as long as expected from the length field
- segmentLen := uint16(segment[3])<<8 | uint16(segment[4])
- if len(segment[recordLayerHeaderLen:]) < int(segmentLen) {
- return &ParseError{LengthErr, 2}
- }
- // Keep the Handshake messege, ignore any additional following record types
- hs := segment[recordLayerHeaderLen : recordLayerHeaderLen+int(segmentLen)]
- err := j.parseHandshake(hs)
- return err
- }
- // parseHandshake body
- func (j *ClientHello) parseHandshake(hs []byte) error {
- // Check if we can decode the next fields
- if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen {
- return &ParseError{LengthErr, 3}
- }
- // Check if we have "Handshake Type: Client Hello (1)"
- handshType := uint8(hs[0])
- if handshType != handshakeType {
- return &ParseError{errType: HandshakeTypeErr}
- }
- // Check if actual length of handshake matches (this is a great exclusion criterion for false positives,
- // as these fields have to match the actual length of the rest of the segment)
- handshakeLen := uint32(hs[1])<<16 | uint32(hs[2])<<8 | uint32(hs[3])
- if len(hs[4:]) != int(handshakeLen) {
- return &ParseError{LengthErr, 4}
- }
- // Check if Client Hello version is supported
- tlsVersion := uint16(hs[4])<<8 | uint16(hs[5])
- if tlsVersion&tlsVersionBitmask != 0x0300 && tlsVersion != tls13 {
- return &ParseError{VersionErr, 2}
- }
- j.Version = tlsVersion
- // Check if we can decode the next fields
- sessionIDLen := uint8(hs[38])
- if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen) {
- return &ParseError{LengthErr, 5}
- }
- // Cipher Suites
- cs := hs[handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen):]
- // Check if we can decode the next fields
- if len(cs) < cipherSuiteHeaderLen {
- return &ParseError{LengthErr, 6}
- }
- csLen := uint16(cs[0])<<8 | uint16(cs[1])
- numCiphers := int(csLen / 2)
- cipherSuites := make([]uint16, 0, numCiphers)
- // Check if we can decode the next fields
- if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen {
- return &ParseError{LengthErr, 7}
- }
- for i := 0; i < numCiphers; i++ {
- cipherSuite := uint16(cs[2+i<<1])<<8 | uint16(cs[3+i<<1])
- cipherSuites = append(cipherSuites, cipherSuite)
- }
- j.CipherSuites = cipherSuites
- // Check if we can decode the next fields
- compressMethodLen := uint16(cs[cipherSuiteHeaderLen+int(csLen)])
- if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen) {
- return &ParseError{LengthErr, 8}
- }
- // Extensions
- exs := cs[cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen):]
- err := j.parseExtensions(exs)
- return err
- }
- // parseExtensions of the handshake
- func (j *ClientHello) parseExtensions(exs []byte) error {
- // Check for no extensions, this fields header is nonexistent if no body is used
- if len(exs) == 0 {
- return nil
- }
- // Check if we can decode the next fields
- if len(exs) < extensionsHeaderLen {
- return &ParseError{LengthErr, 9}
- }
- exsLen := uint16(exs[0])<<8 | uint16(exs[1])
- exs = exs[extensionsHeaderLen:]
- // Check if we can decode the next fields
- if len(exs) < int(exsLen) {
- return &ParseError{LengthErr, 10}
- }
- var sni []byte
- var extensions, ellipticCurves []uint16
- var ellipticCurvePF []uint8
- var versions []uint16
- var signatureAlgorithms []uint16
- for len(exs) > 0 {
- // Check if we can decode the next fields
- if len(exs) < extensionHeaderLen {
- return &ParseError{LengthErr, 11}
- }
- exType := uint16(exs[0])<<8 | uint16(exs[1])
- exLen := uint16(exs[2])<<8 | uint16(exs[3])
- // Ignore any GREASE extensions
- extensions = append(extensions, exType)
- // Check if we can decode the next fields
- if len(exs) < extensionHeaderLen+int(exLen) {
- return &ParseError{LengthErr, 12}
- }
- sex := exs[extensionHeaderLen : extensionHeaderLen+int(exLen)]
- switch exType {
- case sniExtensionType: // Extensions: server_name
- // Check if we can decode the next fields
- if len(sex) < sniExtensionHeaderLen {
- return &ParseError{LengthErr, 13}
- }
- sniType := uint8(sex[2])
- sniLen := uint16(sex[3])<<8 | uint16(sex[4])
- sex = sex[sniExtensionHeaderLen:]
- // Check if we can decode the next fields
- if len(sex) != int(sniLen) {
- return &ParseError{LengthErr, 14}
- }
- switch sniType {
- case sniNameDNSHostnameType:
- sni = sex
- default:
- return &ParseError{errType: SNITypeErr}
- }
- case ecExtensionType: // Extensions: supported_groups
- // Check if we can decode the next fields
- if len(sex) < ecExtensionHeaderLen {
- return &ParseError{LengthErr, 15}
- }
- ecsLen := uint16(sex[0])<<8 | uint16(sex[1])
- numCurves := int(ecsLen / 2)
- ellipticCurves = make([]uint16, 0, numCurves)
- sex = sex[ecExtensionHeaderLen:]
- // Check if we can decode the next fields
- if len(sex) != int(ecsLen) {
- return &ParseError{LengthErr, 16}
- }
- for i := 0; i < numCurves; i++ {
- ecType := uint16(sex[i*2])<<8 | uint16(sex[1+i*2])
- ellipticCurves = append(ellipticCurves, ecType)
- }
- case ecpfExtensionType: // Extensions: ec_point_formats
- // Check if we can decode the next fields
- if len(sex) < ecpfExtensionHeaderLen {
- return &ParseError{LengthErr, 17}
- }
- ecpfsLen := uint8(sex[0])
- numPF := int(ecpfsLen)
- ellipticCurvePF = make([]uint8, numPF)
- sex = sex[ecpfExtensionHeaderLen:]
- // Check if we can decode the next fields
- if len(sex) != numPF {
- return &ParseError{LengthErr, 18}
- }
- for i := 0; i < numPF; i++ {
- ellipticCurvePF[i] = uint8(sex[i])
- }
- case versionExtensionType:
- if len(sex) < versionExtensionHeaderLen {
- return &ParseError{LengthErr, 19}
- }
- versionsLen := int(sex[0])
- for i := 0; i < versionsLen; i += 2 {
- versions = append(versions, binary.BigEndian.Uint16(sex[1:][i:]))
- }
- case signatureAlgorithmsExtensionType:
- if len(sex) < signatureAlgorithmsExtensionHeaderLen {
- return &ParseError{LengthErr, 20}
- }
- ssaLen := binary.BigEndian.Uint16(sex)
- for i := 0; i < int(ssaLen); i += 2 {
- signatureAlgorithms = append(signatureAlgorithms, binary.BigEndian.Uint16(sex[2:][i:]))
- }
- }
- exs = exs[4+exLen:]
- }
- j.ServerName = string(sni)
- j.Extensions = extensions
- j.EllipticCurves = ellipticCurves
- j.EllipticCurvePF = ellipticCurvePF
- j.Versions = versions
- j.SignatureAlgorithms = signatureAlgorithms
- return nil
- }
- // marshalJA3 into a byte string
- func (j *ClientHello) marshalJA3() {
- // An uint16 can contain numbers with up to 5 digits and an uint8 can contain numbers with up to 3 digits, but we
- // also need a byte for each separating character, except at the end.
- byteStringLen := 6*(1+len(j.CipherSuites)+len(j.Extensions)+len(j.EllipticCurves)) + 4*len(j.EllipticCurvePF) - 1
- byteString := make([]byte, 0, byteStringLen)
- // Version
- byteString = strconv.AppendUint(byteString, uint64(j.Version), 10)
- byteString = append(byteString, commaByte)
- // Cipher Suites
- if len(j.CipherSuites) != 0 {
- for _, val := range j.CipherSuites {
- if val&GreaseBitmask != 0x0A0A {
- continue
- }
- byteString = strconv.AppendUint(byteString, uint64(val), 10)
- byteString = append(byteString, dashByte)
- }
- // Replace last dash with a comma
- byteString[len(byteString)-1] = commaByte
- } else {
- byteString = append(byteString, commaByte)
- }
- // Extensions
- if len(j.Extensions) != 0 {
- for _, val := range j.Extensions {
- if val&GreaseBitmask != 0x0A0A {
- continue
- }
- byteString = strconv.AppendUint(byteString, uint64(val), 10)
- byteString = append(byteString, dashByte)
- }
- // Replace last dash with a comma
- byteString[len(byteString)-1] = commaByte
- } else {
- byteString = append(byteString, commaByte)
- }
- // Elliptic curves
- if len(j.EllipticCurves) != 0 {
- for _, val := range j.EllipticCurves {
- if val&GreaseBitmask != 0x0A0A {
- continue
- }
- byteString = strconv.AppendUint(byteString, uint64(val), 10)
- byteString = append(byteString, dashByte)
- }
- // Replace last dash with a comma
- byteString[len(byteString)-1] = commaByte
- } else {
- byteString = append(byteString, commaByte)
- }
- // ECPF
- if len(j.EllipticCurvePF) != 0 {
- for _, val := range j.EllipticCurvePF {
- byteString = strconv.AppendUint(byteString, uint64(val), 10)
- byteString = append(byteString, dashByte)
- }
- // Remove last dash
- byteString = byteString[:len(byteString)-1]
- }
- j.ja3ByteString = byteString
- }
|