Skip to content

Commit c779f58

Browse files
authored
Merge pull request #8 from kjelle/master
Allow for out-of-order TLS Handshake processing and partial outputs
2 parents d997cb8 + 45159fd commit c779f58

File tree

5 files changed

+120
-42
lines changed

5 files changed

+120
-42
lines changed

d4-tlsf.go

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ var ignorefsmerr = flag.Bool("ignorefsmerr", false, "Ignore TCP FSM errors")
3636
var verbose = flag.Bool("verbose", false, "Be verbose")
3737
var debug = flag.Bool("debug", false, "Display debug information")
3838
var quiet = flag.Bool("quiet", false, "Be quiet regarding errors")
39+
var partial = flag.Bool("partial", true, "Output partial TLS Sessions, e.g. where we don't see all three of client hello, server hello and certificate")
3940

4041
// capture
4142
var iface = flag.String("i", "eth0", "Interface to read packets from")
@@ -101,6 +102,11 @@ func (factory *tcpStreamFactory) New(net, transport gopacket.Flow, tcp *layers.T
101102
optchecker: reassembly.NewTCPOptionCheck(),
102103
tlsSession: d4tls.TLSSession{},
103104
}
105+
106+
// Set network information in the TLSSession
107+
cip, sip, cp, sp := getIPPorts(stream)
108+
stream.tlsSession.SetNetwork(cip, sip, cp, sp)
109+
104110
return stream
105111
}
106112

