طبقة أمان TLS
مقدمة
توفر طبقة TLS في Xray-core تشفير TLS قياسي مع تخصيص واسع. تدعم كلاً من crypto/tls القياسي في Go و uTLS (refraction-networking/utls) لبصمة TLS، وتوليد الشهادات الديناميكي من CA، وتدبيس OCSP، وتثبيت الشهادات، واستئناف الجلسة، و ECH (مرحبا العميل المشفر)، ومجموعات التشفير المخصصة، وتفضيلات المنحنيات، وتسجيل المفاتيح. TLS ليس نقلاً بحد ذاته بل هو طبقة أمان تغلف أي نقل.
الملفات الرئيسية
transport/internet/tls/tls.go-- الأنواع الأساسية:Conn،UConn، سجل البصماتtransport/internet/tls/config.go--Config.GetTLSConfig()، إدارة الشهادات، الخياراتtransport/internet/tls/pin.go-- دوال تجزئة الشهادات للتثبيتtransport/internet/tls/ech.go-- دعم ECH (مرحبا العميل المشفر)transport/internet/tls/grpc.go-- مساعدات TLS الخاصة بـ gRPCtransport/internet/tls/unsafe.go-- وصول غير آمن لمجمع الشهادات
أنواع الاتصال
Conn (TLS القياسي)
tls.Conn (tls/tls.go:26-28) يغلف crypto/tls.Conn من Go:
type Conn struct {
*tls.Conn
}الميزات:
- مهلة إغلاق رشيق: مهلة 250 مللي ثانية لإشعار إغلاق TLS، وبعدها يتم إغلاق الاتصال الأساسي قسريًا (
tls.go:30-38) - كتابة MultiBuffer: تضغط وتكتب المخازن المتعددة بكفاءة (
tls.go:40-45) - NegotiatedProtocol: تُبلغ عن نتيجة ALPN (
tls.go:54-57)
UConn (بصمة uTLS)
tls.UConn (tls/tls.go:71-73) يغلف utls.UConn لبصمة TLS:
type UConn struct {
*utls.UConn
}طريقة WebsocketHandshakeContext (tls.go:94-117) تفرض ALPN http/1.1 لاتصالات WebSocket باستخدام 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 (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
سجل البصمات
ثلاث مستويات من البصمات معرّفة (tls/tls.go:185-257):
PresetFingerprints (خيارات واجهة المستخدم الموصى بها):
| الاسم | معرف uTLS |
|---|---|
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 | يُختار عشوائيًا عند بدء التشغيل |
randomized | HelloRandomizedALPN مع أوزان مخصصة |
randomizednoalpn | HelloRandomizedNoALPN مع أوزان مخصصة |
ModernFingerprints: إصدارات متصفحات محددة (Chrome 83-131، Firefox 99-120، إلخ.)
OtherFingerprints: إصدارات قديمة وبصمات خاصة.
اختيار البصمة العشوائية
عند بدء التشغيل (tls.go:145-167)، يُعيّن "random" إلى بصمة حديثة مختارة عشوائيًا:
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
}خيار "randomized" يستخدم التعشية المدمجة في uTLS مع أوزان مخصصة:
- يفرض TLS 1.3 (
TLSVersMax_Set_VersionTLS13 = 1) - يعطل P256 لمشاركة المفتاح الأولى (
FirstKeyShare_Set_CurveP256 = 0)
GetFingerprint
GetFingerprint (tls.go:169-183) يبحث عن البصمات عبر الخرائط الثلاث:
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
}الاسم الفارغ يعود إلى Chrome Auto كقيمة افتراضية. إذا لم يتم العثور على تطابق، يُعاد nil، مما يشير إلى المستدعي باستخدام TLS القياسي في Go.
بناء العميل/الخادم
العميل
// 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}
}الخادم
// tls.go:66-69
func Server(c net.Conn, config *tls.Config) net.Conn {
tlsConn := tls.Server(c, config)
return &Conn{Conn: tlsConn}
}نسخ الإعدادات لـ uTLS
copyConfig (tls.go:133-143) تترجم crypto/tls.Config إلى 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,
}
}إدارة الشهادات
GetTLSConfig
Config.GetTLSConfig (tls/config.go:364-481) يبني 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
}أنواع الشهادات
الشهادات لها وضعان للاستخدام:
- ENCIPHERMENT (
Certificate_ENCIPHERMENT): شهادة خادم/عميل عادية - AUTHORITY_ISSUE (
Certificate_AUTHORITY_ISSUE): شهادة CA للإصدار الديناميكي
توليد الشهادات الديناميكي
عند وجود شهادات AUTHORITY_ISSUE، تولّد getGetCertificateFunc (config.go:172-244) شهادات عند الطلب:
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
}
}هذا يمكّن سيناريوهات MitM ويزيل الحاجة لشهادات مولدة مسبقًا.
اختيار الشهادات الثابتة
getNewGetCertificateFunc (config.go:246-274) يطابق الشهادات حسب 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
}إعادة التحميل السريع و OCSP
setupOcspTicker (config.go:96-131) يقوم دوريًا بـ:
- إعادة تحميل ملفات الشهادات من القرص (إذا تم تعيين
CertificatePath/KeyPath) - جلب بيانات تدبيس OCSP
الفاصل الزمني الافتراضي: 3600 ثانية (ساعة واحدة)، أو قيمة إعداد OcspStapling.
تثبيت الشهادات
PinnedPeerCertSha256
تجزئات SHA-256 للشهادات الموثوقة. التحقق في 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
}بنية RandCarrier (config.go:352-357) تستغل tls.Config.Rand لحمل حالة التحقق (لأن VerifyPeerCertificate ليس لديها آلية لتمرير بيانات مخصصة):
type RandCarrier struct {
Config *tls.Config
RootCAs *x509.CertPool
VerifyPeerCertByName []string
PinnedPeerCertSha256 [][]byte
}توليد التجزئة
GenerateCertHash (tls/pin.go:10-19) تحسب SHA-256 لشهادة:
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[:]
}نظام الخيارات
دوال Option (config.go:484) تعدّل tls.Config بعد البناء الأولي:
type Option func(*tls.Config)الخيارات المتاحة:
- WithDestination: تعيّن
ServerNameمن عنوان الوجهة (فقط إذا لم يكن معيّنًا مسبقًا) (config.go:490-496) - WithOverrideName: تفرض
ServerNameمحددًا (config.go:498-502) - WithNextProto: تعيّن بروتوكولات ALPN (فقط إذا لم تكن معيّنة مسبقًا) (
config.go:505-511)
أولوية حل SNI
نظرًا لبنية GetTLSConfig (config.go:415-417)، ترتيب حل ServerName هو:
ServerNameمن الإعداد (إذا كان غير فارغ وليس "frommitm")- خيار
WithDestination(عنوان الوجهة) - فارغ (لإعدادات جانب الخادم)
إدارة الجلسات
ذاكرة تخزين مؤقت عامة LRU للجلسات (config.go:24):
var globalSessionCache = tls.NewLRUClientSessionCache(128)تذاكر الجلسة معطلة افتراضيًا (SessionTicketsDisabled: !c.EnableSessionResumption).
إعدادات الإصدار والتشفير
إصدار TLS
إصدارات حد أدنى/أقصى قابلة للتكوين (config.go:427-447):
"1.0"حتى"1.3"
مجموعات التشفير
قائمة مجموعات تشفير مخصصة من أسماء مفصولة بنقطتين (config.go:449-459):
if len(c.CipherSuites) > 0 {
for _, n := range strings.Split(c.CipherSuites, ":") {
config.CipherSuites = append(config.CipherSuites, id[n])
}
}تفضيلات المنحنيات
ParseCurveName (config.go:525-545) يربط أسماء النصوص بـ tls.CurveID:
| الاسم | المنحنى |
|---|---|
curvep256 | P-256 |
curvep384 | P-384 |
curvep521 | P-521 |
x25519 | X25519 |
x25519mlkem768 | X25519MLKEM768 (ما بعد الكم) |
secp256r1mlkem768 | SecP256r1MLKEM768 |
secp384r1mlkem1024 | SecP384r1MLKEM1024 |
ConfigFromStreamSettings
دالة الاستخراج (config.go:514-523) تُستدعى بواسطة كل نقل للتحقق من إعدادات TLS:
func ConfigFromStreamSettings(settings *internet.MemoryStreamConfig) *Config {
if settings == nil { return nil }
config, ok := settings.SecuritySettings.(*Config)
if !ok { return nil }
return config
}تُعيد nil عندما لا يكون TLS مُعدًا، مما يشير إلى النقل بتخطي تغليف TLS.
ملاحظات التنفيذ
- ALPN الافتراضي: إذا لم يتم تكوين
NextProtocol، تعود إلى["h2", "http/1.1"](config.go:423-425). - مهلة الإغلاق: إغلاق TLS لديه مهلة صارمة 250 مللي ثانية لمنع التعليق عند الإيقاف الرشيق (
tls.go:30-38). - دعم MitM: دالة
IsFromMitm(config.go:547-549) تتحقق من علامة"frommitm"الخاصة في ServerName، المستخدمة لميزات بروكسي MitM. - VerifyPeerCertByName: عند التعيين، يُفرض
InsecureSkipVerifyعلىtrue(config.go:393-397)، ويتم التحقق يدويًا فيverifyPeerCertمقابل أسماء نطاقات متعددة. - تسجيل المفاتيح: يُفعّل عبر إعداد
MasterKeyLog، مع الكتابة إلى ملف لتصحيح الأخطاء باستخدام Wireshark (config.go:461-468). - ECH: يُطبق مرحبا العميل المشفر عبر
ApplyECHعند تكوينEchConfigListأوEchServerKeys(config.go:469-478). - انتهاء صلاحية الشهادة: يُفحص مع فترة سماح مدتها دقيقتان (
config.go:133-142). - بناء السلسلة: عندما يكون
BuildChainمفعلاً على شهادة CA، تُلحق شهادة CA بالشهادات الصادرة حديثًا (config.go:155).