GingerCal 1.4: bundled TLS 1.2, no bridge needed
CalDAV now goes through Minamp's own HTTP/TLS client (Bouncy Castle TLS 1.2, Mozilla roots, keep-alive) instead of Apache HttpClient on Android 2.3's TLS 1.0, as the iPod version does with BearSSL. The phone reaches the normal https:// Nextcloud address at home and away; a pinned self-signed server or the old bridge still works. - NetHttp transport; requests run on big-stack threads (the handshake overflows AsyncTask/IntentService stacks on Android 2.x) - http:// -> https:// redirects on the same host are followed; other hosts still refused - bridge wording replaced in Galician and English; README updated - NetTest (TLS + CalDAV discovery through the new transport) replaces BridgeTest Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
42c3d6da91
commit
f7428287eb
145 files changed
+1278
-407
No files matched your search
@@ -80,9 +80,9 @@ public class Dav {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request, following redirects on the same server (Apache won't for PROPFIND/REPORT).
|
||||
* Redirects elsewhere, typically Nextcloud forcing its public https:// address, can't work
|
||||
* on Android 2.3, so they fail with an explanation instead of a doomed TLS 1.2 attempt.
|
||||
* Sends a request, following redirects on the same server (Apache won't for PROPFIND/REPORT),
|
||||
* including http:// to https:// on the same host. Redirects to another server fail with an
|
||||
* explanation: the password must not follow them.
|
||||
*/
|
||||
Http.Resp send(String method, String target, String depth, String body, String[] extra)
|
||||
throws IOException {
|
||||
@@ -119,16 +119,19 @@ public class Dav {
|
||||
throw new DavException(DavException.REDIRECT, L.t("Too many redirects from the server."));
|
||||
}
|
||||
|
||||
static String host(String url) {
|
||||
String h = URI.create(url).getHost();
|
||||
return h == null ? "" : h.toLowerCase(java.util.Locale.US);
|
||||
}
|
||||
|
||||
static void checkRedirect(String from, String to, List<String> visited, int code) throws DavException {
|
||||
String a = originKey(from), b = originKey(to);
|
||||
if (a.startsWith("http:") && b.startsWith("https:")) {
|
||||
throw new DavException(DavException.REDIRECT, L.f("The server redirected http:// to %s (Nextcloud forces HTTPS / HSTS). Android 2.3 can't follow that: use the legacy-TLS bridge address instead (see README, 'Forced HTTPS / HSTS').", origin(to)));
|
||||
}
|
||||
if (!a.equals(b)) {
|
||||
throw new DavException(DavException.REDIRECT, L.f("The server redirected to %s, not the address set in the app. Nextcloud is sending its public URL; check the bridge's proxy_redirect line (README, 'Forced HTTPS / HSTS').", origin(to)));
|
||||
boolean upgrade = a.startsWith("http:") && b.startsWith("https:") && host(from).equals(host(to));
|
||||
if (!a.equals(b) && !upgrade) {
|
||||
throw new DavException(DavException.REDIRECT, L.f("The server redirected to %s, not the address set in the app. Put that address in Settings.", origin(to)));
|
||||
}
|
||||
if (visited.contains(to)) {
|
||||
throw new DavException(DavException.REDIRECT, L.f("Redirect loop (HTTP %d back to the same address). Nextcloud forces HTTPS but the bridge reaches it over plain HTTP: switch the bridge to proxy_pass https:// (README, 'Forced HTTPS / HSTS').", code));
|
||||
throw new DavException(DavException.REDIRECT, L.f("Redirect loop (HTTP %d back to the same address). Use the server's https:// address in Settings.", code));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
-17
@@ -150,8 +150,8 @@ public final class L {
|
||||
|
||||
// settings
|
||||
put("GingerCal settings", "Configuración de GingerCal");
|
||||
put("Nextcloud server. Android 2.3 can't speak TLS 1.2, so use either your legacy-TLS bridge (https://cloud.example.com:8443) or http:// to the server on your home Wi-Fi. Use an app password, not your real one.",
|
||||
"Servidor Nextcloud. Android 2.3 non admite TLS 1.2, así que usa a túa ponte TLS antiga (https://cloud.example.com:8443) ou http:// ao servidor na wifi da casa. Usa un contrasinal de aplicación, non o teu contrasinal real.");
|
||||
put("Nextcloud server: your normal https:// address. This app brings its own modern TLS, so no bridge is needed, at home or away. Use an app password, not your real one.",
|
||||
"Servidor Nextcloud: o teu enderezo https:// de sempre. Esta aplicación trae o seu propio TLS moderno, así que non fai falta ningunha ponte, nin na casa nin fóra. Usa un contrasinal de aplicación, non o teu contrasinal real.");
|
||||
put("Server address", "Enderezo do servidor");
|
||||
put("User name", "Usuario");
|
||||
put("App password", "Contrasinal de aplicación");
|
||||
@@ -173,8 +173,8 @@ public final class L {
|
||||
put("Re-downloading in the background", "Descargando todo de novo en segundo plano");
|
||||
put("Server address and user name are required.", "O enderezo do servidor e o usuario son obrigatorios.");
|
||||
put("Unencrypted connection", "Conexión sen cifrar");
|
||||
put("http:// sends your password in clear text over the internet. Use the legacy-TLS bridge instead, or only use http:// to a LAN address.",
|
||||
"Con http:// o contrasinal viaxa sen cifrar por internet. Usa a ponte TLS antiga ou usa http:// só cun enderezo da rede local.");
|
||||
put("http:// sends your password in clear text over the internet. Use the https:// address instead, or only use http:// to a LAN address.",
|
||||
"Con http:// o contrasinal viaxa sen cifrar por internet. Usa o enderezo https://, ou usa http:// só cun enderezo da rede local.");
|
||||
put("Use anyway", "Usar igualmente");
|
||||
put("Connecting…", "Conectando…");
|
||||
put("Connection failed", "Erro de conexión");
|
||||
@@ -183,21 +183,25 @@ public final class L {
|
||||
put("Sync now", "Sincronizar agora");
|
||||
|
||||
// certificate trust
|
||||
put("WARNING: the server certificate changed since you trusted it. That happens when you regenerate the bridge certificate - or when someone is intercepting the connection.\n\n",
|
||||
"AVISO: o certificado do servidor cambiou desde que confiaches nel. Isto pasa se rexeneras o certificado da ponte, ou se alguén está a interceptar a conexión.\n\n");
|
||||
put("This phone doesn't trust the server certificate (normal for the self-signed legacy bridge, or for Let's Encrypt on Android 2.3).\n\n",
|
||||
"Este teléfono non confía no certificado do servidor (é normal coa ponte antiga autoasinada ou con Let's Encrypt en Android 2.3).\n\n");
|
||||
put("WARNING: the server certificate changed since you trusted it. That happens when a self-signed certificate is regenerated - or when someone is intercepting the connection.\n\n",
|
||||
"AVISO: o certificado do servidor cambiou desde que confiaches nel. Isto pasa se se rexenera un certificado autoasinado, ou se alguén está a interceptar a conexión.\n\n");
|
||||
put("The server certificate isn't signed by a known authority (normal for a self-signed server or an old TLS 1.0 bridge).\n\n",
|
||||
"O certificado do servidor non está asinado por unha autoridade coñecida (é normal nun servidor autoasinado ou nunha ponte TLS 1.0 antiga).\n\n");
|
||||
put("Subject: %s\nIssuer: %s\nExpires: %s\n\nSHA-256:\n%s\n\nOnly trust it if this matches the fingerprint printed on your server.",
|
||||
"Suxeito: %s\nEmisor: %s\nCaduca: %s\n\nSHA-256:\n%s\n\nConfía nel só se coincide coa pegada dixital que mostra o teu servidor.");
|
||||
put("Certificate changed!", "O certificado cambiou!");
|
||||
put("Trust this certificate?", "Confiar neste certificado?");
|
||||
put("Trust", "Confiar");
|
||||
put("Interrupted", "Interrompido");
|
||||
put("The server certificate is for a different address (%1$s).", "O certificado do servidor é doutro enderezo (%1$s).");
|
||||
put("The server certificate has expired, or the phone's date is wrong.", "O certificado do servidor caducou ou a data do teléfono é incorrecta.");
|
||||
put("Server certificate changed (does not match the pinned fingerprint)", "O certificado do servidor cambiou (non coincide coa pegada fixada)");
|
||||
put("Server certificate is not trusted by this phone", "Este teléfono non confía no certificado do servidor");
|
||||
|
||||
// errors
|
||||
put("Can't resolve %s. No connection, or a typo in the server address.", "Non se pode resolver %s. Non hai conexión ou hai un erro no enderezo do servidor.");
|
||||
put("Can't connect to the server (%s). Wrong port, or the bridge is down?", "Non se pode conectar co servidor (%s). Porto incorrecto ou ponte caída?");
|
||||
put("Can't connect to the server (%s). Wrong address or port, or the server is down?",
|
||||
"Non se pode conectar co servidor (%s). Enderezo ou porto incorrecto, ou servidor caído?");
|
||||
put("The server took too long to answer.", "O servidor tardou de máis en responder.");
|
||||
put("TLS error: %s", "Erro TLS: %s");
|
||||
put("Network error: %s", "Erro de rede: %s");
|
||||
@@ -207,12 +211,10 @@ public final class L {
|
||||
"Acceso rexeitado (401). Comproba o usuario e usa un contrasinal de aplicación de Nextcloud (Configuración > Seguranza > Dispositivos e sesións).");
|
||||
put("Not allowed to write to this calendar (403). It may be read-only/shared.", "Non tes permiso para escribir neste calendario (403). Pode ser de só lectura ou compartido.");
|
||||
put("Too many redirects from the server.", "O servidor fixo demasiadas redireccións.");
|
||||
put("The server redirected http:// to %s (Nextcloud forces HTTPS / HSTS). Android 2.3 can't follow that: use the legacy-TLS bridge address instead (see README, 'Forced HTTPS / HSTS').",
|
||||
"O servidor redirixiu http:// a %s (Nextcloud obriga a usar HTTPS / HSTS). Android 2.3 non pode seguir esa redirección: usa o enderezo da ponte TLS antiga (consulta o README, «Forced HTTPS / HSTS»).");
|
||||
put("The server redirected to %s, not the address set in the app. Nextcloud is sending its public URL; check the bridge's proxy_redirect line (README, 'Forced HTTPS / HSTS').",
|
||||
"O servidor redirixiu a %s, que non é o enderezo configurado na aplicación. Nextcloud está a enviar o seu URL público; revisa a liña proxy_redirect da ponte (README, «Forced HTTPS / HSTS»).");
|
||||
put("Redirect loop (HTTP %d back to the same address). Nextcloud forces HTTPS but the bridge reaches it over plain HTTP: switch the bridge to proxy_pass https:// (README, 'Forced HTTPS / HSTS').",
|
||||
"Bucle de redirección (HTTP %d ao mesmo enderezo). Nextcloud obriga a usar HTTPS pero a ponte conecta con el por HTTP: cambia a ponte a proxy_pass https:// (README, «Forced HTTPS / HSTS»).");
|
||||
put("The server redirected to %s, not the address set in the app. Put that address in Settings.",
|
||||
"O servidor redirixiu a %s, que non é o enderezo configurado na aplicación. Pon ese enderezo na configuración.");
|
||||
put("Redirect loop (HTTP %d back to the same address). Use the server's https:// address in Settings.",
|
||||
"Bucle de redirección (HTTP %d ao mesmo enderezo). Usa o enderezo https:// do servidor na configuración.");
|
||||
put("%s %s failed: HTTP %d%s", "%s %s fallou: HTTP %d%s");
|
||||
put(" (not found)", " (non atopado)");
|
||||
put(" (not a WebDAV URL?)", " (non é un URL WebDAV?)");
|
||||
@@ -221,8 +223,6 @@ public final class L {
|
||||
put("Saving failed: HTTP %d", "Non se puido gardar: HTTP %d");
|
||||
put("Event was changed on the server meanwhile. Sync and try again.", "O evento cambiou no servidor mentres tanto. Sincroniza e téntao de novo.");
|
||||
put("Delete failed: HTTP %d", "Non se puido eliminar: HTTP %d");
|
||||
put("TLS handshake failed before any certificate was sent. The server most likely requires TLS 1.2+, which Android 2.3 cannot do. Point the app at the legacy-TLS bridge (see README) or use http:// on your LAN. (%s)",
|
||||
"A negociación TLS fallou antes de recibir ningún certificado. Seguramente o servidor esixe TLS 1.2 ou superior, que Android 2.3 non admite. Configura a aplicación coa ponte TLS antiga (consulta o README) ou usa http:// na rede local. (%s)");
|
||||
put("Bad XML from server: %s", "XML incorrecto do servidor: %s");
|
||||
put("Empty XML from server", "XML baleiro do servidor");
|
||||
put("%d calendars: %d refreshed (%d events)", "Calendarios: %d, actualizados: %d (eventos: %d)");
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
package org.gingercal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
/** Certificate pinning for servers the phone can't validate itself (self-signed bridge). */
|
||||
/** Certificate fingerprints for pinning servers that aren't CA-signed (self-signed, old bridge). */
|
||||
public final class Tls {
|
||||
private Tls() {}
|
||||
|
||||
@@ -38,73 +28,4 @@ public final class Tls {
|
||||
if (s == null) return "";
|
||||
return s.replaceAll("[^0-9A-Fa-f]", "").toUpperCase(Locale.US);
|
||||
}
|
||||
|
||||
/** Accepts the system CAs, or exactly one pinned leaf certificate (SHA-256). */
|
||||
public static class PinTrust implements X509TrustManager {
|
||||
final String pin;
|
||||
final X509TrustManager system;
|
||||
String seenFp, seenSubject, seenIssuer, seenExpiry;
|
||||
boolean rejected, mismatch;
|
||||
|
||||
public PinTrust(String pin) throws GeneralSecurityException {
|
||||
this.pin = normPin(pin);
|
||||
TrustManagerFactory f = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
f.init((KeyStore) null);
|
||||
X509TrustManager s = null;
|
||||
TrustManager[] tms = f.getTrustManagers();
|
||||
for (int i = 0; i < tms.length; i++) if (tms[i] instanceof X509TrustManager) s = (X509TrustManager) tms[i];
|
||||
system = s;
|
||||
}
|
||||
|
||||
public boolean pinned() {
|
||||
return pin.length() > 0;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
seenFp = null;
|
||||
rejected = false;
|
||||
mismatch = false;
|
||||
}
|
||||
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||
throw new CertificateException("client certs not supported");
|
||||
}
|
||||
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||
if (chain == null || chain.length == 0) throw new CertificateException("empty chain");
|
||||
X509Certificate leaf = chain[0];
|
||||
seenFp = sha256(leaf.getEncoded());
|
||||
seenSubject = leaf.getSubjectDN().getName();
|
||||
seenIssuer = leaf.getIssuerDN().getName();
|
||||
seenExpiry = new SimpleDateFormat("yyyy-MM-dd", Locale.US).format(leaf.getNotAfter());
|
||||
if (pinned()) {
|
||||
if (normPin(seenFp).equals(pin)) return;
|
||||
rejected = true;
|
||||
mismatch = true;
|
||||
throw new CertificateException("certificate does not match pin");
|
||||
}
|
||||
try {
|
||||
if (system == null) throw new CertificateException("no system trust store");
|
||||
system.checkServerTrusted(chain, authType);
|
||||
} catch (CertificateException e) {
|
||||
rejected = true;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return system == null ? new X509Certificate[0] : system.getAcceptedIssuers();
|
||||
}
|
||||
|
||||
public IOException translate(IOException e) {
|
||||
if (rejected && seenFp != null) {
|
||||
return new DavException.UntrustedCert(seenFp, seenSubject, seenIssuer, seenExpiry, mismatch);
|
||||
}
|
||||
if (e instanceof SSLException && seenFp == null) {
|
||||
return new DavException(0, L.f("TLS handshake failed before any certificate was sent. The server most likely requires TLS 1.2+, which Android 2.3 cannot do. Point the app at the legacy-TLS bridge (see README) or use http:// on your LAN. (%s)", e.getMessage()));
|
||||
}
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user