@@ -134,6 +140,7 @@ type tcpStream struct {
134140
urls []string
135141
ident string
136142
tlsSession d4tls.TLSSession
143+
queued bool
137144
ignorefsmerr bool
138145
nooptcheck bool
139146
checksum bool
@@ -181,6 +188,7 @@ func (t *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.Ass
181188
}
182189
data := sg.Fetch(length)
183190
if t.isTLS {
191+
184192
if length > 0 {
185193
// We attempt to decode TLS
186194
tls := &etls.ETLS{}
@@ -195,13 +203,18 @@ func (t *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.Ass
195203
//Debug("TLS: %s\n", gopacket.LayerDump(tls))
196204
// Debug("TLS: %s\n", gopacket.LayerGoString(tls))
197205
if tls.Handshake != nil {
206+
207+
// If the timestamp has not been set, we set it for the first time.
208+
if t.tlsSession.Record.Timestamp.IsZero() {
209+
info := sg.CaptureInfo(0)
210+
t.tlsSession.SetTimestamp(info.Timestamp)
211+
}
212+
198213
for _, tlsrecord := range tls.Handshake {
199214
switch tlsrecord.ETLSHandshakeMsgType {
200215
// Client Hello
201216
case 1:
202-
info := sg.CaptureInfo(0)
203-
cip, sip, cp, sp := getIPPorts(t)
204-
t.tlsSession.PopulateClientHello(tlsrecord.ETLSHandshakeClientHello, cip, sip, cp, sp, info.Timestamp)
217+
t.tlsSession.PopulateClientHello(tlsrecord.ETLSHandshakeClientHello)
205218
t.tlsSession.D4Fingerprinting("ja3")
206219
// Server Hello
207220
case 2:
@@ -210,14 +223,15 @@ func (t *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.Ass
210223
// Server Certificate
211224
case 11:
212225
t.tlsSession.PopulateCertificate(tlsrecord.ETLSHandshakeCertificate)
213-
214226
t.tlsSession.D4Fingerprinting("tlsh")
215-
// If we get a cert, we consider the handshake as finished and ready to ship to D4
216-
queueSession(t.tlsSession)
217-
default:
218-
break
219227
}
220228
}
229+
230+
// If the handshake is considered finished and we have not yet outputted it we ship it to output.
231+
if t.tlsSession.HandshakeComplete() && !t.queued {
232+
t.queueSession()
233+
}
234+
221235
}
222236
}
223237
}
@@ -235,6 +249,12 @@ func getIPPorts(t *tcpStream) (string, string, string, string) {
235249
}
236250

237251
func (t *tcpStream) ReassemblyComplete(ac reassembly.AssemblerContext) bool {
252+
// If the handshakre has not yet been outputted, but there are some information such as
253+
// either the client hello or the server hello, we also output a partial.
254+
if *partial && !t.queued && t.tlsSession.HandshakeAny() {
255+
t.queueSession()
256+
}
257+
238258
// remove connection from the pool
239259
return true
240260
}
@@ -288,7 +308,7 @@ func main() {
288308
signal.Notify(signalChan, os.Interrupt)
289309

290310
// Job chan to hold Completed sessions to write
291-
jobQ = make(chan d4tls.TLSSession, 100)
311+
jobQ = make(chan d4tls.TLSSession, 4096)
292312
cancelC := make(chan string)
293313

294314
// We start a worker to send the processed TLS connection the outside world
@@ -387,10 +407,13 @@ func main() {
387407
w.Wait()
388408
}
389409

390-
// Tries to enqueue or false
391-
func queueSession(t d4tls.TLSSession) bool {
410+
// queueSession tries to enqueue the tlsSession for output
411+
// returns true if it succeeded or false if it failed to publish the
412+
// tlsSession for output
413+
func (t *tcpStream) queueSession() bool {
414+
t.queued = true
392415
select {
393-
case jobQ <- t:
416+
case jobQ <- t.tlsSession:
394417
return true
395418
default:
396419
return false
@@ -414,7 +437,6 @@ func processCompletedSession(cancelC <-chan string, jobQ <-chan d4tls.TLSSession
414437
}
415438

416439
func output(t d4tls.TLSSession) {
417-
418440
jsonRecord, _ := json.MarshalIndent(t.Record, "", " ")
419441

420442
// If an output folder was specified for certificates

d4tls/fingerprinter.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ func (t *TLSSession) d4fg() string {
4949

5050
func (t *TLSSession) ja3s() bool {
5151
var buf []byte
52+
5253
buf = strconv.AppendInt(buf, int64(t.handShakeRecord.ETLSHandshakeServerHello.Vers), 10)
5354
// byte (44) is ","
5455
buf = append(buf, byte(44))
@@ -81,6 +82,7 @@ func (t *TLSSession) ja3s() bool {
8182

8283
func (t *TLSSession) ja3() bool {
8384
var buf []byte
85+
8486
buf = strconv.AppendInt(buf, int64(t.handShakeRecord.ETLSHandshakeClientHello.Vers), 10)
8587
// byte (44) is ","
8688
buf = append(buf, byte(44))

d4tls/fingerprinter_test.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,8 @@ func TestJA3(t *testing.T) {
203203
for _, etlsrecord := range tls.Handshake {
204204
if etlsrecord.ETLSHandshakeMsgType == 1 {
205205
// Populate Client Hello related fields
206-
tlss.PopulateClientHello(etlsrecord.ETLSHandshakeClientHello, "", "", "", "", time.Now())
206+
tlss.PopulateClientHello(etlsrecord.ETLSHandshakeClientHello)
207+
tlss.SetTimestamp(time.Now())
207208
tlss.D4Fingerprinting("ja3")
208209
t.Logf("%v", tlss.Record.JA3)
209210
t.Logf("%v", tlss.Record.JA3Digest)
@@ -279,7 +280,8 @@ func TestGreaseClientHelloExtensionExlusion(t *testing.T) {
279280
for _, etlsrecord := range tls.Handshake {
280281
if etlsrecord.ETLSHandshakeMsgType == 1 {
281282
// Populate Client Hello related fields
282-
tlss.PopulateClientHello(etlsrecord.ETLSHandshakeClientHello, "", "", "", "", time.Now())
283+
tlss.PopulateClientHello(etlsrecord.ETLSHandshakeClientHello)
284+
tlss.SetTimestamp(time.Now())
283285
tlss.D4Fingerprinting("ja3")
284286
t.Logf("%v", tlss.Record.JA3Digest)
285287
if strings.Index(tlss.Record.JA3, "2570") != -1 {
@@ -312,7 +314,8 @@ func TestGreaseClientHelloCipherExlusion(t *testing.T) {
312314
for _, etlsrecord := range tls.Handshake {
313315
if etlsrecord.ETLSHandshakeMsgType == 1 {
314316
// Populate Client Hello related fields
315-
tlss.PopulateClientHello(etlsrecord.ETLSHandshakeClientHello, "", "", "", "", time.Now())
317+
tlss.PopulateClientHello(etlsrecord.ETLSHandshakeClientHello)
318+
tlss.SetTimestamp(time.Now())
316319
tlss.D4Fingerprinting("ja3")
317320
if strings.Index(tlss.Record.JA3, "2570") != -1 {
318321
t.Logf("GREASE values should not end up in JA3\n")

d4tls/flags.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package d4tls
2+
3+
// HandshakeState is a flag which keeps record of which handeshake message types
4+
// have been parsed.
5+
type HandshakeState uint8
6+
7+
const (
8+
StateClientHello = 1 << iota
9+
StateServerHello
10+
StateCertificate
11+
)
12+
13+
func (s *HandshakeState) Set(flag HandshakeState) {
14+
*s |= flag
15+
}
16+
17+
func (s HandshakeState) Has(flag HandshakeState) bool {
18+
return s&flag != 0
19+
}

d4tls/tlsdecoder.go

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,35 @@ type sessionRecord struct {
3434
type TLSSession struct {
3535
Record sessionRecord
3636
handShakeRecord etls.ETLSHandshakeRecord
37-
stage int
37+
state HandshakeState
38+
}
39+
40+
// HandshakeComplete returns true if the TLS session has seen all three client helo, server helo and the certificate.
41+
func (t *TLSSession) HandshakeComplete() bool {
42+
return t.state.Has(StateClientHello) &&
43+
t.state.Has(StateServerHello) &&
44+
t.state.Has(StateCertificate)
45+
}
46+
47+
// HandshakePartially returns true if the client hello and server hello is set, but not the certificate.
48+
func (t *TLSSession) HandshakePartially() bool {
49+
return t.state.Has(StateClientHello) &&
50+
t.state.Has(StateServerHello) &&
51+
!t.state.Has(StateCertificate)
52+
}
53+
54+
// HandshakeAny returns true if any of the client or server has been seen
55+
func (t *TLSSession) HandshakeAny() bool {
56+
return t.state.Has(StateClientHello) ||
57+
t.state.Has(StateServerHello)
58+
}
59+
60+
func (t *TLSSession) HandshakeState() string {
61+
return fmt.Sprintf("ClientHello:%t ServerHello:%t Certificate:%t",
62+
t.state.Has(StateClientHello),
63+
t.state.Has(StateServerHello),
64+
t.state.Has(StateCertificate),
65+
)
3866
}
3967

4068
// String returns a string that describes a TLSSession
@@ -59,40 +87,44 @@ func (t *TLSSession) String() string {
5987
return buf.String()
6088
}
6189

90+
// SetNetwork sets the network part in the TLSSession.Record struct.
91+
func (t *TLSSession) SetNetwork(cip string, sip string, cp string, sp string) {
92+
t.Record.ClientIP = cip
93+
t.Record.ServerIP = sip
94+
t.Record.ClientPort = cp
95+
t.Record.ServerPort = sp
96+
}
97+
98+
// SetTimestamp sets the timestamp of this TLSSession in its TLSSession.Record struct
99+
func (t *TLSSession) SetTimestamp(ti time.Time) {
100+
t.Record.Timestamp = ti
101+
}
102+
62103
// PopulateClientHello takes a pointer to an etls ClientHelloMsg and writes it to the the TLSSession struct
63-
func (t *TLSSession) PopulateClientHello(h *etls.ClientHelloMsg, cip string, sip string, cp string, sp string, ti time.Time) {
64-
if t.stage < 1 {
65-
t.Record.ClientIP = cip
66-
t.Record.ServerIP = sip
67-
t.Record.ClientPort = cp
68-
t.Record.ServerPort = sp
69-
t.Record.Timestamp = ti
70-
t.handShakeRecord.ETLSHandshakeClientHello = h
71-
t.stage = 1
72-
}
104+
func (t *TLSSession) PopulateClientHello(h *etls.ClientHelloMsg) {
105+
t.state.Set(StateClientHello)
106+
t.handShakeRecord.ETLSHandshakeClientHello = h
73107
}
74108

75109
// PopulateServerHello takes a pointer to an etls ServerHelloMsg and writes it to the TLSSession struct
76110
func (t *TLSSession) PopulateServerHello(h *etls.ServerHelloMsg) {
77-
if t.stage < 2 {
78-
t.handShakeRecord.ETLSHandshakeServerHello = h
79-
t.stage = 2
80-
}
111+
t.state.Set(StateServerHello)
112+
t.handShakeRecord.ETLSHandshakeServerHello = h
81113
}
82114

83115
// PopulateCertificate takes a pointer to an etls ServerHelloMsg and writes it to the TLSSession struct
84116
func (t *TLSSession) PopulateCertificate(c *etls.CertificateMsg) {
85-
if t.stage < 3 {
86-
t.handShakeRecord.ETLSHandshakeCertificate = c
87-
for _, asn1Data := range t.handShakeRecord.ETLSHandshakeCertificate.Certificates {
88-
cert, err := x509.ParseCertificate(asn1Data)
89-
if err != nil {
90-
//return err
91-
} else {
92-
h := sha256.New()
93-
h.Write(cert.Raw)
94-
t.Record.Certificates = append(t.Record.Certificates, certMapElm{Certificate: cert, CertHash: fmt.Sprintf("%x", h.Sum(nil))})
95-
}
117+
t.state.Set(StateCertificate)
118+
t.handShakeRecord.ETLSHandshakeCertificate = c
119+
120+
for _, asn1Data := range t.handShakeRecord.ETLSHandshakeCertificate.Certificates {
121+
cert, err := x509.ParseCertificate(asn1Data)
122+
if err != nil {
123+
//return err
124+
} else {
125+
h := sha256.New()
126+
h.Write(cert.Raw)
127+
t.Record.Certificates = append(t.Record.Certificates, certMapElm{Certificate: cert, CertHash: fmt.Sprintf("%x", h.Sum(nil))})
96128
}
97129
}
98130
}

0 commit comments

Comments
 (0)