Browse Source

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 <[email protected]>
风扇滑翔翼 1 month ago
parent
commit
74c726ff62

+ 17 - 0
common/utils/access_field.go

@@ -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
infra/conf/transport_internet.go

@@ -1,6 +1,7 @@
 package conf
 
 import (
+	"context"
 	"encoding/base64"
 	"encoding/hex"
 	"encoding/json"
@@ -10,6 +11,7 @@ import (
 	"strconv"
 	"strings"
 	"syscall"
+	"time"
 
 	"github.com/xtls/xray-core/common/errors"
 	"github.com/xtls/xray-core/common/net"
@@ -747,7 +749,12 @@ func (c *TLSConfig) Build() (proto.Message, error) {
 	config.MasterKeyLog = c.MasterKeyLog
 
 	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 != "" {
 		for v := range strings.SplitSeq(c.PinnedPeerCertSha256, ",") {

+ 36 - 21
main/commands/all/tls/ping.go

@@ -6,8 +6,13 @@ import (
 	"encoding/hex"
 	"fmt"
 	"net"
+	"os"
 	"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/transport/internet/tls"
 )
@@ -46,6 +51,7 @@ func executePing(cmd *base.Command, args []string) {
 	} else {
 		TargetPort, _ = strconv.Atoi(port)
 	}
+	tabWriter := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
 
 	var ip net.IP
 	if len(*pingIPStr) > 0 {
@@ -70,7 +76,7 @@ func executePing(cmd *base.Command, args []string) {
 		if err != nil {
 			base.Fatalf("Failed to dial tcp: %s", err)
 		}
-		tlsConn := gotls.Client(tcpConn, &gotls.Config{
+		tlsConn := GeneraticUClient(tcpConn, &gotls.Config{
 			InsecureSkipVerify: true,
 			NextProtos:         []string{"h2", "http/1.1"},
 			MaxVersion:         gotls.VersionTLS13,
@@ -81,8 +87,9 @@ func executePing(cmd *base.Command, args []string) {
 			fmt.Println("Handshake failure: ", err)
 		} else {
 			fmt.Println("Handshake succeeded")
-			printTLSConnDetail(tlsConn)
-			printCertificates(tlsConn.ConnectionState().PeerCertificates)
+			printTLSConnDetail(tabWriter, tlsConn)
+			printCertificates(tabWriter, tlsConn.ConnectionState().PeerCertificates)
+			tabWriter.Flush()
 		}
 		tlsConn.Close()
 	}
@@ -94,7 +101,7 @@ func executePing(cmd *base.Command, args []string) {
 		if err != nil {
 			base.Fatalf("Failed to dial tcp: %s", err)
 		}
-		tlsConn := gotls.Client(tcpConn, &gotls.Config{
+		tlsConn := GeneraticUClient(tcpConn, &gotls.Config{
 			ServerName: domain,
 			NextProtos: []string{"h2", "http/1.1"},
 			MaxVersion: gotls.VersionTLS13,
@@ -105,8 +112,9 @@ func executePing(cmd *base.Command, args []string) {
 			fmt.Println("Handshake failure: ", err)
 		} else {
 			fmt.Println("Handshake succeeded")
-			printTLSConnDetail(tlsConn)
-			printCertificates(tlsConn.ConnectionState().PeerCertificates)
+			printTLSConnDetail(tabWriter, tlsConn)
+			printCertificates(tabWriter, tlsConn.ConnectionState().PeerCertificates)
+			tabWriter.Flush()
 		}
 		tlsConn.Close()
 	}
@@ -115,38 +123,45 @@ func executePing(cmd *base.Command, args []string) {
 	fmt.Println("TLS ping finished")
 }
 
-func printCertificates(certs []*x509.Certificate) {
+func printCertificates(tabWriter *tabwriter.Writer, certs []*x509.Certificate) {
 	var leaf *x509.Certificate
+	var CAs []*x509.Certificate
 	var length int
 	for _, cert := range certs {
 		length += len(cert.Raw)
 		if len(cert.DNSNames) != 0 {
 			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 {
-		fmt.Println("Cert's signature algorithm: ", leaf.SignatureAlgorithm.String())
-		fmt.Println("Cert's publicKey algorithm: ", leaf.PublicKeyAlgorithm.String())
-		fmt.Println("Cert's allowed domains: ", leaf.DNSNames)
-		fmt.Println("Cert's leaf SHA256: ", hex.EncodeToString(GenerateCertHash(leaf)))
+		fmt.Fprintf(tabWriter, "Cert's signature algorithm: \t %s\n", leaf.SignatureAlgorithm.String())
+		fmt.Fprintf(tabWriter, "Cert's publicKey algorithm: \t %s\n", leaf.PublicKeyAlgorithm.String())
+		fmt.Fprintf(tabWriter, "Cert's leaf SHA256: \t %s\n", 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()
 	var tlsVersion string
-	if connectionState.Version == gotls.VersionTLS13 {
+	switch connectionState.Version {
+	case gotls.VersionTLS13:
 		tlsVersion = "TLS 1.3"
-	} else if connectionState.Version == gotls.VersionTLS12 {
+	case gotls.VersionTLS12:
 		tlsVersion = "TLS 1.2"
 	}
-	fmt.Println("TLS Version: ", tlsVersion)
-	curveID := connectionState.CurveID
-	if curveID != 0 {
-		PostQuantum := (curveID == gotls.X25519MLKEM768)
-		fmt.Println("TLS Post-Quantum key exchange: ", PostQuantum, "("+curveID.String()+")")
+	fmt.Fprintf(tabWriter, "TLS Version: \t %s\n", tlsVersion)
+	curveID := utils.AccessField[utls.CurveID](tlsConn.Conn, "curveID")
+	if curveID != nil {
+		PostQuantum := (*curveID == utls.X25519MLKEM768)
+		fmt.Fprintf(tabWriter, "TLS Post-Quantum key exchange: \t %t (%s)\n", PostQuantum, curveID.String())
 	} else {
-		fmt.Println("TLS Post-Quantum key exchange:  false (RSA Exchange)")
+		fmt.Fprintf(tabWriter, "TLS Post-Quantum key exchange:  false (RSA Exchange)\n")
 	}
 }

+ 1 - 0
transport/internet/tls/config.go

@@ -384,6 +384,7 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
 		PinnedPeerCertSha256: c.PinnedPeerCertSha256,
 	}
 	config := &tls.Config{
+		InsecureSkipVerify:     c.AllowInsecure,
 		Rand:                   randCarrier,
 		ClientSessionCache:     globalSessionCache,
 		RootCAs:                root,

+ 12 - 3
transport/internet/tls/config.pb.go

@@ -177,7 +177,8 @@ func (x *Certificate) GetBuildChain() bool {
 }
 
 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.
 	Certificate []*Certificate `protobuf:"bytes,2,rep,name=certificate,proto3" json:"certificate,omitempty"`
 	// Override server name.
@@ -241,6 +242,13 @@ func (*Config) Descriptor() ([]byte, []int) {
 	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 {
 	if x != nil {
 		return x.Certificate
@@ -385,8 +393,9 @@ const file_transport_internet_tls_config_proto_rawDesc = "" +
 	"\x05Usage\x12\x10\n" +
 	"\fENCIPHERMENT\x10\x00\x12\x14\n" +
 	"\x10AUTHORITY_VERIFY\x10\x01\x12\x13\n" +
-	"\x0fAUTHORITY_ISSUE\x10\x02\"\xce\x06\n" +
-	"\x06Config\x12J\n" +
+	"\x0fAUTHORITY_ISSUE\x10\x02\"\xf5\x06\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" +
 	"\vserver_name\x18\x03 \x01(\tR\n" +
 	"serverName\x12#\n" +

+ 2 - 0
transport/internet/tls/config.proto

@@ -38,6 +38,8 @@ message Certificate {
 }
 
 message Config {
+  bool allow_insecure = 1;
+
   // List of certificates to be served on server.
   repeated Certificate certificate = 2;
 

+ 4 - 0
transport/internet/tls/tls.go

@@ -126,6 +126,10 @@ func UClient(c net.Conn, config *tls.Config, fingerprint *utls.ClientHelloID) ne
 	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 {
 	return &utls.Config{
 		Rand:                           c.Rand,