mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-26 14:26:07 +00:00
feat(tls,reality): port xray TLS/REALITY fields, cert-hash helpers, fallback UX
TLS: add verifyPeerCertByName (vcn) to inbound settings + emit in both share-link generators (frontend + Go sub) and outbound parser; the allowInsecure replacement xray removed after 2026-06-01. Add server-side curvePreferences, masterKeyLog, echSockopt (passthrough + form) at tlsSettings top-level so they survive the panel-only settings strip. REALITY: add limitFallbackUpload/Download (afterBytes/bytesPerSec/burstBytesPerSec) with per-field tooltips, plus masterKeyLog. Verified field names/semantics against pinned xray v1.260327.1 (bytesPerSec=0 disables). Hosts: fix verify_peer_cert_by_name column bool->string (xray expects comma-separated names) with an idempotent, history-gate-free migration (SQLite typeof blank; Postgres ALTER once); emit vcn for hosts/external proxies. Server: add getCertHash (local cert DER SHA-256) and getRemoteCertHash (xray tls ping) endpoints + api-docs; wire pinned-cert field buttons. Drop the meaningless random-hash button. Xray UI: metrics endpoint (listen/tag) config in Basics; import/export for routing rules and outbounds. Fallbacks card: compact empty state, header-aligned actions, responsive labeled grid rows. i18n: add all new keys to every locale; drop unused generateRandomPin.
This commit is contained in:
@@ -4,9 +4,12 @@ import (
|
||||
"archive/zip"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
@@ -1721,6 +1724,102 @@ func (s *ServerService) GetNewmldsa65() (any, error) {
|
||||
return keyPair, nil
|
||||
}
|
||||
|
||||
// GetCertHash parses a certificate (from a file path or inline PEM/DER content)
|
||||
// and returns the hex-encoded SHA-256 over each certificate's raw DER — the
|
||||
// value xray-core's pinnedPeerCertSha256 (pcs) expects. Lets the panel fill the
|
||||
// pinned-cert field from the inbound's own certificate without the user
|
||||
// computing the hash by hand.
|
||||
func (s *ServerService) GetCertHash(certFile string, certContent string) ([]string, error) {
|
||||
var certBytes []byte
|
||||
if path := strings.TrimSpace(certFile); path != "" {
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certBytes = b
|
||||
} else if strings.TrimSpace(certContent) != "" {
|
||||
certBytes = []byte(certContent)
|
||||
} else {
|
||||
return nil, common.NewError("no certificate provided")
|
||||
}
|
||||
|
||||
var certs []*x509.Certificate
|
||||
if bytes.Contains(certBytes, []byte("BEGIN")) {
|
||||
rest := certBytes
|
||||
for {
|
||||
block, remain := pem.Decode(rest)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, common.NewError("unable to decode certificate: ", err)
|
||||
}
|
||||
certs = append(certs, cert)
|
||||
rest = remain
|
||||
}
|
||||
} else {
|
||||
parsed, err := x509.ParseCertificates(certBytes)
|
||||
if err != nil {
|
||||
return nil, common.NewError("unable to parse certificates: ", err)
|
||||
}
|
||||
certs = parsed
|
||||
}
|
||||
|
||||
if len(certs) == 0 {
|
||||
return nil, common.NewError("no certificates found")
|
||||
}
|
||||
|
||||
hashes := make([]string, 0, len(certs))
|
||||
for _, cert := range certs {
|
||||
sum := sha256.Sum256(cert.Raw)
|
||||
hashes = append(hashes, hex.EncodeToString(sum[:]))
|
||||
}
|
||||
return hashes, nil
|
||||
}
|
||||
|
||||
// GetRemoteCertHash runs `xray tls ping <server>` to fetch the live certificate
|
||||
// SHA-256 of a remote endpoint — the value to put in pinnedPeerCertSha256 (pcs)
|
||||
// when pinning a server whose certificate file you don't hold (a CDN front, a
|
||||
// REALITY dest, an external proxy). Returns the unique leaf-certificate hashes.
|
||||
func (s *ServerService) GetRemoteCertHash(server string) ([]string, error) {
|
||||
server = strings.TrimSpace(server)
|
||||
if server == "" {
|
||||
return nil, common.NewError("no server provided")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
cmd := exec.CommandContext(ctx, xray.GetBinaryPath(), "tls", "ping", server)
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &out
|
||||
if err := cmd.Run(); err != nil && out.Len() == 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hexRe := regexp.MustCompile(`[0-9a-fA-F]{64}`)
|
||||
seen := make(map[string]struct{})
|
||||
var leaves []string
|
||||
for _, line := range strings.Split(out.String(), "\n") {
|
||||
if !strings.Contains(line, "leaf SHA256") {
|
||||
continue
|
||||
}
|
||||
hash := strings.ToLower(hexRe.FindString(line))
|
||||
if hash == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[hash]; !ok {
|
||||
seen[hash] = struct{}{}
|
||||
leaves = append(leaves, hash)
|
||||
}
|
||||
}
|
||||
if len(leaves) == 0 {
|
||||
return nil, common.NewError("no certificate hash found for ", server)
|
||||
}
|
||||
return leaves, nil
|
||||
}
|
||||
|
||||
func (s *ServerService) GetNewEchCert(sni string) (any, error) {
|
||||
// Run the command
|
||||
cmd := exec.Command(xray.GetBinaryPath(), "tls", "ech", "--serverName", sni)
|
||||
|
||||
Reference in New Issue
Block a user