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

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

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

अपने ऐप्लिकेशन में ईसीएच को शामिल करने के लिए:

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

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

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

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

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

दिखाए गए मोड के हिसाब से, ईसीएच को इस तरह मैनेज करें:

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

ईसीएच के कॉन्फ़िगरेशन वापस पाना

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

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

अगर आपकी लाइब्रेरी को डीएनएस रिज़ॉल्यूशन के लिए कस्टम मेकैनिज़्म की ज़रूरत नहीं है, तो आप प्लैटफ़ॉर्म के हाई-लेवल 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 और एचटीटीपीएस क्वेरी के बीच की गड़बड़ियों को मैनेज करना होगा.
  • रेस कंडीशन: आपको पैरलल डीएनएस लुकअप के नतीजों को सिंक करना होगा. अगर एक क्वेरी, दूसरी क्वेरी से पहले रिज़ॉल्व हो जाती है या अगर एचटीटीपीएस क्वेरी का टाइम आउट हो जाता है, तो आपको सही तरीके से फ़ॉलबैक करना होगा. उदाहरण के लिए, अगर एचटीटीपीएस क्वेरी फ़ेल हो जाती है, तो ईसीएच के बिना स्टैंडर्ड टीएलएस कनेक्शन की कोशिश करें. वहीं, अगर नीति के तहत ईसीएच GREASE चालू है, तो उसका इस्तेमाल करें.

टीएलएस कॉन्फ़िगर करना

लाइब्रेरी के ईसीएच कॉन्फ़िगरेशन की सूची (EchConfigList) को HttpsRecord से वापस पाने के बाद, टीएलएस हैंडशेक शुरू करने से पहले, इस सूची को 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 की सबक्लास है) के साथ फ़ेल हो जाता है. सर्वर, अस्वीकार करने के मैसेज में, अपडेट किए गए ईसीएच कॉन्फ़िगरेशन शामिल कर सकता है. इनका इस्तेमाल, नया कनेक्शन बनाने के लिए किया जाना चाहिए. अगर सर्वर, फिर से कोशिश करने के लिए मान्य कॉन्फ़िगरेशन उपलब्ध कराता है, लेकिन फिर भी फिर से कोशिश नहीं की जाती है, तो लाइब्रेरी को कॉल करने वाले ऐप्लिकेशन को गड़बड़ी की रिपोर्ट करनी होगी.

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

  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
    }
}

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