Compare commits

...

3 Commits

Author SHA1 Message Date
nervuri c8f3395362 add normalized JA3
Normalized JA3 is JA3 with sorted extension codes.  This is an
adaptation to a change in Chromium which randomizes extension order so
as to counter protocol ossification.  See:

https://chromestatus.com/feature/5124606246518784
https://www.fastly.com/blog/a-first-look-at-chromes-tls-clienthello-permutation-in-the-wild
2023-08-24 14:52:28 +00:00
nervuri 92bcf86376 append to error log in systemd unit file example
Append, don't overwrite.
2023-06-28 14:42:02 +00:00
nervuri 02666e2997 don't log "extension data not read" 2023-05-12 12:50:13 +00:00
2 changed files with 31 additions and 7 deletions

View File

@ -55,7 +55,7 @@ After=network.target
[Service]
Type=simple
Restart=always
ExecStart=client-hello-mirror -u www-data -c /etc/letsencrypt/live/example.com/fullchain.pem -k /etc/letsencrypt/live/example.com/privkey.pem :443 2>/var/log/client-hello-mirror-error.log
ExecStart=client-hello-mirror -u www-data -c /etc/letsencrypt/live/example.com/fullchain.pem -k /etc/letsencrypt/live/example.com/privkey.pem :443 2>>/var/log/client-hello-mirror-error.log
[Install]
WantedBy=multi-user.target

View File

@ -13,10 +13,12 @@ import (
"crypto/md5"
"encoding/binary"
"encoding/hex"
"golang.org/x/crypto/cryptobyte"
"log"
"sort"
"strconv"
"strings"
"golang.org/x/crypto/cryptobyte"
)
// Check if this is a GREASE value (RFC 8701).
@ -65,6 +67,8 @@ type highlights struct {
EarlyData bool `json:"-"` // don't include in JSON
JA3 string `json:"ja3"`
JA3MD5 byteSlice `json:"ja3_md5"`
NJA3 string `json:"nja3"` // normalized JA3 (extensions sorted)
NJA3MD5 byteSlice `json:"nja3_md5"`
}
type ClientHelloMsg struct {
@ -394,11 +398,11 @@ func (m *ClientHelloMsg) Unmarshal(data []byte) bool {
log.Println("Unknown extension:", extension.Code)
}
if !extData.Empty() &&
extension.Name != "GREASE" &&
extension.Code != extensionPadding {
log.Printf("Extension %d data not read.\n", extension.Code)
}
//if !extData.Empty() &&
// extension.Name != "GREASE" &&
// extension.Code != extensionPadding {
// log.Printf("Extension %d data not read.\n", extension.Code)
//}
}
// JA3
@ -415,12 +419,14 @@ func (m *ClientHelloMsg) Unmarshal(data []byte) bool {
}
}
ja3.WriteString(",")
var extCodes []uint16 // non-GREASE extension codes
for i, e := range m.Extensions {
if !isGREASE(e.Code) { // ignore GREASE values
ja3.WriteString(strconv.FormatUint(uint64(e.Code), 10))
if i+1 != len(m.Extensions) { // if not last element, add "-"
ja3.WriteString("-")
}
extCodes = append(extCodes, e.Code)
if e.Code == extensionSupportedGroups {
supportedGroups = e.Data.SupportedGroups
} else if e.Code == extensionSupportedPointFormats {
@ -448,6 +454,24 @@ func (m *ClientHelloMsg) Unmarshal(data []byte) bool {
hash := md5.Sum([]byte(m.Highlights.JA3))
m.Highlights.JA3MD5 = hash[:]
// Make normalized JA3:
// 1. sort extension codes;
sort.Slice(extCodes, func(i, j int) bool { return extCodes[i] < extCodes[j] })
// 2. build string of sorted codes;
var sortedExtString strings.Builder
for i, code := range extCodes {
sortedExtString.WriteString(strconv.FormatUint(uint64(code), 10))
if i+1 != len(extCodes) { // if not last element, add "-"
sortedExtString.WriteString("-")
}
}
// 3. replace extensions part of the JA3 string.
splitJA3 := strings.Split(m.Highlights.JA3, ",")
splitJA3[2] = sortedExtString.String()
m.Highlights.NJA3 = strings.Join(splitJA3, ",")
nhash := md5.Sum([]byte(m.Highlights.NJA3))
m.Highlights.NJA3MD5 = nhash[:]
return true
}