client_test.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. package tls
  2. import (
  3. "context"
  4. "crypto/tls"
  5. "net"
  6. "testing"
  7. tf "github.com/sagernet/sing-box/common/tlsfragment"
  8. "github.com/sagernet/sing-box/common/tlsspoof"
  9. "github.com/sagernet/sing-box/option"
  10. "github.com/stretchr/testify/require"
  11. )
  12. func TestParseTLSSpoofOptions_Disabled(t *testing.T) {
  13. t.Parallel()
  14. spoof, method, err := parseTLSSpoofOptions("example.com", option.OutboundTLSOptions{})
  15. require.NoError(t, err)
  16. require.Empty(t, spoof)
  17. require.Equal(t, tlsspoof.MethodWrongSequence, method)
  18. }
  19. func TestParseTLSSpoofOptions_MethodWithoutSpoof(t *testing.T) {
  20. t.Parallel()
  21. _, _, err := parseTLSSpoofOptions("example.com", option.OutboundTLSOptions{
  22. SpoofMethod: tlsspoof.MethodNameWrongChecksum,
  23. })
  24. require.Error(t, err)
  25. }
  26. func TestParseTLSSpoofOptions_IPLiteralRejected(t *testing.T) {
  27. t.Parallel()
  28. _, _, err := parseTLSSpoofOptions("1.2.3.4", option.OutboundTLSOptions{
  29. Spoof: "example.com",
  30. })
  31. require.Error(t, err)
  32. }
  33. func TestParseTLSSpoofOptions_EmptyServerNameRejected(t *testing.T) {
  34. t.Parallel()
  35. _, _, err := parseTLSSpoofOptions("", option.OutboundTLSOptions{
  36. Spoof: "example.com",
  37. })
  38. require.Error(t, err)
  39. }
  40. func TestParseTLSSpoofOptions_DisableSNIRejected(t *testing.T) {
  41. t.Parallel()
  42. _, _, err := parseTLSSpoofOptions("example.com", option.OutboundTLSOptions{
  43. Spoof: "decoy.com",
  44. DisableSNI: true,
  45. })
  46. require.Error(t, err)
  47. }
  48. // TestParseTLSSpoofOptions_RejectsSameSNI is the primary regression test for
  49. // the "spoofed packet contains the original SNI" bug report: when a user
  50. // configures spoof equal to server_name, the rewriter produces a byte-identical
  51. // record, so the fake and real ClientHellos on the wire look the same. Reject
  52. // at parse time.
  53. func TestParseTLSSpoofOptions_RejectsSameSNI(t *testing.T) {
  54. t.Parallel()
  55. _, _, err := parseTLSSpoofOptions("example.com", option.OutboundTLSOptions{
  56. Spoof: "example.com",
  57. })
  58. require.Error(t, err)
  59. _, _, err = parseTLSSpoofOptions("example.com", option.OutboundTLSOptions{
  60. Spoof: "EXAMPLE.com",
  61. })
  62. require.Error(t, err, "comparison must be case-insensitive")
  63. }
  64. func TestParseTLSSpoofOptions_UnknownMethodRejected(t *testing.T) {
  65. t.Parallel()
  66. _, _, err := parseTLSSpoofOptions("example.com", option.OutboundTLSOptions{
  67. Spoof: "decoy.com",
  68. SpoofMethod: "nonsense",
  69. })
  70. require.Error(t, err)
  71. }
  72. func TestParseTLSSpoofOptions_DistinctSNIAccepted(t *testing.T) {
  73. t.Parallel()
  74. if !tlsspoof.PlatformSupported {
  75. t.Skip("tlsspoof not supported on this platform")
  76. }
  77. spoof, method, err := parseTLSSpoofOptions("example.com", option.OutboundTLSOptions{
  78. Spoof: "decoy.com",
  79. SpoofMethod: tlsspoof.MethodNameWrongSequence,
  80. })
  81. require.NoError(t, err)
  82. require.Equal(t, "decoy.com", spoof)
  83. require.Equal(t, tlsspoof.MethodWrongSequence, method)
  84. }
  85. // The following tests guard the wrap gate in STDClientConfig.Client():
  86. // tf.Conn must wrap the underlying connection whenever either `fragment` or
  87. // `record_fragment` is set, so that TLS fragmentation coexists with features
  88. // like tls_spoof that layer on top of tf.Conn.
  89. func newSTDClientConfigForGateTest(fragment, recordFragment bool) *STDClientConfig {
  90. return &STDClientConfig{
  91. ctx: context.Background(),
  92. config: &tls.Config{ServerName: "example.com", InsecureSkipVerify: true},
  93. fragment: fragment,
  94. recordFragment: recordFragment,
  95. }
  96. }
  97. func TestSTDClient_Client_NoFragment_DoesNotWrap(t *testing.T) {
  98. t.Parallel()
  99. client, server := net.Pipe()
  100. defer client.Close()
  101. defer server.Close()
  102. wrapped, err := newSTDClientConfigForGateTest(false, false).Client(client)
  103. require.NoError(t, err)
  104. _, isTF := wrapped.NetConn().(*tf.Conn)
  105. require.False(t, isTF, "no fragment flags: must not wrap with tf.Conn")
  106. }
  107. func TestSTDClient_Client_FragmentOnly_Wraps(t *testing.T) {
  108. t.Parallel()
  109. client, server := net.Pipe()
  110. defer client.Close()
  111. defer server.Close()
  112. wrapped, err := newSTDClientConfigForGateTest(true, false).Client(client)
  113. require.NoError(t, err)
  114. _, isTF := wrapped.NetConn().(*tf.Conn)
  115. require.True(t, isTF, "fragment=true: must wrap with tf.Conn")
  116. }
  117. func TestSTDClient_Client_RecordFragmentOnly_Wraps(t *testing.T) {
  118. t.Parallel()
  119. client, server := net.Pipe()
  120. defer client.Close()
  121. defer server.Close()
  122. wrapped, err := newSTDClientConfigForGateTest(false, true).Client(client)
  123. require.NoError(t, err)
  124. _, isTF := wrapped.NetConn().(*tf.Conn)
  125. require.True(t, isTF, "record_fragment=true: must wrap with tf.Conn")
  126. }
  127. func TestSTDClient_Client_BothFragment_Wraps(t *testing.T) {
  128. t.Parallel()
  129. client, server := net.Pipe()
  130. defer client.Close()
  131. defer server.Close()
  132. wrapped, err := newSTDClientConfigForGateTest(true, true).Client(client)
  133. require.NoError(t, err)
  134. _, isTF := wrapped.NetConn().(*tf.Conn)
  135. require.True(t, isTF, "both fragment flags: must wrap with tf.Conn")
  136. }