Commands: Print CA cert's SHA256 in tls ping (#5644)

And https://github.com/XTLS/Xray-core/issues/5642#issuecomment-3840806246

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
This commit is contained in:
风扇滑翔翼
2026-02-04 05:03:48 +08:00
committed by GitHub
parent d14767d4f3
commit 74c726ff62
7 changed files with 80 additions and 25 deletions
+17
View File
@@ -0,0 +1,17 @@
package utils
import (
"reflect"
"unsafe"
)
// AccessField can used to access unexported field of a struct
// valueType must be the exact type of the field or it will panic
func AccessField[valueType any](obj any, fieldName string) *valueType {
field := reflect.ValueOf(obj).Elem().FieldByName(fieldName)
if field.Type() != reflect.TypeOf(*new(valueType)) {
panic("field type: " + field.Type().String() + ", valueType: " + reflect.TypeOf(*new(valueType)).String())
}
v := (*valueType)(unsafe.Pointer(field.UnsafeAddr()))
return v
}
+8 -1
View File
@@ -1,6 +1,7 @@
package conf package conf
import ( import (
"context"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
@@ -10,6 +11,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
"time"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
@@ -747,7 +749,12 @@ func (c *TLSConfig) Build() (proto.Message, error) {
config.MasterKeyLog = c.MasterKeyLog config.MasterKeyLog = c.MasterKeyLog
if c.AllowInsecure { if c.AllowInsecure {
return nil, errors.PrintRemovedFeatureError(`"allowInsecure"`, `"pinnedPeerCertSha256"`) if time.Now().After(time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC)) {
return nil, errors.PrintRemovedFeatureError(`"allowInsecure"`, `"pinnedPeerCertSha256"`)
} else {
errors.LogWarning(context.Background(), `"allowInsecure" will be removed automatically after 2026-06-01, please use "pinnedPeerCertSha256"(pcs) and "verifyPeerCertByName"(vcn) instead, PLEASE CONTACT YOUR SERVICE PROVIDER (AIRPORT)`)
config.AllowInsecure = true
}
} }
if c.PinnedPeerCertSha256 != "" { if c.PinnedPeerCertSha256 != "" {
for v := range strings.SplitSeq(c.PinnedPeerCertSha256, ",") { for v := range strings.SplitSeq(c.PinnedPeerCertSha256, ",") {
+36 -21
View File
@@ -6,8 +6,13 @@ import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"net" "net"
"os"
"strconv" "strconv"
"text/tabwriter"
utls "github.com/refraction-networking/utls"
"github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/main/commands/base" "github.com/xtls/xray-core/main/commands/base"
. "github.com/xtls/xray-core/transport/internet/tls" . "github.com/xtls/xray-core/transport/internet/tls"
) )
@@ -46,6 +51,7 @@ func executePing(cmd *base.Command, args []string) {
} else { } else {
TargetPort, _ = strconv.Atoi(port) TargetPort, _ = strconv.Atoi(port)
} }
tabWriter := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
var ip net.IP var ip net.IP
if len(*pingIPStr) > 0 { if len(*pingIPStr) > 0 {
@@ -70,7 +76,7 @@ func executePing(cmd *base.Command, args []string) {
if err != nil { if err != nil {
base.Fatalf("Failed to dial tcp: %s", err) base.Fatalf("Failed to dial tcp: %s", err)
} }
tlsConn := gotls.Client(tcpConn, &gotls.Config{ tlsConn := GeneraticUClient(tcpConn, &gotls.Config{
InsecureSkipVerify: true, InsecureSkipVerify: true,
NextProtos: []string{"h2", "http/1.1"}, NextProtos: []string{"h2", "http/1.1"},
MaxVersion: gotls.VersionTLS13, MaxVersion: gotls.VersionTLS13,
@@ -81,8 +87,9 @@ func executePing(cmd *base.Command, args []string) {
fmt.Println("Handshake failure: ", err) fmt.Println("Handshake failure: ", err)
} else { } else {
fmt.Println("Handshake succeeded") fmt.Println("Handshake succeeded")
printTLSConnDetail(tlsConn) printTLSConnDetail(tabWriter, tlsConn)
printCertificates(tlsConn.ConnectionState().PeerCertificates) printCertificates(tabWriter, tlsConn.ConnectionState().PeerCertificates)
tabWriter.Flush()
} }
tlsConn.Close() tlsConn.Close()
} }
@@ -94,7 +101,7 @@ func executePing(cmd *base.Command, args []string) {
if err != nil { if err != nil {
base.Fatalf("Failed to dial tcp: %s", err) base.Fatalf("Failed to dial tcp: %s", err)
} }
tlsConn := gotls.Client(tcpConn, &gotls.Config{ tlsConn := GeneraticUClient(tcpConn, &gotls.Config{
ServerName: domain, ServerName: domain,
NextProtos: []string{"h2", "http/1.1"}, NextProtos: []string{"h2", "http/1.1"},
MaxVersion: gotls.VersionTLS13, MaxVersion: gotls.VersionTLS13,
@@ -105,8 +112,9 @@ func executePing(cmd *base.Command, args []string) {
fmt.Println("Handshake failure: ", err) fmt.Println("Handshake failure: ", err)
} else { } else {
fmt.Println("Handshake succeeded") fmt.Println("Handshake succeeded")
printTLSConnDetail(tlsConn) printTLSConnDetail(tabWriter, tlsConn)
printCertificates(tlsConn.ConnectionState().PeerCertificates) printCertificates(tabWriter, tlsConn.ConnectionState().PeerCertificates)
tabWriter.Flush()
} }
tlsConn.Close() tlsConn.Close()
} }
@@ -115,38 +123,45 @@ func executePing(cmd *base.Command, args []string) {
fmt.Println("TLS ping finished") fmt.Println("TLS ping finished")
} }
func printCertificates(certs []*x509.Certificate) { func printCertificates(tabWriter *tabwriter.Writer, certs []*x509.Certificate) {
var leaf *x509.Certificate var leaf *x509.Certificate
var CAs []*x509.Certificate
var length int var length int
for _, cert := range certs { for _, cert := range certs {
length += len(cert.Raw) length += len(cert.Raw)
if len(cert.DNSNames) != 0 { if len(cert.DNSNames) != 0 {
leaf = cert leaf = cert
} else {
CAs = append(CAs, cert)
} }
} }
fmt.Println("Certificate chain's total length: ", length, "(certs count: "+strconv.Itoa(len(certs))+")") fmt.Fprintf(tabWriter, "Certificate chain's total length: \t %d (certs count: %s)\n", length, strconv.Itoa(len(certs)))
if leaf != nil { if leaf != nil {
fmt.Println("Cert's signature algorithm: ", leaf.SignatureAlgorithm.String()) fmt.Fprintf(tabWriter, "Cert's signature algorithm: \t %s\n", leaf.SignatureAlgorithm.String())
fmt.Println("Cert's publicKey algorithm: ", leaf.PublicKeyAlgorithm.String()) fmt.Fprintf(tabWriter, "Cert's publicKey algorithm: \t %s\n", leaf.PublicKeyAlgorithm.String())
fmt.Println("Cert's allowed domains: ", leaf.DNSNames) fmt.Fprintf(tabWriter, "Cert's leaf SHA256: \t %s\n", hex.EncodeToString(GenerateCertHash(leaf)))
fmt.Println("Cert's leaf SHA256: ", hex.EncodeToString(GenerateCertHash(leaf))) for _, ca := range CAs {
fmt.Fprintf(tabWriter, "Cert's CA: %s SHA256: \t %s\n", ca.Subject.CommonName, hex.EncodeToString(GenerateCertHash(ca)))
}
fmt.Fprintf(tabWriter, "Cert's allowed domains: \t %v\n", leaf.DNSNames)
} }
} }
func printTLSConnDetail(tlsConn *gotls.Conn) { func printTLSConnDetail(tabWriter *tabwriter.Writer, tlsConn *utls.UConn) {
connectionState := tlsConn.ConnectionState() connectionState := tlsConn.ConnectionState()
var tlsVersion string var tlsVersion string
if connectionState.Version == gotls.VersionTLS13 { switch connectionState.Version {
case gotls.VersionTLS13:
tlsVersion = "TLS 1.3" tlsVersion = "TLS 1.3"
} else if connectionState.Version == gotls.VersionTLS12 { case gotls.VersionTLS12:
tlsVersion = "TLS 1.2" tlsVersion = "TLS 1.2"
} }
fmt.Println("TLS Version: ", tlsVersion) fmt.Fprintf(tabWriter, "TLS Version: \t %s\n", tlsVersion)
curveID := connectionState.CurveID curveID := utils.AccessField[utls.CurveID](tlsConn.Conn, "curveID")
if curveID != 0 { if curveID != nil {
PostQuantum := (curveID == gotls.X25519MLKEM768) PostQuantum := (*curveID == utls.X25519MLKEM768)
fmt.Println("TLS Post-Quantum key exchange: ", PostQuantum, "("+curveID.String()+")") fmt.Fprintf(tabWriter, "TLS Post-Quantum key exchange: \t %t (%s)\n", PostQuantum, curveID.String())
} else { } else {
fmt.Println("TLS Post-Quantum key exchange: false (RSA Exchange)") fmt.Fprintf(tabWriter, "TLS Post-Quantum key exchange: false (RSA Exchange)\n")
} }
} }
+1
View File
@@ -384,6 +384,7 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
PinnedPeerCertSha256: c.PinnedPeerCertSha256, PinnedPeerCertSha256: c.PinnedPeerCertSha256,
} }
config := &tls.Config{ config := &tls.Config{
InsecureSkipVerify: c.AllowInsecure,
Rand: randCarrier, Rand: randCarrier,
ClientSessionCache: globalSessionCache, ClientSessionCache: globalSessionCache,
RootCAs: root, RootCAs: root,
+12 -3
View File
@@ -177,7 +177,8 @@ func (x *Certificate) GetBuildChain() bool {
} }
type Config struct { type Config struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
AllowInsecure bool `protobuf:"varint,1,opt,name=allow_insecure,json=allowInsecure,proto3" json:"allow_insecure,omitempty"`
// List of certificates to be served on server. // List of certificates to be served on server.
Certificate []*Certificate `protobuf:"bytes,2,rep,name=certificate,proto3" json:"certificate,omitempty"` Certificate []*Certificate `protobuf:"bytes,2,rep,name=certificate,proto3" json:"certificate,omitempty"`
// Override server name. // Override server name.
@@ -241,6 +242,13 @@ func (*Config) Descriptor() ([]byte, []int) {
return file_transport_internet_tls_config_proto_rawDescGZIP(), []int{1} return file_transport_internet_tls_config_proto_rawDescGZIP(), []int{1}
} }
func (x *Config) GetAllowInsecure() bool {
if x != nil {
return x.AllowInsecure
}
return false
}
func (x *Config) GetCertificate() []*Certificate { func (x *Config) GetCertificate() []*Certificate {
if x != nil { if x != nil {
return x.Certificate return x.Certificate
@@ -385,8 +393,9 @@ const file_transport_internet_tls_config_proto_rawDesc = "" +
"\x05Usage\x12\x10\n" + "\x05Usage\x12\x10\n" +
"\fENCIPHERMENT\x10\x00\x12\x14\n" + "\fENCIPHERMENT\x10\x00\x12\x14\n" +
"\x10AUTHORITY_VERIFY\x10\x01\x12\x13\n" + "\x10AUTHORITY_VERIFY\x10\x01\x12\x13\n" +
"\x0fAUTHORITY_ISSUE\x10\x02\"\xce\x06\n" + "\x0fAUTHORITY_ISSUE\x10\x02\"\xf5\x06\n" +
"\x06Config\x12J\n" + "\x06Config\x12%\n" +
"\x0eallow_insecure\x18\x01 \x01(\bR\rallowInsecure\x12J\n" +
"\vcertificate\x18\x02 \x03(\v2(.xray.transport.internet.tls.CertificateR\vcertificate\x12\x1f\n" + "\vcertificate\x18\x02 \x03(\v2(.xray.transport.internet.tls.CertificateR\vcertificate\x12\x1f\n" +
"\vserver_name\x18\x03 \x01(\tR\n" + "\vserver_name\x18\x03 \x01(\tR\n" +
"serverName\x12#\n" + "serverName\x12#\n" +
+2
View File
@@ -38,6 +38,8 @@ message Certificate {
} }
message Config { message Config {
bool allow_insecure = 1;
// List of certificates to be served on server. // List of certificates to be served on server.
repeated Certificate certificate = 2; repeated Certificate certificate = 2;
+4
View File
@@ -126,6 +126,10 @@ func UClient(c net.Conn, config *tls.Config, fingerprint *utls.ClientHelloID) ne
return &UConn{UConn: utlsConn} return &UConn{UConn: utlsConn}
} }
func GeneraticUClient(c net.Conn, config *tls.Config) *utls.UConn {
return utls.UClient(c, copyConfig(config), utls.HelloChrome_Auto)
}
func copyConfig(c *tls.Config) *utls.Config { func copyConfig(c *tls.Config) *utls.Config {
return &utls.Config{ return &utls.Config{
Rand: c.Rand, Rand: c.Rand,