refacto
parent
d6d9f8a111
commit
549437b355
207
d4-tlsf.go
207
d4-tlsf.go
|
@ -2,13 +2,10 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/md5"
|
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/x509"
|
|
||||||
|
|
||||||
// TODO consider
|
// TODO consider
|
||||||
//"github.com/google/certificate-transparency-go/x509"
|
//"github.com/google/certificate-transparency-go/x509"
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -17,12 +14,10 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/glaslos/tlsh"
|
|
||||||
"github.com/google/gopacket"
|
"github.com/google/gopacket"
|
||||||
"github.com/google/gopacket/examples/util"
|
"github.com/google/gopacket/examples/util"
|
||||||
"github.com/google/gopacket/ip4defrag"
|
"github.com/google/gopacket/ip4defrag"
|
||||||
|
@ -30,6 +25,7 @@ import (
|
||||||
"github.com/google/gopacket/pcap"
|
"github.com/google/gopacket/pcap"
|
||||||
"github.com/google/gopacket/reassembly"
|
"github.com/google/gopacket/reassembly"
|
||||||
|
|
||||||
|
"github.com/D4-project/sensor-d4-tls-fingerprinting/d4tls"
|
||||||
"github.com/D4-project/sensor-d4-tls-fingerprinting/etls"
|
"github.com/D4-project/sensor-d4-tls-fingerprinting/etls"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -52,38 +48,7 @@ var fname = flag.String("r", "", "Filename to read from, overrides -i")
|
||||||
// writing
|
// writing
|
||||||
var outCerts = flag.String("w", "", "Folder to write certificates into")
|
var outCerts = flag.String("w", "", "Folder to write certificates into")
|
||||||
var outJSON = flag.String("j", "", "Folder to write certificates into, stdin if not set")
|
var outJSON = flag.String("j", "", "Folder to write certificates into, stdin if not set")
|
||||||
var jobQ chan TLSSession
|
var jobQ chan d4tls.TLSSession
|
||||||
|
|
||||||
type CertMapElm struct {
|
|
||||||
CertHash string
|
|
||||||
*x509.Certificate
|
|
||||||
}
|
|
||||||
|
|
||||||
type SessionRecord struct {
|
|
||||||
ServerIP string
|
|
||||||
ServerPort string
|
|
||||||
ClientIP string
|
|
||||||
ClientPort string
|
|
||||||
TLSH string
|
|
||||||
Timestamp time.Time
|
|
||||||
JA3 string
|
|
||||||
JA3Digest string
|
|
||||||
JA3S string
|
|
||||||
JA3SDigest string
|
|
||||||
Certificates []CertMapElm
|
|
||||||
}
|
|
||||||
|
|
||||||
type TLSSession struct {
|
|
||||||
record SessionRecord
|
|
||||||
tlsHdskR etls.ETLSHandshakeRecord
|
|
||||||
}
|
|
||||||
|
|
||||||
var grease = map[uint16]bool{
|
|
||||||
0x0a0a: true, 0x1a1a: true, 0x2a2a: true, 0x3a3a: true,
|
|
||||||
0x4a4a: true, 0x5a5a: true, 0x6a6a: true, 0x7a7a: true,
|
|
||||||
0x8a8a: true, 0x9a9a: true, 0xaaaa: true, 0xbaba: true,
|
|
||||||
0xcaca: true, 0xdada: true, 0xeaea: true, 0xfafa: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
const closeTimeout time.Duration = time.Hour * 24 // Closing inactive: TODO: from CLI
|
const closeTimeout time.Duration = time.Hour * 24 // Closing inactive: TODO: from CLI
|
||||||
const timeout time.Duration = time.Minute * 5 // Pending bytes: TODO: from CLI
|
const timeout time.Duration = time.Minute * 5 // Pending bytes: TODO: from CLI
|
||||||
|
@ -93,27 +58,6 @@ var errorsMap map[string]uint
|
||||||
var errorsMapMutex sync.Mutex
|
var errorsMapMutex sync.Mutex
|
||||||
var errors uint
|
var errors uint
|
||||||
|
|
||||||
func (t *TLSSession) String() string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
buf.WriteString(fmt.Sprintf("---------------SESSION START-------------------\n"))
|
|
||||||
buf.WriteString(fmt.Sprintf("Time: %d\n", t.record.Timestamp))
|
|
||||||
buf.WriteString(fmt.Sprintf("Client: %v:%v\n", t.record.ClientIP, t.record.ClientPort))
|
|
||||||
buf.WriteString(fmt.Sprintf("Server: %v:%v\n", t.record.ServerIP, t.record.ServerPort))
|
|
||||||
buf.WriteString(fmt.Sprintf("TLSH: %q\n", t.record.TLSH))
|
|
||||||
buf.WriteString(fmt.Sprintf("ja3: %q\n", t.record.JA3))
|
|
||||||
buf.WriteString(fmt.Sprintf("ja3 Digest: %q\n", t.record.JA3Digest))
|
|
||||||
buf.WriteString(fmt.Sprintf("ja3s: %q\n", t.record.JA3S))
|
|
||||||
buf.WriteString(fmt.Sprintf("ja3s Digest: %q\n", t.record.JA3SDigest))
|
|
||||||
for _, certMe := range t.record.Certificates {
|
|
||||||
buf.WriteString(fmt.Sprintf("Certificate Issuer: %q\n", certMe.Certificate.Issuer))
|
|
||||||
buf.WriteString(fmt.Sprintf("Certificate Subject: %q\n", certMe.Certificate.Subject))
|
|
||||||
buf.WriteString(fmt.Sprintf("Certificate is CA: %t\n", certMe.Certificate.IsCA))
|
|
||||||
buf.WriteString(fmt.Sprintf("Certificate SHA256: %t\n", certMe.CertHash))
|
|
||||||
}
|
|
||||||
buf.WriteString(fmt.Sprintf("---------------SESSION END--------------------\n"))
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Too bad for perf that a... is evaluated
|
// Too bad for perf that a... is evaluated
|
||||||
func Error(t string, s string, a ...interface{}) {
|
func Error(t string, s string, a ...interface{}) {
|
||||||
errorsMapMutex.Lock()
|
errorsMapMutex.Lock()
|
||||||
|
@ -155,6 +99,7 @@ func (factory *tcpStreamFactory) New(net, transport gopacket.Flow, tcp *layers.T
|
||||||
tcpstate: reassembly.NewTCPSimpleFSM(fsmOptions),
|
tcpstate: reassembly.NewTCPSimpleFSM(fsmOptions),
|
||||||
ident: fmt.Sprintf("%s:%s", net, transport),
|
ident: fmt.Sprintf("%s:%s", net, transport),
|
||||||
optchecker: reassembly.NewTCPOptionCheck(),
|
optchecker: reassembly.NewTCPOptionCheck(),
|
||||||
|
tlsSession: d4tls.TLSSession{},
|
||||||
}
|
}
|
||||||
return stream
|
return stream
|
||||||
}
|
}
|
||||||
|
@ -188,7 +133,7 @@ type tcpStream struct {
|
||||||
reversed bool
|
reversed bool
|
||||||
urls []string
|
urls []string
|
||||||
ident string
|
ident string
|
||||||
tlsSession TLSSession
|
tlsSession d4tls.TLSSession
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,32 +203,19 @@ func (t *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.Ass
|
||||||
switch tlsrecord.ETLSHandshakeMsgType {
|
switch tlsrecord.ETLSHandshakeMsgType {
|
||||||
// Client Hello
|
// Client Hello
|
||||||
case 1:
|
case 1:
|
||||||
t.tlsSession.tlsHdskR.ETLSHandshakeClientHello = tlsrecord.ETLSHandshakeClientHello
|
|
||||||
t.tlsSession.record.ClientIP, t.tlsSession.record.ServerIP, t.tlsSession.record.ClientPort, t.tlsSession.record.ServerPort = getIPPorts(t)
|
|
||||||
// Set up first seen
|
|
||||||
info := sg.CaptureInfo(0)
|
info := sg.CaptureInfo(0)
|
||||||
t.tlsSession.record.Timestamp = info.Timestamp
|
cip, sip, cp, sp := getIPPorts(t)
|
||||||
t.tlsSession.gatherJa3()
|
t.tlsSession.PopulateClientHello(tlsrecord.ETLSHandshakeClientHello, cip, sip, cp, sp, info.Timestamp)
|
||||||
|
t.tlsSession.D4Fingerprinting("ja3")
|
||||||
// Server Hello
|
// Server Hello
|
||||||
case 2:
|
case 2:
|
||||||
t.tlsSession.tlsHdskR.ETLSHandshakeServerHello = tlsrecord.ETLSHandshakeServerHello
|
t.tlsSession.PopulateServerHello(tlsrecord.ETLSHandshakeServerHello)
|
||||||
t.tlsSession.gatherJa3s()
|
t.tlsSession.D4Fingerprinting("ja3s")
|
||||||
// Server Certificate
|
// Server Certificate
|
||||||
case 11:
|
case 11:
|
||||||
for _, asn1Data := range tlsrecord.ETLSHandshakeCertificate.Certificates {
|
t.tlsSession.PopulateCertificate(tlsrecord.ETLSHandshakeCertificate)
|
||||||
cert, err := x509.ParseCertificate(asn1Data)
|
|
||||||
if err != nil {
|
|
||||||
Error("tls", "Failed to parse certificate from server: %x", err)
|
|
||||||
} else {
|
|
||||||
h := sha256.New()
|
|
||||||
h.Write(cert.Raw)
|
|
||||||
t.tlsSession.record.Certificates = append(t.tlsSession.record.Certificates, CertMapElm{Certificate: cert, CertHash: fmt.Sprintf("%x", h.Sum(nil))})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// We compute ja3jl
|
|
||||||
out, _ := tlsh.HashBytes(t.tlsSession.record.ja3jl())
|
|
||||||
t.tlsSession.record.TLSH = out.String()
|
|
||||||
|
|
||||||
|
t.tlsSession.D4Fingerprinting("tlsh")
|
||||||
// If we get a cert, we consider the handshake as finished and ready to ship to D4
|
// If we get a cert, we consider the handshake as finished and ready to ship to D4
|
||||||
queueSession(t.tlsSession)
|
queueSession(t.tlsSession)
|
||||||
default:
|
default:
|
||||||
|
@ -306,107 +238,6 @@ func getIPPorts(t *tcpStream) (string, string, string, string) {
|
||||||
return ipc, ips, cp, ps
|
return ipc, ips, cp, ps
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sr *SessionRecord) ja3jl() []byte {
|
|
||||||
buf := sr.JA3 + sr.JA3S
|
|
||||||
for _, cert := range sr.Certificates {
|
|
||||||
buf += fmt.Sprintf("%q", cert.Issuer) + fmt.Sprintf("%q", cert.Subject)
|
|
||||||
}
|
|
||||||
buf = strings.Replace(buf, "-", "", -1)
|
|
||||||
buf = strings.Replace(buf, ",", "", -1)
|
|
||||||
buf = strings.Replace(buf, "\"", "", -1)
|
|
||||||
|
|
||||||
return []byte(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *TLSSession) gatherJa3s() bool {
|
|
||||||
var buf []byte
|
|
||||||
buf = strconv.AppendInt(buf, int64(ts.tlsHdskR.ETLSHandshakeServerHello.Vers), 10)
|
|
||||||
// byte (44) is ","
|
|
||||||
buf = append(buf, byte(44))
|
|
||||||
|
|
||||||
// If there are Cipher Suites
|
|
||||||
buf = strconv.AppendInt(buf, int64(ts.tlsHdskR.ETLSHandshakeServerHello.CipherSuite), 10)
|
|
||||||
buf = append(buf, byte(44))
|
|
||||||
|
|
||||||
// If there are extensions
|
|
||||||
if len(ts.tlsHdskR.ETLSHandshakeServerHello.AllExtensions) > 0 {
|
|
||||||
for i, e := range ts.tlsHdskR.ETLSHandshakeServerHello.AllExtensions {
|
|
||||||
// TODO check this grease thingy
|
|
||||||
if grease[uint16(e)] == false {
|
|
||||||
buf = strconv.AppendInt(buf, int64(e), 10)
|
|
||||||
if (i + 1) < len(ts.tlsHdskR.ETLSHandshakeServerHello.AllExtensions) {
|
|
||||||
// byte(45) is "-"
|
|
||||||
buf = append(buf, byte(45))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ts.record.JA3S = string(buf)
|
|
||||||
tmp := md5.Sum(buf)
|
|
||||||
ts.record.JA3SDigest = hex.EncodeToString(tmp[:])
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *TLSSession) gatherJa3() bool {
|
|
||||||
var buf []byte
|
|
||||||
buf = strconv.AppendInt(buf, int64(ts.tlsHdskR.ETLSHandshakeClientHello.Vers), 10)
|
|
||||||
// byte (44) is ","
|
|
||||||
buf = append(buf, byte(44))
|
|
||||||
|
|
||||||
// If there are Cipher Suites
|
|
||||||
if len(ts.tlsHdskR.ETLSHandshakeClientHello.CipherSuites) > 0 {
|
|
||||||
for i, cs := range ts.tlsHdskR.ETLSHandshakeClientHello.CipherSuites {
|
|
||||||
buf = strconv.AppendInt(buf, int64(cs), 10)
|
|
||||||
// byte(45) is "-"
|
|
||||||
if (i + 1) < len(ts.tlsHdskR.ETLSHandshakeClientHello.CipherSuites) {
|
|
||||||
buf = append(buf, byte(45))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf = append(buf, byte(44))
|
|
||||||
|
|
||||||
// If there are extensions
|
|
||||||
if len(ts.tlsHdskR.ETLSHandshakeClientHello.AllExtensions) > 0 {
|
|
||||||
for i, e := range ts.tlsHdskR.ETLSHandshakeClientHello.AllExtensions {
|
|
||||||
// TODO check this grease thingy
|
|
||||||
if grease[uint16(e)] == false {
|
|
||||||
buf = strconv.AppendInt(buf, int64(e), 10)
|
|
||||||
if (i + 1) < len(ts.tlsHdskR.ETLSHandshakeClientHello.AllExtensions) {
|
|
||||||
buf = append(buf, byte(45))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf = append(buf, byte(44))
|
|
||||||
|
|
||||||
// If there are Supported Curves
|
|
||||||
if len(ts.tlsHdskR.ETLSHandshakeClientHello.SupportedCurves) > 0 {
|
|
||||||
for i, cs := range ts.tlsHdskR.ETLSHandshakeClientHello.SupportedCurves {
|
|
||||||
buf = strconv.AppendInt(buf, int64(cs), 10)
|
|
||||||
if (i + 1) < len(ts.tlsHdskR.ETLSHandshakeClientHello.SupportedCurves) {
|
|
||||||
buf = append(buf, byte(45))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf = append(buf, byte(44))
|
|
||||||
|
|
||||||
// If there are Supported Points
|
|
||||||
if len(ts.tlsHdskR.ETLSHandshakeClientHello.SupportedPoints) > 0 {
|
|
||||||
for i, cs := range ts.tlsHdskR.ETLSHandshakeClientHello.SupportedPoints {
|
|
||||||
buf = strconv.AppendInt(buf, int64(cs), 10)
|
|
||||||
if (i + 1) < len(ts.tlsHdskR.ETLSHandshakeClientHello.SupportedPoints) {
|
|
||||||
buf = append(buf, byte(45))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ts.record.JA3 = string(buf)
|
|
||||||
tmp := md5.Sum(buf)
|
|
||||||
ts.record.JA3Digest = hex.EncodeToString(tmp[:])
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tcpStream) ReassemblyComplete(ac reassembly.AssemblerContext) bool {
|
func (t *tcpStream) ReassemblyComplete(ac reassembly.AssemblerContext) bool {
|
||||||
Debug("%s: Connection closed\n", t.ident)
|
Debug("%s: Connection closed\n", t.ident)
|
||||||
// do not remove the connection to allow last ACK
|
// do not remove the connection to allow last ACK
|
||||||
|
@ -465,7 +296,7 @@ func main() {
|
||||||
signal.Notify(signalChan, os.Interrupt)
|
signal.Notify(signalChan, os.Interrupt)
|
||||||
|
|
||||||
// Job chan to hold Completed sessions to write
|
// Job chan to hold Completed sessions to write
|
||||||
jobQ = make(chan TLSSession, 100)
|
jobQ = make(chan d4tls.TLSSession, 100)
|
||||||
cancelC := make(chan string)
|
cancelC := make(chan string)
|
||||||
|
|
||||||
// We start a worker to send the processed TLS connection the outside world
|
// We start a worker to send the processed TLS connection the outside world
|
||||||
|
@ -543,7 +374,7 @@ func main() {
|
||||||
w.Wait()
|
w.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func processCompletedSession(jobQ <-chan TLSSession, w *sync.WaitGroup) {
|
func processCompletedSession(jobQ <-chan d4tls.TLSSession, w *sync.WaitGroup) {
|
||||||
for {
|
for {
|
||||||
tlss, more := <-jobQ
|
tlss, more := <-jobQ
|
||||||
if more {
|
if more {
|
||||||
|
@ -556,7 +387,7 @@ func processCompletedSession(jobQ <-chan TLSSession, w *sync.WaitGroup) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tries to enqueue or false
|
// Tries to enqueue or false
|
||||||
func queueSession(t TLSSession) bool {
|
func queueSession(t d4tls.TLSSession) bool {
|
||||||
select {
|
select {
|
||||||
case jobQ <- t:
|
case jobQ <- t:
|
||||||
return true
|
return true
|
||||||
|
@ -565,14 +396,14 @@ func queueSession(t TLSSession) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func output(t TLSSession) {
|
func output(t d4tls.TLSSession) {
|
||||||
|
|
||||||
jsonRecord, _ := json.MarshalIndent(t.record, "", " ")
|
jsonRecord, _ := json.MarshalIndent(t.Record, "", " ")
|
||||||
|
|
||||||
// If an output folder was specified for certificates
|
// If an output folder was specified for certificates
|
||||||
if *outCerts != "" {
|
if *outCerts != "" {
|
||||||
if _, err := os.Stat(fmt.Sprintf("./%s", *outCerts)); !os.IsNotExist(err) {
|
if _, err := os.Stat(fmt.Sprintf("./%s", *outCerts)); !os.IsNotExist(err) {
|
||||||
for _, certMe := range t.record.Certificates {
|
for _, certMe := range t.Record.Certificates {
|
||||||
err := ioutil.WriteFile(fmt.Sprintf("./%s/%s.crt", *outCerts, certMe.CertHash), certMe.Certificate.Raw, 0644)
|
err := ioutil.WriteFile(fmt.Sprintf("./%s/%s.crt", *outCerts, certMe.CertHash), certMe.Certificate.Raw, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("Could not write to file.")
|
panic("Could not write to file.")
|
||||||
|
@ -586,7 +417,7 @@ func output(t TLSSession) {
|
||||||
// If an output folder was specified for json files
|
// If an output folder was specified for json files
|
||||||
if *outJSON != "" {
|
if *outJSON != "" {
|
||||||
if _, err := os.Stat(fmt.Sprintf("./%s", *outJSON)); !os.IsNotExist(err) {
|
if _, err := os.Stat(fmt.Sprintf("./%s", *outJSON)); !os.IsNotExist(err) {
|
||||||
err := ioutil.WriteFile(fmt.Sprintf("./%s/%s.json", *outJSON, t.record.Timestamp.Format(time.RFC3339)), jsonRecord, 0644)
|
err := ioutil.WriteFile(fmt.Sprintf("./%s/%s.json", *outJSON, t.Record.Timestamp.Format(time.RFC3339)), jsonRecord, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("Could not write to file.")
|
panic("Could not write to file.")
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,220 @@
|
||||||
|
package d4tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/D4-project/sensor-d4-tls-fingerprinting/etls"
|
||||||
|
"github.com/glaslos/tlsh"
|
||||||
|
)
|
||||||
|
|
||||||
|
var grease = map[uint16]bool{
|
||||||
|
0x0a0a: true, 0x1a1a: true, 0x2a2a: true, 0x3a3a: true,
|
||||||
|
0x4a4a: true, 0x5a5a: true, 0x6a6a: true, 0x7a7a: true,
|
||||||
|
0x8a8a: true, 0x9a9a: true, 0xaaaa: true, 0xbaba: true,
|
||||||
|
0xcaca: true, 0xdada: true, 0xeaea: true, 0xfafa: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
type certMapElm struct {
|
||||||
|
CertHash string
|
||||||
|
*x509.Certificate
|
||||||
|
}
|
||||||
|
|
||||||
|
type sessionRecord struct {
|
||||||
|
ServerIP string
|
||||||
|
ServerPort string
|
||||||
|
ClientIP string
|
||||||
|
ClientPort string
|
||||||
|
TLSH string
|
||||||
|
Timestamp time.Time
|
||||||
|
JA3 string
|
||||||
|
JA3Digest string
|
||||||
|
JA3S string
|
||||||
|
JA3SDigest string
|
||||||
|
Certificates []certMapElm
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSSession contains a handshakeRecord that had to be filled during the handshake,
|
||||||
|
// and a Record that will be at last exported to Json
|
||||||
|
type TLSSession struct {
|
||||||
|
Record sessionRecord
|
||||||
|
handShakeRecord etls.ETLSHandshakeRecord
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string that describes a TLSSession
|
||||||
|
func (t *TLSSession) String() string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteString(fmt.Sprintf("---------------SESSION START-------------------\n"))
|
||||||
|
buf.WriteString(fmt.Sprintf("Time: %v\n", t.Record.Timestamp))
|
||||||
|
buf.WriteString(fmt.Sprintf("Client: %v:%v\n", t.Record.ClientIP, t.Record.ClientPort))
|
||||||
|
buf.WriteString(fmt.Sprintf("Server: %v:%v\n", t.Record.ServerIP, t.Record.ServerPort))
|
||||||
|
buf.WriteString(fmt.Sprintf("TLSH: %q\n", t.Record.TLSH))
|
||||||
|
buf.WriteString(fmt.Sprintf("ja3: %q\n", t.Record.JA3))
|
||||||
|
buf.WriteString(fmt.Sprintf("ja3 Digest: %q\n", t.Record.JA3Digest))
|
||||||
|
buf.WriteString(fmt.Sprintf("ja3s: %q\n", t.Record.JA3S))
|
||||||
|
buf.WriteString(fmt.Sprintf("ja3s Digest: %q\n", t.Record.JA3SDigest))
|
||||||
|
for _, certMe := range t.Record.Certificates {
|
||||||
|
buf.WriteString(fmt.Sprintf("Certificate Issuer: %q\n", certMe.Certificate.Issuer))
|
||||||
|
buf.WriteString(fmt.Sprintf("Certificate Subject: %q\n", certMe.Certificate.Subject))
|
||||||
|
buf.WriteString(fmt.Sprintf("Certificate is CA: %t\n", certMe.Certificate.IsCA))
|
||||||
|
buf.WriteString(fmt.Sprintf("Certificate SHA256: %q\n", certMe.CertHash))
|
||||||
|
}
|
||||||
|
buf.WriteString(fmt.Sprintf("---------------SESSION END--------------------\n"))
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// PopulateClientHello takes a pointer to an etls ClientHelloMsg and writes it to the the TLSSession struct
|
||||||
|
func (t *TLSSession) PopulateClientHello(h *etls.ClientHelloMsg, cip string, sip string, cp string, sp string, ti time.Time) {
|
||||||
|
t.Record.ClientIP = cip
|
||||||
|
t.Record.ServerIP = sip
|
||||||
|
t.Record.ClientPort = cp
|
||||||
|
t.Record.ServerPort = sp
|
||||||
|
t.Record.Timestamp = ti
|
||||||
|
t.handShakeRecord.ETLSHandshakeClientHello = h
|
||||||
|
}
|
||||||
|
|
||||||
|
// PopulateServerHello takes a pointer to an etls ServerHelloMsg and writes it to the TLSSession struct
|
||||||
|
func (t *TLSSession) PopulateServerHello(h *etls.ServerHelloMsg) {
|
||||||
|
t.handShakeRecord.ETLSHandshakeServerHello = h
|
||||||
|
}
|
||||||
|
|
||||||
|
// PopulateCertificate takes a pointer to an etls ServerHelloMsg and writes it to the TLSSession struct
|
||||||
|
func (t *TLSSession) PopulateCertificate(c *etls.CertificateMsg) {
|
||||||
|
t.handShakeRecord.ETLSHandshakeCertificate = c
|
||||||
|
for _, asn1Data := range t.handShakeRecord.ETLSHandshakeCertificate.Certificates {
|
||||||
|
cert, err := x509.ParseCertificate(asn1Data)
|
||||||
|
if err != nil {
|
||||||
|
//return err
|
||||||
|
} else {
|
||||||
|
h := sha256.New()
|
||||||
|
h.Write(cert.Raw)
|
||||||
|
t.Record.Certificates = append(t.Record.Certificates, certMapElm{Certificate: cert, CertHash: fmt.Sprintf("%x", h.Sum(nil))})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// D4Fingerprinting computes fingerprints doh
|
||||||
|
func (t *TLSSession) D4Fingerprinting(fd string) bool {
|
||||||
|
switch fd {
|
||||||
|
case "ja3":
|
||||||
|
t.ja3()
|
||||||
|
case "ja3s":
|
||||||
|
t.ja3s()
|
||||||
|
case "tlsh":
|
||||||
|
t.d4fg()
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TLSSession) d4fg() string {
|
||||||
|
buf := t.Record.JA3 + t.Record.JA3S
|
||||||
|
for _, cert := range t.Record.Certificates {
|
||||||
|
buf += fmt.Sprintf("%q", cert.Issuer) + fmt.Sprintf("%q", cert.Subject)
|
||||||
|
}
|
||||||
|
buf = strings.Replace(buf, "-", "", -1)
|
||||||
|
buf = strings.Replace(buf, ",", "", -1)
|
||||||
|
buf = strings.Replace(buf, "\"", "", -1)
|
||||||
|
|
||||||
|
out, _ := tlsh.HashBytes([]byte(buf))
|
||||||
|
t.Record.TLSH = out.String()
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TLSSession) ja3s() bool {
|
||||||
|
var buf []byte
|
||||||
|
buf = strconv.AppendInt(buf, int64(t.handShakeRecord.ETLSHandshakeServerHello.Vers), 10)
|
||||||
|
// byte (44) is ","
|
||||||
|
buf = append(buf, byte(44))
|
||||||
|
|
||||||
|
// If there are Cipher Suites
|
||||||
|
buf = strconv.AppendInt(buf, int64(t.handShakeRecord.ETLSHandshakeServerHello.CipherSuite), 10)
|
||||||
|
buf = append(buf, byte(44))
|
||||||
|
|
||||||
|
// If there are extensions
|
||||||
|
if len(t.handShakeRecord.ETLSHandshakeServerHello.AllExtensions) > 0 {
|
||||||
|
for i, e := range t.handShakeRecord.ETLSHandshakeServerHello.AllExtensions {
|
||||||
|
// TODO check this grease thingy
|
||||||
|
if grease[uint16(e)] == false {
|
||||||
|
buf = strconv.AppendInt(buf, int64(e), 10)
|
||||||
|
if (i + 1) < len(t.handShakeRecord.ETLSHandshakeServerHello.AllExtensions) {
|
||||||
|
// byte(45) is "-"
|
||||||
|
buf = append(buf, byte(45))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Record.JA3S = string(buf)
|
||||||
|
tmp := md5.Sum(buf)
|
||||||
|
t.Record.JA3SDigest = hex.EncodeToString(tmp[:])
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TLSSession) ja3() bool {
|
||||||
|
var buf []byte
|
||||||
|
buf = strconv.AppendInt(buf, int64(t.handShakeRecord.ETLSHandshakeClientHello.Vers), 10)
|
||||||
|
// byte (44) is ","
|
||||||
|
buf = append(buf, byte(44))
|
||||||
|
|
||||||
|
// If there are Cipher Suites
|
||||||
|
if len(t.handShakeRecord.ETLSHandshakeClientHello.CipherSuites) > 0 {
|
||||||
|
for i, cs := range t.handShakeRecord.ETLSHandshakeClientHello.CipherSuites {
|
||||||
|
buf = strconv.AppendInt(buf, int64(cs), 10)
|
||||||
|
// byte(45) is "-"
|
||||||
|
if (i + 1) < len(t.handShakeRecord.ETLSHandshakeClientHello.CipherSuites) {
|
||||||
|
buf = append(buf, byte(45))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf = append(buf, byte(44))
|
||||||
|
|
||||||
|
// If there are extensions
|
||||||
|
if len(t.handShakeRecord.ETLSHandshakeClientHello.AllExtensions) > 0 {
|
||||||
|
for i, e := range t.handShakeRecord.ETLSHandshakeClientHello.AllExtensions {
|
||||||
|
// TODO check this grease thingy
|
||||||
|
if grease[uint16(e)] == false {
|
||||||
|
buf = strconv.AppendInt(buf, int64(e), 10)
|
||||||
|
if (i + 1) < len(t.handShakeRecord.ETLSHandshakeClientHello.AllExtensions) {
|
||||||
|
buf = append(buf, byte(45))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf = append(buf, byte(44))
|
||||||
|
|
||||||
|
// If there are Supported Curves
|
||||||
|
if len(t.handShakeRecord.ETLSHandshakeClientHello.SupportedCurves) > 0 {
|
||||||
|
for i, cs := range t.handShakeRecord.ETLSHandshakeClientHello.SupportedCurves {
|
||||||
|
buf = strconv.AppendInt(buf, int64(cs), 10)
|
||||||
|
if (i + 1) < len(t.handShakeRecord.ETLSHandshakeClientHello.SupportedCurves) {
|
||||||
|
buf = append(buf, byte(45))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf = append(buf, byte(44))
|
||||||
|
|
||||||
|
// If there are Supported Points
|
||||||
|
if len(t.handShakeRecord.ETLSHandshakeClientHello.SupportedPoints) > 0 {
|
||||||
|
for i, cs := range t.handShakeRecord.ETLSHandshakeClientHello.SupportedPoints {
|
||||||
|
buf = strconv.AppendInt(buf, int64(cs), 10)
|
||||||
|
if (i + 1) < len(t.handShakeRecord.ETLSHandshakeClientHello.SupportedPoints) {
|
||||||
|
buf = append(buf, byte(45))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Record.JA3 = string(buf)
|
||||||
|
tmp := md5.Sum(buf)
|
||||||
|
t.Record.JA3Digest = hex.EncodeToString(tmp[:])
|
||||||
|
return true
|
||||||
|
}
|
|
@ -14,9 +14,9 @@ import (
|
||||||
type ETLSHandshakeRecord struct {
|
type ETLSHandshakeRecord struct {
|
||||||
ETLSRecordHeader
|
ETLSRecordHeader
|
||||||
ETLSHandshakeMsgType uint8
|
ETLSHandshakeMsgType uint8
|
||||||
ETLSHandshakeServerHello *serverHelloMsg
|
ETLSHandshakeServerHello *ServerHelloMsg
|
||||||
ETLSHandshakeClientHello *clientHelloMsg
|
ETLSHandshakeClientHello *ClientHelloMsg
|
||||||
ETLSHandshakeCertificate *certificateMsg
|
ETLSHandshakeCertificate *CertificateMsg
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeFromBytes decodes the slice into the ETLS struct.
|
// DecodeFromBytes decodes the slice into the ETLS struct.
|
||||||
|
@ -30,15 +30,15 @@ func (t *ETLSHandshakeRecord) decodeFromBytes(h ETLSRecordHeader, data []byte, d
|
||||||
switch uint8(data[0]) {
|
switch uint8(data[0]) {
|
||||||
case typeClientHello:
|
case typeClientHello:
|
||||||
t.ETLSHandshakeMsgType = typeClientHello
|
t.ETLSHandshakeMsgType = typeClientHello
|
||||||
t.ETLSHandshakeClientHello = new(clientHelloMsg)
|
t.ETLSHandshakeClientHello = new(ClientHelloMsg)
|
||||||
t.ETLSHandshakeClientHello.unmarshal(data)
|
t.ETLSHandshakeClientHello.unmarshal(data)
|
||||||
case typeServerHello:
|
case typeServerHello:
|
||||||
t.ETLSHandshakeMsgType = typeServerHello
|
t.ETLSHandshakeMsgType = typeServerHello
|
||||||
t.ETLSHandshakeServerHello = new(serverHelloMsg)
|
t.ETLSHandshakeServerHello = new(ServerHelloMsg)
|
||||||
t.ETLSHandshakeServerHello.unmarshal(data)
|
t.ETLSHandshakeServerHello.unmarshal(data)
|
||||||
case typeCertificate:
|
case typeCertificate:
|
||||||
t.ETLSHandshakeMsgType = typeCertificate
|
t.ETLSHandshakeMsgType = typeCertificate
|
||||||
t.ETLSHandshakeCertificate = new(certificateMsg)
|
t.ETLSHandshakeCertificate = new(CertificateMsg)
|
||||||
t.ETLSHandshakeCertificate.unmarshal(data)
|
t.ETLSHandshakeCertificate.unmarshal(data)
|
||||||
}
|
}
|
||||||
// Please see the following url if you are interested into implementing the rest:
|
// Please see the following url if you are interested into implementing the rest:
|
||||||
|
|
|
@ -12,7 +12,7 @@ type handshakeMessage interface {
|
||||||
unmarshal([]byte) bool
|
unmarshal([]byte) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type clientHelloMsg struct {
|
type ClientHelloMsg struct {
|
||||||
raw []byte
|
raw []byte
|
||||||
extensions map[Extension]uint16
|
extensions map[Extension]uint16
|
||||||
AllExtensions []uint16
|
AllExtensions []uint16
|
||||||
|
@ -35,7 +35,7 @@ type clientHelloMsg struct {
|
||||||
alpnProtocols []string
|
alpnProtocols []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *clientHelloMsg) unmarshal(data []byte) bool {
|
func (m *ClientHelloMsg) unmarshal(data []byte) bool {
|
||||||
if len(data) < 42 {
|
if len(data) < 42 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -158,7 +158,7 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
type serverHelloMsg struct {
|
type ServerHelloMsg struct {
|
||||||
raw []byte
|
raw []byte
|
||||||
extensions map[Extension]uint16
|
extensions map[Extension]uint16
|
||||||
AllExtensions []uint16
|
AllExtensions []uint16
|
||||||
|
@ -177,7 +177,7 @@ type serverHelloMsg struct {
|
||||||
alpnProtocol string
|
alpnProtocol string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *serverHelloMsg) unmarshal(data []byte) bool {
|
func (m *ServerHelloMsg) unmarshal(data []byte) bool {
|
||||||
if len(data) < 42 {
|
if len(data) < 42 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -327,12 +327,12 @@ func (m *serverHelloMsg) unmarshal(data []byte) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
type certificateMsg struct {
|
type CertificateMsg struct {
|
||||||
raw []byte
|
raw []byte
|
||||||
Certificates [][]byte
|
Certificates [][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *certificateMsg) unmarshal(data []byte) bool {
|
func (m *CertificateMsg) unmarshal(data []byte) bool {
|
||||||
if len(data) < 7 {
|
if len(data) < 7 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue