test: improve coverage across package
This commit is contained in:
319
analyzer/internal/tls_test.go
Normal file
319
analyzer/internal/tls_test.go
Normal file
@@ -0,0 +1,319 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"git.difuse.io/Difuse/Mellaris/analyzer/utils"
|
||||
)
|
||||
|
||||
func buildClientHelloMsg(t *testing.T) *utils.ByteBuffer {
|
||||
t.Helper()
|
||||
// Bytes taken from the standard TLS test vector, starting after the record
|
||||
// header (5 bytes) and handshake header (4 bytes).
|
||||
body := []byte{
|
||||
0x03, 0x03, // version TLS 1.2
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, // random
|
||||
0x00, // session ID length = 0
|
||||
0x00, 0x20, // cipher suites length = 32 bytes = 16 suites
|
||||
0xcc, 0xa8, 0xcc, 0xa9, 0xc0, 0x2f, 0xc0, 0x30,
|
||||
0xc0, 0x2b, 0xc0, 0x2c, 0xc0, 0x13, 0xc0, 0x09,
|
||||
0xc0, 0x14, 0xc0, 0x0a, 0x00, 0x9c, 0x00, 0x9d,
|
||||
0x00, 0x2f, 0x00, 0x35, 0xc0, 0x12, 0x00, 0x0a, // ciphers
|
||||
0x01, // compression methods length = 1
|
||||
0x00, // compression method = null
|
||||
0x00, 0x58, // extensions length = 88
|
||||
// extension: server_name
|
||||
0x00, 0x00, // type = server_name
|
||||
0x00, 0x18, // length = 24
|
||||
0x00, 0x16, // server name list length = 22
|
||||
0x00, // name type = hostname
|
||||
0x00, 0x13, // name length = 19
|
||||
'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'u', 'l', 'f', 'h', 'e', 'i', 'm', '.', 'n', 'e', 't',
|
||||
// extension: status_request
|
||||
0x00, 0x05, // type = status_request
|
||||
0x00, 0x05, // length = 5
|
||||
0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
// extension: supported_groups
|
||||
0x00, 0x0a, // type = supported_groups
|
||||
0x00, 0x0a, // length = 10
|
||||
0x00, 0x08, // list length = 8
|
||||
0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19,
|
||||
// extension: ec_point_formats
|
||||
0x00, 0x0b, // type = ec_point_formats
|
||||
0x00, 0x02, // length = 2
|
||||
0x01, 0x00,
|
||||
// extension: signature_algorithms
|
||||
0x00, 0x0d, // type = signature_algorithms
|
||||
0x00, 0x12, // length = 18
|
||||
0x00, 0x10, // list length = 16
|
||||
0x04, 0x01, 0x04, 0x03, 0x05, 0x01, 0x05, 0x03,
|
||||
0x06, 0x01, 0x06, 0x03, 0x02, 0x01, 0x02, 0x03,
|
||||
// extension: renegotiation_info
|
||||
0xff, 0x01, // type = renegotiation_info
|
||||
0x00, 0x01, // length = 1
|
||||
0x00,
|
||||
// extension: extended_master_secret (empty)
|
||||
0x00, 0x17, // type = extended_master_secret
|
||||
0x00, 0x00, // length = 0
|
||||
// extension: session_ticket (empty)
|
||||
0x00, 0x23, // type = session_ticket
|
||||
0x00, 0x00, // length = 0
|
||||
}
|
||||
return &utils.ByteBuffer{Buf: body}
|
||||
}
|
||||
|
||||
func TestParseTLSClientHelloMsgData(t *testing.T) {
|
||||
chBuf := buildClientHelloMsg(t)
|
||||
m := ParseTLSClientHelloMsgData(chBuf)
|
||||
if m == nil {
|
||||
t.Fatal("ParseTLSClientHelloMsgData returned nil")
|
||||
}
|
||||
|
||||
wantVersion := uint16(0x0303)
|
||||
if v, ok := m["version"].(uint16); !ok || v != wantVersion {
|
||||
t.Errorf("version = %v, want %v", m["version"], wantVersion)
|
||||
}
|
||||
|
||||
wantCiphers := []uint16{52392, 52393, 49199, 49200, 49195, 49196, 49171, 49161, 49172, 49162, 156, 157, 47, 53, 49170, 10}
|
||||
if c, ok := m["ciphers"].([]uint16); ok {
|
||||
if !reflect.DeepEqual(c, wantCiphers) {
|
||||
t.Errorf("ciphers = %v, want %v", c, wantCiphers)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("ciphers missing or wrong type: %T", m["ciphers"])
|
||||
}
|
||||
|
||||
if sni, ok := m["sni"].(string); !ok || sni != "example.ulfheim.net" {
|
||||
t.Errorf("sni = %q, want %q", m["sni"], "example.ulfheim.net")
|
||||
}
|
||||
|
||||
if _, ok := m["compression"]; !ok {
|
||||
t.Error("compression key missing")
|
||||
}
|
||||
|
||||
if _, ok := m["session"]; !ok {
|
||||
t.Error("session key missing")
|
||||
}
|
||||
|
||||
if _, ok := m["random"]; !ok {
|
||||
t.Error("random key missing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTLSServerHelloMsgData(t *testing.T) {
|
||||
// ServerHello message body (after record header + handshake header)
|
||||
body := []byte{
|
||||
0x03, 0x03, // version TLS 1.2
|
||||
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
|
||||
0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
|
||||
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
|
||||
0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, // random
|
||||
0x00, // session ID length = 0
|
||||
0xc0, 0x13, // cipher suite
|
||||
0x00, // compression method
|
||||
0x00, 0x05, // extensions length = 5
|
||||
// extension: renegotiation_info
|
||||
0xff, 0x01, // type
|
||||
0x00, 0x01, // length = 1
|
||||
0x00,
|
||||
}
|
||||
|
||||
m := ParseTLSServerHelloMsgData(&utils.ByteBuffer{Buf: body})
|
||||
if m == nil {
|
||||
t.Fatal("ParseTLSServerHelloMsgData returned nil")
|
||||
}
|
||||
|
||||
wantCipher := uint16(0xc013)
|
||||
if c, ok := m["cipher"].(uint16); !ok || c != wantCipher {
|
||||
t.Errorf("cipher = %v, want %v", m["cipher"], wantCipher)
|
||||
}
|
||||
|
||||
wantCompression := uint8(0)
|
||||
if c, ok := m["compression"].(uint8); !ok || c != wantCompression {
|
||||
t.Errorf("compression = %v, want %v", m["compression"], wantCompression)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTLSServerHelloMsgData_NoExtensions(t *testing.T) {
|
||||
// ServerHello message without extensions
|
||||
body := []byte{
|
||||
0x03, 0x03, // version
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, // random
|
||||
0x00, // session ID length
|
||||
0x00, 0xff, // cipher suite
|
||||
0x00, // compression method
|
||||
}
|
||||
|
||||
m := ParseTLSServerHelloMsgData(&utils.ByteBuffer{Buf: body})
|
||||
if m == nil {
|
||||
t.Fatal("ParseTLSServerHelloMsgData returned nil for no-extensions case")
|
||||
}
|
||||
if _, ok := m["cipher"]; !ok {
|
||||
t.Error("cipher key missing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTLSClientHelloMsgData_Truncated(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
buf []byte
|
||||
}{
|
||||
{"too short for session id", []byte{0x03, 0x03, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f}},
|
||||
{"odd cipher suites length", []byte{
|
||||
0x03, 0x03, // version
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, // random
|
||||
0x00, // session ID length
|
||||
0x00, 0x03, // odd cipher suites length
|
||||
}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
m := ParseTLSClientHelloMsgData(&utils.ByteBuffer{Buf: tt.buf})
|
||||
if m != nil {
|
||||
t.Error("expected nil for truncated input")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTLSClientHelloMsgData_ECH(t *testing.T) {
|
||||
// ClientHello with ECH extension
|
||||
body := []byte{
|
||||
0x03, 0x03, // version
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, // random
|
||||
0x00, // session ID length
|
||||
0x00, 0x02, // cipher suites length = 2
|
||||
0x13, 0x01, // TLS_AES_128_GCM_SHA256
|
||||
0x01, // compression methods length = 1
|
||||
0x00, // null compression
|
||||
0x00, 0x07, // extensions length = 7
|
||||
// ECH extension
|
||||
0xfe, 0x0d, // type = encrypted_client_hello
|
||||
0x00, 0x03, // length = 3
|
||||
0x01, 0x02, 0x03, // some ECH data
|
||||
}
|
||||
|
||||
m := ParseTLSClientHelloMsgData(&utils.ByteBuffer{Buf: body})
|
||||
if m == nil {
|
||||
t.Fatal("ParseTLSClientHelloMsgData returned nil")
|
||||
}
|
||||
ech, ok := m["ech"].(bool)
|
||||
if !ok || !ech {
|
||||
t.Errorf("ech = %v, want true", m["ech"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTLSClientHelloMsgData_ALPN(t *testing.T) {
|
||||
// ClientHello with ALPN extension
|
||||
body := []byte{
|
||||
0x03, 0x03, // version
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, // random
|
||||
0x00, // session ID length
|
||||
0x00, 0x02, // cipher suites length = 2
|
||||
0x13, 0x01, // cipher suite
|
||||
0x01, // compression methods length = 1
|
||||
0x00, // compression method
|
||||
0x00, 0x12, // extensions length = 18
|
||||
// ALPN extension
|
||||
0x00, 0x10, // type = ALPN
|
||||
0x00, 0x0e, // length = 14
|
||||
0x00, 0x0c, // list length = 12
|
||||
0x08, 'h', 't', 't', 'p', '/', '1', '.', '1',
|
||||
0x02, 'h', '2',
|
||||
}
|
||||
|
||||
m := ParseTLSClientHelloMsgData(&utils.ByteBuffer{Buf: body})
|
||||
if m == nil {
|
||||
t.Fatal("ParseTLSClientHelloMsgData returned nil")
|
||||
}
|
||||
alpn, ok := m["alpn"].([]string)
|
||||
if !ok {
|
||||
t.Fatalf("alpn missing or wrong type: %T", m["alpn"])
|
||||
}
|
||||
if len(alpn) != 2 || alpn[0] != "http/1.1" || alpn[1] != "h2" {
|
||||
t.Errorf("alpn = %v, want [http/1.1 h2]", alpn)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTLSClientHelloMsgData_SupportedVersionsClient(t *testing.T) {
|
||||
// ClientHello with supported_versions extension (client format - list)
|
||||
body := []byte{
|
||||
0x03, 0x03, // version
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, // random
|
||||
0x00, // session ID length
|
||||
0x00, 0x02, // cipher suites length = 2
|
||||
0x13, 0x01, // cipher suite
|
||||
0x01, // compression methods length = 1
|
||||
0x00, // compression method
|
||||
0x00, 0x0b, // extensions length = 11
|
||||
// supported_versions (client list format)
|
||||
0x00, 0x2b, // type = supported_versions
|
||||
0x00, 0x07, // length = 7
|
||||
0x06, // list length = 6
|
||||
0x03, 0x04, // TLS 1.3
|
||||
0x03, 0x03, // TLS 1.2
|
||||
0x03, 0x01, // TLS 1.0
|
||||
}
|
||||
|
||||
m := ParseTLSClientHelloMsgData(&utils.ByteBuffer{Buf: body})
|
||||
if m == nil {
|
||||
t.Fatal("ParseTLSClientHelloMsgData returned nil")
|
||||
}
|
||||
versions, ok := m["supported_versions"].([]uint16)
|
||||
if !ok {
|
||||
t.Fatalf("supported_versions missing or wrong type: %T", m["supported_versions"])
|
||||
}
|
||||
want := []uint16{0x0304, 0x0303, 0x0301}
|
||||
if !reflect.DeepEqual(versions, want) {
|
||||
t.Errorf("supported_versions = %v, want %v", versions, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTLSServerHelloMsgData_SupportedVersionsServer(t *testing.T) {
|
||||
// ServerHello with supported_versions extension (server format - single value)
|
||||
body := []byte{
|
||||
0x03, 0x03, // version
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, // random
|
||||
0x00, // session ID length
|
||||
0x13, 0x01, // cipher suite
|
||||
0x00, // compression method
|
||||
0x00, 0x06, // extensions length = 6
|
||||
// supported_versions (server format - single value)
|
||||
0x00, 0x2b, // type
|
||||
0x00, 0x02, // length = 2
|
||||
0x03, 0x04, // TLS 1.3
|
||||
}
|
||||
|
||||
m := ParseTLSServerHelloMsgData(&utils.ByteBuffer{Buf: body})
|
||||
if m == nil {
|
||||
t.Fatal("ParseTLSServerHelloMsgData returned nil")
|
||||
}
|
||||
v, ok := m["supported_versions"].(uint16)
|
||||
if !ok {
|
||||
t.Fatalf("supported_versions missing or wrong type: %T", m["supported_versions"])
|
||||
}
|
||||
if v != 0x0304 {
|
||||
t.Errorf("supported_versions = 0x%04x, want 0x0304", v)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user