diff --git a/infra/conf/freedom.go b/infra/conf/freedom.go index d90b7c2d6ee7..4df6b15cc740 100644 --- a/infra/conf/freedom.go +++ b/infra/conf/freedom.go @@ -62,6 +62,10 @@ func (c *FreedomConfig) Build() (proto.Message, error) { var err, err2 error switch strings.ToLower(c.Fragment.Packets) { + case "singletlshello": + // TLS Hello Fragmentation (into multiple handshake messages but one tcp segment) + config.Fragment.PacketsFrom = 1 + config.Fragment.PacketsTo = 1 case "tlshello": // TLS Hello Fragmentation (into multiple handshake messages) config.Fragment.PacketsFrom = 0 diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index 55ad353d0cd1..3afa05deb4a8 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -392,6 +392,31 @@ type FragmentWriter struct { func (f *FragmentWriter) Write(b []byte) (int, error) { f.count++ + if f.fragment.PacketsFrom == 1 && f.fragment.PacketsTo == 1 && f.count == 1 { + // TLS record layer fragments(for client hello) + // Note: TLS handshake doesn't check record layer, so it can be modified by us. + if f.count != 1 || len(b) <= 5 || b[0] != 22 { + return f.writer.Write(b) + } + recordLen := 5 + ((int(b[3]) << 8) | int(b[4])) + if len(b) < recordLen { // Size not match, maybe already fragmented somehow + return f.writer.Write(b) + } + + // Build new fragmented client hello + p2 := b[5 : len(b)/2] + p1 := []byte{b[0], b[1], b[2], byte(len(p2) >> 8), byte(len(p2))} + p4 := b[len(b)/2:] + p3 := []byte{b[0], b[1], b[2], byte(len(p4) >> 8), byte(len(p4))} + + // 😐 Concat requires go v1.22 + x := append(p1, p2...) + x = append(x, p3...) + x = append(x, p4...) + + return f.writer.Write(x) + } + if f.fragment.PacketsFrom == 0 && f.fragment.PacketsTo == 1 { if f.count != 1 || len(b) <= 5 || b[0] != 22 { return f.writer.Write(b)