Encrypted Client Hello (ECH) को अपनाना

Encrypted Client Hello (ECH), टीएलएस का एक एक्सटेंशन है. यह क्लाइंट के हैंडशेक मैसेज में मौजूद Server Name Indication (SNI) फ़ील्ड को एन्क्रिप्ट (सुरक्षित) करता है. Android 17 (एपीआई लेवल 37) और इसके बाद के वर्शन में, ECH की सुविधा डिफ़ॉल्ट रूप से उपलब्ध होती है. ECH की मदद से, उपयोगकर्ताओं के वेब ट्रैफ़िक को निजी बनाए रखने में मदद मिलती है. इससे नेटवर्क इंटरमीडियरी को उन होस्टनेम को देखने से रोका जा सकता है जिनसे कोई ऐप्लिकेशन कनेक्ट होता है.

ऐप्लिकेशन डेवलपर के लिए

अपने ऐप्लिकेशन में ECH को लागू करने के लिए:

  1. देखें कि आपकी नेटवर्किंग लाइब्रेरी में ECH काम करता है या नहीं: पक्का करें कि Android पर ECH के साथ काम करने वाले लाइब्रेरी वर्शन का इस्तेमाल किया जा रहा हो:
    • OkHttp: OkHttp 5.5.0 से, OkHttpClient.Builder पर AndroidDns या DnsOverHttps को कॉन्फ़िगर करके, ECH की सुविधा चालू की जा सकती है. ज़्यादा जानकारी के लिए, बदलाव का लॉग देखें.
    • HttpEngine: यह Android 17 QPR2 (एपीआई लेवल 37.2) में उपलब्ध होगा. इसके लिए, किसी खास कॉन्फ़िगरेशन की ज़रूरत नहीं होगी.
    • WebView: यह सुविधा आने वाले समय में उपलब्ध होगी.
  2. नेटवर्क सुरक्षा कॉन्फ़िगरेशन सेट अप करना: अगर आपकी लाइब्रेरी में ECH की सुविधा काम करती है, तो यह डिफ़ॉल्ट रूप से सभी डोमेन के लिए चालू होती है. अगर आपको ईसीएच बंद करना है या लागू करना है, तो अपने नेटवर्क सिक्योरिटी कॉन्फ़िगरेशन में domainEncryption एलिमेंट को कॉन्फ़िगर करें.
  3. टारगेट एसडीके लेवल अपडेट करें: ईसीएच, सिर्फ़ Android 17 (एपीआई लेवल 37) और इसके बाद के वर्शन पर उपलब्ध है.

लाइब्रेरी डेवलपर के लिए

अगर आपको कस्टम एचटीटीपी नेटवर्किंग लाइब्रेरी डेवलप करनी है या किसी मौजूदा लाइब्रेरी को बढ़ाना है, तो आपको प्लैटफ़ॉर्म एपीआई के साथ इंटरैक्ट करके, ईसीएच की सुविधा लागू करनी चाहिए.

डोमेन के लिए एन्क्रिप्शन की नीति देखना

ECH कॉन्फ़िगरेशन के बारे में क्वेरी करने या कनेक्शन शुरू करने से पहले, NetworkSecurityPolicy.getDomainEncryptionMode को कॉल करके, ऐप्लिकेशन की डोमेन एन्क्रिप्शन नीति देखें.

लौटाए गए मोड के आधार पर, ईसीएच को इस तरह मैनेज करें:

  • DOMAIN_ENCRYPTION_MODE_DISABLED और DOMAIN_ENCRYPTION_MODE_UNKNOWN: ECH कॉन्फ़िगरेशन फ़ेच न करें या ECH की कोशिश न करें.
  • DOMAIN_ENCRYPTION_MODE_ENABLED और DOMAIN_ENCRYPTION_MODE_OPPORTUNISTIC: ईसीएच लागू करें. ECH कॉन्फ़िगरेशन वापस पाएं और अगर सर्वर इसका इस्तेमाल कर सकता है, तो ECH का इस्तेमाल करें. अगर सर्वर ECH के साथ काम नहीं करता है, तो ECH GREASE चालू करें.

ECH कॉन्फ़िगरेशन वापस पाना

