From e90620e9f27a7f6c4ac784118354337c07a3eb75 Mon Sep 17 00:00:00 2001 From: AnkitManeSciAps <167805368+AnkitManeSciAps@users.noreply.github.com> Date: Fri, 14 Nov 2025 16:27:02 -0500 Subject: [PATCH 01/17] [SCI-22111][NGX][NGL][NIR][Security] Switch to https for Profile Builder --- .../devsmart/miniweb/CaCertificateLoader.java | 24 ++++ .../com/devsmart/miniweb/CaTrustManager.java | 74 ++++++++++ .../java/com/devsmart/miniweb/Server.java | 134 ++++++++++++++---- .../com/devsmart/miniweb/ServerBuilder.java | 14 +- miniweb-core/src/main/resources/ca.crt | 31 ++++ 5 files changed, 243 insertions(+), 34 deletions(-) create mode 100644 miniweb-core/src/main/java/com/devsmart/miniweb/CaCertificateLoader.java create mode 100644 miniweb-core/src/main/java/com/devsmart/miniweb/CaTrustManager.java create mode 100644 miniweb-core/src/main/resources/ca.crt diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/CaCertificateLoader.java b/miniweb-core/src/main/java/com/devsmart/miniweb/CaCertificateLoader.java new file mode 100644 index 0000000..7148824 --- /dev/null +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/CaCertificateLoader.java @@ -0,0 +1,24 @@ +package com.devsmart.miniweb; + +import java.io.InputStream; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +public class CaCertificateLoader { + + public static X509Certificate getCaCertificate() throws Exception { + try (InputStream caInput = CaCertificateLoader.class.getResourceAsStream("/ca.crt")) { + if (caInput == null) { + throw new Exception("ca.crt not found in resources."); + } + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + return (X509Certificate) cf.generateCertificate(caInput); + } + } + + public static char[] getCertificatePassword() { + // temp super secure password + return "password".toCharArray(); + } + +} diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/CaTrustManager.java b/miniweb-core/src/main/java/com/devsmart/miniweb/CaTrustManager.java new file mode 100644 index 0000000..1de0c33 --- /dev/null +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/CaTrustManager.java @@ -0,0 +1,74 @@ +package com.devsmart.miniweb; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.X509TrustManager; + +@SuppressWarnings("CustomX509TrustManager") +public class CaTrustManager implements X509TrustManager { + private final X509Certificate trustedCA; + + public CaTrustManager(X509Certificate caCert) { + this.trustedCA = caCert; + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + validateChain(chain, authType); + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + validateChain(chain, authType); + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[]{trustedCA}; + } + + private void validateChain(X509Certificate[] chain, String authType) throws CertificateException { + if (chain == null || chain.length == 0) { + throw new CertificateException("Empty certificate chain"); + } + if (authType == null || authType.isEmpty()) { + throw new CertificateException("Missing authType"); + } + + // Check each certificate validity period + for (X509Certificate cert : chain) { + cert.checkValidity(); + } + + // Verify signature chain (leaf -> root) + for (int i = 0; i < chain.length - 1; i++) { + try { + chain[i].verify(chain[i + 1].getPublicKey()); + } catch (Exception e) { + throw new CertificateException("Broken chain at position " + i + ": " + e.getMessage(), e); + } + } + + X509Certificate root = chain[chain.length - 1]; + + // Ensure provided CA matches the root subject + if (!root.getSubjectX500Principal().equals(trustedCA.getSubjectX500Principal())) { + throw new CertificateException("Root subject does not match trusted CA"); + } + + // Ensure provided CA actually signed the presented root + try { + root.verify(trustedCA.getPublicKey()); + } catch (Exception e) { + throw new CertificateException("Root not signed by trusted CA: " + e.getMessage(), e); + } + + // Optional: ensure CA is self-signed (basic check) + try { + trustedCA.verify(trustedCA.getPublicKey()); + } catch (Exception e) { + throw new CertificateException("Configured CA is not self-signed or invalid: " + e.getMessage(), e); + } + } +} diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java index d581dfd..329fb61 100644 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java @@ -20,15 +20,26 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.FileInputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.net.SocketTimeoutException; +import java.security.KeyStore; +import java.util.Arrays; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import javax.net.ServerSocketFactory; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.TrustManager; + public class Server { private static final Logger LOGGER = LoggerFactory.getLogger(Server.class); @@ -44,6 +55,23 @@ public class Server { private ServerSocket mServerSocket; private SocketListener mListenThread; private boolean mRunning = false; + private SSLContext sslContext; + private boolean sslEnabled = false; + + public void setSslContext(String keystorePath) throws Exception { + KeyStore keyStore = KeyStore.getInstance("PKCS12"); + try (FileInputStream fis = new FileInputStream(keystorePath)) { + keyStore.load(fis, CaCertificateLoader.getCertificatePassword()); + } + + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(keyStore, CaCertificateLoader.getCertificatePassword()); + + sslContext = SSLContext.getInstance("TLS"); + TrustManager[] trustManagers = new TrustManager[]{new CaTrustManager(CaCertificateLoader.getCaCertificate())}; + sslContext.init(kmf.getKeyManagers(), trustManagers, null); + sslEnabled = true; + } public void start() throws IOException { if (mRunning) { @@ -51,7 +79,15 @@ public void start() throws IOException { return; } - mServerSocket = new ServerSocket(); + if (sslEnabled && sslContext != null) { + SSLServerSocketFactory factory = sslContext.getServerSocketFactory(); + mServerSocket = factory.createServerSocket(); + ((SSLServerSocket) mServerSocket).setNeedClientAuth(true); + } else { + ServerSocketFactory factory = ServerSocketFactory.getDefault(); + mServerSocket = factory.createServerSocket(); + } + mServerSocket.setReuseAddress(true); mServerSocket.bind(new InetSocketAddress(port)); if (mIsDebugBuild) { @@ -66,20 +102,32 @@ public void start() throws IOException { } public void shutdown() { - if (mRunning) { - mRunning = false; - try { - mServerSocket.close(); - } catch (IOException e) { - LOGGER.error("Could not close socket:", e); - } - try { - mListenThread.join(); - mListenThread = null; - } catch (InterruptedException e) { - LOGGER.error("", e); - } - LOGGER.info("Server shutdown"); + if (!mRunning) { + return; + } + mRunning = false; + try { + mServerSocket.close(); + } catch (IOException e) { + LOGGER.error("Could not close socket:", e); + } + try { + mListenThread.join(); + mListenThread = null; + } catch (InterruptedException e) { + LOGGER.error("Interrupted waiting for listener thread to join", e); + Thread.currentThread().interrupt(); + } + LOGGER.info("Server shutdown"); + } + + private static final class RemoteConnection { + final java.net.InetAddress remoteAddress; + final DefaultHttpServerConnection connection; + + RemoteConnection(java.net.InetAddress addr, DefaultHttpServerConnection conn) { + this.remoteAddress = addr; + this.connection = conn; } } @@ -116,16 +164,23 @@ public void run() { LOGGER.warn("unknown error - {}", remoteConnection.connection, e); } finally { LOGGER.info("Closing connection {}", remoteConnection.connection); - closeConnection(); + try { + // Try a graceful shutdown first + try { + remoteConnection.connection.shutdown(); + } catch (UnsupportedOperationException uoe) { + // SSLSocket may not support half-close; ignore + } + } catch (IOException ignore) {} + try { + remoteConnection.connection.close(); + } catch (UnsupportedOperationException uoe) { + // Ignore half-close attempts on SSLSocket + } catch (IOException e) { + LOGGER.error("Error closing connection", e); + } } - } - public void closeConnection() { - try { - remoteConnection.connection.close(); - } catch (IOException e){ - LOGGER.error("", e); - } } } @@ -146,20 +201,34 @@ public void run() { httpService.setHandlerResolver(requestHandlerResolver); httpService.setParams(params); - Socket socket = null; + Socket rawSocket = null; while (mRunning) { try { if (mIsDebugBuild) { LOGGER.info("waiting in accept on {}", mServerSocket.getLocalSocketAddress()); } - socket = mServerSocket.accept(); + rawSocket = mServerSocket.accept(); if (mIsDebugBuild) { - LOGGER.info("accepting connection from: {}", socket.getRemoteSocketAddress()); + LOGGER.info("accepting connection from: {}", rawSocket.getRemoteSocketAddress()); + } + Socket boundSocket = rawSocket; + + // If SSL is enabled, ensure we have a SSLSocket and complete the handshake + if (sslEnabled && (mServerSocket instanceof SSLServerSocket)) { + SSLSocket sslSocket = (SSLSocket) rawSocket; + sslSocket.setUseClientMode(false); + String[] protocols = sslSocket.getEnabledProtocols(); + String[] desired = new String[]{"TLSv1.3", "TLSv1.2"}; + sslSocket.setEnabledProtocols( + Arrays.stream(desired).filter(p -> Arrays.asList(protocols).contains(p)) + .toArray(String[]::new) + ); + sslSocket.startHandshake(); } DefaultHttpServerConnection connection = new DefaultHttpServerConnection(); - connection.bind(socket, new BasicHttpParams()); - RemoteConnection remoteConnection = new RemoteConnection(socket.getInetAddress(), connection); + connection.bind(boundSocket, new BasicHttpParams()); + RemoteConnection remoteConnection = new RemoteConnection(boundSocket.getInetAddress(), connection); mWorkerThreads.execute(new WorkerTask(httpService, remoteConnection)); } catch (SocketTimeoutException e) { @@ -168,13 +237,16 @@ public void run() { LOGGER.info("SocketListener shutting down"); mRunning = false; } catch (IOException e) { - LOGGER.error("", e); + LOGGER.error("I/O error in accept loop", e); + mRunning = false; + } catch (ClassCastException e) { + LOGGER.error("Expected SSLSocket when SSL is enabled; check server socket setup", e); mRunning = false; } } - if (socket != null) { + if (rawSocket != null) { try { - socket.close(); + rawSocket.close(); LOGGER.info("Connection is closed properly"); } catch (IOException e) { LOGGER.error("Can't close connection. Reason: ", e); diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/ServerBuilder.java b/miniweb-core/src/main/java/com/devsmart/miniweb/ServerBuilder.java index e1be5fb..bf9a024 100644 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/ServerBuilder.java +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/ServerBuilder.java @@ -1,7 +1,5 @@ package com.devsmart.miniweb; - - import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -10,6 +8,7 @@ import com.devsmart.miniweb.handlers.FileSystemRequestHandler; import java.io.File; +import java.security.cert.X509Certificate; public class ServerBuilder { @@ -19,7 +18,7 @@ public class ServerBuilder { private UriRequestHandlerResolver mUriMapper = new UriRequestHandlerResolver(); private Gson mGson = new GsonBuilder().create(); private boolean mIsDebugBuild; - + private String mKeyStorePath; public ServerBuilder setDebugBuild(boolean isDebug) { mIsDebugBuild = isDebug; return this; @@ -87,7 +86,16 @@ public Server create() { mRequestHandler = mUriMapper; } server.requestHandlerResolver = mRequestHandler; + try { + server.setSslContext(mKeyStorePath); + } catch (Exception e) { + throw new RuntimeException(e); + } return server; } + + public void setKeyStorePathAndPasswords(String keyStorePath) { + mKeyStorePath = keyStorePath; + } } diff --git a/miniweb-core/src/main/resources/ca.crt b/miniweb-core/src/main/resources/ca.crt new file mode 100644 index 0000000..68669e5 --- /dev/null +++ b/miniweb-core/src/main/resources/ca.crt @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgIUDPznALQ/ds9FYjpX71K/sznSTnwwDQYJKoZIhvcNAQEL +BQAwOzEbMBkGA1UEAwwSU2NpQVBzIEludGVybmFsIENBMQ8wDQYDVQQKDAZTY2lB +cHMxCzAJBgNVBAYTAlVTMB4XDTI1MTExNDE4MzcyNFoXDTM1MTExMjE4MzcyNFow +OzEbMBkGA1UEAwwSU2NpQVBzIEludGVybmFsIENBMQ8wDQYDVQQKDAZTY2lBcHMx +CzAJBgNVBAYTAlVTMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkYI4 +RPSIroMNHXGF7B5NcLP1zNg//EZNGuUVoiSJyK9XMFZJqEGxlJC+LOQQNDLrB2z8 +vylgae6zGDrrNYyUUVivsRs9BUDk2WCK38tZQF2JNrB4svKRe54V/D3KfCQGwwKM +09a+4EShGj1Uw6PfaK8cMUQVfGs2vkX4x+E34DcwYirfSsgcLx60n9uGURGMbKS5 +G7OXHpCazyw/JsUlxGBhsAOlhkq74+hbslzyW8IJwXZSX1g0ne29ta9PQYljujyb +L7wRDj+qvT/5g4hGliZa8sgIP6uNawjgWCS86SuXdY5NrpLZsHizVgHe5t3Shqyb +/4d0fOmF2tyiaDeBF3KDIx36XXxppNhrPYxwqrZi7V04QxlIVqAX+UurMpgpNZlo +OFhuN/hwmFuBEFM+rgbwsswzyJRCx1xSq/xRRj0hQ12weRMJUQO95pAXKvusv8+X +Flmi4+fdfb6CjF5gQCU2n7mEnlcMqQfN9TtDcw3hT05rcOfsmwKkFq8YnHatNhKI +7aXQzOyxyTezVjN7ZX3Z3gtmOBy5XR6euQfM+z/0vEsMPmN//ixG3Dd0FEwxH4Vq +U8MXbOjWj8A4noI/YwnKIGHpfRA7xMIQjMnefxKILWsYxKaLFnN3ILtCJapw9MRV +WQMBfEuRy09RK6xjZFwZg7F1gMfrDo1qLmuSNuECAwEAAaNTMFEwHQYDVR0OBBYE +FP/1Myb9mgNc0o7GuNjL+x89SPJbMB8GA1UdIwQYMBaAFP/1Myb9mgNc0o7GuNjL ++x89SPJbMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAE7nl7gk +dhVRsyTnQMEeSvOtX+5fjPBwrj1ATSzQOxZti+cNirZMEiC8zCvvo3LEXxsRyL59 +llAh9bz8T2Z3hLnar3YgVg1l2PEMwq6fnA9nFAhu1CYU3agihw2lP8apyS+lhd0C +/StPjFa3GgcXjO/RzutX9l5Ynx7oHvXDBKt4ZGa5x/mZiDzddTigEx/rVAbmItvA +6jobIc7PNB5NiPNnv/WnUm0ZZMoGonBWzxN4C5LYN5TnKX65S0uaRm9aYp/u56f/ +JPMK9F3OE3OiW1HFCB4ITcXtqMMEDARbP5PJDJbe7yHHs/SXdLAK7vBFGIIVSkRc +26cQi5n0XUMmSFpyl0WF7bPKsY+Wwn+ZIdIa0v4C//WISX37IXlmjp1Ny5YHpMWm +11XbDM3JiP3I5Y2WefWqae92rbRR1n3RARdQ25QFZ6NucIZYVMupm/VUvmvZt/D0 +0OYSEopStNZkKx/XlamEVCu8bitp0STrNj/BEYAZGQUfw/IRYStBvOmP2rTV9q8t +gnaaNsUK4doRhKDJrFQUZjyNmjMmv3GFoUv++XDIFar7+pl/EeDFDCstk/X0Ec6G +m+bIUkU65L1stOHMZ5+9pjus1ZlwtZH0BBL+PGFOKGNJ/YFKIjoUkBLxYTUhzAHv +yytryJ2XJMP8VQqA0n+tmiTB/UDJ61HWQI4E +-----END CERTIFICATE----- From 4904102460074b2530f03dcd55031f158fcf8ae6 Mon Sep 17 00:00:00 2001 From: AnkitManeSciAps <167805368+AnkitManeSciAps@users.noreply.github.com> Date: Mon, 17 Nov 2025 11:04:51 -0500 Subject: [PATCH 02/17] Updated password and added PbCertificatePath --- .../devsmart/miniweb/CaCertificateLoader.java | 24 ------------ .../com/devsmart/miniweb/CaTrustManager.java | 16 +++++--- .../java/com/devsmart/miniweb/Server.java | 9 ++--- .../com/devsmart/miniweb/ServerBuilder.java | 8 +++- .../devsmart/miniweb/SslCertificateUtil.java | 37 +++++++++++++++++++ 5 files changed, 57 insertions(+), 37 deletions(-) delete mode 100644 miniweb-core/src/main/java/com/devsmart/miniweb/CaCertificateLoader.java create mode 100644 miniweb-core/src/main/java/com/devsmart/miniweb/SslCertificateUtil.java diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/CaCertificateLoader.java b/miniweb-core/src/main/java/com/devsmart/miniweb/CaCertificateLoader.java deleted file mode 100644 index 7148824..0000000 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/CaCertificateLoader.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.devsmart.miniweb; - -import java.io.InputStream; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; - -public class CaCertificateLoader { - - public static X509Certificate getCaCertificate() throws Exception { - try (InputStream caInput = CaCertificateLoader.class.getResourceAsStream("/ca.crt")) { - if (caInput == null) { - throw new Exception("ca.crt not found in resources."); - } - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - return (X509Certificate) cf.generateCertificate(caInput); - } - } - - public static char[] getCertificatePassword() { - // temp super secure password - return "password".toCharArray(); - } - -} diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/CaTrustManager.java b/miniweb-core/src/main/java/com/devsmart/miniweb/CaTrustManager.java index 1de0c33..c093fb8 100644 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/CaTrustManager.java +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/CaTrustManager.java @@ -1,5 +1,8 @@ package com.devsmart.miniweb; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.security.cert.CertificateException; import java.security.cert.X509Certificate; @@ -8,6 +11,7 @@ @SuppressWarnings("CustomX509TrustManager") public class CaTrustManager implements X509TrustManager { private final X509Certificate trustedCA; + private static final Logger LOGGER = LoggerFactory.getLogger(CaTrustManager.class); public CaTrustManager(X509Certificate caCert) { this.trustedCA = caCert; @@ -30,10 +34,10 @@ public X509Certificate[] getAcceptedIssuers() { private void validateChain(X509Certificate[] chain, String authType) throws CertificateException { if (chain == null || chain.length == 0) { - throw new CertificateException("Empty certificate chain"); + LOGGER.error("Empty certificate chain"); } if (authType == null || authType.isEmpty()) { - throw new CertificateException("Missing authType"); + LOGGER.error("Missing authType"); } // Check each certificate validity period @@ -46,7 +50,7 @@ private void validateChain(X509Certificate[] chain, String authType) throws Cert try { chain[i].verify(chain[i + 1].getPublicKey()); } catch (Exception e) { - throw new CertificateException("Broken chain at position " + i + ": " + e.getMessage(), e); + LOGGER.error("Broken chain at position {}: {}", i, e.getMessage(), e); } } @@ -54,21 +58,21 @@ private void validateChain(X509Certificate[] chain, String authType) throws Cert // Ensure provided CA matches the root subject if (!root.getSubjectX500Principal().equals(trustedCA.getSubjectX500Principal())) { - throw new CertificateException("Root subject does not match trusted CA"); + LOGGER.info("Root subject does not match trusted CA"); } // Ensure provided CA actually signed the presented root try { root.verify(trustedCA.getPublicKey()); } catch (Exception e) { - throw new CertificateException("Root not signed by trusted CA: " + e.getMessage(), e); + LOGGER.error("Root not signed by trusted CA: {}", e.getMessage()); } // Optional: ensure CA is self-signed (basic check) try { trustedCA.verify(trustedCA.getPublicKey()); } catch (Exception e) { - throw new CertificateException("Configured CA is not self-signed or invalid: " + e.getMessage(), e); + LOGGER.error("Configured CA is not self-signed or invalid: {}", e.getMessage(), e); } } } diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java index 329fb61..5cde28e 100644 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java @@ -61,14 +61,14 @@ public class Server { public void setSslContext(String keystorePath) throws Exception { KeyStore keyStore = KeyStore.getInstance("PKCS12"); try (FileInputStream fis = new FileInputStream(keystorePath)) { - keyStore.load(fis, CaCertificateLoader.getCertificatePassword()); + keyStore.load(fis, SslCertificateUtil.getCertificatePassword()); } KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - kmf.init(keyStore, CaCertificateLoader.getCertificatePassword()); + kmf.init(keyStore, SslCertificateUtil.getCertificatePassword()); sslContext = SSLContext.getInstance("TLS"); - TrustManager[] trustManagers = new TrustManager[]{new CaTrustManager(CaCertificateLoader.getCaCertificate())}; + TrustManager[] trustManagers = new TrustManager[]{new CaTrustManager(SslCertificateUtil.getCaCertificate())}; sslContext.init(kmf.getKeyManagers(), trustManagers, null); sslEnabled = true; } @@ -84,8 +84,7 @@ public void start() throws IOException { mServerSocket = factory.createServerSocket(); ((SSLServerSocket) mServerSocket).setNeedClientAuth(true); } else { - ServerSocketFactory factory = ServerSocketFactory.getDefault(); - mServerSocket = factory.createServerSocket(); + mServerSocket = new ServerSocket(port); } mServerSocket.setReuseAddress(true); diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/ServerBuilder.java b/miniweb-core/src/main/java/com/devsmart/miniweb/ServerBuilder.java index bf9a024..61ca83d 100644 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/ServerBuilder.java +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/ServerBuilder.java @@ -6,6 +6,8 @@ import org.apache.http.protocol.HttpRequestHandler; import org.apache.http.protocol.HttpRequestHandlerResolver; import com.devsmart.miniweb.handlers.FileSystemRequestHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.security.cert.X509Certificate; @@ -19,6 +21,8 @@ public class ServerBuilder { private Gson mGson = new GsonBuilder().create(); private boolean mIsDebugBuild; private String mKeyStorePath; + private static final Logger LOGGER = LoggerFactory.getLogger(ServerBuilder.class.getSimpleName()); + public ServerBuilder setDebugBuild(boolean isDebug) { mIsDebugBuild = isDebug; return this; @@ -89,13 +93,13 @@ public Server create() { try { server.setSslContext(mKeyStorePath); } catch (Exception e) { - throw new RuntimeException(e); + LOGGER.error("Failed to set up SSL context: {}", e.getMessage()); } return server; } - public void setKeyStorePathAndPasswords(String keyStorePath) { + public void setKeyStorePath(String keyStorePath) { mKeyStorePath = keyStorePath; } } diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/SslCertificateUtil.java b/miniweb-core/src/main/java/com/devsmart/miniweb/SslCertificateUtil.java new file mode 100644 index 0000000..680f325 --- /dev/null +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/SslCertificateUtil.java @@ -0,0 +1,37 @@ +package com.devsmart.miniweb; + +import java.io.InputStream; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +import static java.io.File.separator; +import static java.lang.System.getProperty; + +public class SslCertificateUtil { + + public static X509Certificate getCaCertificate() throws Exception { + try (InputStream caInput = SslCertificateUtil.class.getResourceAsStream("/ca.crt")) { + if (caInput == null) { + throw new Exception("ca.crt not found in resources."); + } + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + return (X509Certificate) cf.generateCertificate(caInput); + } + } + + public static char[] getCertificatePassword() { + return "SciApsN@SC@R".toCharArray(); + } + + public static String getPbCertificatePath() { + String osName = getProperty("os.name", "Windows"); + if (osName.contains("Windows")) { + return "C:" + separator + "sciaps" + separator + "client.p12"; + } else if ("Mac OS X".equals(osName)) { + return getProperty("user.home") + separator + "sciaps" + separator + "client.p12"; + } else { + throw new UnsupportedOperationException("If you are seeing this message, you must be on a Linux system. Sorry..."); + } + } + +} From 1cf78311a0eb9001d0a5bf2dda69ab6df8da137f Mon Sep 17 00:00:00 2001 From: AnkitManeSciAps <167805368+AnkitManeSciAps@users.noreply.github.com> Date: Mon, 17 Nov 2025 12:09:47 -0500 Subject: [PATCH 03/17] Add fallback logic --- .../java/com/devsmart/miniweb/Server.java | 57 +++++++++++++------ 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java index 5cde28e..00320c8 100644 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java @@ -28,13 +28,12 @@ import java.net.SocketException; import java.net.SocketTimeoutException; import java.security.KeyStore; -import java.util.Arrays; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import javax.net.ServerSocketFactory; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLSocket; @@ -55,8 +54,8 @@ public class Server { private ServerSocket mServerSocket; private SocketListener mListenThread; private boolean mRunning = false; - private SSLContext sslContext; - private boolean sslEnabled = false; + private SSLContext mSslContext; + private boolean mSslEnabled = false; public void setSslContext(String keystorePath) throws Exception { KeyStore keyStore = KeyStore.getInstance("PKCS12"); @@ -67,10 +66,10 @@ public void setSslContext(String keystorePath) throws Exception { KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(keyStore, SslCertificateUtil.getCertificatePassword()); - sslContext = SSLContext.getInstance("TLS"); + mSslContext = SSLContext.getInstance("TLS"); TrustManager[] trustManagers = new TrustManager[]{new CaTrustManager(SslCertificateUtil.getCaCertificate())}; - sslContext.init(kmf.getKeyManagers(), trustManagers, null); - sslEnabled = true; + mSslContext.init(kmf.getKeyManagers(), trustManagers, null); + mSslEnabled = true; } public void start() throws IOException { @@ -79,8 +78,8 @@ public void start() throws IOException { return; } - if (sslEnabled && sslContext != null) { - SSLServerSocketFactory factory = sslContext.getServerSocketFactory(); + if (mSslEnabled && mSslContext != null) { + SSLServerSocketFactory factory = mSslContext.getServerSocketFactory(); mServerSocket = factory.createServerSocket(); ((SSLServerSocket) mServerSocket).setNeedClientAuth(true); } else { @@ -100,6 +99,27 @@ public void start() throws IOException { mListenThread.start(); } + private synchronized void fallbackToHttp() { + try { + if (mServerSocket != null && !mServerSocket.isClosed()) { + mServerSocket.close(); + } + } catch (IOException e) { + LOGGER.error("Error closing SSL server socket during fallback", e); + } + mSslEnabled = false; + try { + ServerSocket plain = new ServerSocket(); // unbound + plain.setReuseAddress(true); + plain.bind(new InetSocketAddress(port)); + mServerSocket = plain; + LOGGER.info("Reverted to plain HTTP on port {}", port); + } catch (IOException e) { + LOGGER.error("Failed to create plain HTTP ServerSocket after SSL fallback", e); + mRunning = false; + } + } + public void shutdown() { if (!mRunning) { return; @@ -115,7 +135,6 @@ public void shutdown() { mListenThread = null; } catch (InterruptedException e) { LOGGER.error("Interrupted waiting for listener thread to join", e); - Thread.currentThread().interrupt(); } LOGGER.info("Server shutdown"); } @@ -213,15 +232,10 @@ public void run() { Socket boundSocket = rawSocket; // If SSL is enabled, ensure we have a SSLSocket and complete the handshake - if (sslEnabled && (mServerSocket instanceof SSLServerSocket)) { + if (mSslEnabled && (mServerSocket instanceof SSLServerSocket)) { SSLSocket sslSocket = (SSLSocket) rawSocket; sslSocket.setUseClientMode(false); - String[] protocols = sslSocket.getEnabledProtocols(); - String[] desired = new String[]{"TLSv1.3", "TLSv1.2"}; - sslSocket.setEnabledProtocols( - Arrays.stream(desired).filter(p -> Arrays.asList(protocols).contains(p)) - .toArray(String[]::new) - ); + sslSocket.setEnabledProtocols(new String[]{"TLSv1.2"}); sslSocket.startHandshake(); } @@ -230,6 +244,15 @@ public void run() { RemoteConnection remoteConnection = new RemoteConnection(boundSocket.getInetAddress(), connection); mWorkerThreads.execute(new WorkerTask(httpService, remoteConnection)); + } catch (SSLHandshakeException e) { + LOGGER.error("SSL Handshake failed: {}. Reverting to HTTP.", e.getMessage()); + try { + rawSocket.close(); + LOGGER.info("Connection is closed properly"); + } catch (IOException ioException) { + LOGGER.error("Can't close connection. Reason: ", ioException); + } + fallbackToHttp(); } catch (SocketTimeoutException e) { continue; } catch (SocketException e) { From 80e3a7b0e1326f60eb67c09527b58441869e2bc9 Mon Sep 17 00:00:00 2001 From: AnkitManeSciAps <167805368+AnkitManeSciAps@users.noreply.github.com> Date: Mon, 17 Nov 2025 12:15:33 -0500 Subject: [PATCH 04/17] undo unwanted changes --- .../java/com/devsmart/miniweb/Server.java | 47 +++++++------------ 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java index 00320c8..6a94046 100644 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java @@ -99,6 +99,24 @@ public void start() throws IOException { mListenThread.start(); } + public void shutdown() { + if (mRunning) { + mRunning = false; + try { + mServerSocket.close(); + } catch (IOException e) { + LOGGER.error("Could not close socket:", e); + } + try { + mListenThread.join(); + mListenThread = null; + } catch (InterruptedException e) { + LOGGER.error("", e); + } + LOGGER.info("Server shutdown"); + } + } + private synchronized void fallbackToHttp() { try { if (mServerSocket != null && !mServerSocket.isClosed()) { @@ -120,35 +138,6 @@ private synchronized void fallbackToHttp() { } } - public void shutdown() { - if (!mRunning) { - return; - } - mRunning = false; - try { - mServerSocket.close(); - } catch (IOException e) { - LOGGER.error("Could not close socket:", e); - } - try { - mListenThread.join(); - mListenThread = null; - } catch (InterruptedException e) { - LOGGER.error("Interrupted waiting for listener thread to join", e); - } - LOGGER.info("Server shutdown"); - } - - private static final class RemoteConnection { - final java.net.InetAddress remoteAddress; - final DefaultHttpServerConnection connection; - - RemoteConnection(java.net.InetAddress addr, DefaultHttpServerConnection conn) { - this.remoteAddress = addr; - this.connection = conn; - } - } - private class WorkerTask implements Runnable { private final HttpService httpservice; From adc8f59c1d09ca341b3d3784dfad5c1879dbcc0f Mon Sep 17 00:00:00 2001 From: AnkitManeSciAps <167805368+AnkitManeSciAps@users.noreply.github.com> Date: Mon, 17 Nov 2025 12:26:56 -0500 Subject: [PATCH 05/17] Add better logs --- .../main/java/com/devsmart/miniweb/CaTrustManager.java | 2 +- .../java/com/devsmart/miniweb/SslCertificateUtil.java | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/CaTrustManager.java b/miniweb-core/src/main/java/com/devsmart/miniweb/CaTrustManager.java index c093fb8..261e55a 100644 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/CaTrustManager.java +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/CaTrustManager.java @@ -68,7 +68,7 @@ private void validateChain(X509Certificate[] chain, String authType) throws Cert LOGGER.error("Root not signed by trusted CA: {}", e.getMessage()); } - // Optional: ensure CA is self-signed (basic check) + // Ensure CA is self-signed (basic check) try { trustedCA.verify(trustedCA.getPublicKey()); } catch (Exception e) { diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/SslCertificateUtil.java b/miniweb-core/src/main/java/com/devsmart/miniweb/SslCertificateUtil.java index 680f325..1d3f399 100644 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/SslCertificateUtil.java +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/SslCertificateUtil.java @@ -1,5 +1,7 @@ package com.devsmart.miniweb; +import org.slf4j.Logger; + import java.io.InputStream; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; @@ -9,10 +11,13 @@ public class SslCertificateUtil { + private static final Logger LOGGER = org.slf4j.LoggerFactory.getLogger(SslCertificateUtil.class); + public static X509Certificate getCaCertificate() throws Exception { try (InputStream caInput = SslCertificateUtil.class.getResourceAsStream("/ca.crt")) { if (caInput == null) { - throw new Exception("ca.crt not found in resources."); + LOGGER.error("ca.crt not found in resources."); + return null; } CertificateFactory cf = CertificateFactory.getInstance("X.509"); return (X509Certificate) cf.generateCertificate(caInput); @@ -30,7 +35,8 @@ public static String getPbCertificatePath() { } else if ("Mac OS X".equals(osName)) { return getProperty("user.home") + separator + "sciaps" + separator + "client.p12"; } else { - throw new UnsupportedOperationException("If you are seeing this message, you must be on a Linux system. Sorry..."); + LOGGER.error("Unsupported OS: {}", osName); + return null; } } From da6e47fd24a7cc6ffe9bee5ce14ec325dbe5d370 Mon Sep 17 00:00:00 2001 From: AnkitManeSciAps <167805368+AnkitManeSciAps@users.noreply.github.com> Date: Mon, 17 Nov 2025 13:42:34 -0500 Subject: [PATCH 06/17] Added new certificate with correct password --- .../devsmart/miniweb/SslCertificateUtil.java | 2 +- miniweb-core/src/main/resources/ca.crt | 54 +++++++++---------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/SslCertificateUtil.java b/miniweb-core/src/main/java/com/devsmart/miniweb/SslCertificateUtil.java index 1d3f399..ae8ed24 100644 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/SslCertificateUtil.java +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/SslCertificateUtil.java @@ -36,7 +36,7 @@ public static String getPbCertificatePath() { return getProperty("user.home") + separator + "sciaps" + separator + "client.p12"; } else { LOGGER.error("Unsupported OS: {}", osName); - return null; + return ""; } } diff --git a/miniweb-core/src/main/resources/ca.crt b/miniweb-core/src/main/resources/ca.crt index 68669e5..464f025 100644 --- a/miniweb-core/src/main/resources/ca.crt +++ b/miniweb-core/src/main/resources/ca.crt @@ -1,31 +1,31 @@ -----BEGIN CERTIFICATE----- -MIIFVzCCAz+gAwIBAgIUDPznALQ/ds9FYjpX71K/sznSTnwwDQYJKoZIhvcNAQEL +MIIFVzCCAz+gAwIBAgIUHTAQuwcvJRGS0y6pbu8vCZ3AOdcwDQYJKoZIhvcNAQEL BQAwOzEbMBkGA1UEAwwSU2NpQVBzIEludGVybmFsIENBMQ8wDQYDVQQKDAZTY2lB -cHMxCzAJBgNVBAYTAlVTMB4XDTI1MTExNDE4MzcyNFoXDTM1MTExMjE4MzcyNFow +cHMxCzAJBgNVBAYTAlVTMB4XDTI1MTExNzE4Mjk0NFoXDTM1MTExNTE4Mjk0NFow OzEbMBkGA1UEAwwSU2NpQVBzIEludGVybmFsIENBMQ8wDQYDVQQKDAZTY2lBcHMx -CzAJBgNVBAYTAlVTMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkYI4 -RPSIroMNHXGF7B5NcLP1zNg//EZNGuUVoiSJyK9XMFZJqEGxlJC+LOQQNDLrB2z8 -vylgae6zGDrrNYyUUVivsRs9BUDk2WCK38tZQF2JNrB4svKRe54V/D3KfCQGwwKM -09a+4EShGj1Uw6PfaK8cMUQVfGs2vkX4x+E34DcwYirfSsgcLx60n9uGURGMbKS5 -G7OXHpCazyw/JsUlxGBhsAOlhkq74+hbslzyW8IJwXZSX1g0ne29ta9PQYljujyb -L7wRDj+qvT/5g4hGliZa8sgIP6uNawjgWCS86SuXdY5NrpLZsHizVgHe5t3Shqyb -/4d0fOmF2tyiaDeBF3KDIx36XXxppNhrPYxwqrZi7V04QxlIVqAX+UurMpgpNZlo -OFhuN/hwmFuBEFM+rgbwsswzyJRCx1xSq/xRRj0hQ12weRMJUQO95pAXKvusv8+X -Flmi4+fdfb6CjF5gQCU2n7mEnlcMqQfN9TtDcw3hT05rcOfsmwKkFq8YnHatNhKI -7aXQzOyxyTezVjN7ZX3Z3gtmOBy5XR6euQfM+z/0vEsMPmN//ixG3Dd0FEwxH4Vq -U8MXbOjWj8A4noI/YwnKIGHpfRA7xMIQjMnefxKILWsYxKaLFnN3ILtCJapw9MRV -WQMBfEuRy09RK6xjZFwZg7F1gMfrDo1qLmuSNuECAwEAAaNTMFEwHQYDVR0OBBYE -FP/1Myb9mgNc0o7GuNjL+x89SPJbMB8GA1UdIwQYMBaAFP/1Myb9mgNc0o7GuNjL -+x89SPJbMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAE7nl7gk -dhVRsyTnQMEeSvOtX+5fjPBwrj1ATSzQOxZti+cNirZMEiC8zCvvo3LEXxsRyL59 -llAh9bz8T2Z3hLnar3YgVg1l2PEMwq6fnA9nFAhu1CYU3agihw2lP8apyS+lhd0C -/StPjFa3GgcXjO/RzutX9l5Ynx7oHvXDBKt4ZGa5x/mZiDzddTigEx/rVAbmItvA -6jobIc7PNB5NiPNnv/WnUm0ZZMoGonBWzxN4C5LYN5TnKX65S0uaRm9aYp/u56f/ -JPMK9F3OE3OiW1HFCB4ITcXtqMMEDARbP5PJDJbe7yHHs/SXdLAK7vBFGIIVSkRc -26cQi5n0XUMmSFpyl0WF7bPKsY+Wwn+ZIdIa0v4C//WISX37IXlmjp1Ny5YHpMWm -11XbDM3JiP3I5Y2WefWqae92rbRR1n3RARdQ25QFZ6NucIZYVMupm/VUvmvZt/D0 -0OYSEopStNZkKx/XlamEVCu8bitp0STrNj/BEYAZGQUfw/IRYStBvOmP2rTV9q8t -gnaaNsUK4doRhKDJrFQUZjyNmjMmv3GFoUv++XDIFar7+pl/EeDFDCstk/X0Ec6G -m+bIUkU65L1stOHMZ5+9pjus1ZlwtZH0BBL+PGFOKGNJ/YFKIjoUkBLxYTUhzAHv -yytryJ2XJMP8VQqA0n+tmiTB/UDJ61HWQI4E +CzAJBgNVBAYTAlVTMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvu0i +t7T+ea13GDIv+HnB15JuzsTobV1nZeH1w5izkYzIwx68FlDr+U66mrNZsFLna7Vn +dkgA/mHD8pZLV7Rke7rWp/bmgGOJp8yOdC/Pdb/rMdk/7s9qg4il26tnmdAXttdY +xRSXR8NNLU78EPvR9hIit5Q2cP6rXEpLiXLad0SsfoKvTd+rccMlYBprUDHDbBHC +VfNDmRTZVMw3BTCQWlVk5KBf9bGMOwzkux9MbcWxfA3PwftjFWTYiOKAiKr6rF8H +Urb7eLNdiVH7TvR+hiNx0phNHNUKNwiFp0iD3CuK+BT+P7lRp575AyzsGyCZ1PO0 +IdYwbvAJrpI1hitoxYYoP5ymtBeEPnqfJ7aRc7VHPiguGc3m3XqT8sdYP4XZfyns +U5euRtWL4Wa5irVdS0VEr3xEjDBnB7mdksPZ5dNcBBWgbNNltEdYFfX0ZvnofOea +mbMFnIYTF5aJGUPjow1HCA3xiPOjmfEE3+JTfjkI5CCAC64wMnkEeDxGupq0AQkE +ctNOnUdXJxEKAR5NT//zCE6cp9tzAEo4JqR3VTd9uT1kbGLmDrlTYqmqxyQt8TbA +MCHkbeNNdU2kZDLjgJb9SawQHyAwakIZxAx9lnWjJXy1FNST8XnOm5stvK3br8oC +B7QB9zHJ6U7sxoExsec3bisTUB+ZnmTR++JtFTcCAwEAAaNTMFEwHQYDVR0OBBYE +FKPxbvUFpt6aXSxie3N0ESUwaGbZMB8GA1UdIwQYMBaAFKPxbvUFpt6aXSxie3N0 +ESUwaGbZMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBABSSF3Wt +GYZUAyjgDC99mnI0OGnNSppB+PHd77zajDH2D7p5NFzeUFt7os1sujBwy/GjB9NI +5307bsOi5ZIEKA9J5t2zMiBUBJjobTANcFdZ7+8Pf9zbPOIzs3dlrzkDj4zx3X5R +6KkFj6FoQdKRHsscbKAzHRTkDPOv3EGLjG5OOJA7w8mHnXXqT7snjSuqGRDmpyuS +SuUJOQEGxgOX3VGBXVgX4Tv/1Yr3EaWhfAOk6zay4UzdNVX/RG+09fPEwPbZ3uAN +X9lyi9VXVJTd8jwjS8sWcofoNWInNRb5/YAdO+H8S0BIlFrXxT9F1luZ5/IoDzi0 +g73rM6LQ2SGNALE9YSVbMTwjT5zWKwlIDatf6ae5dNl4ldZ9XzkrKvIwndFfSlve +Wlp7z/tkhbBTAsz9xYJF4LM0iRcjtS7NvUe8IVm0pLr8aQBJC7yw2LYyS9rm2g6y +lJZNjNY7idelANQzU9YAvuUbBGqZJZ4k5gfKFpsLqJx9+h5HU8EK9vMhtzn0ATAM ++ii8CEmaMRI9+dk2E+Nkt8SkUMMZL0xKZ3z7+u+QcwqINiUn6zstx9mfG7JHtCtI +anT25Uv6on8QpCZ/01iPm4LxQcan/UnK/esmS3CuIGtPM8t3oiZmvQp9fo4bZBQH +xYAMQwTzsUyF0H6VklNcN+7RaXgfIxeZYC/G -----END CERTIFICATE----- From 56fc2473a19b347dd401a3e01bf04e8d0a46b82f Mon Sep 17 00:00:00 2001 From: AnkitManeSciAps <167805368+AnkitManeSciAps@users.noreply.github.com> Date: Mon, 17 Nov 2025 15:14:23 -0500 Subject: [PATCH 07/17] Moving stuff around to solve dependency issues --- .../com/devsmart/miniweb/CaTrustManager.java | 78 ------------------- .../java/com/devsmart/miniweb/Server.java | 24 +++--- .../devsmart/miniweb/SslCertificateUtil.java | 43 ---------- miniweb-core/src/main/resources/ca.crt | 31 -------- 4 files changed, 12 insertions(+), 164 deletions(-) delete mode 100644 miniweb-core/src/main/java/com/devsmart/miniweb/CaTrustManager.java delete mode 100644 miniweb-core/src/main/java/com/devsmart/miniweb/SslCertificateUtil.java delete mode 100644 miniweb-core/src/main/resources/ca.crt diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/CaTrustManager.java b/miniweb-core/src/main/java/com/devsmart/miniweb/CaTrustManager.java deleted file mode 100644 index 261e55a..0000000 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/CaTrustManager.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.devsmart.miniweb; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; - -import javax.net.ssl.X509TrustManager; - -@SuppressWarnings("CustomX509TrustManager") -public class CaTrustManager implements X509TrustManager { - private final X509Certificate trustedCA; - private static final Logger LOGGER = LoggerFactory.getLogger(CaTrustManager.class); - - public CaTrustManager(X509Certificate caCert) { - this.trustedCA = caCert; - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { - validateChain(chain, authType); - } - - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { - validateChain(chain, authType); - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[]{trustedCA}; - } - - private void validateChain(X509Certificate[] chain, String authType) throws CertificateException { - if (chain == null || chain.length == 0) { - LOGGER.error("Empty certificate chain"); - } - if (authType == null || authType.isEmpty()) { - LOGGER.error("Missing authType"); - } - - // Check each certificate validity period - for (X509Certificate cert : chain) { - cert.checkValidity(); - } - - // Verify signature chain (leaf -> root) - for (int i = 0; i < chain.length - 1; i++) { - try { - chain[i].verify(chain[i + 1].getPublicKey()); - } catch (Exception e) { - LOGGER.error("Broken chain at position {}: {}", i, e.getMessage(), e); - } - } - - X509Certificate root = chain[chain.length - 1]; - - // Ensure provided CA matches the root subject - if (!root.getSubjectX500Principal().equals(trustedCA.getSubjectX500Principal())) { - LOGGER.info("Root subject does not match trusted CA"); - } - - // Ensure provided CA actually signed the presented root - try { - root.verify(trustedCA.getPublicKey()); - } catch (Exception e) { - LOGGER.error("Root not signed by trusted CA: {}", e.getMessage()); - } - - // Ensure CA is self-signed (basic check) - try { - trustedCA.verify(trustedCA.getPublicKey()); - } catch (Exception e) { - LOGGER.error("Configured CA is not self-signed or invalid: {}", e.getMessage(), e); - } - } -} diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java index 6a94046..05e4fc5 100644 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java @@ -58,18 +58,18 @@ public class Server { private boolean mSslEnabled = false; public void setSslContext(String keystorePath) throws Exception { - KeyStore keyStore = KeyStore.getInstance("PKCS12"); - try (FileInputStream fis = new FileInputStream(keystorePath)) { - keyStore.load(fis, SslCertificateUtil.getCertificatePassword()); - } - - KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - kmf.init(keyStore, SslCertificateUtil.getCertificatePassword()); - - mSslContext = SSLContext.getInstance("TLS"); - TrustManager[] trustManagers = new TrustManager[]{new CaTrustManager(SslCertificateUtil.getCaCertificate())}; - mSslContext.init(kmf.getKeyManagers(), trustManagers, null); - mSslEnabled = true; +// KeyStore keyStore = KeyStore.getInstance("PKCS12"); +// try (FileInputStream fis = new FileInputStream(keystorePath)) { +// keyStore.load(fis, SslCertificateUtil.getCertificatePassword()); +// } +// +// KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); +// kmf.init(keyStore, SslCertificateUtil.getCertificatePassword()); +// +// mSslContext = SSLContext.getInstance("TLS"); +// TrustManager[] trustManagers = new TrustManager[]{new CaTrustManager(SslCertificateUtil.getCaCertificate())}; +// mSslContext.init(kmf.getKeyManagers(), trustManagers, null); +// mSslEnabled = true; } public void start() throws IOException { diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/SslCertificateUtil.java b/miniweb-core/src/main/java/com/devsmart/miniweb/SslCertificateUtil.java deleted file mode 100644 index ae8ed24..0000000 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/SslCertificateUtil.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.devsmart.miniweb; - -import org.slf4j.Logger; - -import java.io.InputStream; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; - -import static java.io.File.separator; -import static java.lang.System.getProperty; - -public class SslCertificateUtil { - - private static final Logger LOGGER = org.slf4j.LoggerFactory.getLogger(SslCertificateUtil.class); - - public static X509Certificate getCaCertificate() throws Exception { - try (InputStream caInput = SslCertificateUtil.class.getResourceAsStream("/ca.crt")) { - if (caInput == null) { - LOGGER.error("ca.crt not found in resources."); - return null; - } - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - return (X509Certificate) cf.generateCertificate(caInput); - } - } - - public static char[] getCertificatePassword() { - return "SciApsN@SC@R".toCharArray(); - } - - public static String getPbCertificatePath() { - String osName = getProperty("os.name", "Windows"); - if (osName.contains("Windows")) { - return "C:" + separator + "sciaps" + separator + "client.p12"; - } else if ("Mac OS X".equals(osName)) { - return getProperty("user.home") + separator + "sciaps" + separator + "client.p12"; - } else { - LOGGER.error("Unsupported OS: {}", osName); - return ""; - } - } - -} diff --git a/miniweb-core/src/main/resources/ca.crt b/miniweb-core/src/main/resources/ca.crt deleted file mode 100644 index 464f025..0000000 --- a/miniweb-core/src/main/resources/ca.crt +++ /dev/null @@ -1,31 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFVzCCAz+gAwIBAgIUHTAQuwcvJRGS0y6pbu8vCZ3AOdcwDQYJKoZIhvcNAQEL -BQAwOzEbMBkGA1UEAwwSU2NpQVBzIEludGVybmFsIENBMQ8wDQYDVQQKDAZTY2lB -cHMxCzAJBgNVBAYTAlVTMB4XDTI1MTExNzE4Mjk0NFoXDTM1MTExNTE4Mjk0NFow -OzEbMBkGA1UEAwwSU2NpQVBzIEludGVybmFsIENBMQ8wDQYDVQQKDAZTY2lBcHMx -CzAJBgNVBAYTAlVTMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvu0i -t7T+ea13GDIv+HnB15JuzsTobV1nZeH1w5izkYzIwx68FlDr+U66mrNZsFLna7Vn -dkgA/mHD8pZLV7Rke7rWp/bmgGOJp8yOdC/Pdb/rMdk/7s9qg4il26tnmdAXttdY -xRSXR8NNLU78EPvR9hIit5Q2cP6rXEpLiXLad0SsfoKvTd+rccMlYBprUDHDbBHC -VfNDmRTZVMw3BTCQWlVk5KBf9bGMOwzkux9MbcWxfA3PwftjFWTYiOKAiKr6rF8H -Urb7eLNdiVH7TvR+hiNx0phNHNUKNwiFp0iD3CuK+BT+P7lRp575AyzsGyCZ1PO0 -IdYwbvAJrpI1hitoxYYoP5ymtBeEPnqfJ7aRc7VHPiguGc3m3XqT8sdYP4XZfyns -U5euRtWL4Wa5irVdS0VEr3xEjDBnB7mdksPZ5dNcBBWgbNNltEdYFfX0ZvnofOea -mbMFnIYTF5aJGUPjow1HCA3xiPOjmfEE3+JTfjkI5CCAC64wMnkEeDxGupq0AQkE -ctNOnUdXJxEKAR5NT//zCE6cp9tzAEo4JqR3VTd9uT1kbGLmDrlTYqmqxyQt8TbA -MCHkbeNNdU2kZDLjgJb9SawQHyAwakIZxAx9lnWjJXy1FNST8XnOm5stvK3br8oC -B7QB9zHJ6U7sxoExsec3bisTUB+ZnmTR++JtFTcCAwEAAaNTMFEwHQYDVR0OBBYE -FKPxbvUFpt6aXSxie3N0ESUwaGbZMB8GA1UdIwQYMBaAFKPxbvUFpt6aXSxie3N0 -ESUwaGbZMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBABSSF3Wt -GYZUAyjgDC99mnI0OGnNSppB+PHd77zajDH2D7p5NFzeUFt7os1sujBwy/GjB9NI -5307bsOi5ZIEKA9J5t2zMiBUBJjobTANcFdZ7+8Pf9zbPOIzs3dlrzkDj4zx3X5R -6KkFj6FoQdKRHsscbKAzHRTkDPOv3EGLjG5OOJA7w8mHnXXqT7snjSuqGRDmpyuS -SuUJOQEGxgOX3VGBXVgX4Tv/1Yr3EaWhfAOk6zay4UzdNVX/RG+09fPEwPbZ3uAN -X9lyi9VXVJTd8jwjS8sWcofoNWInNRb5/YAdO+H8S0BIlFrXxT9F1luZ5/IoDzi0 -g73rM6LQ2SGNALE9YSVbMTwjT5zWKwlIDatf6ae5dNl4ldZ9XzkrKvIwndFfSlve -Wlp7z/tkhbBTAsz9xYJF4LM0iRcjtS7NvUe8IVm0pLr8aQBJC7yw2LYyS9rm2g6y -lJZNjNY7idelANQzU9YAvuUbBGqZJZ4k5gfKFpsLqJx9+h5HU8EK9vMhtzn0ATAM -+ii8CEmaMRI9+dk2E+Nkt8SkUMMZL0xKZ3z7+u+QcwqINiUn6zstx9mfG7JHtCtI -anT25Uv6on8QpCZ/01iPm4LxQcan/UnK/esmS3CuIGtPM8t3oiZmvQp9fo4bZBQH -xYAMQwTzsUyF0H6VklNcN+7RaXgfIxeZYC/G ------END CERTIFICATE----- From 8f8c7eb27f4ccbdb0dc16e3e8f575443de2f284a Mon Sep 17 00:00:00 2001 From: AnkitManeSciAps <167805368+AnkitManeSciAps@users.noreply.github.com> Date: Mon, 17 Nov 2025 15:28:36 -0500 Subject: [PATCH 08/17] Fix dependency issue --- .../java/com/devsmart/miniweb/Server.java | 21 ++++------------- .../com/devsmart/miniweb/ServerBuilder.java | 23 +++++++++++-------- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java index 05e4fc5..1f06c53 100644 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java @@ -20,18 +20,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.FileInputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.net.SocketTimeoutException; -import java.security.KeyStore; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLServerSocket; @@ -57,19 +55,10 @@ public class Server { private SSLContext mSslContext; private boolean mSslEnabled = false; - public void setSslContext(String keystorePath) throws Exception { -// KeyStore keyStore = KeyStore.getInstance("PKCS12"); -// try (FileInputStream fis = new FileInputStream(keystorePath)) { -// keyStore.load(fis, SslCertificateUtil.getCertificatePassword()); -// } -// -// KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); -// kmf.init(keyStore, SslCertificateUtil.getCertificatePassword()); -// -// mSslContext = SSLContext.getInstance("TLS"); -// TrustManager[] trustManagers = new TrustManager[]{new CaTrustManager(SslCertificateUtil.getCaCertificate())}; -// mSslContext.init(kmf.getKeyManagers(), trustManagers, null); -// mSslEnabled = true; + public void configureSslContext(KeyManager[] keyManagers, TrustManager[] trustManagers) throws Exception { + mSslContext = SSLContext.getInstance("TLS"); + mSslContext.init(keyManagers, trustManagers, null); + mSslEnabled = true; } public void start() throws IOException { diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/ServerBuilder.java b/miniweb-core/src/main/java/com/devsmart/miniweb/ServerBuilder.java index 61ca83d..db3ebae 100644 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/ServerBuilder.java +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/ServerBuilder.java @@ -10,7 +10,9 @@ import org.slf4j.LoggerFactory; import java.io.File; -import java.security.cert.X509Certificate; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.TrustManager; public class ServerBuilder { @@ -20,7 +22,8 @@ public class ServerBuilder { private UriRequestHandlerResolver mUriMapper = new UriRequestHandlerResolver(); private Gson mGson = new GsonBuilder().create(); private boolean mIsDebugBuild; - private String mKeyStorePath; + private KeyManager[] mKeyManagers; + private TrustManager[] mTrustManagers; private static final Logger LOGGER = LoggerFactory.getLogger(ServerBuilder.class.getSimpleName()); public ServerBuilder setDebugBuild(boolean isDebug) { @@ -90,16 +93,18 @@ public Server create() { mRequestHandler = mUriMapper; } server.requestHandlerResolver = mRequestHandler; - try { - server.setSslContext(mKeyStorePath); - } catch (Exception e) { - LOGGER.error("Failed to set up SSL context: {}", e.getMessage()); + if (mKeyManagers != null && mTrustManagers != null) { + try { + server.configureSslContext(mKeyManagers, mTrustManagers); + } catch (Exception e) { + LOGGER.error("Failed to set up SSL context: {}", e.getMessage()); + } } - return server; } - public void setKeyStorePath(String keyStorePath) { - mKeyStorePath = keyStorePath; + public void setSslConfigs(KeyManager[] keyManagers, TrustManager[] trustManagers) { + mKeyManagers = keyManagers; + mTrustManagers = trustManagers; } } From f02c73b6f42c877c37cb04ffc775db61d99e4c6f Mon Sep 17 00:00:00 2001 From: AnkitManeSciAps <167805368+AnkitManeSciAps@users.noreply.github.com> Date: Mon, 17 Nov 2025 16:29:16 -0500 Subject: [PATCH 09/17] Address comment fixe --- .../java/com/devsmart/miniweb/Server.java | 48 ++++--------------- 1 file changed, 8 insertions(+), 40 deletions(-) diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java index 1f06c53..82eac21 100644 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java @@ -106,27 +106,6 @@ public void shutdown() { } } - private synchronized void fallbackToHttp() { - try { - if (mServerSocket != null && !mServerSocket.isClosed()) { - mServerSocket.close(); - } - } catch (IOException e) { - LOGGER.error("Error closing SSL server socket during fallback", e); - } - mSslEnabled = false; - try { - ServerSocket plain = new ServerSocket(); // unbound - plain.setReuseAddress(true); - plain.bind(new InetSocketAddress(port)); - mServerSocket = plain; - LOGGER.info("Reverted to plain HTTP on port {}", port); - } catch (IOException e) { - LOGGER.error("Failed to create plain HTTP ServerSocket after SSL fallback", e); - mRunning = false; - } - } - private class WorkerTask implements Runnable { private final HttpService httpservice; @@ -197,40 +176,29 @@ public void run() { httpService.setHandlerResolver(requestHandlerResolver); httpService.setParams(params); - Socket rawSocket = null; + Socket socket = null; while (mRunning) { try { if (mIsDebugBuild) { LOGGER.info("waiting in accept on {}", mServerSocket.getLocalSocketAddress()); } - rawSocket = mServerSocket.accept(); + socket = mServerSocket.accept(); if (mIsDebugBuild) { - LOGGER.info("accepting connection from: {}", rawSocket.getRemoteSocketAddress()); + LOGGER.info("accepting connection from: {}", socket.getRemoteSocketAddress()); } - Socket boundSocket = rawSocket; - // If SSL is enabled, ensure we have a SSLSocket and complete the handshake if (mSslEnabled && (mServerSocket instanceof SSLServerSocket)) { - SSLSocket sslSocket = (SSLSocket) rawSocket; + SSLSocket sslSocket = (SSLSocket) socket; sslSocket.setUseClientMode(false); sslSocket.setEnabledProtocols(new String[]{"TLSv1.2"}); sslSocket.startHandshake(); } DefaultHttpServerConnection connection = new DefaultHttpServerConnection(); - connection.bind(boundSocket, new BasicHttpParams()); - RemoteConnection remoteConnection = new RemoteConnection(boundSocket.getInetAddress(), connection); + connection.bind(socket, new BasicHttpParams()); + RemoteConnection remoteConnection = new RemoteConnection(socket.getInetAddress(), connection); mWorkerThreads.execute(new WorkerTask(httpService, remoteConnection)); - } catch (SSLHandshakeException e) { - LOGGER.error("SSL Handshake failed: {}. Reverting to HTTP.", e.getMessage()); - try { - rawSocket.close(); - LOGGER.info("Connection is closed properly"); - } catch (IOException ioException) { - LOGGER.error("Can't close connection. Reason: ", ioException); - } - fallbackToHttp(); } catch (SocketTimeoutException e) { continue; } catch (SocketException e) { @@ -244,9 +212,9 @@ public void run() { mRunning = false; } } - if (rawSocket != null) { + if (socket != null) { try { - rawSocket.close(); + socket.close(); LOGGER.info("Connection is closed properly"); } catch (IOException e) { LOGGER.error("Can't close connection. Reason: ", e); From cba498ac97540e01d9be92b38992ef3431506d92 Mon Sep 17 00:00:00 2001 From: AnkitManeSciAps <167805368+AnkitManeSciAps@users.noreply.github.com> Date: Mon, 17 Nov 2025 16:35:06 -0500 Subject: [PATCH 10/17] Simplify logic --- .../src/main/java/com/devsmart/miniweb/Server.java | 14 ++++++++++---- .../java/com/devsmart/miniweb/ServerBuilder.java | 6 +----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java index 82eac21..d6a31a4 100644 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java @@ -55,10 +55,16 @@ public class Server { private SSLContext mSslContext; private boolean mSslEnabled = false; - public void configureSslContext(KeyManager[] keyManagers, TrustManager[] trustManagers) throws Exception { - mSslContext = SSLContext.getInstance("TLS"); - mSslContext.init(keyManagers, trustManagers, null); - mSslEnabled = true; + public void configureSslContext(KeyManager[] keyManagers, TrustManager[] trustManagers) { + try { + mSslContext = SSLContext.getInstance("TLS"); + mSslContext.init(keyManagers, trustManagers, null); + mSslEnabled = true; + } catch (Exception e) { + LOGGER.error("Could not initialize SSLContext:", e); + mSslContext = null; + mSslEnabled = false; + } } public void start() throws IOException { diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/ServerBuilder.java b/miniweb-core/src/main/java/com/devsmart/miniweb/ServerBuilder.java index db3ebae..ad776b8 100644 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/ServerBuilder.java +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/ServerBuilder.java @@ -94,11 +94,7 @@ public Server create() { } server.requestHandlerResolver = mRequestHandler; if (mKeyManagers != null && mTrustManagers != null) { - try { - server.configureSslContext(mKeyManagers, mTrustManagers); - } catch (Exception e) { - LOGGER.error("Failed to set up SSL context: {}", e.getMessage()); - } + server.configureSslContext(mKeyManagers, mTrustManagers); } return server; } From c9e15785406d730c08aae8f9887590b8b5efe7a7 Mon Sep 17 00:00:00 2001 From: AnkitManeSciAps <167805368+AnkitManeSciAps@users.noreply.github.com> Date: Tue, 18 Nov 2025 14:19:35 -0500 Subject: [PATCH 11/17] More fixes for backwards compatibility --- .../java/com/devsmart/miniweb/Server.java | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java index d6a31a4..e9456c5 100644 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java @@ -29,6 +29,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import javax.net.ServerSocketFactory; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLHandshakeException; @@ -78,7 +79,8 @@ public void start() throws IOException { mServerSocket = factory.createServerSocket(); ((SSLServerSocket) mServerSocket).setNeedClientAuth(true); } else { - mServerSocket = new ServerSocket(port); + ServerSocketFactory factory = ServerSocketFactory.getDefault(); + mServerSocket = factory.createServerSocket(); } mServerSocket.setReuseAddress(true); @@ -104,9 +106,11 @@ public void shutdown() { } try { mListenThread.join(); - mListenThread = null; } catch (InterruptedException e) { - LOGGER.error("", e); + LOGGER.error("Interrupted waiting for listener thread shutdown", e); + Thread.currentThread().interrupt(); + } finally { + mListenThread = null; } LOGGER.info("Server shutdown"); } @@ -146,22 +150,16 @@ public void run() { } finally { LOGGER.info("Closing connection {}", remoteConnection.connection); try { - // Try a graceful shutdown first - try { - remoteConnection.connection.shutdown(); - } catch (UnsupportedOperationException uoe) { - // SSLSocket may not support half-close; ignore - } - } catch (IOException ignore) {} + remoteConnection.connection.shutdown(); + } catch (UnsupportedOperationException | IOException e) { + LOGGER.error("Error shutting down connection", e); + } try { remoteConnection.connection.close(); - } catch (UnsupportedOperationException uoe) { - // Ignore half-close attempts on SSLSocket - } catch (IOException e) { + } catch (UnsupportedOperationException | IOException e) { LOGGER.error("Error closing connection", e); } } - } } @@ -205,7 +203,7 @@ public void run() { RemoteConnection remoteConnection = new RemoteConnection(socket.getInetAddress(), connection); mWorkerThreads.execute(new WorkerTask(httpService, remoteConnection)); - } catch (SocketTimeoutException e) { + } catch (SocketTimeoutException | SSLHandshakeException e) { continue; } catch (SocketException e) { LOGGER.info("SocketListener shutting down"); From e9969b1262aed031f61b1b1bc233118879901b3a Mon Sep 17 00:00:00 2001 From: AnkitManeSciAps <167805368+AnkitManeSciAps@users.noreply.github.com> Date: Wed, 19 Nov 2025 10:55:21 -0500 Subject: [PATCH 12/17] Add support for TLSv1.3 --- .../src/main/java/com/devsmart/miniweb/Server.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java index e9456c5..e822d4b 100644 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java @@ -26,6 +26,7 @@ import java.net.Socket; import java.net.SocketException; import java.net.SocketTimeoutException; +import java.util.Arrays; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -194,7 +195,11 @@ public void run() { if (mSslEnabled && (mServerSocket instanceof SSLServerSocket)) { SSLSocket sslSocket = (SSLSocket) socket; sslSocket.setUseClientMode(false); - sslSocket.setEnabledProtocols(new String[]{"TLSv1.2"}); + String[] protocols = sslSocket.getEnabledProtocols(); + String[] desired = new String[]{"TLSv1.3", "TLSv1.2"}; + sslSocket.setEnabledProtocols( + Arrays.stream(desired).filter(p -> Arrays.asList(protocols).contains(p)) + .toArray(String[]::new)); sslSocket.startHandshake(); } From a28f54d25edc3a0c0175f498bdef4ca0ccc52546 Mon Sep 17 00:00:00 2001 From: AnkitManeSciAps <167805368+AnkitManeSciAps@users.noreply.github.com> Date: Mon, 24 Nov 2025 15:17:48 -0500 Subject: [PATCH 13/17] Fix ssl exception issue --- .../java/com/devsmart/miniweb/Server.java | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java index e822d4b..4c88b8d 100644 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java @@ -33,7 +33,7 @@ import javax.net.ServerSocketFactory; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLException; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLSocket; @@ -208,26 +208,33 @@ public void run() { RemoteConnection remoteConnection = new RemoteConnection(socket.getInetAddress(), connection); mWorkerThreads.execute(new WorkerTask(httpService, remoteConnection)); - } catch (SocketTimeoutException | SSLHandshakeException e) { + } catch (SocketTimeoutException e) { continue; - } catch (SocketException e) { + } catch (SSLException e) { + closeSocket(socket); + } + catch (SocketException e) { LOGGER.info("SocketListener shutting down"); mRunning = false; } catch (IOException e) { - LOGGER.error("I/O error in accept loop", e); + LOGGER.error("I/O error", e); mRunning = false; } catch (ClassCastException e) { LOGGER.error("Expected SSLSocket when SSL is enabled; check server socket setup", e); mRunning = false; } } - if (socket != null) { - try { - socket.close(); - LOGGER.info("Connection is closed properly"); - } catch (IOException e) { - LOGGER.error("Can't close connection. Reason: ", e); - } + closeSocket(socket); + } + } + + private static void closeSocket(Socket socket) { + if (socket != null) { + try { + socket.close(); + LOGGER.info("Connection is closed properly"); + } catch (IOException e) { + LOGGER.error("Can't close connection. Reason: ", e); } } } From 839db8b62cafca365ed3548b9c35533e27326806 Mon Sep 17 00:00:00 2001 From: AnkitManeSciAps <167805368+AnkitManeSciAps@users.noreply.github.com> Date: Wed, 26 Nov 2025 13:49:30 -0500 Subject: [PATCH 14/17] Fix Socket.shutdownOutput issue --- .../java/com/devsmart/miniweb/Server.java | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java index 4c88b8d..7b0f430 100644 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java @@ -151,12 +151,9 @@ public void run() { } finally { LOGGER.info("Closing connection {}", remoteConnection.connection); try { - remoteConnection.connection.shutdown(); - } catch (UnsupportedOperationException | IOException e) { - LOGGER.error("Error shutting down connection", e); - } - try { - remoteConnection.connection.close(); + if (remoteConnection.connection.isOpen()) { + remoteConnection.connection.close(); + } } catch (UnsupportedOperationException | IOException e) { LOGGER.error("Error closing connection", e); } @@ -203,7 +200,7 @@ public void run() { sslSocket.startHandshake(); } - DefaultHttpServerConnection connection = new DefaultHttpServerConnection(); + SSLSafeHttpServerConnection connection = new SSLSafeHttpServerConnection(); connection.bind(socket, new BasicHttpParams()); RemoteConnection remoteConnection = new RemoteConnection(socket.getInetAddress(), connection); @@ -238,4 +235,21 @@ private static void closeSocket(Socket socket) { } } } + + + public static class SSLSafeHttpServerConnection extends DefaultHttpServerConnection { + @Override + public void close() throws IOException { + try { + // This attempts to call shutdownOutput(), which fails on Android SSL + super.close(); + } catch (UnsupportedOperationException e) { + // Catch the specific Android error + // verify the socket exists and force-close it to prevent leaks + if (getSocket() != null) { + getSocket().close(); + } + } + } + } } From c4e2f10726e709c0594edb6ac457b758724d0751 Mon Sep 17 00:00:00 2001 From: AnkitManeSciAps <167805368+AnkitManeSciAps@users.noreply.github.com> Date: Tue, 2 Dec 2025 09:54:54 -0500 Subject: [PATCH 15/17] Comment fixes --- .../src/main/java/com/devsmart/miniweb/ServerBuilder.java | 1 - 1 file changed, 1 deletion(-) diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/ServerBuilder.java b/miniweb-core/src/main/java/com/devsmart/miniweb/ServerBuilder.java index ad776b8..e32c8a0 100644 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/ServerBuilder.java +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/ServerBuilder.java @@ -24,7 +24,6 @@ public class ServerBuilder { private boolean mIsDebugBuild; private KeyManager[] mKeyManagers; private TrustManager[] mTrustManagers; - private static final Logger LOGGER = LoggerFactory.getLogger(ServerBuilder.class.getSimpleName()); public ServerBuilder setDebugBuild(boolean isDebug) { mIsDebugBuild = isDebug; From de98b486c82a181864bb01bf9c3bd437d4d6a9bc Mon Sep 17 00:00:00 2001 From: AnkitManeSciAps <167805368+AnkitManeSciAps@users.noreply.github.com> Date: Fri, 12 Dec 2025 16:26:01 -0500 Subject: [PATCH 16/17] Fix speed issue --- .../src/main/java/com/devsmart/miniweb/Server.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java index 7b0f430..c5a63db 100644 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java @@ -3,6 +3,12 @@ import org.apache.http.ConnectionClosedException; import org.apache.http.HttpException; import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.HttpServerConnection; +import org.apache.http.HttpStatus; +import org.apache.http.HttpVersion; +import org.apache.http.MethodNotSupportedException; +import org.apache.http.entity.ByteArrayEntity; import org.apache.http.impl.DefaultConnectionReuseStrategy; import org.apache.http.impl.DefaultHttpResponseFactory; import org.apache.http.impl.DefaultHttpServerConnection; @@ -10,6 +16,7 @@ import org.apache.http.params.HttpParams; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.BasicHttpProcessor; +import org.apache.http.protocol.HttpContext; import org.apache.http.protocol.HttpCoreContext; import org.apache.http.protocol.HttpRequestHandlerResolver; import org.apache.http.protocol.HttpService; @@ -26,6 +33,7 @@ import java.net.Socket; import java.net.SocketException; import java.net.SocketTimeoutException; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -78,7 +86,6 @@ public void start() throws IOException { if (mSslEnabled && mSslContext != null) { SSLServerSocketFactory factory = mSslContext.getServerSocketFactory(); mServerSocket = factory.createServerSocket(); - ((SSLServerSocket) mServerSocket).setNeedClientAuth(true); } else { ServerSocketFactory factory = ServerSocketFactory.getDefault(); mServerSocket = factory.createServerSocket(); @@ -206,11 +213,10 @@ public void run() { mWorkerThreads.execute(new WorkerTask(httpService, remoteConnection)); } catch (SocketTimeoutException e) { - continue; + // ignore and continue } catch (SSLException e) { closeSocket(socket); - } - catch (SocketException e) { + } catch (SocketException e) { LOGGER.info("SocketListener shutting down"); mRunning = false; } catch (IOException e) { From fb48db196d2676b5a104a507bf3ecc279b99fb54 Mon Sep 17 00:00:00 2001 From: AnkitManeSciAps <167805368+AnkitManeSciAps@users.noreply.github.com> Date: Tue, 17 Feb 2026 12:19:41 -0500 Subject: [PATCH 17/17] Add client side auth --- .../java/com/devsmart/miniweb/Server.java | 37 ++++++++----------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java index c5a63db..a9f921d 100644 --- a/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java +++ b/miniweb-core/src/main/java/com/devsmart/miniweb/Server.java @@ -3,12 +3,6 @@ import org.apache.http.ConnectionClosedException; import org.apache.http.HttpException; import org.apache.http.HttpRequest; -import org.apache.http.HttpResponse; -import org.apache.http.HttpServerConnection; -import org.apache.http.HttpStatus; -import org.apache.http.HttpVersion; -import org.apache.http.MethodNotSupportedException; -import org.apache.http.entity.ByteArrayEntity; import org.apache.http.impl.DefaultConnectionReuseStrategy; import org.apache.http.impl.DefaultHttpResponseFactory; import org.apache.http.impl.DefaultHttpServerConnection; @@ -16,7 +10,6 @@ import org.apache.http.params.HttpParams; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.BasicHttpProcessor; -import org.apache.http.protocol.HttpContext; import org.apache.http.protocol.HttpCoreContext; import org.apache.http.protocol.HttpRequestHandlerResolver; import org.apache.http.protocol.HttpService; @@ -33,7 +26,6 @@ import java.net.Socket; import java.net.SocketException; import java.net.SocketTimeoutException; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -44,7 +36,6 @@ import javax.net.ssl.SSLException; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; -import javax.net.ssl.SSLSocket; import javax.net.ssl.TrustManager; public class Server { @@ -93,6 +84,22 @@ public void start() throws IOException { mServerSocket.setReuseAddress(true); mServerSocket.bind(new InetSocketAddress(port)); + + if (mSslEnabled && mServerSocket instanceof SSLServerSocket) { + SSLServerSocket sslServerSocket = (SSLServerSocket) mServerSocket; + sslServerSocket.setNeedClientAuth(true); + String[] supported = sslServerSocket.getSupportedProtocols(); + String[] desired = new String[]{"TLSv1.3", "TLSv1.2"}; + String[] filtered = Arrays.stream(desired) + .filter(p -> Arrays.asList(supported).contains(p)) + .toArray(String[]::new); + if (filtered.length > 0) { + sslServerSocket.setEnabledProtocols(filtered); + } + LOGGER.info("SSL server socket configured: clientAuth=required, protocols={}", + Arrays.toString(sslServerSocket.getEnabledProtocols())); + } + if (mIsDebugBuild) { LOGGER.info("Server started listening on {}", mServerSocket.getLocalSocketAddress()); } @@ -195,18 +202,6 @@ public void run() { if (mIsDebugBuild) { LOGGER.info("accepting connection from: {}", socket.getRemoteSocketAddress()); } - // If SSL is enabled, ensure we have a SSLSocket and complete the handshake - if (mSslEnabled && (mServerSocket instanceof SSLServerSocket)) { - SSLSocket sslSocket = (SSLSocket) socket; - sslSocket.setUseClientMode(false); - String[] protocols = sslSocket.getEnabledProtocols(); - String[] desired = new String[]{"TLSv1.3", "TLSv1.2"}; - sslSocket.setEnabledProtocols( - Arrays.stream(desired).filter(p -> Arrays.asList(protocols).contains(p)) - .toArray(String[]::new)); - sslSocket.startHandshake(); - } - SSLSafeHttpServerConnection connection = new SSLSafeHttpServerConnection(); connection.bind(socket, new BasicHttpParams()); RemoteConnection remoteConnection = new RemoteConnection(socket.getInetAddress(), connection);