Skip to content

طبقة أمان REALITY

مقدمة

REALITY هو بروتوكول أمان مخصص يمتد على TLS 1.3 للسماح للخادم بانتحال شخصية أي خادم TLS حقيقي دون الحاجة إلى شهادته أو مفتاحه الخاص. بدلاً من تقديم شهادته الخاصة، يقوم خادم REALITY بتوكيل مصافحة TLS إلى خادم "dest" شرعي. يمكن للعملاء المصرح لهم (الذين يعرفون المفتاح العام للخادم و Short ID) مصادقة الخادم من خلال مخطط مخصص مدمج في معرف جلسة ClientHello لـ TLS 1.3. يتلقى العملاء غير المصرح لهم أو المستكشفون النشطون شهادة حقيقية من خادم dest، مما يجعل البروكسي غير قابل للتمييز عن الخدمة الحقيقية. لا حاجة لجهة إصدار شهادات.

الملفات الرئيسية

  • transport/internet/reality/reality.go -- منطق اتصال العميل (UClient) والخادم (Server)
  • transport/internet/reality/config.go -- GetREALITYConfig()، استخراج الإعدادات

البنية

mermaid
sequenceDiagram
    participant C as REALITY Client
    participant S as REALITY Server
    participant D as Dest Server (e.g. google.com)

    Note over C: Knows: PublicKey, ShortId, ServerName
    Note over S: Knows: PrivateKey, ShortIds[], ServerNames[]

    C->>S: TLS ClientHello (SessionId = encrypted auth)
    S->>S: Decrypt SessionId, verify ShortId + timestamp
    alt Authorized Client
        S->>C: TLS ServerHello (with ed25519 cert signed by AuthKey)
        C->>C: Verify cert signature with AuthKey
        Note over C,S: Authenticated REALITY connection
    else Unauthorized / Probe
        S->>D: Forward ClientHello
        D->>S: Real ServerHello + Certificate
        S->>C: Forward real response
        Note over C,S: Client sees real google.com cert
        Note over S: Connection becomes transparent proxy to Dest
    end

تنفيذ العميل

UClient

reality.UClient (reality/reality.go:117-277) ينفذ مصافحة REALITY:

الخطوة 1: بناء اتصال uTLS

go
func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destination) (net.Conn, error) {
    uConn := &UConn{Config: config}
    utlsConfig := &utls.Config{
        VerifyPeerCertificate:  uConn.VerifyPeerCertificate,
        ServerName:             config.ServerName,
        InsecureSkipVerify:     true,
        SessionTicketsDisabled: true,
    }
    fingerprint := tls.GetFingerprint(config.Fingerprint)
    uConn.UConn = utls.UClient(c, utlsConfig, *fingerprint)

InsecureSkipVerify هو true لأن التحقق يتم بواسطة VerifyPeerCertificate المخصص لـ REALITY، وليس سلسلة Go المدمجة.

الخطوة 2: بناء معرف الجلسة المُصادق

يتم بناء وتشفير معرف الجلسة (32 بايت) (reality.go:138-176):

go
hello := uConn.HandshakeState.Hello
hello.SessionId = make([]byte, 32)

// Bytes 0-2: Xray version
hello.SessionId[0] = core.Version_x
hello.SessionId[1] = core.Version_y
hello.SessionId[2] = core.Version_z
hello.SessionId[3] = 0 // reserved

// Bytes 4-7: Current Unix timestamp (big-endian)
binary.BigEndian.PutUint32(hello.SessionId[4:], uint32(time.Now().Unix()))

// Bytes 8-15: Short ID (8 bytes)
copy(hello.SessionId[8:], config.ShortId)

الخطوة 3: اشتقاق مفتاح المصادقة

يُشتق مفتاح المصادقة من السر المشترك ECDH (reality.go:152-169):

go
publicKey, _ := ecdh.X25519().NewPublicKey(config.PublicKey)
ecdhe := uConn.HandshakeState.State13.KeyShareKeys.Ecdhe
// Falls back to MlkemEcdhe for post-quantum key exchange
uConn.AuthKey, _ = ecdhe.ECDH(publicKey)

// HKDF derivation
hkdf.New(sha256.New, uConn.AuthKey, hello.Random[:20], []byte("REALITY")).Read(uConn.AuthKey)

يستخدم HKDF أول 20 بايت من ClientHello.Random كملح والنص الحرفي "REALITY" كمعلومات.

الخطوة 4: تشفير معرف الجلسة

go
aead := crypto.NewAesGcm(uConn.AuthKey)
aead.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw)
  • Nonce: آخر 12 بايت من ClientHello.Random (البايتات 20-31)
  • النص الصريح: أول 16 بايت من معرف الجلسة
  • البيانات الإضافية: بايتات ClientHello الخام بالكامل
  • الإخراج: 16 بايت نص مشفر + 16 بايت علامة GCM = تُكتب فوق معرف الجلسة الكامل البالغ 32 بايت

