setting_mtls.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. package service
  2. import (
  3. "crypto/sha256"
  4. "crypto/tls"
  5. "crypto/x509"
  6. "encoding/hex"
  7. "encoding/pem"
  8. "strings"
  9. "sync"
  10. "gorm.io/gorm"
  11. "github.com/mhsanaei/3x-ui/v3/internal/database"
  12. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  13. "github.com/mhsanaei/3x-ui/v3/internal/util/common"
  14. "github.com/mhsanaei/3x-ui/v3/internal/util/crypto"
  15. "github.com/mhsanaei/3x-ui/v3/internal/web/runtime"
  16. )
  17. var masterClientCredentialMu sync.Mutex
  18. const (
  19. settingNodeMtlsCaCert = "nodeMtlsCaCertPem"
  20. settingNodeMtlsCaKey = "nodeMtlsCaKeyPem"
  21. settingNodeMtlsClientCert = "nodeMtlsClientCertPem"
  22. settingNodeMtlsClientKey = "nodeMtlsClientKeyPem"
  23. settingNodeMtlsClientPin = "nodeMtlsClientCertSha256"
  24. settingNodeMtlsClientCA = "nodeMtlsClientCAPem"
  25. )
  26. // EnsureNodeMtlsCA returns this panel's node-auth CA, minting and persisting it
  27. // on first use and reusing the stored pair thereafter. The CA private key never
  28. // leaves the panel.
  29. func (s *SettingService) EnsureNodeMtlsCA() (crypto.CertKeyPEM, error) {
  30. certPem, err := s.getString(settingNodeMtlsCaCert)
  31. if err != nil {
  32. return crypto.CertKeyPEM{}, err
  33. }
  34. keyPem, err := s.getString(settingNodeMtlsCaKey)
  35. if err != nil {
  36. return crypto.CertKeyPEM{}, err
  37. }
  38. if certPem != "" && keyPem != "" {
  39. return crypto.CertKeyPEM{CertPEM: []byte(certPem), KeyPEM: []byte(keyPem)}, nil
  40. }
  41. // Fail closed on a half-present pair: regenerating here would silently rotate
  42. // the CA and break trust on nodes that already hold the old cert. Only mint
  43. // when neither half exists (first use).
  44. if certPem != "" || keyPem != "" {
  45. return crypto.CertKeyPEM{}, common.NewError("node mTLS CA is incomplete: one of cert/key is missing; refusing to regenerate")
  46. }
  47. ca, err := crypto.GenerateNodeCA("3x-ui node mTLS CA")
  48. if err != nil {
  49. return crypto.CertKeyPEM{}, err
  50. }
  51. if err := s.saveSetting(settingNodeMtlsCaCert, string(ca.CertPEM)); err != nil {
  52. return crypto.CertKeyPEM{}, err
  53. }
  54. if err := s.saveSetting(settingNodeMtlsCaKey, string(ca.KeyPEM)); err != nil {
  55. return crypto.CertKeyPEM{}, err
  56. }
  57. return ca, nil
  58. }
  59. func clientCertSHA256FromPEM(certPEM []byte) (string, error) {
  60. block, rest := pem.Decode(certPEM)
  61. if block == nil || block.Type != "CERTIFICATE" || len(strings.TrimSpace(string(rest))) != 0 {
  62. return "", common.NewError("client certificate is not valid PEM")
  63. }
  64. cert, err := x509.ParseCertificate(block.Bytes)
  65. if err != nil {
  66. return "", err
  67. }
  68. sum := sha256.Sum256(cert.Raw)
  69. return hex.EncodeToString(sum[:]), nil
  70. }
  71. // EnsureMasterClientCert returns the client certificate this panel presents when
  72. // calling its nodes over mTLS, issuing it from the node CA on first use and
  73. // reusing the stored pair thereafter.
  74. func (s *SettingService) EnsureMasterClientCert() (crypto.CertKeyPEM, error) {
  75. masterClientCredentialMu.Lock()
  76. defer masterClientCredentialMu.Unlock()
  77. certPem, err := s.getString(settingNodeMtlsClientCert)
  78. if err != nil {
  79. return crypto.CertKeyPEM{}, err
  80. }
  81. keyPem, err := s.getString(settingNodeMtlsClientKey)
  82. if err != nil {
  83. return crypto.CertKeyPEM{}, err
  84. }
  85. storedPin, err := s.getString(settingNodeMtlsClientPin)
  86. if err != nil {
  87. return crypto.CertKeyPEM{}, err
  88. }
  89. storedPin = strings.ToLower(strings.TrimSpace(storedPin))
  90. if certPem != "" && keyPem != "" {
  91. if _, err := tls.X509KeyPair([]byte(certPem), []byte(keyPem)); err != nil {
  92. return crypto.CertKeyPEM{}, common.NewError("stored master client certificate/key pair is invalid: ", err)
  93. }
  94. actualPin, err := clientCertSHA256FromPEM([]byte(certPem))
  95. if err != nil {
  96. return crypto.CertKeyPEM{}, err
  97. }
  98. if storedPin != "" && storedPin != actualPin {
  99. return crypto.CertKeyPEM{}, common.NewError("stored master client certificate does not match nodeMtlsClientCertSha256; refusing to rotate")
  100. }
  101. if storedPin == "" {
  102. if err := s.saveSetting(settingNodeMtlsClientPin, actualPin); err != nil {
  103. return crypto.CertKeyPEM{}, err
  104. }
  105. }
  106. return crypto.CertKeyPEM{CertPEM: []byte(certPem), KeyPEM: []byte(keyPem)}, nil
  107. }
  108. // Half a stored pair signals corrupted settings; reissuing would rotate the
  109. // master client credential (and indirectly the CA). Only mint on first use.
  110. if certPem != "" || keyPem != "" {
  111. return crypto.CertKeyPEM{}, common.NewError("master client cert is incomplete: one of cert/key is missing; refusing to reissue")
  112. }
  113. ca, err := s.EnsureNodeMtlsCA()
  114. if err != nil {
  115. return crypto.CertKeyPEM{}, err
  116. }
  117. client, err := crypto.IssueClientCert(ca, "3x-ui master")
  118. if err != nil {
  119. return crypto.CertKeyPEM{}, err
  120. }
  121. pin, err := clientCertSHA256FromPEM(client.CertPEM)
  122. if err != nil {
  123. return crypto.CertKeyPEM{}, err
  124. }
  125. if err := saveMasterClientCredential(client, pin); err != nil {
  126. return crypto.CertKeyPEM{}, err
  127. }
  128. runtime.InvalidateMasterClientConnections()
  129. return client, nil
  130. }
  131. func saveMasterClientCredential(client crypto.CertKeyPEM, pin string) error {
  132. values := map[string]string{
  133. settingNodeMtlsClientCert: string(client.CertPEM),
  134. settingNodeMtlsClientKey: string(client.KeyPEM),
  135. settingNodeMtlsClientPin: pin,
  136. }
  137. return database.GetDB().Transaction(func(tx *gorm.DB) error {
  138. for key, value := range values {
  139. result := tx.Model(&model.Setting{}).Where("key = ?", key).Update("value", value)
  140. if result.Error != nil {
  141. return result.Error
  142. }
  143. if result.RowsAffected == 0 {
  144. if err := tx.Create(&model.Setting{Key: key, Value: value}).Error; err != nil {
  145. return err
  146. }
  147. }
  148. }
  149. return nil
  150. })
  151. }
  152. // LoadMasterClientCert returns only the already-persisted credential. It never
  153. // mints or changes CA/client settings.
  154. func (s *SettingService) LoadMasterClientCert() (crypto.CertKeyPEM, error) {
  155. certPem, err := s.getString(settingNodeMtlsClientCert)
  156. if err != nil {
  157. return crypto.CertKeyPEM{}, err
  158. }
  159. keyPem, err := s.getString(settingNodeMtlsClientKey)
  160. if err != nil {
  161. return crypto.CertKeyPEM{}, err
  162. }
  163. if certPem == "" || keyPem == "" {
  164. return crypto.CertKeyPEM{}, common.NewError("master client certificate is not fully configured")
  165. }
  166. return crypto.CertKeyPEM{CertPEM: []byte(certPem), KeyPEM: []byte(keyPem)}, nil
  167. }
  168. // NodeMtlsClientCAPool builds the trust pool used as the panel listener's
  169. // ClientCAs for incoming node-API client certificates. It returns (nil, nil)
  170. // when no trust CA is configured, so mTLS stays off and the listener behaves
  171. // exactly as before.
  172. func (s *SettingService) NodeMtlsClientCAPool() (*x509.CertPool, error) {
  173. caPem, err := s.getString(settingNodeMtlsClientCA)
  174. if err != nil {
  175. return nil, err
  176. }
  177. if caPem == "" {
  178. return nil, nil
  179. }
  180. pool := x509.NewCertPool()
  181. if !pool.AppendCertsFromPEM([]byte(caPem)) {
  182. return nil, common.NewError("nodeMtlsClientCAPem is not a valid certificate")
  183. }
  184. return pool, nil
  185. }