Encrypted Client Hello (ECH) ist eine TLS-Erweiterung, mit der das Feld „Server Name Indication“ (SNI) in der Handshake-Nachricht des Clients verschlüsselt wird. In Android 17 (API-Level 37) und höher wird ECH standardmäßig unterstützt. ECH trägt dazu bei, den Web-Traffic von Nutzern privat zu halten, da Netzwerkvermittler die Hostnamen nicht sehen können, mit denen sich eine App verbindet.
Für App-Entwickler
So übernehmen Sie ECH in Ihre Anwendung:
- Prüfen Sie, ob Ihre Netzwerkbibliothek ECH unterstützt: Achten Sie darauf, dass Sie eine Bibliotheksversion verwenden, die ECH unter Android unterstützt. Die Unterstützung wird bald in OkHttp und HttpEngine eingeführt.
- Netzwerksicherheitskonfiguration konfigurieren: Standardmäßig ist ECH für alle
Domains aktiviert, wenn Ihre Bibliothek es unterstützt. Wenn Sie ECH deaktivieren oder erzwingen müssen,
konfigurieren Sie das Element
domainEncryptionin Ihrer Netzwerksicherheitskonfiguration. - Ziel-SDK-Level aktualisieren: ECH ist nur unter Android 17 (API Level 37) und höher verfügbar.
Für Bibliotheksentwickler
Wenn Sie eine benutzerdefinierte HTTP-Netzwerkbibliothek entwickeln oder eine vorhandene erweitern, sollten Sie die ECH-Unterstützung implementieren, indem Sie mit den Plattform-APIs interagieren.
Domainverschlüsselungsrichtlinie prüfen
Bevor Sie ECH-Konfigurationen abfragen oder Verbindungen initiieren, prüfen Sie die
Domainverschlüsselungsrichtlinie der App mit
NetworkSecurityPolicy.getDomainEncryptionMode.
Je nach zurückgegebenem Modus gehen Sie mit ECH so vor:
DOMAIN_ENCRYPTION_MODE_DISABLEDundDOMAIN_ENCRYPTION_MODE_UNKNOWN: Rufen Sie keine ECH-Konfigurationen ab oder versuchen Sie es nicht mit ECH.DOMAIN_ENCRYPTION_MODE_ENABLEDundDOMAIN_ENCRYPTION_MODE_OPPORTUNISTIC: Erzwingen Sie ECH. Rufen Sie ECH-Konfigurationen ab und verwenden Sie ECH, wenn der Server es unterstützt. Wenn der Server ECH nicht unterstützt, aktivieren Sie ECH GREASE.
ECH-Konfigurationen abrufen
Um eine Verbindung mit ECH herzustellen, müssen Sie den HTTPS-DNS-Eintrag des Servers auflösen, der die ECH-Konfigurationen enthält. Wenn Apps das System-DNS verwenden, können diese Daten mit einer der beiden folgenden Methoden abgerufen werden:
Methode 1: Mit der API DnsResolver.query auf hoher Ebene
Wenn Ihre Bibliothek keine benutzerdefinierten DNS-Auflösungsmechanismen erfordert, können Sie
die API DnsResolver.query auf hoher Ebene der Plattform verwenden. Diese API führt parallele
Abfragen für die A/AAAA/HTTPS-Einträge aus und kombiniert die Ergebnisse in einem
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 */ }
});
Methode 2: Mit getAllByName und DnsResolver.rawQuery
Bei Bibliotheken, die ihre eigenen Socketverbindungen und DNS-Auflösungspipelines verwalten, ist es möglicherweise besser, IP-Adressen mit Standard-APIs aufzulösen und den HTTPS-Eintrag separat abzurufen:
- Lösen Sie A/AAAA-Einträge mit
InetAddress.getAllByNamefür das Standardnetzwerk oderNetwork.getAllByNameauf. - Rufen Sie den HTTPS-Rohdatensatz parallel mit
DnsResolver.rawQueryab. Geben SieDnsResolver.TYPE_HTTPSals Abfragetyp an.
Verantwortung des Entwicklers und Grenzfälle
Wenn Sie Methode 2 auswählen, hat Ihre Bibliothek zusätzliche Verantwortlichkeiten und Grenzfälle zu berücksichtigen.
- DNS-Einträge parsen: Sie müssen die Rohdaten-Nutzlast der DNS
Antwort von
rawQueryparsen, um dieEchConfigListzu extrahieren. - Abweichungen bei Einträgen behandeln: Sie müssen Inkonsistenzen zwischen den A/AAAA- und HTTPS-Abfragen behandeln.
- Race Conditions: Sie müssen die Ergebnisse der parallelen DNS-Lookups synchronisieren. Wenn eine Abfrage vor der anderen aufgelöst wird oder die HTTPS-Abfrage eine Zeitüberschreitung verursacht, müssen Sie entsprechend zurückgreifen (z. B. indem Sie eine Standard-TLS-Verbindung ohne ECH versuchen, wenn die HTTPS-Abfrage fehlschlägt, oder ECH GREASE verwenden, wenn es durch die Richtlinie aktiviert ist).
TLS konfigurieren
Sobald die Bibliothek die ECH-Konfigurationsliste
(EchConfigList) aus dem HttpsRecord abgerufen hat, übergeben Sie diese Liste mit den Dienstprogramm-APIs SSLSockets oder SSLEngines, bevor Sie den TLS
Handshake starten.
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();
}
Wiederholungsablauf behandeln
Wenn die ECH-Konfigurationen des Servers nicht mehr synchron sind, schlägt der Handshake mit einer EchConfigMismatchException fehl (einer Unterklasse von javax.net.ssl.SSLException). Der Server kann aktualisierte ECH-Konfigurationen in seine Ablehnung aufnehmen, die zum Herstellen einer neuen Verbindung verwendet werden sollten. Wenn trotz gültiger Wiederholungskonfigurationen des Servers kein Wiederholungsversuch unternommen wird, muss die Bibliothek der aufrufenden Anwendung einen Fehler melden.
So behandeln Sie ECH-Wiederholungen: Fangen Sie die Ausnahme ab und führen Sie die folgenden Schritte aus:
- Rufen Sie
EchConfigMismatchException.getPublicHostnamefür die Ausnahme auf. - Bestätigen Sie den zurückgegebenen öffentlichen Hostnamen mit Ihrem
HostnameVerifier. Wenn ernullist, brechen Sie die Verbindung ab. - Wenn die Hostname-Bestätigung erfolgreich ist, prüfen Sie mit
EchConfigMismatchException.getRetryConfigListauf aktualisierte Konfigurationen. - Wenn aktualisierte Konfigurationen verfügbar sind, versuchen Sie es mit der
neuen
EchConfigListnoch einmal.
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
}
}
Weitere Informationen zum Wiederholungsablauf finden Sie in RFC 9849, insbesondere warum eine Authentifizierung für den öffentlichen Namen erforderlich ist.