ثم يُنسخ معرف الجلسة المشفر مرة أخرى إلى بايتات ClientHello الخام (reality.go:175).

الخطوة 5: التحقق من شهادة الخادم

VerifyPeerCertificate (reality/reality.go:76-115) يتحقق مما إذا كان الخادم خادم REALITY حقيقي:

go
func (c *UConn) VerifyPeerCertificate(rawCerts [][]byte, _ [][]*x509.Certificate) error {
    certs := // parse rawCerts
    if pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok {
        h := hmac.New(sha512.New, c.AuthKey)
        h.Write(pub)
        if bytes.Equal(h.Sum(nil), certs[0].Signature) {
            // Optional ML-DSA-65 verification for post-quantum
            if len(c.Config.Mldsa65Verify) > 0 {
                // Verify ML-DSA-65 signature over ClientHello + ServerHello
            }
            c.Verified = true
            return nil
        }
    }
    // Fall back to standard x509 verification (real cert from dest server)
    opts := x509.VerifyOptions{DNSName: c.ServerName, ...}
    certs[0].Verify(opts)
    return nil  // verification passes (it is a real cert), but Verified stays false
}

يولّد خادم REALITY شهادة ed25519 حيث:

  • المفتاح العام: مفتاح ed25519 عام
  • التوقيع: HMAC-SHA512(AuthKey, PublicKey)

إذا تطابق HMAC، يعلم العميل أنه يتحدث إلى خادم REALITY الحقيقي. إذا لم يتطابق، يتم التحقق من الشهادة بشكل طبيعي (جاءت من خادم dest)، لكن c.Verified يبقى false.

الخطوة 6: معالجة فشل التحقق

إذا كان uConn.Verified هو false بعد المصافحة (reality.go:183-274)، يحاكي العميل سلوك المتصفح الحقيقي:

go
if !uConn.Verified {
    // "Spider" mode: crawl the dest server to generate realistic traffic
    client := &http.Client{Transport: &http2.Transport{...}}
    // GET pages, follow links, add cookies
    // This makes the connection look like a real browser visiting the site
    time.Sleep(randomDuration)
    return nil, errors.New("REALITY: processed invalid connection")
}

يتصفح العنكبوت صفحات خادم dest بتوقيت واقعي، ثم يغلق الاتصال. هذا يجعل الاستكشاف النشط غير قابل للتمييز عن زيارة متصفح حقيقية.

اتصال الخادم

reality.Server (reality/reality.go:52-55) يغلف بمكتبة reality:

go
func Server(c net.Conn, config *reality.Config) (net.Conn, error) {
    realityConn, err := reality.Server(context.Background(), c, config)
    return &Conn{Conn: realityConn}, err
}

منطق الخادم الفعلي في مكتبة github.com/xtls/reality، التي:

  1. تقرأ ClientHello
  2. تفك تشفير معرف الجلسة باستخدام مفتاحها الخاص
  3. تتحقق من Short ID والطابع الزمني وإصدار العميل
  4. إذا كان مصرحًا: تولّد شهادة ed25519 موقعة بـ AuthKey المشترك
  5. إذا كان غير مصرح: تتوكل إلى خادم dest بشفافية

