First Commit
This commit is contained in:
224
analyzer/internal/tls.go
Normal file
224
analyzer/internal/tls.go
Normal file
@@ -0,0 +1,224 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"git.difuse.io/Difuse/Mellaris/analyzer"
|
||||
"git.difuse.io/Difuse/Mellaris/analyzer/utils"
|
||||
)
|
||||
|
||||
// TLS record types.
|
||||
const (
|
||||
RecordTypeHandshake = 0x16
|
||||
)
|
||||
|
||||
// TLS handshake message types.
|
||||
const (
|
||||
TypeClientHello = 0x01
|
||||
TypeServerHello = 0x02
|
||||
)
|
||||
|
||||
// TLS extension numbers.
|
||||
const (
|
||||
extServerName = 0x0000
|
||||
extALPN = 0x0010
|
||||
extSupportedVersions = 0x002b
|
||||
extEncryptedClientHello = 0xfe0d
|
||||
)
|
||||
|
||||
func ParseTLSClientHelloMsgData(chBuf *utils.ByteBuffer) analyzer.PropMap {
|
||||
var ok bool
|
||||
m := make(analyzer.PropMap)
|
||||
// Version, random & session ID length combined are within 35 bytes,
|
||||
// so no need for bounds checking
|
||||
m["version"], _ = chBuf.GetUint16(false, true)
|
||||
m["random"], _ = chBuf.Get(32, true)
|
||||
sessionIDLen, _ := chBuf.GetByte(true)
|
||||
m["session"], ok = chBuf.Get(int(sessionIDLen), true)
|
||||
if !ok {
|
||||
// Not enough data for session ID
|
||||
return nil
|
||||
}
|
||||
cipherSuitesLen, ok := chBuf.GetUint16(false, true)
|
||||
if !ok {
|
||||
// Not enough data for cipher suites length
|
||||
return nil
|
||||
}
|
||||
if cipherSuitesLen%2 != 0 {
|
||||
// Cipher suites are 2 bytes each, so must be even
|
||||
return nil
|
||||
}
|
||||
ciphers := make([]uint16, cipherSuitesLen/2)
|
||||
for i := range ciphers {
|
||||
ciphers[i], ok = chBuf.GetUint16(false, true)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
m["ciphers"] = ciphers
|
||||
compressionMethodsLen, ok := chBuf.GetByte(true)
|
||||
if !ok {
|
||||
// Not enough data for compression methods length
|
||||
return nil
|
||||
}
|
||||
// Compression methods are 1 byte each, we just put a byte slice here
|
||||
m["compression"], ok = chBuf.Get(int(compressionMethodsLen), true)
|
||||
if !ok {
|
||||
// Not enough data for compression methods
|
||||
return nil
|
||||
}
|
||||
extsLen, ok := chBuf.GetUint16(false, true)
|
||||
if !ok {
|
||||
// No extensions, I guess it's possible?
|
||||
return m
|
||||
}
|
||||
extBuf, ok := chBuf.GetSubBuffer(int(extsLen), true)
|
||||
if !ok {
|
||||
// Not enough data for extensions
|
||||
return nil
|
||||
}
|
||||
for extBuf.Len() > 0 {
|
||||
extType, ok := extBuf.GetUint16(false, true)
|
||||
if !ok {
|
||||
// Not enough data for extension type
|
||||
return nil
|
||||
}
|
||||
extLen, ok := extBuf.GetUint16(false, true)
|
||||
if !ok {
|
||||
// Not enough data for extension length
|
||||
return nil
|
||||
}
|
||||
extDataBuf, ok := extBuf.GetSubBuffer(int(extLen), true)
|
||||
if !ok || !parseTLSExtensions(extType, extDataBuf, m) {
|
||||
// Not enough data for extension data, or invalid extension
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func ParseTLSServerHelloMsgData(shBuf *utils.ByteBuffer) analyzer.PropMap {
|
||||
var ok bool
|
||||
m := make(analyzer.PropMap)
|
||||
// Version, random & session ID length combined are within 35 bytes,
|
||||
// so no need for bounds checking
|
||||
m["version"], _ = shBuf.GetUint16(false, true)
|
||||
m["random"], _ = shBuf.Get(32, true)
|
||||
sessionIDLen, _ := shBuf.GetByte(true)
|
||||
m["session"], ok = shBuf.Get(int(sessionIDLen), true)
|
||||
if !ok {
|
||||
// Not enough data for session ID
|
||||
return nil
|
||||
}
|
||||
cipherSuite, ok := shBuf.GetUint16(false, true)
|
||||
if !ok {
|
||||
// Not enough data for cipher suite
|
||||
return nil
|
||||
}
|
||||
m["cipher"] = cipherSuite
|
||||
compressionMethod, ok := shBuf.GetByte(true)
|
||||
if !ok {
|
||||
// Not enough data for compression method
|
||||
return nil
|
||||
}
|
||||
m["compression"] = compressionMethod
|
||||
extsLen, ok := shBuf.GetUint16(false, true)
|
||||
if !ok {
|
||||
// No extensions, I guess it's possible?
|
||||
return m
|
||||
}
|
||||
extBuf, ok := shBuf.GetSubBuffer(int(extsLen), true)
|
||||
if !ok {
|
||||
// Not enough data for extensions
|
||||
return nil
|
||||
}
|
||||
for extBuf.Len() > 0 {
|
||||
extType, ok := extBuf.GetUint16(false, true)
|
||||
if !ok {
|
||||
// Not enough data for extension type
|
||||
return nil
|
||||
}
|
||||
extLen, ok := extBuf.GetUint16(false, true)
|
||||
if !ok {
|
||||
// Not enough data for extension length
|
||||
return nil
|
||||
}
|
||||
extDataBuf, ok := extBuf.GetSubBuffer(int(extLen), true)
|
||||
if !ok || !parseTLSExtensions(extType, extDataBuf, m) {
|
||||
// Not enough data for extension data, or invalid extension
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func parseTLSExtensions(extType uint16, extDataBuf *utils.ByteBuffer, m analyzer.PropMap) bool {
|
||||
switch extType {
|
||||
case extServerName:
|
||||
ok := extDataBuf.Skip(2) // Ignore list length, we only care about the first entry for now
|
||||
if !ok {
|
||||
// Not enough data for list length
|
||||
return false
|
||||
}
|
||||
sniType, ok := extDataBuf.GetByte(true)
|
||||
if !ok || sniType != 0 {
|
||||
// Not enough data for SNI type, or not hostname
|
||||
return false
|
||||
}
|
||||
sniLen, ok := extDataBuf.GetUint16(false, true)
|
||||
if !ok {
|
||||
// Not enough data for SNI length
|
||||
return false
|
||||
}
|
||||
m["sni"], ok = extDataBuf.GetString(int(sniLen), true)
|
||||
if !ok {
|
||||
// Not enough data for SNI
|
||||
return false
|
||||
}
|
||||
case extALPN:
|
||||
ok := extDataBuf.Skip(2) // Ignore list length, as we read until the end
|
||||
if !ok {
|
||||
// Not enough data for list length
|
||||
return false
|
||||
}
|
||||
var alpnList []string
|
||||
for extDataBuf.Len() > 0 {
|
||||
alpnLen, ok := extDataBuf.GetByte(true)
|
||||
if !ok {
|
||||
// Not enough data for ALPN length
|
||||
return false
|
||||
}
|
||||
alpn, ok := extDataBuf.GetString(int(alpnLen), true)
|
||||
if !ok {
|
||||
// Not enough data for ALPN
|
||||
return false
|
||||
}
|
||||
alpnList = append(alpnList, alpn)
|
||||
}
|
||||
m["alpn"] = alpnList
|
||||
case extSupportedVersions:
|
||||
if extDataBuf.Len() == 2 {
|
||||
// Server only selects one version
|
||||
m["supported_versions"], _ = extDataBuf.GetUint16(false, true)
|
||||
} else {
|
||||
// Client sends a list of versions
|
||||
ok := extDataBuf.Skip(1) // Ignore list length, as we read until the end
|
||||
if !ok {
|
||||
// Not enough data for list length
|
||||
return false
|
||||
}
|
||||
var versions []uint16
|
||||
for extDataBuf.Len() > 0 {
|
||||
ver, ok := extDataBuf.GetUint16(false, true)
|
||||
if !ok {
|
||||
// Not enough data for version
|
||||
return false
|
||||
}
|
||||
versions = append(versions, ver)
|
||||
}
|
||||
m["supported_versions"] = versions
|
||||
}
|
||||
case extEncryptedClientHello:
|
||||
// We can't parse ECH for now, just set a flag
|
||||
m["ech"] = true
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user