resolves #2 - Took option 2

DecodingLayerParser
Jean-Louis Huynen 2019-02-01 11:28:20 +01:00
parent e59ea05e57
commit 3cf6ece2e4
10 changed files with 1346 additions and 38 deletions

View File

@ -1,14 +1,11 @@
# 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.
# 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").
Check the list of issues.
This project is currently in its very early stage and should not be used in production.
Check the list of issues.
## Install dependencies & go get
``` shell
$go get github.com/gallypette/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
```
make allows to compile for amd64 and arm ATM.

View File

@ -24,14 +24,13 @@ import (
"github.com/google/gopacket"
"github.com/google/gopacket/examples/util"
"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/pcap"
"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")
@ -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 fname = flag.String("r", "", "Filename to read from, overrides -i")
// decoding
//var LayerTypeETLS gopacket.LayerType
// writing
var outCerts = flag.String("w", "", "Folder to write certificates into")
var outJSON = flag.String("j", "", "Folder to write certificates into, stdin if not set")
@ -68,7 +70,7 @@ type SessionRecord struct {
type TLSSession struct {
record SessionRecord
tlsHdskR layers.TLSHandshakeRecord
tlsHdskR etls.ETLSHandshakeRecord
}
var grease = map[uint16]bool{
@ -231,25 +233,25 @@ func (t *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.Ass
if length > 0 {
// 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.
// Now we attempts TLS decoding
tls := &layers.TLS{}
// Now we attempts Extended TLS decoding
tls := &etls.ETLS{}
var decoded []gopacket.LayerType
p := gopacket.NewDecodingLayerParser(etls.LayerTypeETLS, tls)
p.DecodingLayerParserOptions.IgnoreUnsupported = true
// First we check if the packet is fragmented
p := gopacket.NewDecodingLayerParser(layers.LayerTypeTLS, tls)
err := p.DecodeLayers(data, &decoded)
if err != nil {
// Error("TLS-parser", "Failed to decode TLS: %v\n", err)
// Debug("RAW %s\n", hex.Dump(data))
// If it's malformed as it we keep for next round
sg.KeepFrom(0)
} else {
//Debug("TLS: %s\n", gopacket.LayerDump(tls))
// Debug("TLS: %s\n", gopacket.LayerGoString(tls))
if tls.Handshake != nil {
for _, tlsrecord := range tls.Handshake {
switch tlsrecord.TLSHandshakeMsgType {
switch tlsrecord.ETLSHandshakeMsgType {
// Client Hello
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)
// Set up first seen
info := sg.CaptureInfo(0)
@ -257,12 +259,12 @@ func (t *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.Ass
t.tlsSession.gatherJa3()
// Server Hello
case 2:
t.tlsSession.tlsHdskR.TLSHandshakeServerHello = tlsrecord.TLSHandshakeServerHello
t.tlsSession.tlsHdskR.ETLSHandshakeServerHello = tlsrecord.ETLSHandshakeServerHello
t.tlsSession.gatherJa3s()
// Server Certificate
case 11:
//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)
if err != nil {
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 {
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 ","
buf = append(buf, byte(44))
// 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))
// If there are extensions
if len(ts.tlsHdskR.TLSHandshakeServerHello.AllExtensions) > 0 {
for i, e := range ts.tlsHdskR.TLSHandshakeServerHello.AllExtensions {
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.TLSHandshakeServerHello.AllExtensions) {
if (i + 1) < len(ts.tlsHdskR.ETLSHandshakeServerHello.AllExtensions) {
// byte(45) is "-"
buf = append(buf, byte(45))
}
@ -325,16 +327,16 @@ func (ts *TLSSession) gatherJa3s() bool {
func (ts *TLSSession) gatherJa3() bool {
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 ","
buf = append(buf, byte(44))
// If there are Cipher Suites
if len(ts.tlsHdskR.TLSHandshakeClientHello.CipherSuites) > 0 {
for i, cs := range ts.tlsHdskR.TLSHandshakeClientHello.CipherSuites {
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.TLSHandshakeClientHello.CipherSuites) {
if (i + 1) < len(ts.tlsHdskR.ETLSHandshakeClientHello.CipherSuites) {
buf = append(buf, byte(45))
}
}
@ -342,12 +344,12 @@ func (ts *TLSSession) gatherJa3() bool {
buf = append(buf, byte(44))
// If there are extensions
if len(ts.tlsHdskR.TLSHandshakeClientHello.AllExtensions) > 0 {
for i, e := range ts.tlsHdskR.TLSHandshakeClientHello.AllExtensions {
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.TLSHandshakeClientHello.AllExtensions) {
if (i + 1) < len(ts.tlsHdskR.ETLSHandshakeClientHello.AllExtensions) {
buf = append(buf, byte(45))
}
}
@ -356,10 +358,10 @@ func (ts *TLSSession) gatherJa3() bool {
buf = append(buf, byte(44))
// If there are Supported Curves
if len(ts.tlsHdskR.TLSHandshakeClientHello.SupportedCurves) > 0 {
for i, cs := range ts.tlsHdskR.TLSHandshakeClientHello.SupportedCurves {
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.TLSHandshakeClientHello.SupportedCurves) {
if (i + 1) < len(ts.tlsHdskR.ETLSHandshakeClientHello.SupportedCurves) {
buf = append(buf, byte(45))
}
}
@ -367,10 +369,10 @@ func (ts *TLSSession) gatherJa3() bool {
buf = append(buf, byte(44))
// If there are Supported Points
if len(ts.tlsHdskR.TLSHandshakeClientHello.SupportedPoints) > 0 {
for i, cs := range ts.tlsHdskR.TLSHandshakeClientHello.SupportedPoints {
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.TLSHandshakeClientHello.SupportedPoints) {
if (i + 1) < len(ts.tlsHdskR.ETLSHandshakeClientHello.SupportedPoints) {
buf = append(buf, byte(45))
}
}

215
etls/etls.go Normal file
View File

@ -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)})
}

165
etls/etls_alert.go Normal file
View File

@ -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"
}
}

34
etls/etls_appdata.go Normal file
View File

@ -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
}

64
etls/etls_cipherspec.go Normal file
View File

@ -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"
}
}

75
etls/etls_common.go Normal file
View File

@ -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
)

48
etls/etls_handshake.go Normal file
View File

@ -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
}

View File

@ -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
}

339
etls/etls_test.go Normal file
View File

@ -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")
}
}