الإعدادات

إعدادات العميل

من reality/config.go:74-83:

go
func ConfigFromStreamSettings(settings *internet.MemoryStreamConfig) *Config {
    config, ok := settings.SecuritySettings.(*Config)
    if !ok { return nil }
    return config
}

يحتاج العميل إلى:

  • ServerName: SNI لمصافحة TLS (الخادم المنتحل)
  • Fingerprint: بصمة uTLS (مطلوبة، لا يمكن أن تكون فارغة)
  • PublicKey: المفتاح العام X25519 للخادم (32 بايت)
  • ShortId: معرف Short ID للعميل (حتى 8 بايت)
  • SpiderX: المسار الأولي لتصفح العنكبوت
  • SpiderY: مصفوفة من 10 قيم int64 تتحكم في سلوك العنكبوت (حشو ملفات تعريف الارتباط، التزامن، الفواصل، إلخ.)
  • Show: وضع التصحيح الذي يطبع حالة REALITY إلى stdout
  • Mldsa65Verify: مفتاح ML-DSA-65 العام للتحقق ما بعد الكمي

إعدادات الخادم

GetREALITYConfig (reality/config.go:16-58) يبني reality.Config:

go
func (c *Config) GetREALITYConfig() *reality.Config {
    config := &reality.Config{
        Show: c.Show,
        Type: c.Type,
        Dest: c.Dest,                    // dest server address
        Xver: byte(c.Xver),              // PROXY protocol version

        PrivateKey:   c.PrivateKey,       // X25519 private key
        MinClientVer: c.MinClientVer,     // minimum client version
        MaxClientVer: c.MaxClientVer,     // maximum client version
        MaxTimeDiff:  time.Duration(c.MaxTimeDiff) * time.Millisecond,

        SessionTicketsDisabled: true,
    }
    // Populate ServerNames map and ShortIds map
    config.ServerNames[serverName] = true
    config.ShortIds[shortId] = true
    // Optional ML-DSA-65 signing key
    // Optional rate limiting for fallback connections
    return config
}

يحتاج الخادم إلى:

  • PrivateKey: المفتاح الخاص X25519 (32 بايت)
  • ServerNames: قيم SNI المسموح بها
  • ShortIds: معرفات Short ID المسموح بها (قاموس [8]byte)
  • Dest: عنوان الخادم الحقيقي للتوكيل إليه للعملاء غير المصرح لهم
  • MaxTimeDiff: الحد الأقصى المسموح لانحراف الساعة (مللي ثانية)
  • MinClientVer / MaxClientVer: نطاق إصدار عميل Xray المسموح

تنسيق معرف الجلسة على السلك

Byte  0-2:  Xray version (x.y.z)
Byte  3:    Reserved (0)
Byte  4-7:  Unix timestamp (big-endian uint32)
Byte  8-15: Short ID (8 bytes, may be partially zero)

[Encrypted with AES-GCM using AuthKey]
[Nonce = ClientHello.Random[20:32]]
[AAD = raw ClientHello bytes]

يفك الخادم تشفير هذا للتحقق من:

  1. Short ID مقابل قائمته المسموحة
  2. الطابع الزمني ضمن MaxTimeDiff
  3. إصدار العميل ضمن النطاق المسموح

دعم ما بعد الكم

تبادل المفاتيح

يدعم REALITY تبادل مفاتيح X25519-MLKEM768 عند توفره في بصمة TLS (reality.go:156-161):

go
ecdhe := uConn.HandshakeState.State13.KeyShareKeys.Ecdhe
if ecdhe == nil {
    ecdhe = uConn.HandshakeState.State13.KeyShareKeys.MlkemEcdhe
}

التحقق من الشهادات

تحقق ML-DSA-65 (Dilithium) اختياري (reality.go:88-95):

