Skip to content

Commit ebc9720

Browse files
More stuff and things and stuff
1 parent cee9520 commit ebc9720

File tree

18 files changed

+297
-253
lines changed

18 files changed

+297
-253
lines changed

constants.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package dactyloscopy
22

33
const (
44
// Configuration Constants
5-
minPacketLength = 47
5+
minPacketLength = 45 // Theoretical minimum size of smallest TLS header (TLSv1.0)
66

77
// TLS Extension types
88
ExtServerName uint16 = 0x0000

example/example.go

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,49 +29,43 @@ import (
2929
"github.com/google/gopacket"
3030
"github.com/google/gopacket/ip4defrag"
3131
"github.com/google/gopacket/layers"
32-
"github.com/google/gopacket/pcap"
32+
"github.com/google/gopacket/pcapgo"
3333
"github.com/google/gopacket/tcpassembly"
3434
"github.com/google/gopacket/tcpassembly/tcpreader"
3535
)
3636

3737
func doSniff(device string, file string) error {
3838
var (
39-
handle *pcap.Handle
39+
handle gopacket.PacketDataSource
4040
err error
4141
)
4242
if len(file) > 0 {
4343
pcapFile, err := os.Open(file)
4444
if err != nil {
4545
return err
4646
}
47-
handle, err = pcap.OpenOfflineFile(pcapFile)
47+
r, err := pcapgo.NewReader(pcapFile)
4848
if err != nil {
4949
return err
5050
}
51+
handle = r
5152
} else if len(device) > 0 {
52-
// Open device
53-
// the 0 and true refer to snaplen and promisc mode. For now we always want these.
54-
handle, err = pcap.OpenLive(device, 0, true, pcap.BlockForever)
55-
if err != nil {
56-
return err
57-
}
53+
return fmt.Errorf("live capture not supported in Go-native mode; use a pcap file")
5854
} else {
5955
return fmt.Errorf("need a file or interface")
6056
}
61-
// Yes yes, I know... But offsetting this to the kernel *drastically* reduces processing time
62-
//err = handle.SetBPFFilter("((tcp[tcp[12]/16*4]=22 and (tcp[tcp[12]/16*4+5]=1) and (tcp[tcp[12]/16*4+9]=3) and (tcp[tcp[12]/16*4+1]=3)) or (ip6[(ip6[52]/16*4)+40]=22 and (ip6[(ip6[52]/16*4+5)+40]=1) and (ip6[(ip6[52]/16*4+9)+40]=3) and (ip6[(ip6[52]/16*4+1)+40]=3)) or ((udp[14] = 6 and udp[16] = 32 and udp[17] = 1) and ((udp[(udp[60]/16*4)+48]=22) and (udp[(udp[60]/16*4)+53]=1) and (udp[(udp[60]/16*4)+57]=3) and (udp[(udp[60]/16*4)+49]=3))) or (proto 41 and ip[26] = 6 and ip[(ip[72]/16*4)+60]=22 and (ip[(ip[72]/16*4+5)+60]=1) and (ip[(ip[72]/16*4+9)+60]=3) and (ip[(ip[72]/16*4+1)+60]=3))) or (ip[6:2] & 0x1fff != 0)")
63-
//if err != nil {
64-
// return err
65-
//}
66-
defer handle.Close()
57+
defer func() {
58+
if c, ok := handle.(interface{ Close() error }); ok {
59+
c.Close()
60+
}
61+
}()
6762

6863
ip4defragger := ip4defrag.NewIPv4Defragmenter()
6964
streamFactory := &tlsStreamFactory{}
7065
streamPool := tcpassembly.NewStreamPool(streamFactory)
7166
assembler := tcpassembly.NewAssembler(streamPool)
7267

73-
// Use the handle as a packet source to process all packets
74-
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
68+
packetSource := gopacket.NewPacketSource(handle, layers.LinkTypeEthernet)
7569

7670
for packet := range packetSource.Packets() {
7771
// IP defragmentation

example/go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ require (
1212
)
1313

1414
require (
15-
golang.org/x/crypto v0.38.0 // indirect
15+
golang.org/x/crypto v0.39.0 // indirect
16+
golang.org/x/net v0.21.0 // indirect
1617
golang.org/x/sys v0.33.0 // indirect
1718
)

example/go.sum

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
13
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
24
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
5+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
6+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
7+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
8+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
39
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
410
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
5-
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
6-
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
11+
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
12+
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
713
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
814
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
915
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -18,3 +24,5 @@ golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
1824
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
1925
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
2026
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
27+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
28+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

extensions.go

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,7 @@ func (f *Fingerprint) handleExtension(extensionType uint16, extContent cryptobyt
8080
f.Extensions = append(f.Extensions, extensionType)
8181

8282
case ExtALPN:
83-
// ALPN (Application-Layer Protocol Negotiation) handling
84-
f.ALPNProtocols = nil // Always initialize to avoid stale data
83+
// ALPN (Application-Layer Protocol Negotiation)
8584
var alpnList cryptobyte.String
8685
if !extContent.ReadUint16LengthPrefixed(&alpnList) {
8786
return fmt.Errorf("could not read ALPN protocol list")
@@ -95,6 +94,66 @@ func (f *Fingerprint) handleExtension(extensionType uint16, extContent cryptobyt
9594
}
9695
f.Extensions = append(f.Extensions, extensionType)
9796

97+
// KeyShare (0x0033)
98+
case 0x0033:
99+
// TLS 1.3 KeyShare extension
100+
var keyShareList cryptobyte.String
101+
if !extContent.ReadUint16LengthPrefixed(&keyShareList) {
102+
return fmt.Errorf("could not read key share list")
103+
}
104+
for !keyShareList.Empty() {
105+
var group uint16
106+
if !keyShareList.ReadUint16(&group) {
107+
return fmt.Errorf("could not read key share group")
108+
}
109+
// Skip key_exchange length and value
110+
var keyEx cryptobyte.String
111+
if !keyShareList.ReadUint16LengthPrefixed(&keyEx) {
112+
return fmt.Errorf("could not read key exchange value")
113+
}
114+
f.KeyShareGroups = append(f.KeyShareGroups, group)
115+
}
116+
f.Extensions = append(f.Extensions, extensionType)
117+
118+
// PSK Key Exchange Modes (0x002d)
119+
case 0x002d:
120+
var modes cryptobyte.String
121+
if !extContent.ReadUint8LengthPrefixed(&modes) {
122+
return fmt.Errorf("could not read PSK key exchange modes")
123+
}
124+
for !modes.Empty() {
125+
var mode uint8
126+
if !modes.ReadUint8(&mode) {
127+
return fmt.Errorf("could not read PSK key exchange mode")
128+
}
129+
f.PSKKeyExchangeModes = append(f.PSKKeyExchangeModes, mode)
130+
}
131+
f.Extensions = append(f.Extensions, extensionType)
132+
133+
// Cookie (0x002c)
134+
case 0x002c:
135+
var cookie cryptobyte.String
136+
if !extContent.ReadUint16LengthPrefixed(&cookie) {
137+
return fmt.Errorf("could not read cookie value")
138+
}
139+
f.Cookie = string(cookie)
140+
f.Extensions = append(f.Extensions, extensionType)
141+
142+
// Renegotiation Info (0xff01)
143+
case 0xff01:
144+
var reneg cryptobyte.String
145+
if !extContent.ReadUint8LengthPrefixed(&reneg) {
146+
return fmt.Errorf("could not read renegotiation info")
147+
}
148+
f.RenegotiationInfo = string(reneg)
149+
f.Extensions = append(f.Extensions, extensionType)
150+
151+
// SessionTicket (0x0023)
152+
case 0x0023:
153+
// SessionTicket is just a length-prefixed blob
154+
f.SessionTicketLen = len(extContent)
155+
f.Extensions = append(f.Extensions, extensionType)
156+
98157
default:
99158
// Append this list first as there are 0-length extensions
100159
f.Extensions = append(f.Extensions, extensionType)

fingerprint_pub_test.go

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package dactyloscopy_test
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/LeeBrotherston/dactyloscopy"
8+
"github.com/google/gopacket"
9+
"github.com/google/gopacket/pcapgo"
10+
)
11+
12+
func TestProcessClientHello(t *testing.T) {
13+
tests := []struct {
14+
name string
15+
input []byte
16+
wantJA3 string
17+
wantErr bool
18+
}{
19+
{
20+
name: "Empty input",
21+
input: []byte{},
22+
wantErr: true,
23+
},
24+
// Add more test cases here
25+
}
26+
27+
for _, tt := range tests {
28+
t.Run(tt.name, func(t *testing.T) {
29+
fp := &dactyloscopy.Fingerprint{}
30+
err := fp.ProcessClientHello(tt.input)
31+
if (err != nil) != tt.wantErr {
32+
t.Errorf("ProcessClientHello() error = %v, wantErr %v", err, tt.wantErr)
33+
return
34+
}
35+
if !tt.wantErr && fp.JA3 != tt.wantJA3 {
36+
t.Errorf("JA3 = %v, want %v", fp.JA3, tt.wantJA3)
37+
}
38+
})
39+
}
40+
}
41+
42+
func TestFingerprint_Validate(t *testing.T) {
43+
tests := []struct {
44+
name string
45+
fp *dactyloscopy.Fingerprint
46+
wantErr bool
47+
}{
48+
{
49+
name: "Valid fingerprint",
50+
fp: &dactyloscopy.Fingerprint{
51+
MessageType: dactyloscopy.HandshakeType,
52+
TLSVersion: dactyloscopy.VersionTLS12,
53+
Ciphersuite: []uint16{0x1301, 0x1302},
54+
Extensions: []uint16{dactyloscopy.ExtServerName},
55+
},
56+
wantErr: false,
57+
},
58+
{
59+
name: "Invalid message type",
60+
fp: &dactyloscopy.Fingerprint{
61+
MessageType: 0,
62+
},
63+
wantErr: true,
64+
},
65+
}
66+
67+
for _, tt := range tests {
68+
t.Run(tt.name, func(t *testing.T) {
69+
err := tt.fp.Validate()
70+
if (err != nil) != tt.wantErr {
71+
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
72+
}
73+
})
74+
}
75+
}
76+
77+
func TestSamplePcaps(t *testing.T) {
78+
counter := 1
79+
for _, file := range []string{"thing2.pcapng"} {
80+
pcapFile, err := os.Open(file)
81+
if err != nil {
82+
t.Fatal(err)
83+
}
84+
r, err := pcapgo.NewNgReader(pcapFile, pcapgo.DefaultNgReaderOptions)
85+
if err != nil {
86+
t.Fatal(err)
87+
}
88+
defer pcapFile.Close()
89+
90+
packetSource := gopacket.NewPacketSource(r, r.LinkType())
91+
for packet := range packetSource.Packets() {
92+
if err := dactyloscopy.IsClientHello(packet.ApplicationLayer().Payload()); err != nil {
93+
continue
94+
}
95+
t.Logf("processing client hello %d", counter)
96+
counter++
97+
98+
payload := packet.ApplicationLayer()
99+
if payload != nil {
100+
processHello(t, payload.Payload())
101+
}
102+
}
103+
}
104+
}
105+
106+
func processHello(t *testing.T, data []byte) {
107+
t.Helper()
108+
109+
fp := &dactyloscopy.Fingerprint{}
110+
err := fp.ProcessClientHello(data)
111+
if err != nil {
112+
t.Logf("Oh no, error: %v", err)
113+
t.FailNow()
114+
}
115+
t.Logf("parsed it: %+v", fp)
116+
}
117+
118+
func FuzzProcessClientHello(f *testing.F) {
119+
// Add initial corpus using known valid inputs
120+
for _, file := range []string{"thing2.pcapng"} {
121+
pcapFile, err := os.Open(file)
122+
if err != nil {
123+
f.Fatal(err)
124+
}
125+
r, err := pcapgo.NewNgReader(pcapFile, pcapgo.DefaultNgReaderOptions)
126+
if err != nil {
127+
f.Fatal(err)
128+
}
129+
defer pcapFile.Close()
130+
131+
packetSource := gopacket.NewPacketSource(r, r.LinkType())
132+
for packet := range packetSource.Packets() {
133+
payload := packet.ApplicationLayer()
134+
if payload != nil {
135+
f.Add(payload.Payload())
136+
}
137+
}
138+
}
139+
140+
f.Fuzz(func(t *testing.T, data []byte) {
141+
fp := &dactyloscopy.Fingerprint{}
142+
// We only care that this doesn't panic, in this context errors are graceful handling
143+
_ = fp.ProcessClientHello(data)
144+
})
145+
}

0 commit comments

Comments
 (0)