Encrypted Client Hello (ECH) adalah ekstensi TLS yang mengenkripsi kolom Server Name Indication (SNI) dalam pesan handshake klien. Di Android 17 (API level 37) dan yang lebih tinggi, ECH didukung secara default. ECH membantu menjaga privasi traffic web pengguna dengan mencegah perantara jaringan melihat nama host yang terhubung ke aplikasi.
Untuk Developer Aplikasi
Untuk menerapkan ECH di aplikasi Anda:
- Periksa dukungan ECH di library jaringan Anda: Pastikan Anda menggunakan versi library yang mendukung ECH di Android:
- OkHttp: Mulai OkHttp 5.5.0, Anda dapat mengaktifkan dukungan ECH dengan
mengonfigurasi
AndroidDnsatauDnsOverHttpsdiOkHttpClient.Builder. Untuk mengetahui informasi selengkapnya, lihat log perubahan. - HttpEngine: Dukungan akan hadir di Android 17 QPR2 (level API 37.2). Tidak diperlukan konfigurasi khusus.
- WebView: Dukungan akan ditambahkan dalam rilis mendatang.
- OkHttp: Mulai OkHttp 5.5.0, Anda dapat mengaktifkan dukungan ECH dengan
mengonfigurasi
- Mengonfigurasi Konfigurasi Keamanan Jaringan: Secara default, ECH diaktifkan untuk semua domain jika library Anda mendukungnya. Jika Anda perlu menonaktifkan atau menerapkan ECH,
konfigurasi elemen
domainEncryptiondi Konfigurasi Keamanan Jaringan Anda. - Perbarui level SDK target: ECH hanya tersedia di Android 17 (level API 37) dan yang lebih tinggi.
Untuk Developer Library
Jika Anda mengembangkan library jaringan HTTP kustom atau memperluas library yang sudah ada, Anda harus menerapkan dukungan ECH dengan berinteraksi dengan API platform.
Memeriksa kebijakan enkripsi domain
Sebelum membuat kueri konfigurasi ECH atau memulai koneksi, periksa
kebijakan enkripsi domain aplikasi dengan memanggil
NetworkSecurityPolicy.getDomainEncryptionMode.
Bergantung pada mode yang ditampilkan, tangani ECH sebagai berikut:
DOMAIN_ENCRYPTION_MODE_DISABLEDdanDOMAIN_ENCRYPTION_MODE_UNKNOWN: Jangan mengambil konfigurasi ECH atau mencoba ECH.DOMAIN_ENCRYPTION_MODE_ENABLEDdanDOMAIN_ENCRYPTION_MODE_OPPORTUNISTIC: Menerapkan ECH. Mengambil konfigurasi ECH dan menggunakan ECH jika server mendukungnya. Jika server tidak mendukung ECH, aktifkan ECH GREASE.
Mengambil konfigurasi ECH
Untuk terhubung dengan ECH, Anda harus me-resolve data DNS HTTPS server yang berisi konfigurasi ECH. Saat aplikasi menggunakan DNS sistem, data ini dapat diambil menggunakan salah satu dari dua metode:
Metode 1: Menggunakan DnsResolver.query API tingkat tinggi
Jika library Anda tidak memerlukan mekanisme resolusi DNS kustom, Anda dapat menggunakan
API DnsResolver.query tingkat tinggi platform. API ini membuat kueri paralel untuk data A/AAAA/HTTPS dan menggabungkan hasilnya menjadi 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 */ }
});
Metode 2: Menggunakan getAllByName dan DnsResolver.rawQuery
Untuk library yang mengelola koneksi soket dan pipeline resolusi DNS-nya sendiri, Anda mungkin lebih memilih untuk menyelesaikan alamat IP menggunakan API standar sambil mengambil rekaman HTTPS secara terpisah:
- Selesaikan data A/AAAA menggunakan
InetAddress.getAllByNameuntuk jaringan default atauNetwork.getAllByName. - Ambil rekaman HTTPS mentah secara paralel menggunakan
DnsResolver.rawQuery. TentukanDnsResolver.TYPE_HTTPSsebagai jenis kueri.
Tanggung jawab developer dan kasus ekstrem
Jika Anda memilih Metode 2, library Anda memiliki tanggung jawab tambahan dan kasus ekstrem yang perlu dipertimbangkan.
- Penguraian Data DNS: Anda harus mengurai payload byte mentah respons DNS dari
rawQueryuntuk mengekstrakEchConfigList. - Menangani Ketidakcocokan Data: Anda harus menangani inkonsistensi antara kueri A/AAAA dan HTTPS.
- Kondisi Persaingan (Race Conditions): Anda harus menyinkronkan hasil pencarian DNS paralel. Jika salah satu kueri diselesaikan sebelum kueri lainnya atau jika kueri HTTPS mengalami waktu tunggu habis, Anda harus melakukan penggantian yang sesuai (misalnya, dengan mencoba koneksi TLS standar tanpa ECH jika kueri HTTPS gagal, atau menggunakan ECH GREASE jika diaktifkan oleh kebijakan).
Mengonfigurasi TLS
Setelah library mengambil daftar konfigurasi ECH
(EchConfigList) dari HttpsRecord, teruskan daftar ini menggunakan API utilitas
SSLSockets atau SSLEngines sebelum memulai handshake TLS.
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();
}
Menangani alur percobaan ulang
Jika konfigurasi ECH server tidak sinkron, handshake akan gagal
dengan EchConfigMismatchException (subclass dari
javax.net.ssl.SSLException). Server dapat menyertakan konfigurasi ECH yang diperbarui dalam penolakannya, yang harus digunakan untuk membuat koneksi baru. Jika percobaan ulang tidak dilakukan meskipun server memberikan konfigurasi percobaan ulang yang valid, library harus melaporkan error ke aplikasi yang memanggil.
Untuk menangani percobaan ulang ECH, tangkap pengecualian dan lakukan langkah-langkah berikut:
- Panggil
EchConfigMismatchException.getPublicHostnamepada pengecualian. - Verifikasi nama host publik yang ditampilkan menggunakan
HostnameVerifierAnda. Jikanull, batalkan koneksi. - Jika verifikasi nama host berhasil, periksa konfigurasi yang diperbarui menggunakan
EchConfigMismatchException.getRetryConfigList. - Jika konfigurasi yang diperbarui tersedia, coba hubungkan lagi dengan
EchConfigListbaru.
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
}
}
Lihat detail selengkapnya tentang alur percobaan ulang di RFC 9849, khususnya alasan autentikasi diperlukan untuk nama publik.