ईसीएच से कनेक्ट करने के लिए, आपको सर्वर के एचटीटीपीएस डीएनएस रिकॉर्ड को हल करना होगा. इसमें ईसीएच कॉन्फ़िगरेशन शामिल होते हैं. जब ऐप्लिकेशन सिस्टम डीएनएस का इस्तेमाल करते हैं, तो इस डेटा को इन दो तरीकों में से किसी एक तरीके से वापस पाया जा सकता है:

पहला तरीका: हाई-लेवल DnsResolver.query API का इस्तेमाल करना

अगर आपकी लाइब्रेरी को कस्टम डीएनएस रिज़ॉल्यूशन मेकेनिज़्म की ज़रूरत नहीं है, तो प्लैटफ़ॉर्म के हाई-लेवल DnsResolver.query एपीआई का इस्तेमाल किया जा सकता है. यह एपीआई, A/AAAA/HTTPS रिकॉर्ड के लिए पैरलल क्वेरी करता है और नतीजों को HttpsEndpoint में जोड़ता है.

Kotlin

val resolver = DnsResolver(context, looper)
resolver.query(network, hostname, DnsResolver.TYPE_HTTPS, executor,
    DnsResolver.HTTPS_QUERY_WAIT_AUTO, cancellationSignal,
    object : DnsResolver.Callback<HttpsEndpoint> {
        override fun onAnswer(answer: HttpsEndpoint, rcode: Int) {
            val record = answer.httpsRecords.firstOrNull() ?: return
            val echConfigList = record.echConfigList ?: return
            establishEchConnection(echConfigList)
        }
        override fun onError(error: DnsResolver.DnsException) { /* Handle error */ }
    })

Java

DnsResolver resolver = new DnsResolver(context, looper);
resolver.query(network, hostname, DnsResolver.TYPE_HTTPS, executor,
    DnsResolver.HTTPS_QUERY_WAIT_AUTO, cancellationSignal,
    new DnsResolver.Callback<HttpsEndpoint>() {
        @Override
        public void onAnswer(HttpsEndpoint answer, int rcode) {
            HttpsRecord record = answer.getHttpsRecords().stream().findFirst().orElse(null);
            if (record == null) return;
            EchConfigList echConfigList = record.getEchConfigList();
            if (echConfigList == null) return;
            establishEchConnection(echConfigList);
        }

        @Override
        public void onError(DnsResolver.DnsException error) { /* Handle error */ }
    });

दूसरा तरीका: getAllByName और DnsResolver.rawQuery का इस्तेमाल करना

सॉकेट कनेक्शन और डीएनएस रिज़ॉल्यूशन पाइपलाइन मैनेज करने वाली लाइब्रेरी के लिए, एचटीटीपीएस रिकॉर्ड को अलग से फ़ेच करते समय, स्टैंडर्ड एपीआई का इस्तेमाल करके आईपी पतों को रिज़ॉल्व किया जा सकता है:

  1. डिफ़ॉल्ट नेटवर्क या Network.getAllByName के लिए, InetAddress.getAllByName का इस्तेमाल करके A/AAAA रिकॉर्ड ठीक करें.
  2. DnsResolver.rawQuery का इस्तेमाल करके, एचटीटीपीएस रिकॉर्ड को पैरलल में वापस पाएं. क्वेरी टाइप के तौर पर DnsResolver.TYPE_HTTPS को सेट करें.
डेवलपर की ज़िम्मेदारी और कुछ खास मामलों में

अगर आपने दूसरा तरीका चुना है, तो आपकी लाइब्रेरी को कुछ और ज़िम्मेदारियां निभानी होंगी. साथ ही, कुछ खास मामलों पर भी ध्यान देना होगा.

  • डीएनएस रिकॉर्ड पार्स करना: आपको rawQuery से मिले डीएनएस जवाब के रॉ बाइट पेलोड को पार्स करना होगा, ताकि EchConfigList को निकाला जा सके.
  • रिकॉर्ड के मेल न खाने की समस्या को ठीक करना: आपको A/AAAA और एचटीटीपीएस क्वेरी के बीच मौजूद अंतर को ठीक करना होगा.
  • रेस कंडीशन: आपको पैरलल DNS लुकअप के नतीजों को सिंक करना होगा. अगर एक क्वेरी दूसरी क्वेरी से पहले हल हो जाती है या एचटीटीपीएस क्वेरी का समय खत्म हो जाता है, तो आपको सही तरीके से फ़ॉलबैक करना होगा. उदाहरण के लिए, अगर एचटीटीपीएस क्वेरी पूरी नहीं होती है, तो ईसीएच के बिना स्टैंडर्ड टीएलएस कनेक्शन का इस्तेमाल करें या अगर नीति के तहत ईसीएच ग्रीस चालू है, तो उसका इस्तेमाल करें.

