MiniABS 1.1: HTTPS support
- Bundle current root certificates (Let's Encrypt, GTS, Sectigo, DigiCert G2, Amazon, ...) missing from Android 2.x's store (Roots.java, tools/gen_roots.py) - Ask once for unknown certificates, showing the SHA-256 fingerprint, and remember the answer; replaces "accept any certificate" (Trust*, Settings) - Local streaming proxy so Android 2.x's MediaPlayer can play https:// servers, with seeking and a fresh token per request (StreamProxy.java) - Tests over real TLS with good, expired, wrong-host, forged and self-signed certificates (test/make_pki.sh, TrustTest.java) - README: server setup for a legacy TLS 1.0 port Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4fe9eed870
commit
b7e43d7d61
24 files changed
+1326
-88
No files matched your search
@@ -12,17 +12,10 @@ import java.net.ProtocolException;
|
||||
import java.net.Socket;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
@@ -33,7 +26,7 @@ import android.os.Build;
|
||||
|
||||
/** Audiobookshelf REST client (API as of server 2.x, including the 2.26+ token system). */
|
||||
final class Api {
|
||||
static final String VERSION = "1.0";
|
||||
static final String VERSION = "1.1";
|
||||
|
||||
static class ApiException extends IOException {
|
||||
final int code;
|
||||
@@ -52,7 +45,6 @@ final class Api {
|
||||
}
|
||||
|
||||
private static final Object REFRESH_LOCK = new Object();
|
||||
private static SSLSocketFactory trustAllFactory;
|
||||
|
||||
static {
|
||||
// Froyo's HttpURLConnection reuses dead pooled sockets; turn pooling off.
|
||||
@@ -70,7 +62,7 @@ final class Api {
|
||||
conn.setReadTimeout(30000);
|
||||
conn.setUseCaches(false);
|
||||
conn.setInstanceFollowRedirects(true);
|
||||
if (conn instanceof HttpsURLConnection && Prefs.get(c).trustAll()) trustAll((HttpsURLConnection) conn);
|
||||
if (conn instanceof HttpsURLConnection) Trust.apply(c, (HttpsURLConnection) conn);
|
||||
setMethod(conn, method);
|
||||
conn.setRequestProperty("User-Agent", "MiniABS/" + VERSION + " (Android " + Build.VERSION.RELEASE + ")");
|
||||
return conn;
|
||||
@@ -80,38 +72,6 @@ final class Api {
|
||||
conn.setRequestMethod(method);
|
||||
}
|
||||
|
||||
private static synchronized SSLSocketFactory trustAllFactory() throws Exception {
|
||||
if (trustAllFactory == null) {
|
||||
TrustManager tm = new X509TrustManager() {
|
||||
public void checkClientTrusted(X509Certificate[] chain, String type) {
|
||||
}
|
||||
|
||||
public void checkServerTrusted(X509Certificate[] chain, String type) {
|
||||
}
|
||||
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[0];
|
||||
}
|
||||
};
|
||||
SSLContext ctx = SSLContext.getInstance("TLS");
|
||||
ctx.init(null, new TrustManager[]{tm}, null);
|
||||
trustAllFactory = ctx.getSocketFactory();
|
||||
}
|
||||
return trustAllFactory;
|
||||
}
|
||||
|
||||
private static void trustAll(HttpsURLConnection conn) {
|
||||
try {
|
||||
conn.setSSLSocketFactory(trustAllFactory());
|
||||
conn.setHostnameVerifier(new HostnameVerifier() {
|
||||
public boolean verify(String host, SSLSession session) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PATCH over a plain socket. Android 2.x's HttpURLConnection only sends a body with POST or PUT,
|
||||
* and Audiobookshelf uses PATCH for "mark as played" and for saving progress without a session.
|
||||
@@ -130,16 +90,15 @@ final class Api {
|
||||
sock.setSoTimeout(30000);
|
||||
Socket io = sock;
|
||||
if (https) {
|
||||
boolean trust = p.trustAll();
|
||||
SSLSocketFactory f;
|
||||
try {
|
||||
f = trust ? trustAllFactory() : (SSLSocketFactory) SSLSocketFactory.getDefault();
|
||||
} catch (Exception e) {
|
||||
throw new SSLException("TLS unavailable");
|
||||
SSLSocket ss = (SSLSocket) Trust.factory(c).createSocket(sock, u.getHost(), port, true);
|
||||
try { // send the host name (SNI) where the phone supports it (Android 2.3+)
|
||||
ss.getClass().getMethod("setHostname", String.class).invoke(ss, u.getHost());
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
SSLSocket ss = (SSLSocket) f.createSocket(sock, u.getHost(), port, true);
|
||||
ss.startHandshake();
|
||||
if (!trust && !HttpsURLConnection.getDefaultHostnameVerifier().verify(u.getHost(), ss.getSession())) {
|
||||
java.security.cert.Certificate[] peer = ss.getSession().getPeerCertificates();
|
||||
if (peer.length == 0 || !(peer[0] instanceof java.security.cert.X509Certificate)
|
||||
|| !Trust.hostMatches((java.security.cert.X509Certificate) peer[0], u.getHost())) {
|
||||
ss.close();
|
||||
throw new SSLException("Certificate doesn't match " + u.getHost());
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user