100 lines
2.6 KiB
Go
100 lines
2.6 KiB
Go
// Licensed to Elasticsearch B.V. under one or more contributor
|
|
// license agreements. See the NOTICE file distributed with
|
|
// this work for additional information regarding copyright
|
|
// ownership. Elasticsearch B.V. licenses this file to you under
|
|
// the Apache License, Version 2.0 (the "License"); you may
|
|
// not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package tls
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"encoding/hex"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
func getJa3Fingerprint(hello *helloMessage) (hash string, ja3str string) {
|
|
|
|
// build the array of arrays of numbers
|
|
|
|
data := make([][]uint16, 5)
|
|
|
|
// Version as integer
|
|
data[0] = []uint16{uint16(hello.version.major)*256 + uint16(hello.version.minor)}
|
|
|
|
// ciphersuites, in client-supplied priority
|
|
data[1] = make([]uint16, 0)
|
|
for _, suite := range hello.supported.cipherSuites {
|
|
if !isGreaseValue(uint16(suite)) {
|
|
data[1] = append(data[1], uint16(suite))
|
|
}
|
|
}
|
|
|
|
// extensions
|
|
data[2] = make([]uint16, len(hello.extensions.InOrder))
|
|
for idx, extid := range hello.extensions.InOrder {
|
|
data[2][idx] = uint16(extid)
|
|
}
|
|
|
|
data[3] = extractJa3Array(hello.extensions.Raw[ExtensionSupportedGroups], 2)
|
|
data[4] = extractJa3Array(hello.extensions.Raw[ExtensionEllipticCurvePointsFormats], 1)
|
|
|
|
// build the string
|
|
parts := make([]string, len(data))
|
|
for i, arr := range data {
|
|
strNum := make([]string, len(arr))
|
|
for j, num := range arr {
|
|
strNum[j] = strconv.Itoa(int(num))
|
|
}
|
|
parts[i] = strings.Join(strNum, "-")
|
|
}
|
|
|
|
ja3str = strings.Join(parts, ",")
|
|
sum := md5.Sum([]byte(ja3str))
|
|
|
|
return hex.EncodeToString(sum[:]), ja3str
|
|
}
|
|
|
|
func extractJa3Array(raw []byte, size int) []uint16 {
|
|
if size < 1 || size > 2 {
|
|
return nil
|
|
}
|
|
actual := len(raw)
|
|
if actual < size {
|
|
return nil
|
|
}
|
|
limit := int(raw[0])
|
|
if size == 2 {
|
|
limit = limit*256 + int(raw[1])
|
|
}
|
|
if actual < limit {
|
|
limit = actual
|
|
}
|
|
var array []uint16
|
|
for pos := size; pos <= limit; pos += size {
|
|
value := uint16(raw[pos])
|
|
if size == 2 {
|
|
value = value*256 + uint16(raw[pos+1])
|
|
}
|
|
if !isGreaseValue(value) {
|
|
array = append(array, value)
|
|
}
|
|
}
|
|
return array
|
|
}
|
|
|
|
func isGreaseValue(num uint16) bool {
|
|
hi, lo := byte(num>>8), byte(num)
|
|
return hi == lo && lo&0xf == 0xa
|
|
}
|