TLS कॉन्फ़िगर करना

जब लाइब्रेरी, HttpsRecord से ईसीएच कॉन्फ़िगरेशन की सूची (EchConfigList) को फिर से हासिल कर लेती है, तो टीएलएस हैंडशेक शुरू करने से पहले, इस सूची को SSLSockets या SSLEngines यूटिलिटी एपीआई का इस्तेमाल करके पास करें.

Kotlin

fun establishEchConnection(echConfigList: EchConfigList) {
    val socket = sslSocketFactory.createSocket(ipAddress, port) as SSLSocket
    SSLSockets.setEchConfigList(socket, echConfigList)
    socket.startHandshake()
}

Java

public void establishEchConnection(EchConfigList echConfigList)
    throws IOException {
    SSLSocket socket =
        (SSLSocket) sslSocketFactory.createSocket(ipAddress, port);
    SSLSockets.setEchConfigList(socket, echConfigList);
    socket.startHandshake();
}

फिर से कोशिश करने वाले फ़्लो को मैनेज करना

अगर सर्वर के ईसीएच कॉन्फ़िगरेशन सिंक नहीं होते हैं, तो हैंडशेक पूरा नहीं होता. ऐसा EchConfigMismatchException (javax.net.ssl.SSLException का सब-क्लास) की वजह से होता है. सर्वर, अस्वीकार किए गए अनुरोध में अपडेट किए गए ईसीएच कॉन्फ़िगरेशन शामिल कर सकता है. इनका इस्तेमाल नया कनेक्शन बनाने के लिए किया जाना चाहिए. अगर सर्वर, फिर से कोशिश करने के लिए मान्य कॉन्फ़िगरेशन उपलब्ध कराता है, लेकिन फिर भी फिर से कोशिश नहीं की जाती है, तो लाइब्रेरी को कॉल करने वाले ऐप्लिकेशन को गड़बड़ी की सूचना देनी होगी.

ECH को फिर से आज़माने की सुविधा को मैनेज करने के लिए, अपवाद को पकड़ें और यह तरीका अपनाएं:

  1. अपवाद के बारे में जानने के लिए, EchConfigMismatchException.getPublicHostname पर कॉल करें.
  2. अपने HostnameVerifier का इस्तेमाल करके, दिखाए गए सार्वजनिक होस्टनेम की पुष्टि करें. अगर यह null है, तो कनेक्शन बंद करें.
  3. अगर होस्टनेम की पुष्टि हो जाती है, तो EchConfigMismatchException.getRetryConfigList का इस्तेमाल करके, अपडेट किए गए कॉन्फ़िगरेशन देखें.
  4. अगर अपडेट किए गए कॉन्फ़िगरेशन उपलब्ध हैं, तो नए EchConfigList का इस्तेमाल करके, कनेक्शन को फिर से बनाने की कोशिश करें.

Kotlin

try {
    socket.startHandshake()
} catch (e: EchConfigMismatchException) {
    val publicName = e.publicHostname ?: throw e
    if (hostnameVerifier.verify(publicName, socket.session)) {
        val retryConfigList = e.retryConfigList
        if (retryConfigList != null) {
            retryConnection(retryConfigList)
        }
    } else {
        throw e // Hostname mismatch
    }
}

Java

try {
    socket.startHandshake();
} catch (EchConfigMismatchException e) {
    String publicName = e.getPublicHostname();
    if (publicName == null) {
        throw e;
    }
    if (hostnameVerifier.verify(publicName, socket.getSession())) {
        EchConfigList retryConfigList = e.getRetryConfigList();
        if (retryConfigList != null) {
            retryConnection(retryConfigList);
        }
    } else {
        throw e; // Hostname mismatch
    }
}

फिर से कोशिश करने के फ़्लो के बारे में ज़्यादा जानकारी के लिए, RFC 9849 देखें. खास तौर पर, सार्वजनिक नाम के लिए पुष्टि करना क्यों ज़रूरी है.