go
if len(c.Config.Mldsa65Verify) > 0 {
    h.Write(c.HandshakeState.Hello.Raw)
    h.Write(c.HandshakeState.ServerHello.Raw)
    verify, _ := mldsa65.Scheme().UnmarshalBinaryPublicKey(c.Config.Mldsa65Verify)
    if mldsa65.Verify(verify.(*mldsa65.PublicKey), h.Sum(nil), nil, certs[0].Extensions[0].Value) {
        c.Verified = true
    }
}

هذا يوفر مصادقة ما بعد كمية: يوقع الخادم HMAC(AuthKey, PublicKey || ClientHello.Raw || ServerHello.Raw) بمفتاح ML-DSA-65 الخاص، ويتحقق العميل بالمفتاح العام المشترك مسبقًا.

دعم تسجيل المفاتيح

يدعم كل من العميل والخادم تسجيل مفاتيح TLS للتصحيح (reality/config.go:61-72):

go
func KeyLogWriterFromConfig(c *Config) io.Writer {
    if len(c.MasterKeyLog) <= 0 || c.MasterKeyLog == "none" { return nil }
    writer, _ := os.OpenFile(c.MasterKeyLog, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
    return writer
}

التكامل مع النقل

يتكامل REALITY مع وسائل النقل في نقطتين:

جانب العميل

يتحقق كل نقل من إعدادات REALITY ويطبقها:

go
// tcp/dialer.go:89-93
if config := reality.ConfigFromStreamSettings(streamSettings); config != nil {
    conn, err = reality.UClient(conn, config, ctx, dest)
}

جانب الخادم

تغلف وسائل النقل مستمعها:

go
// tcp/hub.go:76-79
if config := reality.ConfigFromStreamSettings(streamSettings); config != nil {
    l.realityConfig = config.GetREALITYConfig()
    go goreality.DetectPostHandshakeRecordsLens(l.realityConfig)
}

يُستدعى DetectPostHandshakeRecordsLens عند بدء التشغيل لبصم سلوك خادم dest بعد المصافحة، مما يمكّن من انتحال أكثر دقة.

ملاحظات التنفيذ

  • لا حاجة لـ CA: لا يتطلب REALITY أي شهادات. إما يقدم الخادم شهادة خادم dest الحقيقية (للعملاء غير المصرح لهم) أو شهادة ed25519 مولدة ديناميكيًا (للعملاء المصرح لهم).
  • مقاومة الاستكشاف النشط: الاتصالات غير المصرح بها تُتوكل بشفافية إلى خادم dest. تُقدم شهادة خادم dest الحقيقية، مما يجعل الاستكشاف غير قابل للتمييز عن اتصال مباشر.
  • مكافحة بصمة العنكبوت: عندما يتلقى عميل REALITY شهادة حقيقية (مما يشير إلى استكشاف أو خطأ في التكوين)، يتصفح صفحات خادم dest كمتصفح حقيقي قبل قطع الاتصال، مما يمنع الكشف القائم على التوقيت.
  • البصمة مطلوبة: على عكس TLS، يتطلب REALITY بصمة uTLS (reality.go:133-136). لا يمكن استخدام TLS القياسي في Go لأن REALITY يحتاج إلى الوصول إلى تفاصيل ClientHello الداخلية.
  • موضع معرف الجلسة الثابت: يحتل معرف الجلسة البايتات 39+ من ClientHello الخام (reality.go:142)، وهو موضع ثابت في بنية ClientHello لـ TLS 1.3.
  • مزامنة الساعة: يجب أن تكون الطوابع الزمنية للعميل والخادم ضمن MaxTimeDiff (الافتراضي يختلف حسب التكوين). الانحراف الكبير في الساعة يسبب فشل المصادقة.
  • Short ID كتحكم في الوصول: يمكن تعيين معرفات Short ID مختلفة لمستخدمين/عملاء مختلفين، مما يمكّن من التحكم في الوصول لكل مستخدم دون تغيير المفاتيح.
  • تحديد المعدل: يمكن لـ LimitFallbackUpload/LimitFallbackDownload خنق النطاق الترددي للاتصالات غير المصرح بها (الاحتياطية) (config.go:41-49).

تحليل تقني لأغراض إعادة التنفيذ.