Minamp 1.4: faster connecting

- Remember the connection that worked and try it alone first next start
- Prefer plain HTTP on the home network (each TLS handshake costs seconds on a
  600 MHz phone); new setting "Encrypt at home too" to keep HTTPS
- Try secure addresses one at a time instead of in parallel, so they don't
  all time out together
- TLS 1.2 with session resumption: reconnects skip the full handshake
- Cache certificate chains already verified; changing the trusted roots
  clears that cache and all TLS sessions
- README: updated connection order; test: session resumption check and
  HandshakeProfile

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xockoandClaude Opus 5.5 committed 2026-10-14 21:16:09 +02:00
1 parent 58d21b6d68
commit 6fd17bc9c5
9 files changed
+284 -67

No files matched your search

+26 -1
View File
@@ -45,14 +45,39 @@ public final class CertVerifier {
}
private static RootStore roots;
/** Chains already proven to lead to a trusted root (keyed by a hash of the chain), so a
* reconnect only re-checks the host name and dates, not every signature again. */
private static final java.util.Set<String> verifiedChains = new java.util.HashSet<String>();
public static void setRootStore(RootStore store) { roots = store; }
/** Changing what's trusted invalidates everything trusted so far: cached chains and TLS sessions. */
public static void setRootStore(RootStore store) {
roots = store;
synchronized (verifiedChains) { verifiedChains.clear(); }
Http.forgetSessions();
}
private CertVerifier() {}
public static void verify(List<byte[]> chainDer, String host) throws IOException {
if (chainDer == null || chainDer.isEmpty()) throw new IOException("Server sent no certificate");
if (roots == null) throw new IOException("No trust store configured");
java.io.ByteArrayOutputStream all = new java.io.ByteArrayOutputStream();
for (int i = 0; i < chainDer.size(); i++) all.write(chainDer.get(i), 0, chainDer.get(i).length);
String chainKey = sha1Hex(all.toByteArray());
boolean known;
synchronized (verifiedChains) { known = verifiedChains.contains(chainKey); }
if (known) {
Certificate leaf = Certificate.getInstance(chainDer.get(0));
if (!hostMatches(leaf, host)) throw new IOException("Certificate does not match " + host);
if (new Date().getTime() > 1704067200000L) checkDates(leaf, new Date());
return;
}
verifyFully(chainDer, host);
synchronized (verifiedChains) { verifiedChains.add(chainKey); }
}
private static void verifyFully(List<byte[]> chainDer, String host) throws IOException {
List<Certificate> chain = new ArrayList<Certificate>();
for (int i = 0; i < chainDer.size(); i++) chain.add(Certificate.getInstance(chainDer.get(i)));
+21
View File
@@ -133,6 +133,12 @@ public final class Http {
}
}
/** Forget resumable TLS sessions (next connection to each host does a full, verified handshake). */
static void forgetSessions() {
synchronized (sessions) { sessions.clear(); }
evictAll();
}
/** Drop all pooled connections (e.g. after the network changed). */
public static void evictAll() {
final List<Conn> doomed = new ArrayList<Conn>();
@@ -191,6 +197,8 @@ public final class Http {
private final String host;
private final String sessionKey;
private final long started = System.currentTimeMillis();
Client(TlsCrypto crypto, String host, String sessionKey) {
super(crypto);
this.host = host;
@@ -209,6 +217,16 @@ public final class Http {
return v;
}
/**
* TLS 1.2 only: this TLS stack can resume 1.2 sessions but not 1.3 ones, and on a 600 MHz
* phone a resumed handshake (no key exchange, no signature checks) is the difference
* between ~10 s and well under a second for every new connection.
*/
@Override
protected com.minamp.bc.tls.ProtocolVersion[] getSupportedVersions() {
return com.minamp.bc.tls.ProtocolVersion.TLSv12.only();
}
@Override
public TlsSession getSessionToResume() {
synchronized (sessions) { return sessions.get(sessionKey); }
@@ -217,6 +235,9 @@ public final class Http {
@Override
public void notifyHandshakeComplete() throws IOException {
super.notifyHandshakeComplete();
boolean resumed = context.getSecurityParametersConnection().isResumedSession();
DiagLog.log("TLS " + (resumed ? "resumed" : "full handshake") + " with " + host
+ " in " + (System.currentTimeMillis() - started) + "ms");
TlsSession s = context.getSession();
if (s != null && s.isResumable()) {
synchronized (sessions) { sessions.put(sessionKey, s); }