tls_client.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. package runtime
  2. import (
  3. "crypto/sha256"
  4. "crypto/subtle"
  5. "crypto/tls"
  6. "encoding/base64"
  7. "encoding/hex"
  8. "net/http"
  9. "strings"
  10. "time"
  11. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  12. "github.com/mhsanaei/3x-ui/v3/internal/util/common"
  13. "github.com/mhsanaei/3x-ui/v3/internal/util/netsafe"
  14. )
  15. // defaultNodeHTTPClient reaches nodes trusting the system CA store ("verify"
  16. // mode or plain http); shared so connections pool across nodes.
  17. var defaultNodeHTTPClient = &http.Client{
  18. Transport: &http.Transport{
  19. MaxIdleConns: 64,
  20. MaxIdleConnsPerHost: 4,
  21. IdleConnTimeout: 60 * time.Second,
  22. DialContext: netsafe.SSRFGuardedDialContext,
  23. },
  24. }
  25. // HTTPClientForNode returns the node's HTTP client honoring its TLS verify mode
  26. // (verify→system CA, skip→no check, pin→leaf SHA-256). Used by both the probe
  27. // and every Remote op so they can't disagree on a self-signed node (#5264).
  28. func HTTPClientForNode(n *model.Node) (*http.Client, error) {
  29. mode := n.TlsVerifyMode
  30. if mode == "" {
  31. mode = "verify"
  32. }
  33. if mode == "verify" || n.Scheme == "http" {
  34. return defaultNodeHTTPClient, nil
  35. }
  36. tlsCfg := &tls.Config{InsecureSkipVerify: true} // lgtm[go/disabled-certificate-check]
  37. if mode == "pin" {
  38. want, err := DecodeCertPin(n.PinnedCertSha256)
  39. if err != nil {
  40. return nil, err
  41. }
  42. tlsCfg.VerifyConnection = func(cs tls.ConnectionState) error {
  43. if len(cs.PeerCertificates) == 0 {
  44. return common.NewError("node presented no certificate")
  45. }
  46. sum := sha256.Sum256(cs.PeerCertificates[0].Raw)
  47. if subtle.ConstantTimeCompare(sum[:], want) != 1 {
  48. return common.NewError("node certificate does not match pinned SHA-256")
  49. }
  50. return nil
  51. }
  52. }
  53. return &http.Client{
  54. Transport: &http.Transport{
  55. MaxIdleConns: 64,
  56. MaxIdleConnsPerHost: 4,
  57. IdleConnTimeout: 60 * time.Second,
  58. DialContext: netsafe.SSRFGuardedDialContext,
  59. TLSClientConfig: tlsCfg,
  60. },
  61. }, nil
  62. }
  63. // DecodeCertPin decodes a SHA-256 cert pin given as base64 (Xray's
  64. // pinnedPeerCertSha256 form) or hex with optional colons into 32 raw bytes.
  65. func DecodeCertPin(s string) ([]byte, error) {
  66. s = strings.TrimSpace(s)
  67. if s == "" {
  68. return nil, common.NewError("certificate pin is empty")
  69. }
  70. if b, err := hex.DecodeString(strings.ReplaceAll(s, ":", "")); err == nil && len(b) == sha256.Size {
  71. return b, nil
  72. }
  73. for _, enc := range []*base64.Encoding{base64.StdEncoding, base64.RawStdEncoding, base64.URLEncoding, base64.RawURLEncoding} {
  74. if b, err := enc.DecodeString(s); err == nil && len(b) == sha256.Size {
  75. return b, nil
  76. }
  77. }
  78. return nil, common.NewError("certificate pin must be a SHA-256 hash (base64 or hex)")
  79. }