resolves #2 - Took option 2
parent
e59ea05e57
commit
3cf6ece2e4
|
@ -1,14 +1,11 @@
|
||||||
# sensor-d4-tls-fingerprinting
|
# sensor-d4-tls-fingerprinting
|
||||||
Extracts TLS certificates from pcap files or network interfaces (tcpreassembly is done thanks to gopacket), fingerprints TLS client/server interactions with ja3/ja3s and print output in JSON form.
|
Extracts TLS certificates from pcap files or network interfaces (tcpreassembly is done thanks to gopacket), fingerprints TLS client/server interactions with ja3/ja3s and print output in JSON form.
|
||||||
# Use
|
# Use
|
||||||
This project is currently in its very early stage and relies on a customized version of ![gopacket](http://github.com/google/gopacket "gopacket link").
|
This project is currently in its very early stage and should not be used in production.
|
||||||
Check the list of issues.
|
Check the list of issues.
|
||||||
## Install dependencies & go get
|
## Install dependencies & go get
|
||||||
``` shell
|
``` shell
|
||||||
$go get github.com/gallypette/gopacket
|
|
||||||
$go get github.com/google/gopacket
|
$go get github.com/google/gopacket
|
||||||
$cd $GOPATH/src/github.com/google/gopacket
|
|
||||||
$git remote add fork github.com/gallypette/gopacket
|
|
||||||
$go get github.com/D4-project/sensor-d4-tls-fingerprinting
|
$go get github.com/D4-project/sensor-d4-tls-fingerprinting
|
||||||
```
|
```
|
||||||
make allows to compile for amd64 and arm ATM.
|
make allows to compile for amd64 and arm ATM.
|
||||||
|
|
68
d4-tlsf.go
68
d4-tlsf.go
|
@ -24,14 +24,13 @@ import (
|
||||||
"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"
|
||||||
|
|
||||||
// ATM add a fork to gopacket like this to pull my TLS code:
|
|
||||||
// $go get github.com/gallypette/gopacket
|
|
||||||
// $cd $GOPATH/src/github.com/google/gopacket
|
|
||||||
// $git remote add fork github.com/gallypette/gopacket
|
|
||||||
"github.com/google/gopacket/layers"
|
"github.com/google/gopacket/layers"
|
||||||
"github.com/google/gopacket/pcap"
|
"github.com/google/gopacket/pcap"
|
||||||
"github.com/google/gopacket/reassembly"
|
"github.com/google/gopacket/reassembly"
|
||||||
|
|
||||||
|
// My Extended TLS layer
|
||||||
|
|
||||||
|
"github.com/D4-project/sensor-d4-tls-fingerprinting/etls"
|
||||||
)
|
)
|
||||||
|
|
||||||
var nodefrag = flag.Bool("nodefrag", false, "If true, do not do IPv4 defrag")
|
var nodefrag = flag.Bool("nodefrag", false, "If true, do not do IPv4 defrag")
|
||||||
|
@ -47,6 +46,9 @@ var quiet = flag.Bool("quiet", false, "Be quiet regarding errors")
|
||||||
var iface = flag.String("i", "eth0", "Interface to read packets from")
|
var iface = flag.String("i", "eth0", "Interface to read packets from")
|
||||||
var fname = flag.String("r", "", "Filename to read from, overrides -i")
|
var fname = flag.String("r", "", "Filename to read from, overrides -i")
|
||||||
|
|
||||||
|
// decoding
|
||||||
|
//var LayerTypeETLS gopacket.LayerType
|
||||||
|
|
||||||
// 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")
|
||||||
|
@ -68,7 +70,7 @@ type SessionRecord struct {
|
||||||
|
|
||||||
type TLSSession struct {
|
type TLSSession struct {
|
||||||
record SessionRecord
|
record SessionRecord
|
||||||
tlsHdskR layers.TLSHandshakeRecord
|
tlsHdskR etls.ETLSHandshakeRecord
|
||||||
}
|
}
|
||||||
|
|
||||||
var grease = map[uint16]bool{
|
var grease = map[uint16]bool{
|
||||||
|
@ -231,25 +233,25 @@ func (t *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.Ass
|
||||||
if length > 0 {
|
if length > 0 {
|
||||||
// We can't rely on TLS length field has there can be several successive Record Layers
|
// We can't rely on TLS length field has there can be several successive Record Layers
|
||||||
// We attempt to decode, and if it fails, we keep the slice for later.
|
// We attempt to decode, and if it fails, we keep the slice for later.
|
||||||
// Now we attempts TLS decoding
|
// Now we attempts Extended TLS decoding
|
||||||
tls := &layers.TLS{}
|
tls := &etls.ETLS{}
|
||||||
var decoded []gopacket.LayerType
|
var decoded []gopacket.LayerType
|
||||||
|
p := gopacket.NewDecodingLayerParser(etls.LayerTypeETLS, tls)
|
||||||
|
p.DecodingLayerParserOptions.IgnoreUnsupported = true
|
||||||
// First we check if the packet is fragmented
|
// First we check if the packet is fragmented
|
||||||
p := gopacket.NewDecodingLayerParser(layers.LayerTypeTLS, tls)
|
|
||||||
err := p.DecodeLayers(data, &decoded)
|
err := p.DecodeLayers(data, &decoded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Error("TLS-parser", "Failed to decode TLS: %v\n", err)
|
// If it's malformed as it we keep for next round
|
||||||
// Debug("RAW %s\n", hex.Dump(data))
|
|
||||||
sg.KeepFrom(0)
|
sg.KeepFrom(0)
|
||||||
} else {
|
} else {
|
||||||
//Debug("TLS: %s\n", gopacket.LayerDump(tls))
|
//Debug("TLS: %s\n", gopacket.LayerDump(tls))
|
||||||
// Debug("TLS: %s\n", gopacket.LayerGoString(tls))
|
// Debug("TLS: %s\n", gopacket.LayerGoString(tls))
|
||||||
if tls.Handshake != nil {
|
if tls.Handshake != nil {
|
||||||
for _, tlsrecord := range tls.Handshake {
|
for _, tlsrecord := range tls.Handshake {
|
||||||
switch tlsrecord.TLSHandshakeMsgType {
|
switch tlsrecord.ETLSHandshakeMsgType {
|
||||||
// Client Hello
|
// Client Hello
|
||||||
case 1:
|
case 1:
|
||||||
t.tlsSession.tlsHdskR.TLSHandshakeClientHello = tlsrecord.TLSHandshakeClientHello
|
t.tlsSession.tlsHdskR.ETLSHandshakeClientHello = tlsrecord.ETLSHandshakeClientHello
|
||||||
t.tlsSession.record.ClientIP, t.tlsSession.record.ServerIP, t.tlsSession.record.ClientPort, t.tlsSession.record.ServerPort = getIPPorts(t)
|
t.tlsSession.record.ClientIP, t.tlsSession.record.ServerIP, t.tlsSession.record.ClientPort, t.tlsSession.record.ServerPort = getIPPorts(t)
|
||||||
// Set up first seen
|
// Set up first seen
|
||||||
info := sg.CaptureInfo(0)
|
info := sg.CaptureInfo(0)
|
||||||
|
@ -257,12 +259,12 @@ func (t *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.Ass
|
||||||
t.tlsSession.gatherJa3()
|
t.tlsSession.gatherJa3()
|
||||||
// Server Hello
|
// Server Hello
|
||||||
case 2:
|
case 2:
|
||||||
t.tlsSession.tlsHdskR.TLSHandshakeServerHello = tlsrecord.TLSHandshakeServerHello
|
t.tlsSession.tlsHdskR.ETLSHandshakeServerHello = tlsrecord.ETLSHandshakeServerHello
|
||||||
t.tlsSession.gatherJa3s()
|
t.tlsSession.gatherJa3s()
|
||||||
// Server Certificate
|
// Server Certificate
|
||||||
case 11:
|
case 11:
|
||||||
//certs := make([]*x509.Certificate, len(tlsrecord.TLSHandshakeCertificate.Certificates))
|
//certs := make([]*x509.Certificate, len(tlsrecord.TLSHandshakeCertificate.Certificates))
|
||||||
for _, asn1Data := range tlsrecord.TLSHandshakeCertificate.Certificates {
|
for _, asn1Data := range tlsrecord.ETLSHandshakeCertificate.Certificates {
|
||||||
cert, err := x509.ParseCertificate(asn1Data)
|
cert, err := x509.ParseCertificate(asn1Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("tls: failed to parse certificate from server: " + err.Error())
|
panic("tls: failed to parse certificate from server: " + err.Error())
|
||||||
|
@ -294,21 +296,21 @@ func getIPPorts(t *tcpStream) (string, string, string, string) {
|
||||||
|
|
||||||
func (ts *TLSSession) gatherJa3s() bool {
|
func (ts *TLSSession) gatherJa3s() bool {
|
||||||
var buf []byte
|
var buf []byte
|
||||||
buf = strconv.AppendInt(buf, int64(ts.tlsHdskR.TLSHandshakeServerHello.Vers), 10)
|
buf = strconv.AppendInt(buf, int64(ts.tlsHdskR.ETLSHandshakeServerHello.Vers), 10)
|
||||||
// byte (44) is ","
|
// byte (44) is ","
|
||||||
buf = append(buf, byte(44))
|
buf = append(buf, byte(44))
|
||||||
|
|
||||||
// If there are Cipher Suites
|
// If there are Cipher Suites
|
||||||
buf = strconv.AppendInt(buf, int64(ts.tlsHdskR.TLSHandshakeServerHello.CipherSuite), 10)
|
buf = strconv.AppendInt(buf, int64(ts.tlsHdskR.ETLSHandshakeServerHello.CipherSuite), 10)
|
||||||
buf = append(buf, byte(44))
|
buf = append(buf, byte(44))
|
||||||
|
|
||||||
// If there are extensions
|
// If there are extensions
|
||||||
if len(ts.tlsHdskR.TLSHandshakeServerHello.AllExtensions) > 0 {
|
if len(ts.tlsHdskR.ETLSHandshakeServerHello.AllExtensions) > 0 {
|
||||||
for i, e := range ts.tlsHdskR.TLSHandshakeServerHello.AllExtensions {
|
for i, e := range ts.tlsHdskR.ETLSHandshakeServerHello.AllExtensions {
|
||||||
// TODO check this grease thingy
|
// TODO check this grease thingy
|
||||||
if grease[uint16(e)] == false {
|
if grease[uint16(e)] == false {
|
||||||
buf = strconv.AppendInt(buf, int64(e), 10)
|
buf = strconv.AppendInt(buf, int64(e), 10)
|
||||||
if (i + 1) < len(ts.tlsHdskR.TLSHandshakeServerHello.AllExtensions) {
|
if (i + 1) < len(ts.tlsHdskR.ETLSHandshakeServerHello.AllExtensions) {
|
||||||
// byte(45) is "-"
|
// byte(45) is "-"
|
||||||
buf = append(buf, byte(45))
|
buf = append(buf, byte(45))
|
||||||
}
|
}
|
||||||
|
@ -325,16 +327,16 @@ func (ts *TLSSession) gatherJa3s() bool {
|
||||||
|
|
||||||
func (ts *TLSSession) gatherJa3() bool {
|
func (ts *TLSSession) gatherJa3() bool {
|
||||||
var buf []byte
|
var buf []byte
|
||||||
buf = strconv.AppendInt(buf, int64(ts.tlsHdskR.TLSHandshakeClientHello.Vers), 10)
|
buf = strconv.AppendInt(buf, int64(ts.tlsHdskR.ETLSHandshakeClientHello.Vers), 10)
|
||||||
// byte (44) is ","
|
// byte (44) is ","
|
||||||
buf = append(buf, byte(44))
|
buf = append(buf, byte(44))
|
||||||
|
|
||||||
// If there are Cipher Suites
|
// If there are Cipher Suites
|
||||||
if len(ts.tlsHdskR.TLSHandshakeClientHello.CipherSuites) > 0 {
|
if len(ts.tlsHdskR.ETLSHandshakeClientHello.CipherSuites) > 0 {
|
||||||
for i, cs := range ts.tlsHdskR.TLSHandshakeClientHello.CipherSuites {
|
for i, cs := range ts.tlsHdskR.ETLSHandshakeClientHello.CipherSuites {
|
||||||
buf = strconv.AppendInt(buf, int64(cs), 10)
|
buf = strconv.AppendInt(buf, int64(cs), 10)
|
||||||
// byte(45) is "-"
|
// byte(45) is "-"
|
||||||
if (i + 1) < len(ts.tlsHdskR.TLSHandshakeClientHello.CipherSuites) {
|
if (i + 1) < len(ts.tlsHdskR.ETLSHandshakeClientHello.CipherSuites) {
|
||||||
buf = append(buf, byte(45))
|
buf = append(buf, byte(45))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -342,12 +344,12 @@ func (ts *TLSSession) gatherJa3() bool {
|
||||||
buf = append(buf, byte(44))
|
buf = append(buf, byte(44))
|
||||||
|
|
||||||
// If there are extensions
|
// If there are extensions
|
||||||
if len(ts.tlsHdskR.TLSHandshakeClientHello.AllExtensions) > 0 {
|
if len(ts.tlsHdskR.ETLSHandshakeClientHello.AllExtensions) > 0 {
|
||||||
for i, e := range ts.tlsHdskR.TLSHandshakeClientHello.AllExtensions {
|
for i, e := range ts.tlsHdskR.ETLSHandshakeClientHello.AllExtensions {
|
||||||
// TODO check this grease thingy
|
// TODO check this grease thingy
|
||||||
if grease[uint16(e)] == false {
|
if grease[uint16(e)] == false {
|
||||||
buf = strconv.AppendInt(buf, int64(e), 10)
|
buf = strconv.AppendInt(buf, int64(e), 10)
|
||||||
if (i + 1) < len(ts.tlsHdskR.TLSHandshakeClientHello.AllExtensions) {
|
if (i + 1) < len(ts.tlsHdskR.ETLSHandshakeClientHello.AllExtensions) {
|
||||||
buf = append(buf, byte(45))
|
buf = append(buf, byte(45))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -356,10 +358,10 @@ func (ts *TLSSession) gatherJa3() bool {
|
||||||
buf = append(buf, byte(44))
|
buf = append(buf, byte(44))
|
||||||
|
|
||||||
// If there are Supported Curves
|
// If there are Supported Curves
|
||||||
if len(ts.tlsHdskR.TLSHandshakeClientHello.SupportedCurves) > 0 {
|
if len(ts.tlsHdskR.ETLSHandshakeClientHello.SupportedCurves) > 0 {
|
||||||
for i, cs := range ts.tlsHdskR.TLSHandshakeClientHello.SupportedCurves {
|
for i, cs := range ts.tlsHdskR.ETLSHandshakeClientHello.SupportedCurves {
|
||||||
buf = strconv.AppendInt(buf, int64(cs), 10)
|
buf = strconv.AppendInt(buf, int64(cs), 10)
|
||||||
if (i + 1) < len(ts.tlsHdskR.TLSHandshakeClientHello.SupportedCurves) {
|
if (i + 1) < len(ts.tlsHdskR.ETLSHandshakeClientHello.SupportedCurves) {
|
||||||
buf = append(buf, byte(45))
|
buf = append(buf, byte(45))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -367,10 +369,10 @@ func (ts *TLSSession) gatherJa3() bool {
|
||||||
buf = append(buf, byte(44))
|
buf = append(buf, byte(44))
|
||||||
|
|
||||||
// If there are Supported Points
|
// If there are Supported Points
|
||||||
if len(ts.tlsHdskR.TLSHandshakeClientHello.SupportedPoints) > 0 {
|
if len(ts.tlsHdskR.ETLSHandshakeClientHello.SupportedPoints) > 0 {
|
||||||
for i, cs := range ts.tlsHdskR.TLSHandshakeClientHello.SupportedPoints {
|
for i, cs := range ts.tlsHdskR.ETLSHandshakeClientHello.SupportedPoints {
|
||||||
buf = strconv.AppendInt(buf, int64(cs), 10)
|
buf = strconv.AppendInt(buf, int64(cs), 10)
|
||||||
if (i + 1) < len(ts.tlsHdskR.TLSHandshakeClientHello.SupportedPoints) {
|
if (i + 1) < len(ts.tlsHdskR.ETLSHandshakeClientHello.SupportedPoints) {
|
||||||
buf = append(buf, byte(45))
|
buf = append(buf, byte(45))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,215 @@
|
||||||
|
// Copyright 2018 The GoPacket Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style license
|
||||||
|
// that can be found in the LICENSE file in the root of the source
|
||||||
|
// tree.
|
||||||
|
|
||||||
|
package etls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/google/gopacket"
|
||||||
|
"github.com/google/gopacket/layers"
|
||||||
|
)
|
||||||
|
|
||||||
|
var LayerTypeETLS gopacket.LayerType
|
||||||
|
|
||||||
|
// ETLSType defines the type of data after the ETLS Record
|
||||||
|
type ETLSType uint8
|
||||||
|
|
||||||
|
// ETLSType known values.
|
||||||
|
const (
|
||||||
|
ETLSChangeCipherSpec ETLSType = 20
|
||||||
|
ETLSAlert ETLSType = 21
|
||||||
|
ETLSHandshake ETLSType = 22
|
||||||
|
ETLSApplicationData ETLSType = 23
|
||||||
|
ETLSUnknown ETLSType = 255
|
||||||
|
)
|
||||||
|
|
||||||
|
// String shows the register type nicely formatted
|
||||||
|
func (tt ETLSType) String() string {
|
||||||
|
switch tt {
|
||||||
|
default:
|
||||||
|
return "Unknown"
|
||||||
|
case ETLSChangeCipherSpec:
|
||||||
|
return "Change Cipher Spec"
|
||||||
|
case ETLSAlert:
|
||||||
|
return "Alert"
|
||||||
|
case ETLSHandshake:
|
||||||
|
return "Handshake"
|
||||||
|
case ETLSApplicationData:
|
||||||
|
return "Application Data"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ETLSVersion represents the ETLS version in numeric format
|
||||||
|
type ETLSVersion uint16
|
||||||
|
|
||||||
|
// Strings shows the ETLS version nicely formatted
|
||||||
|
func (tv ETLSVersion) String() string {
|
||||||
|
switch tv {
|
||||||
|
default:
|
||||||
|
return "Unknown"
|
||||||
|
case 0x0200:
|
||||||
|
return "SSL 2.0"
|
||||||
|
case 0x0300:
|
||||||
|
return "SSL 3.0"
|
||||||
|
case 0x0301:
|
||||||
|
return "TLS 1.0"
|
||||||
|
case 0x0302:
|
||||||
|
return "TLS 1.1"
|
||||||
|
case 0x0303:
|
||||||
|
return "TLS 1.2"
|
||||||
|
case 0x0304:
|
||||||
|
return "TLS 1.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ETLS is specified in RFC 5246
|
||||||
|
//
|
||||||
|
// ETLS Record Protocol
|
||||||
|
// 0 1 2 3 4 5 6 7 8
|
||||||
|
// +--+--+--+--+--+--+--+--+
|
||||||
|
// | Content Type |
|
||||||
|
// +--+--+--+--+--+--+--+--+
|
||||||
|
// | Version (major) |
|
||||||
|
// +--+--+--+--+--+--+--+--+
|
||||||
|
// | Version (minor) |
|
||||||
|
// +--+--+--+--+--+--+--+--+
|
||||||
|
// | Length |
|
||||||
|
// +--+--+--+--+--+--+--+--+
|
||||||
|
// | Length |
|
||||||
|
// +--+--+--+--+--+--+--+--+
|
||||||
|
|
||||||
|
// ETLS is actually a slide of ETLSrecord structures
|
||||||
|
type ETLS struct {
|
||||||
|
layers.BaseLayer
|
||||||
|
|
||||||
|
// ETLS Records
|
||||||
|
ChangeCipherSpec []ETLSChangeCipherSpecRecord
|
||||||
|
Handshake []ETLSHandshakeRecord
|
||||||
|
AppData []ETLSAppDataRecord
|
||||||
|
Alert []ETLSAlertRecord
|
||||||
|
}
|
||||||
|
|
||||||
|
// ETLSRecordHeader contains all the information that each ETLS Record types should have
|
||||||
|
type ETLSRecordHeader struct {
|
||||||
|
ContentType ETLSType
|
||||||
|
Version ETLSVersion
|
||||||
|
Length uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// LayerType returns gopacket.LayerTypeETLS.
|
||||||
|
func (t *ETLS) LayerType() gopacket.LayerType { return LayerTypeETLS }
|
||||||
|
|
||||||
|
// decodeETLS decodes the byte slice into a ETLS type. It also
|
||||||
|
// setups the application Layer in PacketBuilder.
|
||||||
|
func decodeETLS(data []byte, p gopacket.PacketBuilder) error {
|
||||||
|
t := &ETLS{}
|
||||||
|
err := t.DecodeFromBytes(data, p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.AddLayer(t)
|
||||||
|
p.SetApplicationLayer(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeFromBytes decodes the slice into the ETLS struct.
|
||||||
|
func (t *ETLS) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
|
||||||
|
t.BaseLayer.Contents = data
|
||||||
|
t.BaseLayer.Payload = nil
|
||||||
|
|
||||||
|
t.ChangeCipherSpec = t.ChangeCipherSpec[:0]
|
||||||
|
t.Handshake = t.Handshake[:0]
|
||||||
|
t.AppData = t.AppData[:0]
|
||||||
|
t.Alert = t.Alert[:0]
|
||||||
|
|
||||||
|
return t.decodeETLSRecords(data, df)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ETLS) decodeETLSRecords(data []byte, df gopacket.DecodeFeedback) error {
|
||||||
|
if len(data) < 5 {
|
||||||
|
df.SetTruncated()
|
||||||
|
return errors.New("ETLS record too short")
|
||||||
|
}
|
||||||
|
|
||||||
|
// since there are no further layers, the baselayer's content is
|
||||||
|
// pointing to this layer
|
||||||
|
t.BaseLayer = layers.BaseLayer{Contents: data[:len(data)]}
|
||||||
|
|
||||||
|
var h ETLSRecordHeader
|
||||||
|
h.ContentType = ETLSType(data[0])
|
||||||
|
h.Version = ETLSVersion(binary.BigEndian.Uint16(data[1:3]))
|
||||||
|
h.Length = binary.BigEndian.Uint16(data[3:5])
|
||||||
|
|
||||||
|
if h.ContentType.String() == "Unknown" {
|
||||||
|
return errors.New("Unknown ETLS record type")
|
||||||
|
}
|
||||||
|
|
||||||
|
hl := 5 // header length
|
||||||
|
tl := hl + int(h.Length)
|
||||||
|
if len(data) < tl {
|
||||||
|
df.SetTruncated()
|
||||||
|
return errors.New("ETLS packet length mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch h.ContentType {
|
||||||
|
default:
|
||||||
|
return errors.New("Unknown ETLS record type")
|
||||||
|
case ETLSChangeCipherSpec:
|
||||||
|
var r ETLSChangeCipherSpecRecord
|
||||||
|
e := r.decodeFromBytes(h, data[hl:tl], df)
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
t.ChangeCipherSpec = append(t.ChangeCipherSpec, r)
|
||||||
|
case ETLSAlert:
|
||||||
|
var r ETLSAlertRecord
|
||||||
|
e := r.decodeFromBytes(h, data[hl:tl], df)
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
t.Alert = append(t.Alert, r)
|
||||||
|
case ETLSHandshake:
|
||||||
|
var r ETLSHandshakeRecord
|
||||||
|
e := r.decodeFromBytes(h, data[hl:tl], df)
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
t.Handshake = append(t.Handshake, r)
|
||||||
|
case ETLSApplicationData:
|
||||||
|
var r ETLSAppDataRecord
|
||||||
|
e := r.decodeFromBytes(h, data[hl:tl], df)
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
t.AppData = append(t.AppData, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) == tl {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return t.decodeETLSRecords(data[tl:len(data)], df)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanDecode implements gopacket.DecodingLayer.
|
||||||
|
func (t *ETLS) CanDecode() gopacket.LayerClass {
|
||||||
|
return LayerTypeETLS
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextLayerType implements gopacket.DecodingLayer.
|
||||||
|
func (t *ETLS) NextLayerType() gopacket.LayerType {
|
||||||
|
return gopacket.LayerTypeZero
|
||||||
|
}
|
||||||
|
|
||||||
|
// Payload returns nil, since ETLS encrypted payload is inside ETLSAppDataRecord
|
||||||
|
func (t *ETLS) Payload() []byte {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
_ = gopacket.RegisterLayerType(1337, gopacket.LayerTypeMetadata{Name: "ETLS", Decoder: gopacket.DecodeFunc(decodeETLS)})
|
||||||
|
}
|
|
@ -0,0 +1,165 @@
|
||||||
|
// Copyright 2018 The GoPacket Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style license
|
||||||
|
// that can be found in the LICENSE file in the root of the source
|
||||||
|
// tree.
|
||||||
|
|
||||||
|
package etls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/google/gopacket"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ETLSAlertLevel defines the alert level data type
|
||||||
|
type ETLSAlertLevel uint8
|
||||||
|
|
||||||
|
// ETLSAlertDescr defines the alert descrption data type
|
||||||
|
type ETLSAlertDescr uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
ETLSAlertWarning ETLSAlertLevel = 1
|
||||||
|
ETLSAlertFatal ETLSAlertLevel = 2
|
||||||
|
ETLSAlertUnknownLevel ETLSAlertLevel = 255
|
||||||
|
|
||||||
|
ETLSAlertCloseNotify ETLSAlertDescr = 0
|
||||||
|
ETLSAlertUnexpectedMessage ETLSAlertDescr = 10
|
||||||
|
ETLSAlertBadRecordMac ETLSAlertDescr = 20
|
||||||
|
ETLSAlertDecryptionFailedRESERVED ETLSAlertDescr = 21
|
||||||
|
ETLSAlertRecordOverflow ETLSAlertDescr = 22
|
||||||
|
ETLSAlertDecompressionFailure ETLSAlertDescr = 30
|
||||||
|
ETLSAlertHandshakeFailure ETLSAlertDescr = 40
|
||||||
|
ETLSAlertNoCertificateRESERVED ETLSAlertDescr = 41
|
||||||
|
ETLSAlertBadCertificate ETLSAlertDescr = 42
|
||||||
|
ETLSAlertUnsupportedCertificate ETLSAlertDescr = 43
|
||||||
|
ETLSAlertCertificateRevoked ETLSAlertDescr = 44
|
||||||
|
ETLSAlertCertificateExpired ETLSAlertDescr = 45
|
||||||
|
ETLSAlertCertificateUnknown ETLSAlertDescr = 46
|
||||||
|
ETLSAlertIllegalParameter ETLSAlertDescr = 47
|
||||||
|
ETLSAlertUnknownCa ETLSAlertDescr = 48
|
||||||
|
ETLSAlertAccessDenied ETLSAlertDescr = 49
|
||||||
|
ETLSAlertDecodeError ETLSAlertDescr = 50
|
||||||
|
ETLSAlertDecryptError ETLSAlertDescr = 51
|
||||||
|
ETLSAlertExportRestrictionRESERVED ETLSAlertDescr = 60
|
||||||
|
ETLSAlertProtocolVersion ETLSAlertDescr = 70
|
||||||
|
ETLSAlertInsufficientSecurity ETLSAlertDescr = 71
|
||||||
|
ETLSAlertInternalError ETLSAlertDescr = 80
|
||||||
|
ETLSAlertUserCanceled ETLSAlertDescr = 90
|
||||||
|
ETLSAlertNoRenegotiation ETLSAlertDescr = 100
|
||||||
|
ETLSAlertUnsupportedExtension ETLSAlertDescr = 110
|
||||||
|
ETLSAlertUnknownDescription ETLSAlertDescr = 255
|
||||||
|
)
|
||||||
|
|
||||||
|
// ETLS Alert
|
||||||
|
// 0 1 2 3 4 5 6 7 8
|
||||||
|
// +--+--+--+--+--+--+--+--+
|
||||||
|
// | Level |
|
||||||
|
// +--+--+--+--+--+--+--+--+
|
||||||
|
// | Description |
|
||||||
|
// +--+--+--+--+--+--+--+--+
|
||||||
|
|
||||||
|
// ETLSAlertRecord contains all the information that each Alert Record type should have
|
||||||
|
type ETLSAlertRecord struct {
|
||||||
|
ETLSRecordHeader
|
||||||
|
|
||||||
|
Level ETLSAlertLevel
|
||||||
|
Description ETLSAlertDescr
|
||||||
|
|
||||||
|
EncryptedMsg []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeFromBytes decodes the slice into the ETLS struct.
|
||||||
|
func (t *ETLSAlertRecord) decodeFromBytes(h ETLSRecordHeader, data []byte, df gopacket.DecodeFeedback) error {
|
||||||
|
// ETLS Record Header
|
||||||
|
t.ContentType = h.ContentType
|
||||||
|
t.Version = h.Version
|
||||||
|
t.Length = h.Length
|
||||||
|
|
||||||
|
if len(data) < 2 {
|
||||||
|
df.SetTruncated()
|
||||||
|
return errors.New("ETLS Alert packet too short")
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Length == 2 {
|
||||||
|
t.Level = ETLSAlertLevel(data[0])
|
||||||
|
t.Description = ETLSAlertDescr(data[1])
|
||||||
|
} else {
|
||||||
|
t.Level = ETLSAlertUnknownLevel
|
||||||
|
t.Description = ETLSAlertUnknownDescription
|
||||||
|
t.EncryptedMsg = data
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strings shows the ETLS alert level nicely formatted
|
||||||
|
func (al ETLSAlertLevel) String() string {
|
||||||
|
switch al {
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("Unknown(%d)", al)
|
||||||
|
case ETLSAlertWarning:
|
||||||
|
return "Warning"
|
||||||
|
case ETLSAlertFatal:
|
||||||
|
return "Fatal"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strings shows the ETLS alert description nicely formatted
|
||||||
|
func (ad ETLSAlertDescr) String() string {
|
||||||
|
switch ad {
|
||||||
|
default:
|
||||||
|
return "Unknown"
|
||||||
|
case ETLSAlertCloseNotify:
|
||||||
|
return "close_notify"
|
||||||
|
case ETLSAlertUnexpectedMessage:
|
||||||
|
return "unexpected_message"
|
||||||
|
case ETLSAlertBadRecordMac:
|
||||||
|
return "bad_record_mac"
|
||||||
|
case ETLSAlertDecryptionFailedRESERVED:
|
||||||
|
return "decryption_failed_RESERVED"
|
||||||
|
case ETLSAlertRecordOverflow:
|
||||||
|
return "record_overflow"
|
||||||
|
case ETLSAlertDecompressionFailure:
|
||||||
|
return "decompression_failure"
|
||||||
|
case ETLSAlertHandshakeFailure:
|
||||||
|
return "handshake_failure"
|
||||||
|
case ETLSAlertNoCertificateRESERVED:
|
||||||
|
return "no_certificate_RESERVED"
|
||||||
|
case ETLSAlertBadCertificate:
|
||||||
|
return "bad_certificate"
|
||||||
|
case ETLSAlertUnsupportedCertificate:
|
||||||
|
return "unsupported_certificate"
|
||||||
|
case ETLSAlertCertificateRevoked:
|
||||||
|
return "certificate_revoked"
|
||||||
|
case ETLSAlertCertificateExpired:
|
||||||
|
return "certificate_expired"
|
||||||
|
case ETLSAlertCertificateUnknown:
|
||||||
|
return "certificate_unknown"
|
||||||
|
case ETLSAlertIllegalParameter:
|
||||||
|
return "illegal_parameter"
|
||||||
|
case ETLSAlertUnknownCa:
|
||||||
|
return "unknown_ca"
|
||||||
|
case ETLSAlertAccessDenied:
|
||||||
|
return "access_denied"
|
||||||
|
case ETLSAlertDecodeError:
|
||||||
|
return "decode_error"
|
||||||
|
case ETLSAlertDecryptError:
|
||||||
|
return "decrypt_error"
|
||||||
|
case ETLSAlertExportRestrictionRESERVED:
|
||||||
|
return "export_restriction_RESERVED"
|
||||||
|
case ETLSAlertProtocolVersion:
|
||||||
|
return "protocol_version"
|
||||||
|
case ETLSAlertInsufficientSecurity:
|
||||||
|
return "insufficient_security"
|
||||||
|
case ETLSAlertInternalError:
|
||||||
|
return "internal_error"
|
||||||
|
case ETLSAlertUserCanceled:
|
||||||
|
return "user_canceled"
|
||||||
|
case ETLSAlertNoRenegotiation:
|
||||||
|
return "no_renegotiation"
|
||||||
|
case ETLSAlertUnsupportedExtension:
|
||||||
|
return "unsupported_extension"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright 2018 The GoPacket Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style license
|
||||||
|
// that can be found in the LICENSE file in the root of the source
|
||||||
|
// tree.
|
||||||
|
|
||||||
|
package etls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/google/gopacket"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ETLSAppDataRecord contains all the information that each AppData Record types should have
|
||||||
|
type ETLSAppDataRecord struct {
|
||||||
|
ETLSRecordHeader
|
||||||
|
Payload []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeFromBytes decodes the slice into the ETLS struct.
|
||||||
|
func (t *ETLSAppDataRecord) decodeFromBytes(h ETLSRecordHeader, data []byte, df gopacket.DecodeFeedback) error {
|
||||||
|
// ETLS Record Header
|
||||||
|
t.ContentType = h.ContentType
|
||||||
|
t.Version = h.Version
|
||||||
|
t.Length = h.Length
|
||||||
|
|
||||||
|
if len(data) != int(t.Length) {
|
||||||
|
return errors.New("ETLS Application Data length mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Payload = data
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Copyright 2018 The GoPacket Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style license
|
||||||
|
// that can be found in the LICENSE file in the root of the source
|
||||||
|
// tree.
|
||||||
|
|
||||||
|
package etls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/google/gopacket"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ETLSchangeCipherSpec defines the message value inside ChangeCipherSpec Record
|
||||||
|
type ETLSchangeCipherSpec uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
ETLSChangecipherspecMessage ETLSchangeCipherSpec = 1
|
||||||
|
ETLSChangecipherspecUnknown ETLSchangeCipherSpec = 255
|
||||||
|
)
|
||||||
|
|
||||||
|
// ETLS Change Cipher Spec
|
||||||
|
// 0 1 2 3 4 5 6 7 8
|
||||||
|
// +--+--+--+--+--+--+--+--+
|
||||||
|
// | Message |
|
||||||
|
// +--+--+--+--+--+--+--+--+
|
||||||
|
|
||||||
|
// ETLSChangeCipherSpecRecord defines the type of data inside ChangeCipherSpec Record
|
||||||
|
type ETLSChangeCipherSpecRecord struct {
|
||||||
|
ETLSRecordHeader
|
||||||
|
|
||||||
|
Message ETLSchangeCipherSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeFromBytes decodes the slice into the ETLS struct.
|
||||||
|
func (t *ETLSChangeCipherSpecRecord) decodeFromBytes(h ETLSRecordHeader, data []byte, df gopacket.DecodeFeedback) error {
|
||||||
|
// ETLS Record Header
|
||||||
|
t.ContentType = h.ContentType
|
||||||
|
t.Version = h.Version
|
||||||
|
t.Length = h.Length
|
||||||
|
|
||||||
|
if len(data) != 1 {
|
||||||
|
df.SetTruncated()
|
||||||
|
return errors.New("ETLS Change Cipher Spec record incorrect length")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Message = ETLSchangeCipherSpec(data[0])
|
||||||
|
if t.Message != ETLSChangecipherspecMessage {
|
||||||
|
t.Message = ETLSChangecipherspecUnknown
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String shows the message value nicely formatted
|
||||||
|
func (ccs ETLSchangeCipherSpec) String() string {
|
||||||
|
switch ccs {
|
||||||
|
default:
|
||||||
|
return "Unknown"
|
||||||
|
case ETLSChangecipherspecMessage:
|
||||||
|
return "Change Cipher Spec Message"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package etls
|
||||||
|
|
||||||
|
// ETLS handshake message types.
|
||||||
|
const (
|
||||||
|
typeHelloRequest uint8 = 0
|
||||||
|
typeClientHello uint8 = 1
|
||||||
|
typeServerHello uint8 = 2
|
||||||
|
typeNewSessionTicket uint8 = 4
|
||||||
|
typeCertificate uint8 = 11
|
||||||
|
typeServerKeyExchange uint8 = 12
|
||||||
|
typeCertificateRequest uint8 = 13
|
||||||
|
typeServerHelloDone uint8 = 14
|
||||||
|
typeCertificateVerify uint8 = 15
|
||||||
|
typeClientKeyExchange uint8 = 16
|
||||||
|
typeFinished uint8 = 20
|
||||||
|
typeCertificateStatus uint8 = 22
|
||||||
|
typeNextProtocol uint8 = 67 // Not IANA assigned
|
||||||
|
)
|
||||||
|
|
||||||
|
// ETLS compression types.
|
||||||
|
const (
|
||||||
|
compressionNone uint8 = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
// ETLS extension numbers
|
||||||
|
const (
|
||||||
|
extensionServerName uint16 = 0
|
||||||
|
extensionStatusRequest uint16 = 5
|
||||||
|
extensionSupportedCurves uint16 = 10
|
||||||
|
extensionSupportedPoints uint16 = 11
|
||||||
|
extensionSignatureAlgorithms uint16 = 13
|
||||||
|
extensionALPN uint16 = 16
|
||||||
|
extensionSCT uint16 = 18 // https://tools.ietf.org/html/rfc6962#section-6
|
||||||
|
extensionSessionTicket uint16 = 35
|
||||||
|
extensionNextProtoNeg uint16 = 13172 // not IANA assigned
|
||||||
|
extensionRenegotiationInfo uint16 = 0xff01
|
||||||
|
)
|
||||||
|
|
||||||
|
// CurveID is the type of a ETLS identifier for an elliptic curve. See
|
||||||
|
// https://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-8
|
||||||
|
type CurveID uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
CurveP256 CurveID = 23
|
||||||
|
CurveP384 CurveID = 24
|
||||||
|
CurveP521 CurveID = 25
|
||||||
|
X25519 CurveID = 29
|
||||||
|
)
|
||||||
|
|
||||||
|
// Certificate types (for certificateRequestMsg)
|
||||||
|
const (
|
||||||
|
certTypeRSASign = 1 // A certificate containing an RSA key
|
||||||
|
certTypeDSSSign = 2 // A certificate containing a DSA key
|
||||||
|
certTypeRSAFixedDH = 3 // A certificate containing a static DH key
|
||||||
|
certTypeDSSFixedDH = 4 // A certificate containing a static DH key
|
||||||
|
|
||||||
|
// See RFC 4492 sections 3 and 5.5.
|
||||||
|
certTypeECDSASign = 64 // A certificate containing an ECDSA-capable public key, signed with ECDSA.
|
||||||
|
certTypeRSAFixedECDH = 65 // A certificate containing an ECDH-capable public key, signed with RSA.
|
||||||
|
certTypeECDSAFixedECDH = 66 // A certificate containing an ECDH-capable public key, signed with ECDSA.
|
||||||
|
)
|
||||||
|
|
||||||
|
// SignatureScheme identifies a signature algorithm supported by ETLS. See
|
||||||
|
// https://tools.ietf.org/html/draft-ietf-tls-tls13-18#section-4.2.3.
|
||||||
|
type SignatureScheme uint16
|
||||||
|
type Extension uint16
|
||||||
|
|
||||||
|
// ETLS signaling cipher suite values
|
||||||
|
const (
|
||||||
|
scsvRenegotiation uint16 = 0x00ff
|
||||||
|
)
|
|
@ -0,0 +1,48 @@
|
||||||
|
// Copyright 2018 The GoPacket Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style license
|
||||||
|
// that can be found in the LICENSE file in the root of the source
|
||||||
|
// tree.
|
||||||
|
|
||||||
|
package etls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/gopacket"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ETLSHandshakeRecord defines the structure of a Handskake Record
|
||||||
|
type ETLSHandshakeRecord struct {
|
||||||
|
ETLSRecordHeader
|
||||||
|
ETLSHandshakeMsgType uint8
|
||||||
|
ETLSHandshakeServerHello *serverHelloMsg
|
||||||
|
ETLSHandshakeClientHello *clientHelloMsg
|
||||||
|
ETLSHandshakeCertificate *certificateMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeFromBytes decodes the slice into the ETLS struct.
|
||||||
|
func (t *ETLSHandshakeRecord) decodeFromBytes(h ETLSRecordHeader, data []byte, df gopacket.DecodeFeedback) error {
|
||||||
|
// ETLS Record Header
|
||||||
|
t.ContentType = h.ContentType
|
||||||
|
t.Version = h.Version
|
||||||
|
t.Length = h.Length
|
||||||
|
|
||||||
|
// Switch on Handshake message type
|
||||||
|
switch uint8(data[0]) {
|
||||||
|
case typeClientHello:
|
||||||
|
t.ETLSHandshakeMsgType = typeClientHello
|
||||||
|
t.ETLSHandshakeClientHello = new(clientHelloMsg)
|
||||||
|
t.ETLSHandshakeClientHello.unmarshal(data)
|
||||||
|
case typeServerHello:
|
||||||
|
t.ETLSHandshakeMsgType = typeServerHello
|
||||||
|
t.ETLSHandshakeServerHello = new(serverHelloMsg)
|
||||||
|
t.ETLSHandshakeServerHello.unmarshal(data)
|
||||||
|
case typeCertificate:
|
||||||
|
t.ETLSHandshakeMsgType = typeCertificate
|
||||||
|
t.ETLSHandshakeCertificate = new(certificateMsg)
|
||||||
|
t.ETLSHandshakeCertificate.unmarshal(data)
|
||||||
|
}
|
||||||
|
// Please see the following url if you are interested into implementing the rest:
|
||||||
|
// https://golang.org/src/crypto/tls/conn.go?h=readHandshake#L950
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,369 @@
|
||||||
|
// Copyright 2018 The GoPacket Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style license
|
||||||
|
// that can be found in the LICENSE file in the root of the source
|
||||||
|
// tree.
|
||||||
|
|
||||||
|
// Most of this content was extracted from go tls package
|
||||||
|
|
||||||
|
package etls
|
||||||
|
|
||||||
|
type handshakeMessage interface {
|
||||||
|
unmarshal([]byte) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientHelloMsg struct {
|
||||||
|
raw []byte
|
||||||
|
extensions map[Extension]uint16
|
||||||
|
AllExtensions []uint16
|
||||||
|
Vers uint16
|
||||||
|
random []byte
|
||||||
|
sessionId []byte
|
||||||
|
CipherSuites []uint16
|
||||||
|
compressionMethods []uint8
|
||||||
|
nextProtoNeg bool
|
||||||
|
serverName string
|
||||||
|
ocspStapling bool
|
||||||
|
scts bool
|
||||||
|
SupportedCurves []CurveID
|
||||||
|
SupportedPoints []uint8
|
||||||
|
ticketSupported bool
|
||||||
|
sessionTicket []uint8
|
||||||
|
supportedSignatureAlgorithms []SignatureScheme
|
||||||
|
secureRenegotiation []byte
|
||||||
|
secureRenegotiationSupported bool
|
||||||
|
alpnProtocols []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *clientHelloMsg) unmarshal(data []byte) bool {
|
||||||
|
if len(data) < 42 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
m.raw = data
|
||||||
|
m.Vers = uint16(data[4])<<8 | uint16(data[5])
|
||||||
|
m.random = data[6:38]
|
||||||
|
sessionIdLen := int(data[38])
|
||||||
|
if sessionIdLen > 32 || len(data) < 39+sessionIdLen {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
m.sessionId = data[39 : 39+sessionIdLen]
|
||||||
|
data = data[39+sessionIdLen:]
|
||||||
|
if len(data) < 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// cipherSuiteLen is the number of bytes of cipher suite numbers. Since
|
||||||
|
// they are uint16s, the number must be even.
|
||||||
|
cipherSuiteLen := int(data[0])<<8 | int(data[1])
|
||||||
|
if cipherSuiteLen%2 == 1 || len(data) < 2+cipherSuiteLen {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
numCipherSuites := cipherSuiteLen / 2
|
||||||
|
m.CipherSuites = make([]uint16, numCipherSuites)
|
||||||
|
for i := 0; i < numCipherSuites; i++ {
|
||||||
|
m.CipherSuites[i] = uint16(data[2+2*i])<<8 | uint16(data[3+2*i])
|
||||||
|
if m.CipherSuites[i] == scsvRenegotiation {
|
||||||
|
m.secureRenegotiationSupported = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data = data[2+cipherSuiteLen:]
|
||||||
|
if len(data) < 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
compressionMethodsLen := int(data[0])
|
||||||
|
if len(data) < 1+compressionMethodsLen {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
m.compressionMethods = data[1 : 1+compressionMethodsLen]
|
||||||
|
data = data[1+compressionMethodsLen:]
|
||||||
|
m.nextProtoNeg = false
|
||||||
|
m.serverName = ""
|
||||||
|
m.ocspStapling = false
|
||||||
|
m.ticketSupported = false
|
||||||
|
m.sessionTicket = nil
|
||||||
|
m.supportedSignatureAlgorithms = nil
|
||||||
|
m.alpnProtocols = nil
|
||||||
|
m.scts = false
|
||||||
|
|
||||||
|
if len(data) == 0 {
|
||||||
|
// ClientHello is optionally followed by extension data
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) < 2 {
|
||||||
|
return false
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// We parse extensions as their needed for ja3
|
||||||
|
extensionsLength := int(data[0])<<8 | int(data[1])
|
||||||
|
data = data[2:]
|
||||||
|
m.extensions = make(map[Extension]uint16)
|
||||||
|
|
||||||
|
if extensionsLength != len(data) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for len(data) != 0 {
|
||||||
|
if len(data) < 4 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
extension := uint16(data[0])<<8 | uint16(data[1])
|
||||||
|
length := int(data[2])<<8 | int(data[3])
|
||||||
|
data = data[4:]
|
||||||
|
|
||||||
|
m.AllExtensions = append(m.AllExtensions, uint16(extension))
|
||||||
|
|
||||||
|
if len(data) < length {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch extension {
|
||||||
|
case extensionSupportedCurves:
|
||||||
|
// https://tools.ietf.org/html/rfc4492#section-5.5.1
|
||||||
|
if length < 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
l := int(data[0])<<8 | int(data[1])
|
||||||
|
if l%2 == 1 || length != l+2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
numCurves := l / 2
|
||||||
|
m.SupportedCurves = make([]CurveID, numCurves)
|
||||||
|
d := data[2:]
|
||||||
|
for i := 0; i < numCurves; i++ {
|
||||||
|
m.SupportedCurves[i] = CurveID(d[0])<<8 | CurveID(d[1])
|
||||||
|
d = d[2:]
|
||||||
|
}
|
||||||
|
case extensionSupportedPoints:
|
||||||
|
// https://tools.ietf.org/html/rfc4492#section-5.5.2
|
||||||
|
if length < 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
l := int(data[0])
|
||||||
|
if length != l+1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
m.SupportedPoints = make([]uint8, l)
|
||||||
|
copy(m.SupportedPoints, data[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
data = data[length:]
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type serverHelloMsg struct {
|
||||||
|
raw []byte
|
||||||
|
extensions map[Extension]uint16
|
||||||
|
AllExtensions []uint16
|
||||||
|
Vers uint16
|
||||||
|
random []byte
|
||||||
|
sessionId []byte
|
||||||
|
CipherSuite uint16
|
||||||
|
compressionMethod uint8
|
||||||
|
nextProtoNeg bool
|
||||||
|
nextProtos []string
|
||||||
|
ocspStapling bool
|
||||||
|
scts [][]byte
|
||||||
|
ticketSupported bool
|
||||||
|
secureRenegotiation []byte
|
||||||
|
secureRenegotiationSupported bool
|
||||||
|
alpnProtocol string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverHelloMsg) unmarshal(data []byte) bool {
|
||||||
|
if len(data) < 42 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
m.raw = data
|
||||||
|
m.Vers = uint16(data[4])<<8 | uint16(data[5])
|
||||||
|
m.random = data[6:38]
|
||||||
|
sessionIdLen := int(data[38])
|
||||||
|
if sessionIdLen > 32 || len(data) < 39+sessionIdLen {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
m.sessionId = data[39 : 39+sessionIdLen]
|
||||||
|
data = data[39+sessionIdLen:]
|
||||||
|
if len(data) < 3 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
m.CipherSuite = uint16(data[0])<<8 | uint16(data[1])
|
||||||
|
m.compressionMethod = data[2]
|
||||||
|
data = data[3:]
|
||||||
|
|
||||||
|
m.nextProtoNeg = false
|
||||||
|
m.nextProtos = nil
|
||||||
|
m.ocspStapling = false
|
||||||
|
m.scts = nil
|
||||||
|
m.ticketSupported = false
|
||||||
|
m.alpnProtocol = ""
|
||||||
|
if len(data) == 0 {
|
||||||
|
// ServerHello is optionally followed by extension data
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) < 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import Extension code needed for ja3s
|
||||||
|
extensionsLength := int(data[0])<<8 | int(data[1])
|
||||||
|
data = data[2:]
|
||||||
|
m.extensions = make(map[Extension]uint16)
|
||||||
|
|
||||||
|
if len(data) != extensionsLength {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for len(data) != 0 {
|
||||||
|
if len(data) < 4 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
extension := uint16(data[0])<<8 | uint16(data[1])
|
||||||
|
length := int(data[2])<<8 | int(data[3])
|
||||||
|
data = data[4:]
|
||||||
|
|
||||||
|
m.AllExtensions = append(m.AllExtensions, uint16(extension))
|
||||||
|
|
||||||
|
if len(data) < length {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch extension {
|
||||||
|
|
||||||
|
case extensionNextProtoNeg:
|
||||||
|
m.nextProtoNeg = true
|
||||||
|
d := data[:length]
|
||||||
|
for len(d) > 0 {
|
||||||
|
l := int(d[0])
|
||||||
|
d = d[1:]
|
||||||
|
if l == 0 || l > len(d) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
m.nextProtos = append(m.nextProtos, string(d[:l]))
|
||||||
|
d = d[l:]
|
||||||
|
}
|
||||||
|
|
||||||
|
case extensionStatusRequest:
|
||||||
|
if length > 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
m.ocspStapling = true
|
||||||
|
|
||||||
|
case extensionSessionTicket:
|
||||||
|
if length > 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
m.ticketSupported = true
|
||||||
|
|
||||||
|
case extensionRenegotiationInfo:
|
||||||
|
if length == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
d := data[:length]
|
||||||
|
l := int(d[0])
|
||||||
|
d = d[1:]
|
||||||
|
if l != len(d) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
m.secureRenegotiation = d
|
||||||
|
m.secureRenegotiationSupported = true
|
||||||
|
|
||||||
|
case extensionALPN:
|
||||||
|
d := data[:length]
|
||||||
|
if len(d) < 3 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
l := int(d[0])<<8 | int(d[1])
|
||||||
|
if l != len(d)-2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
d = d[2:]
|
||||||
|
l = int(d[0])
|
||||||
|
if l != len(d)-1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
d = d[1:]
|
||||||
|
if len(d) == 0 {
|
||||||
|
// ALPN protocols must not be empty.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
m.alpnProtocol = string(d)
|
||||||
|
|
||||||
|
case extensionSCT:
|
||||||
|
d := data[:length]
|
||||||
|
if len(d) < 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
l := int(d[0])<<8 | int(d[1])
|
||||||
|
d = d[2:]
|
||||||
|
if len(d) != l || l == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
m.scts = make([][]byte, 0, 3)
|
||||||
|
for len(d) != 0 {
|
||||||
|
if len(d) < 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
sctLen := int(d[0])<<8 | int(d[1])
|
||||||
|
d = d[2:]
|
||||||
|
if sctLen == 0 || len(d) < sctLen {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
m.scts = append(m.scts, d[:sctLen])
|
||||||
|
d = d[sctLen:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data = data[length:]
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type certificateMsg struct {
|
||||||
|
raw []byte
|
||||||
|
Certificates [][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *certificateMsg) unmarshal(data []byte) bool {
|
||||||
|
if len(data) < 7 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
m.raw = data
|
||||||
|
certsLen := uint32(data[4])<<16 | uint32(data[5])<<8 | uint32(data[6])
|
||||||
|
if uint32(len(data)) != certsLen+7 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
numCerts := 0
|
||||||
|
d := data[7:]
|
||||||
|
for certsLen > 0 {
|
||||||
|
if len(d) < 4 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
certLen := uint32(d[0])<<16 | uint32(d[1])<<8 | uint32(d[2])
|
||||||
|
if uint32(len(d)) < 3+certLen {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
d = d[3+certLen:]
|
||||||
|
certsLen -= 3 + certLen
|
||||||
|
numCerts++
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Certificates = make([][]byte, numCerts)
|
||||||
|
d = data[7:]
|
||||||
|
|
||||||
|
for i := 0; i < numCerts; i++ {
|
||||||
|
certLen := uint32(d[0])<<16 | uint32(d[1])<<8 | uint32(d[2])
|
||||||
|
m.Certificates[i] = d[3 : 3+certLen]
|
||||||
|
d = d[3+certLen:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,339 @@
|
||||||
|
package etls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/gopacket"
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://github.com/tintinweb/scapy-ssl_tls/blob/master/tests/files/RSA_WITH_AES_128_CBC_SHA.pcap
|
||||||
|
// WARNING! Tests are specific for each packet. If you change a packet, please review their tests.
|
||||||
|
|
||||||
|
// Packet 4 - Client Hello (full packet, from Ethernet to ETLS layers)
|
||||||
|
var testClientHello = []byte{
|
||||||
|
0x00, 0x0c, 0x29, 0x1f, 0xab, 0x17, 0x00, 0x50, 0x56, 0xc0, 0x00, 0x08, 0x08, 0x00, 0x45, 0x00,
|
||||||
|
0x00, 0xfe, 0x71, 0x42, 0x40, 0x00, 0x80, 0x06, 0x4e, 0xe1, 0xc0, 0xa8, 0xdc, 0x01, 0xc0, 0xa8,
|
||||||
|
0xdc, 0x83, 0x2f, 0x0e, 0x01, 0xbb, 0x25, 0x6c, 0xbd, 0x3d, 0xcc, 0xce, 0xe1, 0xf7, 0x50, 0x18,
|
||||||
|
0xff, 0xff, 0x7c, 0xaf, 0x00, 0x00, 0x16, 0x03, 0x01, 0x00, 0xd1, 0x01, 0x00, 0x00, 0xcd, 0x03,
|
||||||
|
0x01, 0xff, 0xa2, 0x88, 0x97, 0x7c, 0x41, 0xa1, 0x08, 0x34, 0x2c, 0x98, 0xc2, 0x70, 0x04, 0xa0,
|
||||||
|
0x5d, 0x5f, 0x39, 0xef, 0xe0, 0x70, 0xd5, 0x12, 0xf1, 0x35, 0x17, 0xb6, 0x0d, 0xc4, 0xd3, 0x09,
|
||||||
|
0x85, 0x00, 0x00, 0x5a, 0xc0, 0x14, 0xc0, 0x0a, 0x00, 0x39, 0x00, 0x38, 0x00, 0x88, 0x00, 0x87,
|
||||||
|
0xc0, 0x0f, 0xc0, 0x05, 0x00, 0x35, 0x00, 0x84, 0xc0, 0x13, 0xc0, 0x09, 0x00, 0x33, 0x00, 0x32,
|
||||||
|
0x00, 0x9a, 0x00, 0x99, 0x00, 0x45, 0x00, 0x44, 0xc0, 0x0e, 0xc0, 0x04, 0x00, 0x2f, 0x00, 0x96,
|
||||||
|
0x00, 0x41, 0xc0, 0x11, 0xc0, 0x07, 0xc0, 0x0c, 0xc0, 0x02, 0x00, 0x05, 0x00, 0x04, 0xc0, 0x12,
|
||||||
|
0xc0, 0x08, 0x00, 0x16, 0x00, 0x13, 0xc0, 0x0d, 0xc0, 0x03, 0x00, 0x0a, 0x00, 0x15, 0x00, 0x12,
|
||||||
|
0x00, 0x09, 0x00, 0x14, 0x00, 0x11, 0x00, 0x08, 0x00, 0x06, 0x00, 0x03, 0x00, 0xff, 0x02, 0x01,
|
||||||
|
0x00, 0x00, 0x49, 0x00, 0x0b, 0x00, 0x04, 0x03, 0x00, 0x01, 0x02, 0x00, 0x0a, 0x00, 0x34, 0x00,
|
||||||
|
0x32, 0x00, 0x0e, 0x00, 0x0d, 0x00, 0x19, 0x00, 0x0b, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x09, 0x00,
|
||||||
|
0x0a, 0x00, 0x16, 0x00, 0x17, 0x00, 0x08, 0x00, 0x06, 0x00, 0x07, 0x00, 0x14, 0x00, 0x15, 0x00,
|
||||||
|
0x04, 0x00, 0x05, 0x00, 0x12, 0x00, 0x13, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x0f, 0x00,
|
||||||
|
0x10, 0x00, 0x11, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x01, 0x01,
|
||||||
|
}
|
||||||
|
var testClientHelloDecoded = &ETLS{
|
||||||
|
BaseLayer: BaseLayer{
|
||||||
|
Contents: testClientHello[54:],
|
||||||
|
Payload: nil,
|
||||||
|
},
|
||||||
|
ChangeCipherSpec: nil,
|
||||||
|
Handshake: []ETLSHandshakeRecord{
|
||||||
|
{
|
||||||
|
ETLSRecordHeader{
|
||||||
|
ContentType: 22,
|
||||||
|
Version: 0x0301,
|
||||||
|
Length: 209,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AppData: nil,
|
||||||
|
Alert: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Packet 6 - Server Hello, Certificate, Server Hello Done
|
||||||
|
var testServerHello = []byte{
|
||||||
|
0x16, 0x03, 0x01, 0x00, 0x3a, 0x02, 0x00, 0x00, 0x36, 0x03, 0x01, 0x55, 0x5c, 0xd6, 0x97, 0xa3,
|
||||||
|
0x97, 0xe9, 0xf4, 0x0c, 0xf4, 0x56, 0x14, 0x9f, 0xe4, 0x24, 0xf9, 0xeb, 0x49, 0xd4, 0xd1, 0x5f,
|
||||||
|
0xfc, 0x12, 0xb4, 0xfd, 0x45, 0x4e, 0x3d, 0xeb, 0x6a, 0xad, 0xcf, 0x00, 0x00, 0x2f, 0x01, 0x00,
|
||||||
|
0x0e, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x01, 0x01, 0x16,
|
||||||
|
0x03, 0x01, 0x01, 0x90, 0x0b, 0x00, 0x01, 0x8c, 0x00, 0x01, 0x89, 0x00, 0x01, 0x86, 0x30, 0x82,
|
||||||
|
0x01, 0x82, 0x30, 0x82, 0x01, 0x2c, 0x02, 0x01, 0x04, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
|
||||||
|
0x86, 0xf7, 0x0d, 0x01, 0x01, 0x04, 0x05, 0x00, 0x30, 0x38, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
|
||||||
|
0x55, 0x04, 0x06, 0x13, 0x02, 0x41, 0x55, 0x31, 0x0c, 0x30, 0x0a, 0x06, 0x03, 0x55, 0x04, 0x08,
|
||||||
|
0x13, 0x03, 0x51, 0x4c, 0x44, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12,
|
||||||
|
0x53, 0x53, 0x4c, 0x65, 0x61, 0x79, 0x2f, 0x72, 0x73, 0x61, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20,
|
||||||
|
0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x39, 0x35, 0x31, 0x30, 0x30, 0x39, 0x32, 0x33, 0x33, 0x32,
|
||||||
|
0x30, 0x35, 0x5a, 0x17, 0x0d, 0x39, 0x38, 0x30, 0x37, 0x30, 0x35, 0x32, 0x33, 0x33, 0x32, 0x30,
|
||||||
|
0x35, 0x5a, 0x30, 0x60, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x41,
|
||||||
|
0x55, 0x31, 0x0c, 0x30, 0x0a, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x03, 0x51, 0x4c, 0x44, 0x31,
|
||||||
|
0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x4d, 0x69, 0x6e, 0x63, 0x6f, 0x6d,
|
||||||
|
0x20, 0x50, 0x74, 0x79, 0x2e, 0x20, 0x4c, 0x74, 0x64, 0x2e, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
|
||||||
|
0x55, 0x04, 0x0b, 0x13, 0x02, 0x43, 0x53, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03,
|
||||||
|
0x13, 0x12, 0x53, 0x53, 0x4c, 0x65, 0x61, 0x79, 0x20, 0x64, 0x65, 0x6d, 0x6f, 0x20, 0x73, 0x65,
|
||||||
|
0x72, 0x76, 0x65, 0x72, 0x30, 0x5c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
|
||||||
|
0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x4b, 0x00, 0x30, 0x48, 0x02, 0x41, 0x00, 0xb7, 0x2c, 0x25,
|
||||||
|
0xdc, 0x49, 0xc5, 0xae, 0x6b, 0x43, 0xc5, 0x2e, 0x41, 0xc1, 0x2e, 0x6d, 0x95, 0x7a, 0x3a, 0xa9,
|
||||||
|
0x03, 0x51, 0x78, 0x45, 0x0f, 0x2a, 0xd1, 0x58, 0xd1, 0x88, 0xf6, 0x9f, 0x8f, 0x1f, 0xd9, 0xfd,
|
||||||
|
0xa5, 0x87, 0xde, 0x2a, 0x5d, 0x31, 0x5b, 0xee, 0x24, 0x66, 0xbf, 0xc0, 0x55, 0xdb, 0xfe, 0x70,
|
||||||
|
0xc5, 0x2c, 0x39, 0x5f, 0x5a, 0x9f, 0xa8, 0x08, 0xfc, 0x21, 0x06, 0xd5, 0x4f, 0x02, 0x03, 0x01,
|
||||||
|
0x00, 0x01, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x04, 0x05,
|
||||||
|
0x00, 0x03, 0x41, 0x00, 0x2b, 0x34, 0x5b, 0x22, 0x85, 0x62, 0x23, 0x07, 0x36, 0xf4, 0x0c, 0x2b,
|
||||||
|
0x14, 0xd0, 0x1b, 0xcb, 0xd9, 0xbb, 0xd2, 0xc0, 0x9a, 0xcf, 0x12, 0xa1, 0x65, 0x90, 0x3a, 0xb7,
|
||||||
|
0x17, 0x83, 0x3a, 0x10, 0x6b, 0xad, 0x2f, 0xd6, 0xb1, 0x11, 0xc0, 0x0d, 0x5a, 0x06, 0xdb, 0x11,
|
||||||
|
0xd0, 0x2f, 0x34, 0x90, 0xf5, 0x76, 0x61, 0x26, 0xa1, 0x69, 0xf2, 0xdb, 0xb3, 0xe7, 0x20, 0xcb,
|
||||||
|
0x3a, 0x64, 0xe6, 0x41, 0x16, 0x03, 0x01, 0x00, 0x04, 0x0e, 0x00, 0x00, 0x00,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Packet 7 - Client Key Exchange, Change Cipher Spec, Encrypted Handshake Message
|
||||||
|
var testClientKeyExchange = []byte{
|
||||||
|
0x16, 0x03, 0x01, 0x00, 0x46, 0x10, 0x00, 0x00, 0x42, 0x00, 0x40, 0x9e, 0x73, 0xdf, 0xe0, 0xf2,
|
||||||
|
0xd0, 0x40, 0x32, 0x44, 0x9a, 0x34, 0x7f, 0x57, 0x86, 0x10, 0xea, 0x3d, 0xc5, 0xe2, 0xf9, 0xa5,
|
||||||
|
0x69, 0x43, 0xc9, 0x0b, 0x00, 0x7e, 0x91, 0x31, 0x57, 0xfc, 0xc5, 0x65, 0x18, 0x0d, 0x44, 0xfd,
|
||||||
|
0x51, 0xf8, 0xda, 0x8a, 0x7a, 0xab, 0x16, 0x03, 0xeb, 0xac, 0x23, 0x6e, 0x8d, 0xdd, 0xbb, 0xf4,
|
||||||
|
0x75, 0xe7, 0xb7, 0xa3, 0xce, 0xdb, 0x67, 0x6b, 0x7d, 0x30, 0x2a, 0x14, 0x03, 0x01, 0x00, 0x01,
|
||||||
|
0x01, 0x16, 0x03, 0x01, 0x00, 0x30, 0x15, 0xcb, 0x7a, 0x5b, 0x2d, 0xc0, 0x27, 0x09, 0x28, 0x62,
|
||||||
|
0x95, 0x44, 0x9f, 0xa1, 0x1e, 0x4e, 0x6a, 0xfb, 0x49, 0x9d, 0x6a, 0x24, 0x44, 0xc6, 0x8e, 0x26,
|
||||||
|
0xbc, 0xc1, 0x28, 0x8c, 0x27, 0xcc, 0xa2, 0xba, 0xec, 0x38, 0x63, 0x6e, 0x64, 0xd8, 0x52, 0x94,
|
||||||
|
0x17, 0x96, 0x61, 0xfd, 0x9c, 0x54,
|
||||||
|
}
|
||||||
|
var testClientKeyExchangeDecoded = &ETLS{
|
||||||
|
BaseLayer: BaseLayer{
|
||||||
|
Contents: testClientKeyExchange[81:],
|
||||||
|
Payload: nil,
|
||||||
|
},
|
||||||
|
ChangeCipherSpec: []ETLSChangeCipherSpecRecord{
|
||||||
|
{
|
||||||
|
ETLSRecordHeader{
|
||||||
|
ContentType: 20,
|
||||||
|
Version: 0x0301,
|
||||||
|
Length: 1,
|
||||||
|
},
|
||||||
|
1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Handshake: []ETLSHandshakeRecord{
|
||||||
|
{
|
||||||
|
ETLSRecordHeader{
|
||||||
|
ContentType: 22,
|
||||||
|
Version: 0x0301,
|
||||||
|
Length: 70,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ETLSRecordHeader{
|
||||||
|
ContentType: 22,
|
||||||
|
Version: 0x0301,
|
||||||
|
Length: 48,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AppData: nil,
|
||||||
|
Alert: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Packet 9 - New Session Ticket, Change Cipher Spec, Encryption Handshake Message
|
||||||
|
var testNewSessionTicket = []byte{
|
||||||
|
0x16, 0x03, 0x01, 0x00, 0xaa, 0x04, 0x00, 0x00, 0xa6, 0x00, 0x00, 0x1c, 0x20, 0x00, 0xa0, 0xd4,
|
||||||
|
0xee, 0xb0, 0x9b, 0xb5, 0xa2, 0xd3, 0x00, 0x57, 0x84, 0x59, 0xec, 0x0d, 0xbf, 0x05, 0x0c, 0xd5,
|
||||||
|
0xb9, 0xe2, 0xf8, 0x32, 0xb5, 0xec, 0xce, 0xe2, 0x9c, 0x25, 0x25, 0xd9, 0x3e, 0x4a, 0x94, 0x5b,
|
||||||
|
0xca, 0x18, 0x2b, 0x0f, 0x5f, 0xf6, 0x73, 0x38, 0x62, 0xcd, 0xcc, 0xf1, 0x32, 0x39, 0xe4, 0x5e,
|
||||||
|
0x30, 0xf3, 0x94, 0xf5, 0xc5, 0x94, 0x3a, 0x8c, 0x8e, 0xe5, 0x12, 0x4a, 0x1e, 0xd8, 0x31, 0xb5,
|
||||||
|
0x17, 0x09, 0xa6, 0x4c, 0x69, 0xca, 0xae, 0xfb, 0x04, 0x17, 0x64, 0x54, 0x9e, 0xc2, 0xfa, 0xf3,
|
||||||
|
0x6d, 0xe9, 0xa5, 0xed, 0xa6, 0x65, 0xfe, 0x2f, 0xf3, 0xc6, 0xce, 0x78, 0x40, 0xf7, 0x65, 0xe0,
|
||||||
|
0x13, 0xd3, 0x77, 0xc7, 0xc5, 0x79, 0x16, 0x56, 0x4c, 0x30, 0x94, 0xcf, 0xb0, 0x3c, 0x00, 0x91,
|
||||||
|
0xbd, 0x86, 0x08, 0x9f, 0x2f, 0x05, 0x67, 0x03, 0x6f, 0xa7, 0x3b, 0xb9, 0x36, 0xf2, 0x80, 0x4f,
|
||||||
|
0x60, 0x5d, 0x4c, 0xc4, 0x42, 0x5d, 0x02, 0x44, 0xba, 0x31, 0x8f, 0x39, 0x8e, 0x0c, 0x1e, 0xa8,
|
||||||
|
0x26, 0x4f, 0x3e, 0x01, 0x96, 0xb3, 0x6f, 0xc6, 0x25, 0xe4, 0x30, 0x03, 0xd6, 0x3a, 0x7d, 0x14,
|
||||||
|
0x03, 0x01, 0x00, 0x01, 0x01, 0x16, 0x03, 0x01, 0x00, 0x30, 0x25, 0xb8, 0x58, 0xc1, 0xa6, 0x3f,
|
||||||
|
0xf8, 0xbd, 0xe6, 0xae, 0xbd, 0x98, 0xd4, 0x75, 0xa5, 0x45, 0x1b, 0xd8, 0x6a, 0x70, 0x79, 0x86,
|
||||||
|
0x29, 0x4e, 0x4f, 0x64, 0xba, 0xe7, 0x1f, 0xca, 0x4b, 0x96, 0x9b, 0xf7, 0x0b, 0x50, 0xf5, 0x4f,
|
||||||
|
0xfd, 0xda, 0xda, 0xcd, 0xcd, 0x4b, 0x12, 0x2e, 0xdf, 0xd5,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Packet 13 - Two Application Data Records
|
||||||
|
var testDoubleAppData = []byte{
|
||||||
|
0x17, 0x03, 0x01, 0x00, 0x20, 0x77, 0x3a, 0x94, 0x7d, 0xb4, 0x47, 0x4a, 0x1d, 0xd4, 0x6c, 0x5a,
|
||||||
|
0x69, 0x74, 0x03, 0x93, 0x32, 0xca, 0x54, 0x5e, 0xa5, 0x81, 0x99, 0x6a, 0x73, 0x66, 0xbf, 0x06,
|
||||||
|
0xa0, 0xdc, 0x6a, 0x9c, 0xb1, 0x17, 0x03, 0x01, 0x00, 0x20, 0x44, 0x64, 0xc8, 0xc2, 0x5a, 0xfc,
|
||||||
|
0x4a, 0x82, 0xdd, 0x53, 0x6d, 0x30, 0x82, 0x4d, 0x35, 0x22, 0xf1, 0x5f, 0x3b, 0x96, 0x66, 0x79,
|
||||||
|
0x61, 0x9f, 0x51, 0x93, 0x1b, 0xbf, 0x53, 0x3b, 0xf8, 0x26,
|
||||||
|
}
|
||||||
|
var testDoubleAppDataDecoded = &ETLS{
|
||||||
|
BaseLayer: BaseLayer{
|
||||||
|
Contents: testDoubleAppData[37:],
|
||||||
|
Payload: nil,
|
||||||
|
},
|
||||||
|
ChangeCipherSpec: nil,
|
||||||
|
Handshake: nil,
|
||||||
|
AppData: []ETLSAppDataRecord{
|
||||||
|
{
|
||||||
|
ETLSRecordHeader{
|
||||||
|
ContentType: 23,
|
||||||
|
Version: 0x0301,
|
||||||
|
Length: 32,
|
||||||
|
},
|
||||||
|
testDoubleAppData[5 : 5+32],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ETLSRecordHeader{
|
||||||
|
ContentType: 23,
|
||||||
|
Version: 0x0301,
|
||||||
|
Length: 32,
|
||||||
|
},
|
||||||
|
testDoubleAppData[42 : 42+32],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Alert: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
var testAlertEncrypted = []byte{
|
||||||
|
0x15, 0x03, 0x03, 0x00, 0x20, 0x44, 0xb9, 0x9c, 0x2c, 0x6e, 0xab, 0xa3, 0xdf, 0xb1, 0x77, 0x04,
|
||||||
|
0xa2, 0xa4, 0x3a, 0x9a, 0x08, 0x1d, 0xe6, 0x51, 0xac, 0xa0, 0x5f, 0xab, 0x74, 0xa7, 0x96, 0x24,
|
||||||
|
0xfe, 0x62, 0xfe, 0xe8, 0x5e,
|
||||||
|
}
|
||||||
|
var testAlertEncryptedDecoded = &ETLS{
|
||||||
|
BaseLayer: BaseLayer{
|
||||||
|
Contents: testAlertEncrypted,
|
||||||
|
Payload: nil,
|
||||||
|
},
|
||||||
|
ChangeCipherSpec: nil,
|
||||||
|
Handshake: nil,
|
||||||
|
AppData: nil,
|
||||||
|
Alert: []ETLSAlertRecord{
|
||||||
|
{
|
||||||
|
ETLSRecordHeader{
|
||||||
|
ContentType: 21,
|
||||||
|
Version: 0x0303,
|
||||||
|
Length: 32,
|
||||||
|
},
|
||||||
|
0xFF,
|
||||||
|
0xFF,
|
||||||
|
testAlertEncrypted[5:],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Malformed ETLS records
|
||||||
|
var testMalformed = []byte{
|
||||||
|
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||||
|
0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
|
||||||
|
0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
|
||||||
|
}
|
||||||
|
|
||||||
|
var testETLSDecodeOptions = gopacket.DecodeOptions{
|
||||||
|
SkipDecodeRecovery: true,
|
||||||
|
DecodeStreamsAsDatagrams: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseETLSClientHello(t *testing.T) {
|
||||||
|
p := gopacket.NewPacket(testClientHello, LinkTypeEthernet, testETLSDecodeOptions)
|
||||||
|
if p.ErrorLayer() != nil {
|
||||||
|
t.Error("Failed to decode packet:", p.ErrorLayer().Error())
|
||||||
|
}
|
||||||
|
checkLayers(p, []gopacket.LayerType{LayerTypeEthernet, LayerTypeIPv4, LayerTypeTCP, LayerTypeETLS}, t)
|
||||||
|
|
||||||
|
if got, ok := p.Layer(LayerTypeETLS).(*ETLS); ok {
|
||||||
|
want := testClientHelloDecoded
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("ETLS ClientHello packet processing failed:\ngot:\n%#v\n\nwant:\n%#v\n\n", got, want)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Error("No ETLS layer type found in packet")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testETLSClientHelloDecodeFromBytes(t *testing.T) {
|
||||||
|
var got ETLS
|
||||||
|
want := testClientKeyExchangeDecoded
|
||||||
|
|
||||||
|
if err := got.DecodeFromBytes(testClientKeyExchange, gopacket.NilDecodeFeedback); err != nil {
|
||||||
|
t.Errorf("ETLS DecodeFromBytes first decode failed:\ngot:\n%#v\n\nwant:\n%#v\n\n", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("ETLS DecodeFromBytes first decode doesn't match:\ngot:\n%#v\n\nwant:\n%#v\n\n", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := got.DecodeFromBytes(testClientKeyExchange, gopacket.NilDecodeFeedback); err != nil {
|
||||||
|
t.Errorf("ETLS DecodeFromBytes second decode failed:\ngot:\n%#v\n\nwant:\n%#v\n\n", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("ETLS DecodeFromBytes second decode doesn't match:\ngot:\n%#v\n\nwant:\n%#v\n\n", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseETLSChangeCipherSpec(t *testing.T) {
|
||||||
|
p := gopacket.NewPacket(testClientKeyExchange, LayerTypeETLS, testETLSDecodeOptions)
|
||||||
|
if p.ErrorLayer() != nil {
|
||||||
|
t.Error("Failed to decode packet:", p.ErrorLayer().Error())
|
||||||
|
}
|
||||||
|
checkLayers(p, []gopacket.LayerType{LayerTypeETLS}, t)
|
||||||
|
|
||||||
|
if got, ok := p.Layer(LayerTypeETLS).(*ETLS); ok {
|
||||||
|
want := testClientKeyExchangeDecoded
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("ETLS ChangeCipherSpec packet processing failed:\ngot:\n%#v\n\nwant:\n%#v\n\n", got, want)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Error("No ETLS layer type found in packet")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseETLSAppData(t *testing.T) {
|
||||||
|
p := gopacket.NewPacket(testDoubleAppData, LayerTypeETLS, testETLSDecodeOptions)
|
||||||
|
if p.ErrorLayer() != nil {
|
||||||
|
t.Error("Failed to decode packet:", p.ErrorLayer().Error())
|
||||||
|
}
|
||||||
|
checkLayers(p, []gopacket.LayerType{LayerTypeETLS}, t)
|
||||||
|
|
||||||
|
if got, ok := p.Layer(LayerTypeETLS).(*ETLS); ok {
|
||||||
|
want := testDoubleAppDataDecoded
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("ETLS ETLSAppData packet processing failed:\ngot:\n%#v\n\nwant:\n%#v\n\n", got, want)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Error("No ETLS layer type found in packet")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseETLSMalformed(t *testing.T) {
|
||||||
|
p := gopacket.NewPacket(testMalformed, LayerTypeETLS, testETLSDecodeOptions)
|
||||||
|
if p.ErrorLayer() == nil {
|
||||||
|
t.Error("No Decoding Error when parsing a malformed data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseETLSTooShort(t *testing.T) {
|
||||||
|
p := gopacket.NewPacket(testMalformed[0:2], LayerTypeETLS, testETLSDecodeOptions)
|
||||||
|
if p.ErrorLayer() == nil {
|
||||||
|
t.Error("No Decoding Error when parsing a malformed data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseETLSLengthMismatch(t *testing.T) {
|
||||||
|
var testLengthMismatch = make([]byte, len(testDoubleAppData))
|
||||||
|
copy(testLengthMismatch, testDoubleAppData)
|
||||||
|
testLengthMismatch[3] = 0xFF
|
||||||
|
testLengthMismatch[4] = 0xFF
|
||||||
|
p := gopacket.NewPacket(testLengthMismatch, LayerTypeETLS, testETLSDecodeOptions)
|
||||||
|
if p.ErrorLayer() == nil {
|
||||||
|
t.Error("No Decoding Error when parsing a malformed data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseETLSAlertEncrypted(t *testing.T) {
|
||||||
|
p := gopacket.NewPacket(testAlertEncrypted, LayerTypeETLS, testETLSDecodeOptions)
|
||||||
|
if p.ErrorLayer() != nil {
|
||||||
|
t.Error("Failed to decode packet:", p.ErrorLayer().Error())
|
||||||
|
}
|
||||||
|
checkLayers(p, []gopacket.LayerType{LayerTypeETLS}, t)
|
||||||
|
|
||||||
|
if got, ok := p.Layer(LayerTypeETLS).(*ETLS); ok {
|
||||||
|
want := testAlertEncryptedDecoded
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("ETLS ETLSAlert packet processing failed:\ngot:\n%#v\n\nwant:\n%#v\n\n", got, want)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Error("No ETLS layer type found in packet")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue