Encrypted Client Hello (ECH), istemcinin el sıkışma mesajındaki Sunucu Adı Göstergesi (SNI) alanını şifreleyen bir TLS uzantısıdır. Android 17 (API düzeyi 37) ve sonraki sürümlerde ECH varsayılan olarak desteklenir. ECH, bir uygulamanın bağlandığı ana makine adlarının ağ aracıları tarafından görülmesini engelleyerek kullanıcıların web trafiğinin gizli kalmasına yardımcı olur.
Uygulama Geliştiriciler İçin
Uygulamanızda ECH'yi kullanmak için:
- Ağ kitaplığınızda ECH desteği olup olmadığını kontrol edin: Android'de ECH'yi destekleyen bir kitaplık sürümü kullandığınızdan emin olun. OkHttp ve HttpEngine'de destek yakında kullanıma sunulacak.
- Ağ Güvenlik Yapılandırması'nı yapılandırın: Kitaplığınız destekliyorsa ECH varsayılan olarak tüm alan adları için etkindir. ECH'yi devre dışı bırakmanız veya zorunlu kılmanız gerekiyorsa
domainEncryptionöğesini Network Security Config dosyanızda yapılandırın. - Hedef SDK düzeyini güncelleyin: ECH yalnızca Android 17 (API düzeyi 37) ve sonraki sürümlerde kullanılabilir.
Kitaplık Geliştiricileri İçin
Özel bir HTTP ağ kitaplığı geliştiriyorsanız veya mevcut bir kitaplığı genişletiyorsanız platform API'leriyle etkileşimde bulunarak ECH desteğini uygulamanız gerekir.
Alan şifreleme politikasını kontrol etme
ECH yapılandırmalarını sorgulamadan veya bağlantı başlatmadan önce NetworkSecurityPolicy.getDomainEncryptionMode işlevini çağırarak uygulamanın alan şifreleme politikasını kontrol edin.
Döndürülen moda bağlı olarak ECH'yi aşağıdaki gibi işleyin:
DOMAIN_ENCRYPTION_MODE_DISABLEDveDOMAIN_ENCRYPTION_MODE_UNKNOWN: ECH yapılandırmalarını getirmeyin veya ECH'yi denemeyin.DOMAIN_ENCRYPTION_MODE_ENABLEDveDOMAIN_ENCRYPTION_MODE_OPPORTUNISTIC: ECH'yi zorunlu kılın. ECH yapılandırmalarını alıp sunucu destekliyorsa ECH'yi kullanır. Sunucu ECH'yi desteklemiyorsa ECH GREASE'i etkinleştirin.
ECH yapılandırmalarını alma
ECH ile bağlantı kurmak için sunucunun ECH yapılandırmalarını içeren HTTPS DNS kaydını çözümlemeniz gerekir. Uygulamalar sistem DNS'sini kullanırken bu veriler iki yöntemden biriyle alınabilir:
1. yöntem: Üst düzey DnsResolver.query API'yi kullanma
Kitaplığınız özel DNS çözümleme mekanizmaları gerektirmiyorsa platformun üst düzey DnsResolver.query API'sini kullanabilirsiniz. Bu API, A/AAAA/HTTPS kayıtları için paralel sorgular yapar ve sonuçları bir HttpsEndpoint içinde birleştirir.
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 */ }
});
2. yöntem: getAllByName ve DnsResolver.rawQuery özelliğini kullanma
Kendi soket bağlantılarını ve DNS çözümleme işlem hatlarını yöneten kitaplıklar için, HTTPS kaydını ayrı olarak getirirken IP adreslerini standart API'ler kullanarak çözmeyi tercih edebilirsiniz:
- Varsayılan ağ veya
InetAddress.getAllByNameiçinNetwork.getAllByNamekullanarak A/AAAA kayıtlarını çözün. DnsResolver.rawQuerykullanarak ham HTTPS kaydını paralel olarak alın. Sorgu türü olarakDnsResolver.TYPE_HTTPSsimgesini belirtin.
Geliştiricinin sorumluluğu ve uç durumlar
2. yöntemi seçerseniz kitaplığınızın ek sorumlulukları ve dikkate alması gereken uç durumlar olur.
- DNS Kaydı Ayrıştırma:
rawQuery'den gelen DNS yanıtının ham bayt yükünü ayrıştırarakEchConfigList'yi çıkarmanız gerekir. - Kayıt Uyuşmazlıklarını İşleme: A/AAAA ve HTTPS sorguları arasındaki tutarsızlıkları işlemeniz gerekir.
- Yarışma Koşulları: Paralel DNS aramalarının sonuçlarını senkronize etmeniz gerekir. Bir sorgu diğerinden önce çözümlenirse veya HTTPS sorgusunun zaman aşımı olursa uygun şekilde geri dönmeniz gerekir (ör. HTTPS sorgusu başarısız olursa ECH olmadan standart bir TLS bağlantısı denemek veya politika tarafından etkinleştirilmişse ECH GREASE kullanmak).
TLS'yi yapılandırma
Kitaplık, ECH yapılandırma listesini (EchConfigList) HttpsRecord'den aldıktan sonra TLS el sıkışması başlatılmadan önce SSLSockets veya SSLEngines yardımcı programı API'lerini kullanarak bu listeyi iletin.
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();
}
Yeniden deneme akışını işleme
Sunucunun ECH yapılandırmaları senkronize olmamışsa el sıkışma işlemi EchConfigMismatchException (javax.net.ssl.SSLException alt sınıfı) ile başarısız olur. Sunucu, reddetme işlemine güncellenmiş ECH yapılandırmalarını ekleyebilir. Bu yapılandırmalar yeni bir bağlantı oluşturmak için kullanılmalıdır. Sunucu geçerli yeniden deneme yapılandırmaları sağladığı halde yeniden deneme yapılmazsa kitaplık, çağıran uygulamaya bir hata bildirmelidir.
ECH yeniden denemelerini işlemek için istisnayı yakalayın ve şu adımları uygulayın:
- İstisna için
EchConfigMismatchException.getPublicHostnamenumaralı telefonu arayın. HostnameVerifierkullanarak döndürülen genel ana makine adını doğrulayın.nullise bağlantıyı iptal edin.- Ana makine adı doğrulaması başarılı olursa
EchConfigMismatchException.getRetryConfigListkullanarak güncellenmiş yapılandırmaları kontrol edin. - Güncellenmiş yapılandırmalar varsa yeni
EchConfigListile bağlantıyı yeniden deneyin.
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
}
}
Yeniden deneme akışıyla ilgili daha fazla bilgiyi RFC 9849'da, özellikle de ortak ad için neden kimlik doğrulamanın gerekli olduğu hakkında bulabilirsiniz.