TLS Security Layer
Introduction
The TLS layer in Xray-core provides standard TLS encryption with extensive customization. It supports both Go's standard crypto/tls and uTLS (refraction-networking/utls) for TLS fingerprinting, dynamic certificate generation from CA, OCSP stapling, certificate pinning, session resumption, ECH (Encrypted Client Hello), custom cipher suites, curve preferences, and key logging. TLS is not a transport itself but a security layer that wraps any transport.
Key Files
transport/internet/tls/tls.go-- Core types:Conn,UConn, fingerprint registrytransport/internet/tls/config.go--Config.GetTLSConfig(), certificate management, optionstransport/internet/tls/pin.go-- Certificate hash functions for pinningtransport/internet/tls/ech.go-- ECH (Encrypted Client Hello) supporttransport/internet/tls/grpc.go-- gRPC-specific TLS helperstransport/internet/tls/unsafe.go-- Unsafe certificate pool access
Connection Types
Conn (Standard TLS)
tls.Conn (tls/tls.go:26-28) wraps Go's crypto/tls.Conn:
type Conn struct {
*tls.Conn
}Features:
- Graceful close timeout: 250ms timeout for TLS close notify, after which the underlying connection is force-closed (
tls.go:30-38) - MultiBuffer write: Compacts and writes multi-buffers efficiently (
tls.go:40-45) - NegotiatedProtocol: Reports ALPN result (
tls.go:54-57)
UConn (uTLS Fingerprinting)
tls.UConn (tls/tls.go:71-73) wraps utls.UConn for TLS fingerprinting:
type UConn struct {
*utls.UConn
}The WebsocketHandshakeContext method (tls.go:94-117) forces http/1.1 ALPN for WebSocket connections using uTLS:
func (c *UConn) WebsocketHandshakeContext(ctx context.Context) error {
c.BuildHandshakeState()
// Find ALPNExtension and override to ["http/1.1"]
// Rebuild handshake state
return c.HandshakeContext(ctx)
}Interface
Both types implement the Interface (tls.go:15-21):
type Interface interface {
net.Conn
HandshakeContext(ctx context.Context) error
VerifyHostname(host string) error
HandshakeContextServerName(ctx context.Context) string
NegotiatedProtocol() string
}TLS Fingerprinting
Fingerprint Registry
Three tiers of fingerprints are defined (tls/tls.go:185-257):
PresetFingerprints (recommended GUI options):
| Name | uTLS ID |
|---|---|
chrome | HelloChrome_Auto |
firefox | HelloFirefox_Auto |
safari | HelloSafari_Auto |
ios | HelloIOS_Auto |
android | HelloAndroid_11_OkHttp |
edge | HelloEdge_Auto |
360 | Hello360_Auto |
qq | HelloQQ_Auto |
random | Randomly selected at startup |
randomized | HelloRandomizedALPN with custom weights |
randomizednoalpn | HelloRandomizedNoALPN with custom weights |
ModernFingerprints: Specific browser versions (Chrome 83-131, Firefox 99-120, etc.)
OtherFingerprints: Legacy versions and special fingerprints.
Random Fingerprint Selection
At startup (tls.go:145-167), "random" is assigned to a randomly selected modern fingerprint:
func init() {
bigInt, _ := rand.Int(rand.Reader, big.NewInt(int64(len(ModernFingerprints))))
stopAt := int(bigInt.Int64())
// Select the stopAt-th entry from ModernFingerprints
PresetFingerprints["random"] = v
}The "randomized" option uses uTLS's built-in randomization with customized weights:
- Forces TLS 1.3 (
TLSVersMax_Set_VersionTLS13 = 1) - Disables P256 for first key share (
FirstKeyShare_Set_CurveP256 = 0)
GetFingerprint
GetFingerprint (tls.go:169-183) looks up fingerprints across all three maps:
func GetFingerprint(name string) (fingerprint *utls.ClientHelloID) {
if name == "" { return &utls.HelloChrome_Auto }
if fingerprint = PresetFingerprints[name]; fingerprint != nil { return }
if fingerprint = ModernFingerprints[name]; fingerprint != nil { return }
if fingerprint = OtherFingerprints[name]; fingerprint != nil { return }
return // nil = standard Go TLS
}An empty name defaults to Chrome Auto. If no match is found, nil is returned, which signals the caller to use standard Go TLS.
Client/Server Construction
Client
// tls.go:60-63 -- Standard Go TLS
func Client(c net.Conn, config *tls.Config) net.Conn {
tlsConn := tls.Client(c, config)
return &Conn{Conn: tlsConn}
}
// tls.go:124-127 -- uTLS fingerprinted
func UClient(c net.Conn, config *tls.Config, fingerprint *utls.ClientHelloID) net.Conn {
utlsConn := utls.UClient(c, copyConfig(config), *fingerprint)
return &UConn{UConn: utlsConn}
}Server
// tls.go:66-69
func Server(c net.Conn, config *tls.Config) net.Conn {
tlsConn := tls.Server(c, config)
return &Conn{Conn: tlsConn}
}Config Copying for uTLS
copyConfig (tls.go:133-143) translates crypto/tls.Config to utls.Config:
func copyConfig(c *tls.Config) *utls.Config {
return &utls.Config{
Rand: c.Rand,
RootCAs: c.RootCAs,
ServerName: c.ServerName,
InsecureSkipVerify: c.InsecureSkipVerify,
VerifyPeerCertificate: c.VerifyPeerCertificate,
KeyLogWriter: c.KeyLogWriter,
EncryptedClientHelloConfigList: c.EncryptedClientHelloConfigList,
}
}Certificate Management
GetTLSConfig
Config.GetTLSConfig (tls/config.go:364-481) builds the tls.Config:
func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
root, _ := c.getCertPool()
// Build RandCarrier with pinning/verification info
config := &tls.Config{
InsecureSkipVerify: c.AllowInsecure,
Rand: randCarrier,
ClientSessionCache: globalSessionCache,
RootCAs: root,
NextProtos: c.NextProtocol,
SessionTicketsDisabled: !c.EnableSessionResumption,
VerifyPeerCertificate: randCarrier.verifyPeerCert,
}
// Apply options (WithDestination, WithNextProto, etc.)
// Configure GetCertificate (dynamic or static)
// Set ServerName, CurvePreferences, MinVersion, MaxVersion
// Configure CipherSuites, KeyLogWriter, ECH
return config
}Certificate Types
Certificates have two usage modes:
- ENCIPHERMENT (
Certificate_ENCIPHERMENT): Regular server/client certificate - AUTHORITY_ISSUE (
Certificate_AUTHORITY_ISSUE): CA certificate for dynamic issuance
Dynamic Certificate Generation
When AUTHORITY_ISSUE certificates are present, getGetCertificateFunc (config.go:172-244) generates certificates on-demand:
func getGetCertificateFunc(c *tls.Config, ca []*Certificate) func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
return func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
domain := hello.ServerName
// Check existing certs, remove expired ones
// Generate new cert from CA for domain
newCert, _ := issueCertificate(rawCert, domain)
c.Certificates = append(c.Certificates, *newCert)
return issuedCertificate, nil
}
}This enables MitM scenarios and eliminates the need for pre-generated certificates.
Static Certificate Selection
getNewGetCertificateFunc (config.go:246-274) matches certificates by SNI:
func getNewGetCertificateFunc(certs []*tls.Certificate, rejectUnknownSNI bool) ... {
// Match by CommonName or DNSNames
// Support wildcard matching (*.example.com)
// If rejectUnknownSNI, return error for unknown domains
// Otherwise, return first certificate as fallback
}Hot Reloading and OCSP
setupOcspTicker (config.go:96-131) periodically:
- Reloads certificate files from disk (if
CertificatePath/KeyPathare set) - Fetches OCSP stapling data
Default interval: 3600 seconds (1 hour), or OcspStapling config value.
Certificate Pinning
PinnedPeerCertSha256
SHA-256 hashes of trusted certificates. Verification in RandCarrier.verifyPeerCert (config.go:283-350):
func (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
// Check if leaf cert matches any pinned hash -> success
// Check if CA cert matches any pinned hash -> verify leaf against pinned CA
// If VerifyPeerCertByName is set, verify against multiple domain names
// Otherwise verify against ServerName
}The RandCarrier struct (config.go:352-357) piggybacks on tls.Config.Rand to carry verification state (since VerifyPeerCertificate has no mechanism to pass custom data):
type RandCarrier struct {
Config *tls.Config
RootCAs *x509.CertPool
VerifyPeerCertByName []string
PinnedPeerCertSha256 [][]byte
}Hash Generation
GenerateCertHash (tls/pin.go:10-19) computes SHA-256 of a certificate:
func GenerateCertHash[T *x509.Certificate | []byte](cert T) []byte {
var out [32]byte
switch v := any(cert).(type) {
case *x509.Certificate:
out = sha256.Sum256(v.Raw)
case []byte:
out = sha256.Sum256(v)
}
return out[:]
}Options System
Option functions (config.go:484) modify tls.Config after initial construction:
type Option func(*tls.Config)Available options:
- WithDestination: Sets
ServerNamefrom destination address (only if not already set) (config.go:490-496) - WithOverrideName: Forces a specific
ServerName(config.go:498-502) - WithNextProto: Sets ALPN protocols (only if not already set) (
config.go:505-511)
SNI Resolution Priority
Due to GetTLSConfig structure (config.go:415-417), ServerName resolution order is:
- Config's
ServerName(if non-empty and not "frommitm") WithDestinationoption (destination address)- Empty (for server-side configs)
Session Management
A global LRU session cache (config.go:24):
var globalSessionCache = tls.NewLRUClientSessionCache(128)Session tickets are disabled by default (SessionTicketsDisabled: !c.EnableSessionResumption).
Version and Cipher Configuration
TLS Version
Configurable min/max versions (config.go:427-447):
"1.0"through"1.3"
Cipher Suites
Custom cipher suite list from colon-separated names (config.go:449-459):
if len(c.CipherSuites) > 0 {
for _, n := range strings.Split(c.CipherSuites, ":") {
config.CipherSuites = append(config.CipherSuites, id[n])
}
}Curve Preferences
ParseCurveName (config.go:525-545) maps string names to tls.CurveID:
| Name | Curve |
|---|---|
curvep256 | P-256 |
curvep384 | P-384 |
curvep521 | P-521 |
x25519 | X25519 |
x25519mlkem768 | X25519MLKEM768 (post-quantum) |
secp256r1mlkem768 | SecP256r1MLKEM768 |
secp384r1mlkem1024 | SecP384r1MLKEM1024 |
ConfigFromStreamSettings
The extraction function (config.go:514-523) is called by every transport to check for TLS configuration:
func ConfigFromStreamSettings(settings *internet.MemoryStreamConfig) *Config {
if settings == nil { return nil }
config, ok := settings.SecuritySettings.(*Config)
if !ok { return nil }
return config
}Returns nil when TLS is not configured, signaling the transport to skip TLS wrapping.
Implementation Notes
- Default ALPN: If no
NextProtocolis configured, defaults to["h2", "http/1.1"](config.go:423-425). - Close timeout: TLS close has a 250ms hard timeout to prevent hanging on graceful shutdown (
tls.go:30-38). - MitM support: The
IsFromMitmfunction (config.go:547-549) checks for the special"frommitm"ServerName marker, used by MitM proxy features. - VerifyPeerCertByName: When set,
InsecureSkipVerifyis forced totrue(config.go:393-397), and verification is done manually inverifyPeerCertagainst multiple domain names. - Key logging: Enabled via
MasterKeyLogconfig, writing to a file for Wireshark debugging (config.go:461-468). - ECH: Encrypted Client Hello is applied via
ApplyECHwhenEchConfigListorEchServerKeysare configured (config.go:469-478). - Certificate expiry: Checked with a 2-minute grace period (
config.go:133-142). - Chain building: When
BuildChainis true on a CA certificate, the CA cert is appended to newly issued certificates (config.go:155).