1
0

tls_client_test.go 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. package runtime
  2. import (
  3. "context"
  4. "crypto/sha256"
  5. "crypto/tls"
  6. "encoding/base64"
  7. "encoding/hex"
  8. "net/http"
  9. "net/http/httptest"
  10. "net/url"
  11. "strconv"
  12. "strings"
  13. "testing"
  14. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  15. "github.com/mhsanaei/3x-ui/v3/internal/util/crypto"
  16. )
  17. // masterCertForTest builds a real CA-signed client certificate for mtls tests.
  18. func masterCertForTest(t *testing.T) tls.Certificate {
  19. t.Helper()
  20. ca, err := crypto.GenerateNodeCA("test ca")
  21. if err != nil {
  22. t.Fatalf("GenerateNodeCA: %v", err)
  23. }
  24. client, err := crypto.IssueClientCert(ca, "master")
  25. if err != nil {
  26. t.Fatalf("IssueClientCert: %v", err)
  27. }
  28. cert, err := tls.X509KeyPair(client.CertPEM, client.KeyPEM)
  29. if err != nil {
  30. t.Fatalf("X509KeyPair: %v", err)
  31. }
  32. return cert
  33. }
  34. // TestTLSConfigForNode_MTLS_PresentsClientCert asserts the mtls branch presents
  35. // the master client cert and verifies the node's server cert against system
  36. // roots (no InsecureSkipVerify, no custom RootCAs).
  37. func TestTLSConfigForNode_MTLS_PresentsClientCert(t *testing.T) {
  38. cert := masterCertForTest(t)
  39. SetMasterClientCertProvider(func() (tls.Certificate, error) { return cert, nil })
  40. t.Cleanup(func() { SetMasterClientCertProvider(nil) })
  41. cfg, err := tlsConfigForNode(&model.Node{TlsVerifyMode: "mtls"})
  42. if err != nil {
  43. t.Fatalf("tlsConfigForNode(mtls): %v", err)
  44. }
  45. if len(cfg.Certificates) != 1 {
  46. t.Fatalf("mtls config must present exactly one client certificate, got %d", len(cfg.Certificates))
  47. }
  48. if cfg.InsecureSkipVerify {
  49. t.Fatal("mtls must NOT skip server verification")
  50. }
  51. if cfg.RootCAs != nil {
  52. t.Fatal("mtls verifies the node server against system roots (RootCAs must be nil)")
  53. }
  54. }
  55. // TestTLSConfigForNode_MTLS_NoProviderFailsClosed asserts mtls fails closed when
  56. // no master client certificate is available, rather than silently dropping auth.
  57. func TestTLSConfigForNode_MTLS_NoProviderFailsClosed(t *testing.T) {
  58. SetMasterClientCertProvider(nil)
  59. if _, err := tlsConfigForNode(&model.Node{TlsVerifyMode: "mtls"}); err == nil {
  60. t.Fatal("mtls without a configured client cert provider must fail closed")
  61. }
  62. }
  63. // nodeForServer builds a node pointing at a loopback test server (loopback is
  64. // SSRF-blocked, so AllowPrivateAddress is set for the guarded dialer).
  65. func nodeForServer(t *testing.T, srv *httptest.Server, mode, pin string) *model.Node {
  66. t.Helper()
  67. u, err := url.Parse(srv.URL)
  68. if err != nil {
  69. t.Fatalf("parse server url: %v", err)
  70. }
  71. port, err := strconv.Atoi(u.Port())
  72. if err != nil {
  73. t.Fatalf("parse server port: %v", err)
  74. }
  75. return &model.Node{
  76. Id: 1,
  77. Name: "n1",
  78. Scheme: "https",
  79. Address: u.Hostname(),
  80. Port: port,
  81. BasePath: "/",
  82. ApiToken: "token",
  83. Enable: true,
  84. AllowPrivateAddress: true,
  85. TlsVerifyMode: mode,
  86. PinnedCertSha256: pin,
  87. }
  88. }
  89. func leafPinBase64(srv *httptest.Server) string {
  90. sum := sha256.Sum256(srv.Certificate().Raw)
  91. return base64.StdEncoding.EncodeToString(sum[:])
  92. }
  93. // A self-signed node must be reachable by Remote ops under skip/pin and
  94. // rejected under verify — the split issue #5264 reported.
  95. func TestRemoteHonorsTLSVerifyMode(t *testing.T) {
  96. srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
  97. w.Header().Set("Content-Type", "application/json")
  98. _, _ = w.Write([]byte(`{"success":true,"obj":[]}`))
  99. }))
  100. defer srv.Close()
  101. goodPin := leafPinBase64(srv)
  102. wrongPin := base64.StdEncoding.EncodeToString(make([]byte, sha256.Size))
  103. cases := []struct {
  104. name string
  105. mode string
  106. pin string
  107. wantErr bool
  108. }{
  109. {"verify rejects self-signed", "verify", "", true},
  110. {"skip accepts self-signed", "skip", "", false},
  111. {"pin accepts matching cert", "pin", goodPin, false},
  112. {"pin rejects mismatched cert", "pin", wrongPin, true},
  113. }
  114. for _, c := range cases {
  115. t.Run(c.name, func(t *testing.T) {
  116. r := NewRemote(nodeForServer(t, srv, c.mode, c.pin), nil)
  117. _, err := r.ListInboundOptions(context.Background())
  118. if c.wantErr && err == nil {
  119. t.Fatalf("mode %q: expected error, got nil", c.mode)
  120. }
  121. if !c.wantErr && err != nil {
  122. t.Fatalf("mode %q: unexpected error: %v", c.mode, err)
  123. }
  124. })
  125. }
  126. }
  127. // The lazily-built client is cached for the Remote's lifetime so repeated
  128. // operations reuse one pooled transport rather than rebuilding TLS each call.
  129. func TestRemoteClientCached(t *testing.T) {
  130. r := NewRemote(&model.Node{Scheme: "https", TlsVerifyMode: "skip"}, nil)
  131. c1, err1 := r.httpClient()
  132. c2, err2 := r.httpClient()
  133. if err1 != nil || err2 != nil {
  134. t.Fatalf("httpClient errors: %v %v", err1, err2)
  135. }
  136. if c1 != c2 {
  137. t.Fatal("expected the same cached client across calls")
  138. }
  139. }
  140. func TestHTTPClientForNodeVerifyShared(t *testing.T) {
  141. // verify mode and plain http both reuse the shared default client.
  142. for _, n := range []*model.Node{
  143. {Scheme: "https", TlsVerifyMode: "verify"},
  144. {Scheme: "https", TlsVerifyMode: ""},
  145. {Scheme: "http", TlsVerifyMode: "skip"},
  146. } {
  147. c, err := HTTPClientForNode(n, "")
  148. if err != nil {
  149. t.Fatalf("HTTPClientForNode(%+v): %v", n, err)
  150. }
  151. if c != defaultNodeHTTPClient {
  152. t.Fatalf("HTTPClientForNode(%+v) = %p, want shared default %p", n, c, defaultNodeHTTPClient)
  153. }
  154. }
  155. }
  156. func TestHTTPClientForNodePinInvalid(t *testing.T) {
  157. // pin mode must fail closed, and with a specific error per cause — not merely
  158. // "some error" (which a bug anywhere in the build path would also satisfy).
  159. cases := []struct {
  160. name string
  161. pin string
  162. wantErr string
  163. }{
  164. {"garbage pin", "not-a-pin", "must be a SHA-256 hash"},
  165. {"empty pin", "", "certificate pin is empty"},
  166. }
  167. for _, c := range cases {
  168. t.Run(c.name, func(t *testing.T) {
  169. _, err := HTTPClientForNode(&model.Node{Scheme: "https", TlsVerifyMode: "pin", PinnedCertSha256: c.pin}, "")
  170. if err == nil {
  171. t.Fatalf("expected error for pin %q", c.pin)
  172. }
  173. if !strings.Contains(err.Error(), c.wantErr) {
  174. t.Fatalf("error = %q, want it to contain %q", err.Error(), c.wantErr)
  175. }
  176. })
  177. }
  178. }
  179. // TestHTTPClientForNode_ProxyPinPreservesPinEnforcement covers the proxy+pin branch
  180. // (tls_client.go:43-52): when a node uses a proxy AND pin mode, the proxy client's
  181. // transport must carry the pinning tls.Config (the `transport.TLSClientConfig = tlsCfg`
  182. // line). Dropping it would silently disable certificate pinning whenever a proxy is set.
  183. func TestHTTPClientForNode_ProxyPinPreservesPinEnforcement(t *testing.T) {
  184. pin := base64.StdEncoding.EncodeToString(make([]byte, sha256.Size))
  185. n := &model.Node{Scheme: "https", TlsVerifyMode: "pin", PinnedCertSha256: pin}
  186. c, err := HTTPClientForNode(n, "socks5://127.0.0.1:1080")
  187. if err != nil {
  188. t.Fatalf("HTTPClientForNode: %v", err)
  189. }
  190. if c == defaultNodeHTTPClient {
  191. t.Fatal("proxy client must not be the shared default client")
  192. }
  193. tr, ok := c.Transport.(*http.Transport)
  194. if !ok {
  195. t.Fatalf("transport is %T, want *http.Transport", c.Transport)
  196. }
  197. if tr.TLSClientConfig == nil || tr.TLSClientConfig.VerifyConnection == nil {
  198. t.Fatal("pin mode over a proxy must install a pinning tls.Config (VerifyConnection); pin enforcement was dropped")
  199. }
  200. }
  201. // TestHTTPClientForNode_ProxyVerifyNoPin covers the proxy+verify branch
  202. // (tls_client.go:40-42): verify mode over a proxy returns the proxy client as-is,
  203. // using system-CA verification and NOT a pin VerifyConnection.
  204. func TestHTTPClientForNode_ProxyVerifyNoPin(t *testing.T) {
  205. n := &model.Node{Scheme: "https", TlsVerifyMode: "verify"}
  206. c, err := HTTPClientForNode(n, "socks5://127.0.0.1:1080")
  207. if err != nil {
  208. t.Fatalf("HTTPClientForNode: %v", err)
  209. }
  210. if c == defaultNodeHTTPClient {
  211. t.Fatal("proxy client must not be the shared default client")
  212. }
  213. if tr, ok := c.Transport.(*http.Transport); ok && tr.TLSClientConfig != nil && tr.TLSClientConfig.VerifyConnection != nil {
  214. t.Fatal("verify mode must not install a pin VerifyConnection")
  215. }
  216. }
  217. // TestTLSConfigForNode_CurrentContract locks the pre-mTLS behavior of
  218. // tlsConfigForNode so the "mtls" branch added later cannot silently regress the
  219. // existing skip/pin modes (characterization — passes on unchanged code).
  220. func TestTLSConfigForNode_CurrentContract(t *testing.T) {
  221. t.Run("skip disables verification with no VerifyConnection", func(t *testing.T) {
  222. cfg, err := tlsConfigForNode(&model.Node{TlsVerifyMode: "skip"})
  223. if err != nil {
  224. t.Fatalf("unexpected error: %v", err)
  225. }
  226. if !cfg.InsecureSkipVerify {
  227. t.Fatal("skip mode must set InsecureSkipVerify")
  228. }
  229. if cfg.VerifyConnection != nil {
  230. t.Fatal("skip mode must not install a VerifyConnection")
  231. }
  232. })
  233. t.Run("pin installs a VerifyConnection", func(t *testing.T) {
  234. pin := base64.StdEncoding.EncodeToString(make([]byte, sha256.Size))
  235. cfg, err := tlsConfigForNode(&model.Node{TlsVerifyMode: "pin", PinnedCertSha256: pin})
  236. if err != nil {
  237. t.Fatalf("unexpected error: %v", err)
  238. }
  239. if cfg.VerifyConnection == nil {
  240. t.Fatal("pin mode must install a VerifyConnection")
  241. }
  242. })
  243. }
  244. func TestDecodeCertPin(t *testing.T) {
  245. raw := sha256.Sum256([]byte("cert"))
  246. hexColon := strings.ToUpper(hex.EncodeToString(raw[:]))
  247. // reinsert colons in openssl -fingerprint style
  248. var withColons strings.Builder
  249. for i := 0; i < len(hexColon); i += 2 {
  250. if i > 0 {
  251. withColons.WriteByte(':')
  252. }
  253. withColons.WriteString(hexColon[i : i+2])
  254. }
  255. cases := []struct {
  256. name string
  257. in string
  258. wantErr bool
  259. }{
  260. {"base64 std", base64.StdEncoding.EncodeToString(raw[:]), false},
  261. {"base64 raw url", base64.RawURLEncoding.EncodeToString(raw[:]), false},
  262. {"hex bare", hex.EncodeToString(raw[:]), false},
  263. {"hex colon openssl", withColons.String(), false},
  264. {"empty", "", true},
  265. {"garbage", "not-a-pin", true},
  266. }
  267. for _, c := range cases {
  268. t.Run(c.name, func(t *testing.T) {
  269. got, err := DecodeCertPin(c.in)
  270. if c.wantErr {
  271. if err == nil {
  272. t.Fatalf("expected error for %q", c.in)
  273. }
  274. return
  275. }
  276. if err != nil {
  277. t.Fatalf("unexpected error for %q: %v", c.in, err)
  278. }
  279. if string(got) != string(raw[:]) {
  280. t.Fatalf("decoded bytes mismatch for %q", c.in)
  281. }
  282. })
  283. }
  284. }